Merge "Annotate atomic DeviceConfig methods for testing."
diff --git a/Android.bp b/Android.bp
index 25c643d..b3faef1 100644
--- a/Android.bp
+++ b/Android.bp
@@ -221,11 +221,11 @@
":framework-sax-sources",
":framework-telecomm-sources",
":framework-telephony-common-sources",
- ":framework-telephony-sources",
":framework-wifi-annotations",
":framework-wifi-non-updatable-sources",
":PacProcessor-aidl-sources",
":ProxyHandler-aidl-sources",
+ ":net-utils-framework-common-srcs",
// AIDL from frameworks/base/native/
":platform-compat-native-aidl",
@@ -255,6 +255,9 @@
// etc.
":framework-javastream-protos",
":framework-statslog-gen",
+
+ // telephony annotations
+ ":framework-telephony-annotations",
],
}
@@ -262,12 +265,15 @@
name: "framework-updatable-sources",
srcs: [
":framework-appsearch-sources",
- ":framework-sdkext-sources",
+ ":framework-sdkextensions-sources",
":framework-statsd-sources",
":framework-tethering-srcs",
":updatable-media-srcs",
":framework-mediaprovider-sources",
+ ":framework-permission-sources",
":framework-wifi-updatable-sources",
+ ":framework-telephony-sources",
+ ":ike-srcs",
]
}
@@ -284,7 +290,7 @@
name: "framework-aidl-export-defaults",
aidl: {
export_include_dirs: [
- "apex/media/java",
+ "apex/media/framework/java",
"core/java",
"drm/java",
"graphics/java",
@@ -300,7 +306,6 @@
"rs/java",
"sax/java",
"telecomm/java",
- "telephony/java",
"wifi/java",
],
},
@@ -347,7 +352,6 @@
"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",
@@ -381,6 +385,7 @@
"updatable_media_stubs",
"framework_mediaprovider_stubs",
"framework-tethering",
+ "framework-telephony-stubs",
],
jarjar_rules: ":framework-jarjar-rules",
@@ -408,6 +413,7 @@
filegroup {
name: "libincident_aidl",
srcs: [
+ "core/java/android/os/IIncidentDumpCallback.aidl",
"core/java/android/os/IIncidentManager.aidl",
"core/java/android/os/IIncidentReportStatusListener.aidl",
],
@@ -415,6 +421,13 @@
}
filegroup {
+ name: "graphicsstats_proto",
+ srcs: [
+ "libs/hwui/protos/graphicsstats.proto",
+ ],
+}
+
+filegroup {
name: "libvibrator_aidl",
srcs: [
"core/java/android/os/IExternalVibrationController.aidl",
@@ -429,9 +442,11 @@
srcs: [":framework-non-updatable-sources"],
libs: [
"framework-appsearch-stubs",
- // TODO(b/146167933): Use framework-statsd-stubs
- "framework-statsd",
+ "framework-sdkextensions-stubs-systemapi",
+ "framework-statsd", // TODO(b/146167933): Use framework-statsd-stubs
+ "framework-permission-stubs",
"framework-wifi-stubs",
+ "ike-stubs",
],
installable: true,
javac_shard_size: 150,
@@ -440,8 +455,6 @@
"libcore-platform-compat-config",
"services-platform-compat-config",
"media-provider-platform-compat-config",
- "services-devicepolicy-platform-compat-config",
- "services-core-platform-compat-config",
],
static_libs: [
// If MimeMap ever becomes its own APEX, then this dependency would need to be removed
@@ -457,7 +470,9 @@
"//frameworks/base/apex/appsearch/framework",
"//frameworks/base/apex/blobstore/framework",
"//frameworks/base/apex/jobscheduler/framework",
+ "//frameworks/base/apex/permission/framework",
"//frameworks/base/apex/statsd/service",
+ "//frameworks/base/telephony",
"//frameworks/base/wifi",
"//frameworks/opt/net/wifi/service",
],
@@ -481,12 +496,18 @@
"updatable_media_stubs",
"framework_mediaprovider_stubs",
"framework-appsearch", // TODO(b/146218515): should be framework-appsearch-stubs
- "framework-sdkext-stubs-systemapi",
+ "framework-permission-stubs",
+ "framework-sdkextensions-stubs-systemapi",
// TODO(b/146167933): Use framework-statsd-stubs instead.
"framework-statsd",
// TODO(b/140299412): should be framework-wifi-stubs
"framework-wifi",
- // TODO(jiyong): add more stubs for APEXes here
+ "ike-stubs",
+ // TODO(b/147200698): should be the stub of framework-tethering
+ "framework-tethering",
+ // TODO (b/147688669) should be framework-telephony-stubs
+ "framework-telephony",
+ // TODO(jiyong): add stubs for APEXes here
],
sdk_version: "core_platform",
apex_available: ["//apex_available:platform"],
@@ -497,7 +518,11 @@
defaults: ["framework-defaults"],
srcs: [":framework-all-sources"],
installable: false,
- static_libs: ["exoplayer2-core"],
+ static_libs: [
+ "exoplayer2-core",
+ "android.hardware.wifi-V1.0-java-constants",
+ ],
+ libs: ["icing-java-proto-lite"],
apex_available: ["//apex_available:platform"],
}
@@ -595,17 +620,22 @@
filegroup {
name: "framework-annotations",
srcs: [
+ "core/java/android/annotation/CallbackExecutor.java",
+ "core/java/android/annotation/CheckResult.java",
"core/java/android/annotation/IntDef.java",
"core/java/android/annotation/IntRange.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/UnsupportedAppUsage.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",
],
}
@@ -616,6 +646,19 @@
}
filegroup {
+ name: "framework-ike-shared-srcs",
+ visibility: ["//frameworks/opt/net/ike"],
+ srcs: [
+ "core/java/android/annotation/StringDef.java",
+ "core/java/android/net/annotations/PolicyDirection.java",
+ "core/java/com/android/internal/util/IState.java",
+ "core/java/com/android/internal/util/State.java",
+ "core/java/com/android/internal/util/StateMachine.java",
+ "telephony/java/android/telephony/Annotation.java",
+ ],
+}
+
+filegroup {
name: "framework-networkstack-shared-srcs",
srcs: [
// TODO: remove these annotations as soon as we can use andoid.support.annotations.*
@@ -632,6 +675,7 @@
"core/java/com/android/internal/util/StateMachine.java",
"core/java/com/android/internal/util/TrafficStatsConstants.java",
"core/java/com/android/internal/util/WakeupMessage.java",
+ "core/java/com/android/internal/util/TokenBucket.java",
"core/java/android/net/shared/*.java",
],
}
@@ -641,13 +685,14 @@
name: "framework-tethering-shared-srcs",
srcs: [
"core/java/android/util/LocalLog.java",
- "core/java/com/android/internal/util/BitUtils.java",
"core/java/com/android/internal/util/IndentingPrintWriter.java",
"core/java/com/android/internal/util/IState.java",
"core/java/com/android/internal/util/MessageUtils.java",
"core/java/com/android/internal/util/Preconditions.java",
"core/java/com/android/internal/util/State.java",
"core/java/com/android/internal/util/StateMachine.java",
+ "core/java/com/android/internal/util/TrafficStatsConstants.java",
+ "core/java/android/net/shared/Inet4AddressUtils.java",
],
}
@@ -1092,6 +1137,7 @@
"core/java/android/util/TimeUtils.java",
"core/java/com/android/internal/os/SomeArgs.java",
"core/java/com/android/internal/util/AsyncChannel.java",
+ "core/java/com/android/internal/util/AsyncService.java",
"core/java/com/android/internal/util/BitwiseInputStream.java",
"core/java/com/android/internal/util/FastXmlSerializer.java",
"core/java/com/android/internal/util/HexDump.java",
@@ -1128,12 +1174,36 @@
],
}
+// utility classes statically linked into framework-wifi and dynamically linked
+// into wifi-service
+java_library {
+ name: "framework-wifi-util-lib",
+ sdk_version: "core_current",
+ srcs: [
+ "core/java/android/content/pm/BaseParceledListSlice.java",
+ "core/java/android/content/pm/ParceledListSlice.java",
+ "core/java/android/net/shared/Inet4AddressUtils.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",
+ ],
+ libs: [
+ "framework-annotations-lib",
+ "unsupportedappusage",
+ "android_system_stubs_current",
+ ],
+ visibility: ["//frameworks/base/wifi"],
+}
+
+// utility classes statically linked into wifi-service
filegroup {
name: "framework-wifi-service-shared-srcs",
srcs: [
- ":framework-annotations",
"core/java/android/net/InterfaceConfiguration.java",
- "core/java/android/os/HandlerExecutor.java",
+ "core/java/android/os/BasicShellCommandHandler.java",
"core/java/android/util/BackupUtils.java",
"core/java/android/util/LocalLog.java",
"core/java/android/util/Rational.java",
@@ -1141,11 +1211,9 @@
"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/Preconditions.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",
- "core/java/com/android/internal/util/XmlUtils.java",
],
}
@@ -1165,3 +1233,82 @@
"StubLibraries.bp",
"ApiDocs.bp",
]
+
+// TODO(b/147699819): move to frameworks/base/telephony/ folder
+droidstubs {
+ name: "framework-telephony-stubs-srcs",
+ srcs: [
+ ":framework-telephony-sources",
+ ":framework_native_aidl",
+ ":framework-javastream-protos",
+ ],
+ aidl: {
+ local_include_dirs: [
+ "core/java",
+ "telecomm/java"
+ ],
+ },
+ libs: [
+ "framework-annotations-lib",
+ "android.hardware.radio-V1.5-java",
+ ],
+ defaults: ["framework-module-stubs-defaults-systemapi"],
+ filter_packages: ["android.telephony"],
+ sdk_version: "system_current",
+}
+
+java_library {
+ name: "framework-telephony-stubs",
+ srcs: [":framework-telephony-stubs-srcs"],
+ // TODO(b/147699819): move public aidls to a separate folder and potentially remove
+ // below aidl exports.
+ aidl: {
+ export_include_dirs: ["telephony/java"],
+ },
+ sdk_version: "system_current",
+}
+
+java_library {
+ name: "framework-telephony",
+ srcs: [
+ ":framework-telephony-sources",
+ ],
+ // 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: ":telephony-framework-jarjar-rules",
+ dxflags: [
+ "--core-library",
+ "--multi-dex",
+ ],
+ // This is to break the dependency from boot jars.
+ dex_preopt: {
+ enabled: false,
+ },
+ installable: true,
+}
+
+filegroup {
+ // TODO (b/147690217): move to frameworks/base/telephony/common.
+ name: "framework-telephony-annotations",
+ srcs: ["telephony/java/android/telephony/Annotation.java"],
+}
+
+filegroup {
+ name: "telephony-framework-jarjar-rules",
+ srcs: ["telephony/framework-telephony-jarjar-rules.txt"],
+}
diff --git a/ApiDocs.bp b/ApiDocs.bp
index e373db6..c40004c 100644
--- a/ApiDocs.bp
+++ b/ApiDocs.bp
@@ -121,8 +121,10 @@
doc_defaults {
name: "framework-docs-default",
- libs: framework_docs_only_libs +
- ["stub-annotations"],
+ libs: framework_docs_only_libs + [
+ "stub-annotations",
+ "unsupportedappusage",
+ ],
html_dirs: [
"docs/html",
],
diff --git a/StubLibraries.bp b/StubLibraries.bp
index 78f1b9c..cdc0d32 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -41,10 +41,9 @@
]
stubs_defaults {
- name: "metalava-api-stubs-default",
+ name: "metalava-non-updatable-api-stubs-default",
srcs: [
":framework-non-updatable-sources",
- ":framework-updatable-sources",
"core/java/**/*.logtags",
":opt-telephony-srcs",
":opt-net-voip-srcs",
@@ -52,6 +51,10 @@
":core_public_api_files",
":ike-api-srcs",
],
+ // TODO(b/147699819): remove below aidl includes.
+ aidl: {
+ local_include_dirs: ["telephony/java"],
+ },
libs: ["framework-internal-utils"],
installable: false,
annotations_enabled: true,
@@ -64,14 +67,23 @@
"sdk-dir",
"api-versions-jars-dir",
],
- sdk_version: "core_platform",
filter_packages: packages_to_document,
}
+stubs_defaults {
+ name: "metalava-api-stubs-default",
+ defaults: ["metalava-non-updatable-api-stubs-default"],
+ srcs: [":framework-updatable-sources"],
+ sdk_version: "core_platform",
+}
+
/////////////////////////////////////////////////////////////////////
// *-api-stubs-docs modules providing source files for the stub libraries
/////////////////////////////////////////////////////////////////////
+// api-stubs-docs, system-api-stubs-docs, and test-api-stubs-docs have APIs
+// from the non-updatable part of the platform as well as from the updatable
+// modules
droidstubs {
name: "api-stubs-docs",
defaults: ["metalava-api-stubs-default"],
@@ -112,7 +124,10 @@
arg_files: [
"core/res/AndroidManifest.xml",
],
- args: metalava_framework_docs_args + " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS,process=android.annotation.SystemApi.Process.ALL\\)",
+ args: metalava_framework_docs_args +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," +
+ "process=android.annotation.SystemApi.Process.ALL\\)",
check_api: {
current: {
api_file: "api/system-current.txt",
@@ -155,6 +170,111 @@
}
/////////////////////////////////////////////////////////////////////
+// Following droidstubs modules are for extra APIs for modules.
+// The framework currently have two more API surfaces for modules:
+// @SystemApi(client=MODULE_APPS) and @SystemApi(client=MODULE_LIBRARIES)
+/////////////////////////////////////////////////////////////////////
+
+// TODO(b/146727827) remove the *-api modules when we can teach metalava
+// about the relationship among the API surfaces. Currently, these modules are only to generate
+// the API signature files and ensure that the APIs evolve in a backwards compatible manner.
+// They however are NOT used for building the API stub.
+droidstubs {
+ name: "module-app-api",
+ defaults: ["metalava-non-updatable-api-stubs-default"],
+ libs: ["framework-all"],
+ arg_files: ["core/res/AndroidManifest.xml"],
+ args: metalava_framework_docs_args +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.MODULE_APPS," +
+ "process=android.annotation.SystemApi.Process.ALL\\)",
+ check_api: {
+ current: {
+ api_file: "api/module-app-current.txt",
+ removed_api_file: "api/module-app-removed.txt",
+ },
+ // TODO(b/147559833) enable the compatibility check against the last release API
+ // and the API lint
+ //last_released: {
+ // api_file: ":last-released-module-app-api",
+ // removed_api_file: "api/module-app-removed.txt",
+ // baseline_file: ":module-app-api-incompatibilities-with-last-released"
+ //},
+ //api_lint: {
+ // enabled: true,
+ // new_since: ":last-released-module-app-api",
+ // baseline_file: "api/module-app-lint-baseline.txt",
+ //},
+ },
+ //jdiff_enabled: true,
+}
+
+droidstubs {
+ name: "module-lib-api",
+ defaults: ["metalava-non-updatable-api-stubs-default"],
+ libs: ["framework-all"],
+ arg_files: ["core/res/AndroidManifest.xml"],
+ args: metalava_framework_docs_args +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES," +
+ "process=android.annotation.SystemApi.Process.ALL\\)",
+ check_api: {
+ current: {
+ api_file: "api/module-lib-current.txt",
+ removed_api_file: "api/module-lib-removed.txt",
+ },
+ // TODO(b/147559833) enable the compatibility check against the last release API
+ // and the API lint
+ //last_released: {
+ // api_file: ":last-released-module-lib-api",
+ // removed_api_file: "api/module-lib-removed.txt",
+ // baseline_file: ":module-lib-api-incompatibilities-with-last-released"
+ //},
+ //api_lint: {
+ // enabled: true,
+ // new_since: ":last-released-module-lib-api",
+ // baseline_file: "api/module-lib-lint-baseline.txt",
+ //},
+ },
+ //jdiff_enabled: true,
+}
+
+// The following two droidstubs modules generate source files for the API stub libraries for
+// modules. Note that they not only include their own APIs but also other APIs that have
+// narrower scope. For example, module-lib-api-stubs-docs includes all @SystemApis not just
+// the ones with 'client=MODULE_LIBRARIES'.
+droidstubs {
+ name: "module-app-api-stubs-docs",
+ defaults: ["metalava-non-updatable-api-stubs-default"],
+ libs: ["framework-all"],
+ arg_files: ["core/res/AndroidManifest.xml"],
+ args: metalava_framework_docs_args +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," +
+ "process=android.annotation.SystemApi.Process.ALL\\)" +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.MODULE_APPS," +
+ "process=android.annotation.SystemApi.Process.ALL\\)",
+}
+
+droidstubs {
+ name: "module-lib-api-stubs-docs",
+ defaults: ["metalava-non-updatable-api-stubs-default"],
+ libs: ["framework-all"],
+ arg_files: ["core/res/AndroidManifest.xml"],
+ args: metalava_framework_docs_args +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," +
+ "process=android.annotation.SystemApi.Process.ALL\\)" +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.MODULE_APPS," +
+ "process=android.annotation.SystemApi.Process.ALL\\)" +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES," +
+ "process=android.annotation.SystemApi.Process.ALL\\)",
+}
+
+/////////////////////////////////////////////////////////////////////
// android_*_stubs_current modules are the stubs libraries compiled
// from *-api-stubs-docs
/////////////////////////////////////////////////////////////////////
@@ -169,7 +289,6 @@
java_resources: [
":notices-for-framework-stubs",
],
- sdk_version: "core_current",
system_modules: "none",
java_version: "1.8",
compile_dex: true,
@@ -187,6 +306,7 @@
"private-stub-annotations-jar",
],
defaults: ["framework-stubs-default"],
+ sdk_version: "core_current",
}
java_library_static {
@@ -201,6 +321,7 @@
"private-stub-annotations-jar",
],
defaults: ["framework-stubs-default"],
+ sdk_version: "core_current",
}
java_library_static {
@@ -215,21 +336,37 @@
"private-stub-annotations-jar",
],
defaults: ["framework-stubs-default"],
+ sdk_version: "core_current",
}
-java_system_modules {
- name: "android_stubs_current_system_modules",
- libs: ["android_stubs_current"],
+java_library_static {
+ name: "framework_module_app_stubs_current",
+ srcs: [
+ ":module-app-api-stubs-docs",
+ ],
+ libs: [
+ "stub-annotations",
+ "framework-all",
+ ],
+ static_libs: [
+ "private-stub-annotations-jar",
+ ],
+ defaults: ["framework-stubs-default"],
}
-java_system_modules {
- name: "android_system_stubs_current_system_modules",
- libs: ["android_system_stubs_current"],
-}
-
-java_system_modules {
- name: "android_test_stubs_current_system_modules",
- libs: ["android_test_stubs_current"],
+java_library_static {
+ name: "framework_module_lib_stubs_current",
+ srcs: [
+ ":module-lib-api-stubs-docs",
+ ],
+ libs: [
+ "stub-annotations",
+ "framework-all",
+ ],
+ static_libs: [
+ "private-stub-annotations-jar",
+ ],
+ defaults: ["framework-stubs-default"],
}
/////////////////////////////////////////////////////////////////////
diff --git a/apex/Android.bp b/apex/Android.bp
index 56f7db2..abebfa3 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -37,6 +37,36 @@
stubs_defaults {
name: "framework-module-stubs-defaults-systemapi",
- args: mainline_stubs_args + " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS,process=android.annotation.SystemApi.Process.ALL\\) ",
+ args: mainline_stubs_args +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," +
+ "process=android.annotation.SystemApi.Process.ALL\\) ",
+ installable: false,
+}
+
+stubs_defaults {
+ name: "framework-module-stubs-defaults-module_apps_api",
+ args: mainline_stubs_args +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," +
+ "process=android.annotation.SystemApi.Process.ALL\\) " +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.MODULE_APPS," +
+ "process=android.annotation.SystemApi.Process.ALL\\) ",
+ installable: false,
+}
+
+stubs_defaults {
+ name: "framework-module-stubs-defaults-module_libs_api",
+ args: mainline_stubs_args +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.PRIVILEGED_APPS," +
+ "process=android.annotation.SystemApi.Process.ALL\\) " +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.MODULE_APPS," +
+ "process=android.annotation.SystemApi.Process.ALL\\) " +
+ " --show-annotation android.annotation.SystemApi\\(" +
+ "client=android.annotation.SystemApi.Client.MODULE_LIBRARIES," +
+ "process=android.annotation.SystemApi.Process.ALL\\) ",
installable: false,
}
diff --git a/apex/appsearch/framework/Android.bp b/apex/appsearch/framework/Android.bp
index 3dc5a2c..2e88115 100644
--- a/apex/appsearch/framework/Android.bp
+++ b/apex/appsearch/framework/Android.bp
@@ -13,22 +13,32 @@
// limitations under the License.
filegroup {
- name: "framework-appsearch-sources",
- srcs: [
- "java/**/*.java",
- "java/**/*.aidl",
- ],
- path: "java",
+ name: "framework-appsearch-sources",
+ srcs: [
+ "java/**/*.java",
+ "java/**/*.aidl",
+ ],
+ path: "java",
}
java_library {
- name: "framework-appsearch",
- installable: true,
- sdk_version: "core_platform", // TODO(b/146218515) should be core_current
- srcs: [":framework-appsearch-sources"],
- libs: [
- "framework-minus-apex", // TODO(b/146218515) should be framework-system-stubs
- ],
+ name: "framework-appsearch",
+ installable: true,
+ sdk_version: "core_platform", // TODO(b/146218515) should be core_current
+ srcs: [":framework-appsearch-sources"],
+ hostdex: true, // for hiddenapi check
+ libs: [
+ "framework-minus-apex", // TODO(b/146218515) should be framework-system-stubs
+ ],
+ static_libs: [
+ "icing-java-proto-lite",
+ ],
+ visibility: [
+ "//frameworks/base/apex/appsearch:__subpackages__",
+ // TODO(b/146218515) remove this when framework is built with the stub of appsearch
+ "//frameworks/base",
+ ],
+ apex_available: ["com.android.appsearch"],
}
metalava_appsearch_docs_args =
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearch.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearch.java
new file mode 100644
index 0000000..e779b69
--- /dev/null
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearch.java
@@ -0,0 +1,762 @@
+/*
+ * 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.appsearch;
+
+import android.annotation.CurrentTimeSecondsLong;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.ArrayUtils;
+
+import com.google.android.icing.proto.DocumentProto;
+import com.google.android.icing.proto.PropertyProto;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Collection of all AppSearch Document Types.
+ *
+ * @hide
+ */
+// TODO(b/143789408) Spilt this class to make all subclasses to their own file.
+public final class AppSearch {
+
+ private AppSearch() {}
+ /**
+ * Represents a document unit.
+ *
+ * <p>Documents are constructed via {@link Document.Builder}.
+ *
+ * @hide
+ */
+ // TODO(b/143789408) set TTL for document in mProtoBuilder
+ // TODO(b/144518768) add visibility field if the stakeholders are comfortable with a no-op
+ // opt-in for this release.
+ public static class Document {
+ private static final String TAG = "AppSearch.Document";
+
+ /**
+ * The maximum number of elements in a repeatable field. Will reject the request if exceed
+ * this limit.
+ */
+ private static final int MAX_REPEATED_PROPERTY_LENGTH = 100;
+
+ /**
+ * The maximum {@link String#length} of a {@link String} field. Will reject the request if
+ * {@link String}s longer than this.
+ */
+ private static final int MAX_STRING_LENGTH = 20_000;
+
+ /**
+ * Contains {@link Document} basic information (uri, schemaType etc) and properties ordered
+ * by keys.
+ */
+ @NonNull
+ private final DocumentProto mProto;
+
+ /** Contains all properties in {@link #mProto} to support get properties via keys. */
+ @NonNull
+ private final Bundle mPropertyBundle;
+
+ /**
+ * Create a new {@link Document}.
+ * @param proto Contains {@link Document} basic information (uri, schemaType etc) and
+ * properties ordered by keys.
+ * @param propertyBundle Contains all properties in {@link #mProto} to support get
+ * properties via keys.
+ */
+ private Document(@NonNull DocumentProto proto, @NonNull Bundle propertyBundle) {
+ this.mProto = proto;
+ this.mPropertyBundle = propertyBundle;
+ }
+
+ /**
+ * Create a new {@link Document} from an existing instance.
+ *
+ * <p>This method should be only used by constructor of a subclass.
+ */
+ // TODO(b/143789408) add constructor take DocumentProto to create a document.
+ protected Document(@NonNull Document document) {
+ this(document.mProto, document.mPropertyBundle);
+ }
+
+ /**
+ * Creates a new {@link Document.Builder}.
+ *
+ * @param uri The uri of {@link Document}.
+ * @param schemaType The schema type of the {@link Document}. The passed-in
+ * {@code schemaType} must be defined using {@link AppSearchManager#setSchema} prior to
+ * inserting a document of this {@code schemaType} into the AppSearch index using
+ * {@link AppSearchManager#put}. Otherwise, the document will be rejected by
+ * {@link AppSearchManager#put}.
+ * @hide
+ */
+ @NonNull
+ public static Builder newBuilder(@NonNull String uri, @NonNull String schemaType) {
+ return new Builder(uri, schemaType);
+ }
+
+ /**
+ * Get the {@link DocumentProto} of the {@link Document}.
+ *
+ * <p>The {@link DocumentProto} contains {@link Document}'s basic information and all
+ * properties ordered by keys.
+ * @hide
+ */
+ @NonNull
+ @VisibleForTesting
+ public DocumentProto getProto() {
+ return mProto;
+ }
+
+ /**
+ * Get the uri of the {@link Document}.
+ *
+ * @hide
+ */
+ @NonNull
+ public String getUri() {
+ return mProto.getUri();
+ }
+
+ /**
+ * Get the schema type of the {@link Document}.
+ * @hide
+ */
+ @NonNull
+ public String getSchemaType() {
+ return mProto.getSchema();
+ }
+
+ /**
+ * Get the creation timestamp in seconds of the {@link Document}.
+ *
+ * @hide
+ */
+ // TODO(b/143789408) Change seconds to millis with Icing library.
+ @CurrentTimeSecondsLong
+ public long getCreationTimestampSecs() {
+ return mProto.getCreationTimestampSecs();
+ }
+
+ /**
+ * Returns the score of the {@link Document}.
+ *
+ * <p>The score is a query-independent measure of the document's quality, relative to other
+ * {@link Document}s of the same type.
+ *
+ * <p>The default value is 0.
+ *
+ * @hide
+ */
+ public int getScore() {
+ return mProto.getScore();
+ }
+
+ /**
+ * Retrieve a {@link String} value by key.
+ *
+ * @param key The key to look for.
+ * @return The first {@link String} associated with the given key or {@code null} if there
+ * is no such key or the value is of a different type.
+ * @hide
+ */
+ @Nullable
+ public String getPropertyString(@NonNull String key) {
+ String[] propertyArray = getPropertyStringArray(key);
+ if (ArrayUtils.isEmpty(propertyArray)) {
+ return null;
+ }
+ warnIfSinglePropertyTooLong("String", key, propertyArray.length);
+ return propertyArray[0];
+ }
+
+ /**
+ * Retrieve a {@link Long} value by key.
+ *
+ * @param key The key to look for.
+ * @return The first {@link Long} associated with the given key or {@code null} if there
+ * is no such key or the value is of a different type.
+ * @hide
+ */
+ @Nullable
+ public Long getPropertyLong(@NonNull String key) {
+ long[] propertyArray = getPropertyLongArray(key);
+ if (ArrayUtils.isEmpty(propertyArray)) {
+ return null;
+ }
+ warnIfSinglePropertyTooLong("Long", key, propertyArray.length);
+ return propertyArray[0];
+ }
+
+ /**
+ * Retrieve a {@link Double} value by key.
+ *
+ * @param key The key to look for.
+ * @return The first {@link Double} associated with the given key or {@code null} if there
+ * is no such key or the value is of a different type.
+ * @hide
+ */
+ @Nullable
+ public Double getPropertyDouble(@NonNull String key) {
+ double[] propertyArray = getPropertyDoubleArray(key);
+ // TODO(tytytyww): Add support double array to ArraysUtils.isEmpty().
+ if (propertyArray == null || propertyArray.length == 0) {
+ return null;
+ }
+ warnIfSinglePropertyTooLong("Double", key, propertyArray.length);
+ return propertyArray[0];
+ }
+
+ /**
+ * Retrieve a {@link Boolean} value by key.
+ *
+ * @param key The key to look for.
+ * @return The first {@link Boolean} associated with the given key or {@code null} if there
+ * is no such key or the value is of a different type.
+ * @hide
+ */
+ @Nullable
+ public Boolean getPropertyBoolean(@NonNull String key) {
+ boolean[] propertyArray = getPropertyBooleanArray(key);
+ if (ArrayUtils.isEmpty(propertyArray)) {
+ return null;
+ }
+ warnIfSinglePropertyTooLong("Boolean", key, propertyArray.length);
+ return propertyArray[0];
+ }
+
+ /** Prints a warning to logcat if the given propertyLength is greater than 1. */
+ private static void warnIfSinglePropertyTooLong(
+ @NonNull String propertyType, @NonNull String key, int propertyLength) {
+ if (propertyLength > 1) {
+ Log.w(TAG, "The value for \"" + key + "\" contains " + propertyLength
+ + " elements. Only the first one will be returned from "
+ + "getProperty" + propertyType + "(). Try getProperty" + propertyType
+ + "Array().");
+ }
+ }
+
+ /**
+ * Retrieve a repeated {@code String} property by key.
+ *
+ * @param key The key to look for.
+ * @return The {@code String[]} associated with the given key, or {@code null} if no value
+ * is set or the value is of a different type.
+ * @hide
+ */
+ @Nullable
+ public String[] getPropertyStringArray(@NonNull String key) {
+ return getAndCastPropertyArray(key, String[].class);
+ }
+
+ /**
+ * Retrieve a repeated {@code long} property by key.
+ *
+ * @param key The key to look for.
+ * @return The {@code long[]} associated with the given key, or {@code null} if no value is
+ * set or the value is of a different type.
+ * @hide
+ */
+ @Nullable
+ public long[] getPropertyLongArray(@NonNull String key) {
+ return getAndCastPropertyArray(key, long[].class);
+ }
+
+ /**
+ * Retrieve a repeated {@code double} property by key.
+ *
+ * @param key The key to look for.
+ * @return The {@code double[]} associated with the given key, or {@code null} if no value
+ * is set or the value is of a different type.
+ * @hide
+ */
+ @Nullable
+ public double[] getPropertyDoubleArray(@NonNull String key) {
+ return getAndCastPropertyArray(key, double[].class);
+ }
+
+ /**
+ * Retrieve a repeated {@code boolean} property by key.
+ *
+ * @param key The key to look for.
+ * @return The {@code boolean[]} associated with the given key, or {@code null} if no value
+ * is set or the value is of a different type.
+ * @hide
+ */
+ @Nullable
+ public boolean[] getPropertyBooleanArray(@NonNull String key) {
+ return getAndCastPropertyArray(key, boolean[].class);
+ }
+
+ /**
+ * Gets a repeated property of the given key, and casts it to the given class type, which
+ * must be an array class type.
+ */
+ @Nullable
+ private <T> T getAndCastPropertyArray(@NonNull String key, @NonNull Class<T> tClass) {
+ Object value = mPropertyBundle.get(key);
+ if (value == null) {
+ return null;
+ }
+ try {
+ return tClass.cast(value);
+ } catch (ClassCastException e) {
+ Log.w(TAG, "Error casting to requested type for key \"" + key + "\"", e);
+ return null;
+ }
+ }
+
+ @Override
+ public boolean equals(@Nullable Object other) {
+ // Check only proto's equality is sufficient here since all properties in
+ // mPropertyBundle are ordered by keys and stored in proto.
+ if (this == other) {
+ return true;
+ }
+ if (!(other instanceof Document)) {
+ return false;
+ }
+ Document otherDocument = (Document) other;
+ return this.mProto.equals(otherDocument.mProto);
+ }
+
+ @Override
+ public int hashCode() {
+ // Hash only proto is sufficient here since all properties in mPropertyBundle are
+ // ordered by keys and stored in proto.
+ return mProto.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return mProto.toString();
+ }
+
+ /**
+ * The builder class for {@link Document}.
+ *
+ * @param <BuilderType> Type of subclass who extend this.
+ * @hide
+ */
+ public static class Builder<BuilderType extends Builder> {
+
+ private final Bundle mPropertyBundle = new Bundle();
+ private final DocumentProto.Builder mProtoBuilder = DocumentProto.newBuilder();
+ private final BuilderType mBuilderTypeInstance;
+
+ /**
+ * Create a new {@link Document.Builder}.
+ *
+ * @param uri The uri of {@link Document}.
+ * @param schemaType The schema type of the {@link Document}. The passed-in
+ * {@code schemaType} must be defined using {@link AppSearchManager#setSchema} prior
+ * to inserting a document of this {@code schemaType} into the AppSearch index using
+ * {@link AppSearchManager#put}. Otherwise, the document will be rejected by
+ * {@link AppSearchManager#put}.
+ * @hide
+ */
+ protected Builder(@NonNull String uri, @NonNull String schemaType) {
+ mBuilderTypeInstance = (BuilderType) this;
+ mProtoBuilder.setUri(uri).setSchema(schemaType);
+ // Set current timestamp for creation timestamp by default.
+ setCreationTimestampSecs(
+ TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()));
+ }
+
+ /**
+ * Set the score of the {@link Document}.
+ *
+ * <p>The score is a query-independent measure of the document's quality, relative to
+ * other {@link Document}s of the same type.
+ *
+ * @throws IllegalArgumentException If the provided value is negative.
+ * @hide
+ */
+ @NonNull
+ public BuilderType setScore(@IntRange(from = 0, to = Integer.MAX_VALUE) int score) {
+ if (score < 0) {
+ throw new IllegalArgumentException("Document score cannot be negative");
+ }
+ mProtoBuilder.setScore(score);
+ return mBuilderTypeInstance;
+ }
+
+ /**
+ * Set the creation timestamp in seconds of the {@link Document}.
+ *
+ * @hide
+ */
+ @NonNull
+ public BuilderType setCreationTimestampSecs(
+ @CurrentTimeSecondsLong long creationTimestampSecs) {
+ mProtoBuilder.setCreationTimestampSecs(creationTimestampSecs);
+ return mBuilderTypeInstance;
+ }
+
+ /**
+ * Sets one or multiple {@code String} values for a property, replacing its previous
+ * values.
+ *
+ * @param key The key associated with the {@code values}.
+ * @param values The {@code String} values of the property.
+ * @hide
+ */
+ @NonNull
+ public BuilderType setProperty(@NonNull String key, @NonNull String... values) {
+ putInBundle(mPropertyBundle, key, values);
+ return mBuilderTypeInstance;
+ }
+
+ /**
+ * Sets one or multiple {@code boolean} values for a property, replacing its previous
+ * values.
+ *
+ * @param key The key associated with the {@code values}.
+ * @param values The {@code boolean} values of the schema.org property.
+ * @hide
+ */
+ @NonNull
+ public BuilderType setProperty(@NonNull String key, @NonNull boolean... values) {
+ putInBundle(mPropertyBundle, key, values);
+ return mBuilderTypeInstance;
+ }
+
+ /**
+ * Sets one or multiple {@code long} values for a property, replacing its previous
+ * values.
+ *
+ * @param key The key associated with the {@code values}.
+ * @param values The {@code long} values of the schema.org property.
+ * @hide
+ */
+ @NonNull
+ public BuilderType setProperty(@NonNull String key, @NonNull long... values) {
+ putInBundle(mPropertyBundle, key, values);
+ return mBuilderTypeInstance;
+ }
+
+ /**
+ * Sets one or multiple {@code double} values for a property, replacing its previous
+ * values.
+ *
+ * @param key The key associated with the {@code values}.
+ * @param values The {@code double} values of the schema.org property.
+ * @hide
+ */
+ @NonNull
+ public BuilderType setProperty(@NonNull String key, @NonNull double... values) {
+ putInBundle(mPropertyBundle, key, values);
+ return mBuilderTypeInstance;
+ }
+
+ private static void putInBundle(
+ @NonNull Bundle bundle, @NonNull String key, @NonNull String... values)
+ throws IllegalArgumentException {
+ Objects.requireNonNull(key);
+ Objects.requireNonNull(values);
+ validateRepeatedPropertyLength(key, values.length);
+ for (int i = 0; i < values.length; i++) {
+ if (values[i] == null) {
+ throw new IllegalArgumentException("The String at " + i + " is null.");
+ } else if (values[i].length() > MAX_STRING_LENGTH) {
+ throw new IllegalArgumentException("The String at " + i + " length is: "
+ + values[i].length() + ", which exceeds length limit: "
+ + MAX_STRING_LENGTH + ".");
+ }
+ }
+ bundle.putStringArray(key, values);
+ }
+
+ private static void putInBundle(
+ @NonNull Bundle bundle, @NonNull String key, @NonNull boolean... values) {
+ Objects.requireNonNull(key);
+ Objects.requireNonNull(values);
+ validateRepeatedPropertyLength(key, values.length);
+ bundle.putBooleanArray(key, values);
+ }
+
+ private static void putInBundle(
+ @NonNull Bundle bundle, @NonNull String key, @NonNull double... values) {
+ Objects.requireNonNull(key);
+ Objects.requireNonNull(values);
+ validateRepeatedPropertyLength(key, values.length);
+ bundle.putDoubleArray(key, values);
+ }
+
+ private static void putInBundle(
+ @NonNull Bundle bundle, @NonNull String key, @NonNull long... values) {
+ Objects.requireNonNull(key);
+ Objects.requireNonNull(values);
+ validateRepeatedPropertyLength(key, values.length);
+ bundle.putLongArray(key, values);
+ }
+
+ private static void validateRepeatedPropertyLength(@NonNull String key, int length) {
+ if (length == 0) {
+ throw new IllegalArgumentException("The input array is empty.");
+ } else if (length > MAX_REPEATED_PROPERTY_LENGTH) {
+ throw new IllegalArgumentException(
+ "Repeated property \"" + key + "\" has length " + length
+ + ", which exceeds the limit of "
+ + MAX_REPEATED_PROPERTY_LENGTH);
+ }
+ }
+
+ /**
+ * Builds the {@link Document} object.
+ * @hide
+ */
+ public Document build() {
+ // Build proto by sorting the keys in propertyBundle to exclude the influence of
+ // order. Therefore documents will generate same proto as long as the contents are
+ // same. Note that the order of repeated fields is still preserved.
+ ArrayList<String> keys = new ArrayList<>(mPropertyBundle.keySet());
+ Collections.sort(keys);
+ for (String key : keys) {
+ Object values = mPropertyBundle.get(key);
+ PropertyProto.Builder propertyProto = PropertyProto.newBuilder().setName(key);
+ if (values instanceof boolean[]) {
+ for (boolean value : (boolean[]) values) {
+ propertyProto.addBooleanValues(value);
+ }
+ } else if (values instanceof long[]) {
+ for (long value : (long[]) values) {
+ propertyProto.addInt64Values(value);
+ }
+ } else if (values instanceof double[]) {
+ for (double value : (double[]) values) {
+ propertyProto.addDoubleValues(value);
+ }
+ } else if (values instanceof String[]) {
+ for (String value : (String[]) values) {
+ propertyProto.addStringValues(value);
+ }
+ } else {
+ throw new IllegalStateException(
+ "Property \"" + key + "\" has unsupported value type \""
+ + values.getClass().getSimpleName() + "\"");
+ }
+ mProtoBuilder.addProperties(propertyProto);
+ }
+ return new Document(mProtoBuilder.build(), mPropertyBundle);
+ }
+ }
+ }
+
+ /**
+ * Encapsulates a {@link Document} that represent an email.
+ *
+ * <p>This class is a higher level implement of {@link Document}.
+ *
+ * <p>This class will eventually migrate to Jetpack, where it will become public API.
+ *
+ * @hide
+ */
+ public static class Email extends Document {
+
+ /** The name of the schema type for {@link Email} documents.*/
+ public static final String SCHEMA_TYPE = "builtin:Email";
+
+ private static final String KEY_FROM = "from";
+ private static final String KEY_TO = "to";
+ private static final String KEY_CC = "cc";
+ private static final String KEY_BCC = "bcc";
+ private static final String KEY_SUBJECT = "subject";
+ private static final String KEY_BODY = "body";
+
+ /**
+ * Creates a new {@link Email} from the contents of an existing {@link Document}.
+ *
+ * @param document The {@link Document} containing the email content.
+ */
+ public Email(@NonNull Document document) {
+ super(document);
+ }
+
+ /**
+ * Creates a new {@link Email.Builder}.
+ *
+ * @param uri The uri of {@link Email}.
+ */
+ public static Builder newBuilder(@NonNull String uri) {
+ return new Builder(uri);
+ }
+
+ /**
+ * Get the from address of {@link Email}.
+ *
+ * @return Returns the subject of {@link Email} or {@code null} if it's not been set yet.
+ * @hide
+ */
+ @Nullable
+ public String getFrom() {
+ return getPropertyString(KEY_FROM);
+ }
+
+ /**
+ * Get the destination address of {@link Email}.
+ *
+ * @return Returns the destination address of {@link Email} or {@code null} if it's not been
+ * set yet.
+ * @hide
+ */
+ @Nullable
+ public String[] getTo() {
+ return getPropertyStringArray(KEY_TO);
+ }
+
+ /**
+ * Get the CC list of {@link Email}.
+ *
+ * @return Returns the CC list of {@link Email} or {@code null} if it's not been set yet.
+ * @hide
+ */
+ @Nullable
+ public String[] getCc() {
+ return getPropertyStringArray(KEY_CC);
+ }
+
+ /**
+ * Get the BCC list of {@link Email}.
+ *
+ * @return Returns the BCC list of {@link Email} or {@code null} if it's not been set yet.
+ * @hide
+ */
+ @Nullable
+ public String[] getBcc() {
+ return getPropertyStringArray(KEY_BCC);
+ }
+
+ /**
+ * Get the subject of {@link Email}.
+ *
+ * @return Returns the value subject of {@link Email} or {@code null} if it's not been set
+ * yet.
+ * @hide
+ */
+ @Nullable
+ public String getSubject() {
+ return getPropertyString(KEY_SUBJECT);
+ }
+
+ /**
+ * Get the body of {@link Email}.
+ *
+ * @return Returns the body of {@link Email} or {@code null} if it's not been set yet.
+ * @hide
+ */
+ @Nullable
+ public String getBody() {
+ return getPropertyString(KEY_BODY);
+ }
+
+ /**
+ * The builder class for {@link Email}.
+ * @hide
+ */
+ public static class Builder extends Document.Builder<Email.Builder> {
+
+ /**
+ * Create a new {@link Email.Builder}
+ * @param uri The Uri of the Email.
+ * @hide
+ */
+ private Builder(@NonNull String uri) {
+ super(uri, SCHEMA_TYPE);
+ }
+
+ /**
+ * Set the from address of {@link Email}
+ * @hide
+ */
+ @NonNull
+ public Email.Builder setFrom(@NonNull String from) {
+ setProperty(KEY_FROM, from);
+ return this;
+ }
+
+ /**
+ * Set the destination address of {@link Email}
+ * @hide
+ */
+ @NonNull
+ public Email.Builder setTo(@NonNull String... to) {
+ setProperty(KEY_TO, to);
+ return this;
+ }
+
+ /**
+ * Set the CC list of {@link Email}
+ * @hide
+ */
+ @NonNull
+ public Email.Builder setCc(@NonNull String... cc) {
+ setProperty(KEY_CC, cc);
+ return this;
+ }
+
+ /**
+ * Set the BCC list of {@link Email}
+ * @hide
+ */
+ @NonNull
+ public Email.Builder setBcc(@NonNull String... bcc) {
+ setProperty(KEY_BCC, bcc);
+ return this;
+ }
+
+ /**
+ * Set the subject of {@link Email}
+ * @hide
+ */
+ @NonNull
+ public Email.Builder setSubject(@NonNull String subject) {
+ setProperty(KEY_SUBJECT, subject);
+ return this;
+ }
+
+ /**
+ * Set the body of {@link Email}
+ * @hide
+ */
+ @NonNull
+ public Email.Builder setBody(@NonNull String body) {
+ setProperty(KEY_BODY, body);
+ return this;
+ }
+
+ /**
+ * Builds the {@link Email} object.
+ *
+ * @hide
+ */
+ @NonNull
+ @Override
+ public Email build() {
+ return new Email(super.build());
+ }
+ }
+ }
+}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
index a8ee35c..83195dc 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchManager.java
@@ -15,21 +15,116 @@
*/
package android.app.appsearch;
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
import android.annotation.SystemService;
+import android.app.appsearch.AppSearch.Document;
import android.content.Context;
+import android.os.RemoteException;
+
+import com.android.internal.infra.AndroidFuture;
+
+import com.google.android.icing.proto.SchemaProto;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
- * TODO(b/142567528): add comments when implement this class
+ * This class provides access to the centralized AppSearch index maintained by the system.
+ *
+ * <p>Apps can index structured text documents with AppSearch, which can then be retrieved through
+ * the query API.
+ *
* @hide
*/
@SystemService(Context.APP_SEARCH_SERVICE)
public class AppSearchManager {
private final IAppSearchManager mService;
+
+ /** @hide */
+ public AppSearchManager(@NonNull IAppSearchManager service) {
+ mService = service;
+ }
+
/**
- * TODO(b/142567528): add comments when implement this class
+ * Sets the schema being used by documents provided to the #put method.
+ *
+ * <p>This operation is performed asynchronously. On success, the provided callback will be
+ * called with {@code null}. On failure, the provided callback will be called with a
+ * {@link Throwable} describing the failure.
+ *
+ * <p>It is a no-op to set the same schema as has been previously set; this is handled
+ * efficiently.
+ *
+ * <p>AppSearch automatically handles the following types of schema changes:
+ * <ul>
+ * <li>Addition of new types (No changes to storage or index)
+ * <li>Removal of an existing type (All documents of the removed type are deleted)
+ * <li>Addition of new 'optional' property to a type (No changes to storage or index)
+ * <li>Removal of existing property of any cardinality (All documents reindexed)
+ * </ul>
+ *
+ * <p>This method will return an error when attempting to make the following types of changes:
+ * <ul>
+ * <li>Changing the type of an existing property
+ * <li>Adding a 'required' property
+ * </ul>
+ *
+ * @param schema The schema config for this app.
+ * @param executor Executor on which to invoke the callback.
+ * @param callback Callback to receive errors resulting from setting the schema. If the
+ * operation succeeds, the callback will be invoked with {@code null}.
+ *
* @hide
*/
- public AppSearchManager(IAppSearchManager service) {
- mService = service;
+ // TODO(b/143789408): linkify #put after that API is created
+ // TODO(b/145635424): add a 'force' param to setSchema after the corresponding API is finalized
+ // in Icing Library
+ // TODO(b/145635424): Update the documentation above once the Schema mutation APIs of Icing
+ // Library are finalized
+ public void setSchema(
+ @NonNull AppSearchSchema schema,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<? super Throwable> callback) {
+ SchemaProto schemaProto = schema.getProto();
+ byte[] schemaBytes = schemaProto.toByteArray();
+ AndroidFuture<Void> future = new AndroidFuture<>();
+ try {
+ mService.setSchema(schemaBytes, future);
+ } catch (RemoteException e) {
+ future.completeExceptionally(e);
+ }
+ future.whenCompleteAsync((noop, err) -> callback.accept(err), executor);
+ }
+
+ /**
+ * Index {@link Document} to AppSearch
+ *
+ * <p>You should not call this method directly; instead, use the {@code AppSearch#put()} API
+ * provided by JetPack.
+ *
+ * <p>The schema should be set via {@link #setSchema} method.
+ *
+ * @param documents {@link Document Documents} that need to be indexed.
+ * @param executor Executor on which to invoke the callback.
+ * @param callback Callback to receive errors resulting from setting the schema. If the
+ * operation succeeds, the callback will be invoked with {@code null}.
+ */
+ public void put(@NonNull List<Document> documents,
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<? super Throwable> callback) {
+ AndroidFuture<Void> future = new AndroidFuture<>();
+ for (Document document : documents) {
+ // TODO(b/146386470) batching Document protos
+ try {
+ mService.put(document.getProto().toByteArray(), future);
+ } catch (RemoteException e) {
+ future.completeExceptionally(e);
+ break;
+ }
+ }
+ // TODO(b/147614371) Fix error report for multiple documents.
+ future.whenCompleteAsync((noop, err) -> callback.accept(err), executor);
}
}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/AppSearchSchema.java b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSchema.java
new file mode 100644
index 0000000..7e5f187
--- /dev/null
+++ b/apex/appsearch/framework/java/android/app/appsearch/AppSearchSchema.java
@@ -0,0 +1,426 @@
+/*
+ * 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.appsearch;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.google.android.icing.proto.PropertyConfigProto;
+import com.google.android.icing.proto.SchemaProto;
+import com.google.android.icing.proto.SchemaTypeConfigProto;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Representation of the AppSearch Schema.
+ *
+ * <p>The schema is the set of document types, properties, and config (like tokenization type)
+ * understood by AppSearch for this app.
+ *
+ * @hide
+ */
+public final class AppSearchSchema {
+ private final SchemaProto mProto;
+
+ private AppSearchSchema(SchemaProto proto) {
+ mProto = proto;
+ }
+
+ /** Creates a new {@link AppSearchSchema.Builder}. */
+ @NonNull
+ public static AppSearchSchema.Builder newBuilder() {
+ return new AppSearchSchema.Builder();
+ }
+
+ /** Creates a new {@link SchemaType.Builder}. */
+ @NonNull
+ public static SchemaType.Builder newSchemaTypeBuilder(@NonNull String typeName) {
+ return new SchemaType.Builder(typeName);
+ }
+
+ /** Creates a new {@link PropertyConfig.Builder}. */
+ @NonNull
+ public static PropertyConfig.Builder newPropertyBuilder(@NonNull String propertyName) {
+ return new PropertyConfig.Builder(propertyName);
+ }
+
+ /** Creates a new {@link IndexingConfig.Builder}. */
+ @NonNull
+ public static IndexingConfig.Builder newIndexingConfigBuilder() {
+ return new IndexingConfig.Builder();
+ }
+
+ /**
+ * Returns the schema proto populated by the {@link AppSearchSchema} builders.
+ * @hide
+ */
+ @NonNull
+ @VisibleForTesting
+ public SchemaProto getProto() {
+ return mProto;
+ }
+
+ /** Builder for {@link AppSearchSchema objects}. */
+ public static final class Builder {
+ private final SchemaProto.Builder mProtoBuilder = SchemaProto.newBuilder();
+
+ private Builder() {}
+
+ /** Adds a supported type to this app's AppSearch schema. */
+ @NonNull
+ public AppSearchSchema.Builder addType(@NonNull SchemaType schemaType) {
+ mProtoBuilder.addTypes(schemaType.mProto);
+ return this;
+ }
+
+ /**
+ * Constructs a new {@link AppSearchSchema} from the contents of this builder.
+ *
+ * <p>After calling this method, the builder must no longer be used.
+ */
+ @NonNull
+ public AppSearchSchema build() {
+ return new AppSearchSchema(mProtoBuilder.build());
+ }
+ }
+
+ /**
+ * Represents a type of a document.
+ *
+ * <p>For example, an e-mail message or a music recording could be a schema type.
+ */
+ public static final class SchemaType {
+ private final SchemaTypeConfigProto mProto;
+
+ private SchemaType(SchemaTypeConfigProto proto) {
+ mProto = proto;
+ }
+
+ /** Builder for {@link SchemaType} objects. */
+ public static final class Builder {
+ private final SchemaTypeConfigProto.Builder mProtoBuilder =
+ SchemaTypeConfigProto.newBuilder();
+
+ private Builder(@NonNull String typeName) {
+ mProtoBuilder.setSchemaType(typeName);
+ }
+
+ /** Adds a property to the given type. */
+ @NonNull
+ public SchemaType.Builder addProperty(@NonNull PropertyConfig propertyConfig) {
+ mProtoBuilder.addProperties(propertyConfig.mProto);
+ return this;
+ }
+
+ /**
+ * Constructs a new {@link SchemaType} from the contents of this builder.
+ *
+ * <p>After calling this method, the builder must no longer be used.
+ */
+ @NonNull
+ public SchemaType build() {
+ return new SchemaType(mProtoBuilder.build());
+ }
+ }
+ }
+
+ /**
+ * Configuration for a single property (field) of a document type.
+ *
+ * <p>For example, an {@code EmailMessage} would be a type and the {@code subject} would be
+ * a property.
+ */
+ public static final class PropertyConfig {
+ /** Physical data-types of the contents of the property. */
+ // NOTE: The integer values of these constants must match the proto enum constants in
+ // com.google.android.icing.proto.PropertyConfigProto.DataType.Code.
+ @IntDef(prefix = {"DATA_TYPE_"}, value = {
+ DATA_TYPE_STRING,
+ DATA_TYPE_INT64,
+ DATA_TYPE_DOUBLE,
+ DATA_TYPE_BOOLEAN,
+ DATA_TYPE_BYTES,
+ DATA_TYPE_DOCUMENT,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DataType {}
+
+ public static final int DATA_TYPE_STRING = 1;
+ public static final int DATA_TYPE_INT64 = 2;
+ public static final int DATA_TYPE_DOUBLE = 3;
+ public static final int DATA_TYPE_BOOLEAN = 4;
+
+ /** Unstructured BLOB. */
+ public static final int DATA_TYPE_BYTES = 5;
+
+ /**
+ * Indicates that the property itself is an Document, making it part a hierarchical
+ * Document schema. Any property using this DataType MUST have a valid
+ * {@code schemaType}.
+ */
+ public static final int DATA_TYPE_DOCUMENT = 6;
+
+ /** The cardinality of the property (whether it is required, optional or repeated). */
+ // NOTE: The integer values of these constants must match the proto enum constants in
+ // com.google.android.icing.proto.PropertyConfigProto.Cardinality.Code.
+ @IntDef(prefix = {"CARDINALITY_"}, value = {
+ CARDINALITY_REPEATED,
+ CARDINALITY_OPTIONAL,
+ CARDINALITY_REQUIRED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Cardinality {}
+
+ /** Any number of items (including zero) [0...*]. */
+ public static final int CARDINALITY_REPEATED = 1;
+
+ /** Zero or one value [0,1]. */
+ public static final int CARDINALITY_OPTIONAL = 2;
+
+ /** Exactly one value [1]. */
+ public static final int CARDINALITY_REQUIRED = 3;
+
+ private final PropertyConfigProto mProto;
+
+ private PropertyConfig(PropertyConfigProto proto) {
+ mProto = proto;
+ }
+
+ /**
+ * Builder for {@link PropertyConfig}.
+ *
+ * <p>The following properties must be set, or {@link PropertyConfig} construction will
+ * fail:
+ * <ul>
+ * <li>dataType
+ * <li>cardinality
+ * </ul>
+ *
+ * <p>In addition, if {@code schemaType} is {@link #DATA_TYPE_DOCUMENT}, {@code schemaType}
+ * is also required.
+ */
+ public static final class Builder {
+ private final PropertyConfigProto.Builder mProtoBuilder =
+ PropertyConfigProto.newBuilder();
+
+ private Builder(String propertyName) {
+ mProtoBuilder.setPropertyName(propertyName);
+ }
+
+ /**
+ * Type of data the property contains (e.g. string, int, bytes, etc).
+ *
+ * <p>This property must be set.
+ */
+ @NonNull
+ public PropertyConfig.Builder setDataType(@DataType int dataType) {
+ PropertyConfigProto.DataType.Code dataTypeProto =
+ PropertyConfigProto.DataType.Code.forNumber(dataType);
+ if (dataTypeProto == null) {
+ throw new IllegalArgumentException("Invalid dataType: " + dataType);
+ }
+ mProtoBuilder.setDataType(dataTypeProto);
+ return this;
+ }
+
+ /**
+ * The logical schema-type of the contents of this property.
+ *
+ * <p>Only required when {@link #setDataType(int)} is set to
+ * {@link #DATA_TYPE_DOCUMENT}. Otherwise, it is ignored.
+ */
+ @NonNull
+ public PropertyConfig.Builder setSchemaType(@NonNull String schemaType) {
+ mProtoBuilder.setSchemaType(schemaType);
+ return this;
+ }
+
+ /**
+ * The cardinality of the property (whether it is optional, required or repeated).
+ *
+ * <p>This property must be set.
+ */
+ @NonNull
+ public PropertyConfig.Builder setCardinality(@Cardinality int cardinality) {
+ PropertyConfigProto.Cardinality.Code cardinalityProto =
+ PropertyConfigProto.Cardinality.Code.forNumber(cardinality);
+ if (cardinalityProto == null) {
+ throw new IllegalArgumentException("Invalid cardinality: " + cardinality);
+ }
+ mProtoBuilder.setCardinality(cardinalityProto);
+ return this;
+ }
+
+ /**
+ * Configures how this property should be indexed.
+ *
+ * <p>If this is not supplied, the property will not be indexed at all.
+ */
+ @NonNull
+ public PropertyConfig.Builder setIndexingConfig(
+ @NonNull IndexingConfig indexingConfig) {
+ mProtoBuilder.setIndexingConfig(indexingConfig.mProto);
+ return this;
+ }
+
+ /**
+ * Constructs a new {@link PropertyConfig} from the contents of this builder.
+ *
+ * <p>After calling this method, the builder must no longer be used.
+ *
+ * @throws IllegalSchemaException If the property is not correctly populated (e.g.
+ * missing {@code dataType}).
+ */
+ @NonNull
+ public PropertyConfig build() {
+ if (mProtoBuilder.getDataType() == PropertyConfigProto.DataType.Code.UNKNOWN) {
+ throw new IllegalSchemaException("Missing field: dataType");
+ }
+ if (mProtoBuilder.getSchemaType().isEmpty()
+ && mProtoBuilder.getDataType()
+ == PropertyConfigProto.DataType.Code.DOCUMENT) {
+ throw new IllegalSchemaException(
+ "Missing field: schemaType (required for configs with "
+ + "dataType = DOCUMENT)");
+ }
+ if (mProtoBuilder.getCardinality()
+ == PropertyConfigProto.Cardinality.Code.UNKNOWN) {
+ throw new IllegalSchemaException("Missing field: cardinality");
+ }
+ return new PropertyConfig(mProtoBuilder.build());
+ }
+ }
+ }
+
+ /** Configures how a property should be indexed so that it can be retrieved by queries. */
+ public static final class IndexingConfig {
+ /** Encapsulates the configurations on how AppSearch should query/index these terms. */
+ // NOTE: The integer values of these constants must match the proto enum constants in
+ // com.google.android.icing.proto.TermMatchType.Code.
+ @IntDef(prefix = {"TERM_MATCH_TYPE_"}, value = {
+ TERM_MATCH_TYPE_UNKNOWN,
+ TERM_MATCH_TYPE_EXACT_ONLY,
+ TERM_MATCH_TYPE_PREFIX,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TermMatchType {}
+
+ /**
+ * Content in this property will not be tokenized or indexed.
+ *
+ * <p>Useful if the data type is not made up of terms (e.g.
+ * {@link PropertyConfig#DATA_TYPE_DOCUMENT} or {@link PropertyConfig#DATA_TYPE_BYTES}
+ * type). All the properties inside the nested property won't be indexed regardless of the
+ * value of {@code termMatchType} for the nested properties.
+ */
+ public static final int TERM_MATCH_TYPE_UNKNOWN = 0;
+
+ /**
+ * Content in this property should only be returned for queries matching the exact tokens
+ * appearing in this property.
+ *
+ * <p>Ex. A property with "fool" should NOT match a query for "foo".
+ */
+ public static final int TERM_MATCH_TYPE_EXACT_ONLY = 1;
+
+ /**
+ * Content in this property should be returned for queries that are either exact matches or
+ * query matches of the tokens appearing in this property.
+ *
+ * <p>Ex. A property with "fool" <b>should</b> match a query for "foo".
+ */
+ public static final int TERM_MATCH_TYPE_PREFIX = 2;
+
+ /** Configures how tokens should be extracted from this property. */
+ // NOTE: The integer values of these constants must match the proto enum constants in
+ // com.google.android.icing.proto.IndexingConfig.TokenizerType.Code.
+ @IntDef(prefix = {"TOKENIZER_TYPE_"}, value = {
+ TOKENIZER_TYPE_NONE,
+ TOKENIZER_TYPE_PLAIN,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TokenizerType {}
+
+ /**
+ * It is only valid for tokenizer_type to be 'NONE' if the data type is
+ * {@link PropertyConfig#DATA_TYPE_DOCUMENT}.
+ */
+ public static final int TOKENIZER_TYPE_NONE = 0;
+
+ /** Tokenization for plain text. */
+ public static final int TOKENIZER_TYPE_PLAIN = 1;
+
+ private final com.google.android.icing.proto.IndexingConfig mProto;
+
+ private IndexingConfig(com.google.android.icing.proto.IndexingConfig proto) {
+ mProto = proto;
+ }
+
+ /**
+ * Builder for {@link IndexingConfig} objects.
+ *
+ * <p>You may skip adding an {@link IndexingConfig} for a property, which is equivalent to
+ * an {@link IndexingConfig} having {@code termMatchType} equal to
+ * {@link #TERM_MATCH_TYPE_UNKNOWN}. In this case the property will not be indexed.
+ */
+ public static final class Builder {
+ private final com.google.android.icing.proto.IndexingConfig.Builder mProtoBuilder =
+ com.google.android.icing.proto.IndexingConfig.newBuilder();
+
+ private Builder() {}
+
+ /** Configures how the content of this property should be matched in the index. */
+ @NonNull
+ public IndexingConfig.Builder setTermMatchType(@TermMatchType int termMatchType) {
+ com.google.android.icing.proto.TermMatchType.Code termMatchTypeProto =
+ com.google.android.icing.proto.TermMatchType.Code.forNumber(termMatchType);
+ if (termMatchTypeProto == null) {
+ throw new IllegalArgumentException("Invalid termMatchType: " + termMatchType);
+ }
+ mProtoBuilder.setTermMatchType(termMatchTypeProto);
+ return this;
+ }
+
+ /** Configures how this property should be tokenized (split into words). */
+ @NonNull
+ public IndexingConfig.Builder setTokenizerType(@TokenizerType int tokenizerType) {
+ com.google.android.icing.proto.IndexingConfig.TokenizerType.Code
+ tokenizerTypeProto =
+ com.google.android.icing.proto.IndexingConfig
+ .TokenizerType.Code.forNumber(tokenizerType);
+ if (tokenizerTypeProto == null) {
+ throw new IllegalArgumentException("Invalid tokenizerType: " + tokenizerType);
+ }
+ mProtoBuilder.setTokenizerType(tokenizerTypeProto);
+ return this;
+ }
+
+ /**
+ * Constructs a new {@link IndexingConfig} from the contents of this builder.
+ *
+ * <p>After calling this method, the builder must no longer be used.
+ */
+ @NonNull
+ public IndexingConfig build() {
+ return new IndexingConfig(mProtoBuilder.build());
+ }
+ }
+ }
+}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl
index f0f4f51..fc83d8c 100644
--- a/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl
+++ b/apex/appsearch/framework/java/android/app/appsearch/IAppSearchManager.aidl
@@ -14,6 +14,19 @@
* limitations under the License.
*/
package android.app.appsearch;
+
+import com.android.internal.infra.AndroidFuture;
+
/** {@hide} */
interface IAppSearchManager {
+ /**
+ * Sets the schema.
+ *
+ * @param schemaProto serialized SchemaProto
+ * @param callback {@link AndroidFuture}<{@link Void}>. Will be completed with
+ * {@code null} upon successful completion of the setSchema call, or completed exceptionally
+ * if setSchema fails.
+ */
+ void setSchema(in byte[] schemaProto, in AndroidFuture callback);
+ void put(in byte[] documentBytes, in AndroidFuture callback);
}
diff --git a/apex/appsearch/framework/java/android/app/appsearch/IllegalSchemaException.java b/apex/appsearch/framework/java/android/app/appsearch/IllegalSchemaException.java
new file mode 100644
index 0000000..f9e528c
--- /dev/null
+++ b/apex/appsearch/framework/java/android/app/appsearch/IllegalSchemaException.java
@@ -0,0 +1,36 @@
+/*
+ * 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.appsearch;
+
+import android.annotation.NonNull;
+
+/**
+ * Indicates that a {@link android.app.appsearch.AppSearchSchema} has logical inconsistencies such
+ * as unpopulated mandatory fields or illegal combinations of parameters.
+ *
+ * @hide
+ */
+public class IllegalSchemaException extends IllegalArgumentException {
+ /**
+ * Constructs a new {@link IllegalSchemaException}.
+ *
+ * @param message A developer-readable description of the issue with the bundle.
+ */
+ public IllegalSchemaException(@NonNull String message) {
+ super(message);
+ }
+}
diff --git a/apex/appsearch/service/Android.bp b/apex/appsearch/service/Android.bp
index 4ebafce8..04f385e 100644
--- a/apex/appsearch/service/Android.bp
+++ b/apex/appsearch/service/Android.bp
@@ -12,16 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
java_library {
- name: "service-appsearch",
- installable: true,
- srcs: [
- "java/**/*.java",
- ],
- libs: [
- "framework",
- "services.core",
- ],
- static_libs: [
- "icing-java-proto-lite",
- ]
+ name: "service-appsearch",
+ installable: true,
+ srcs: ["java/**/*.java"],
+ libs: [
+ "framework",
+ "framework-appsearch",
+ "services.core",
+ ],
+ apex_available: ["com.android.appsearch"],
}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
index 4d44d9d..ce7e04c 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/AppSearchManagerService.java
@@ -17,8 +17,16 @@
import android.app.appsearch.IAppSearchManager;
import android.content.Context;
+import android.os.Binder;
+import android.os.UserHandle;
+import com.android.internal.infra.AndroidFuture;
+import com.android.internal.util.Preconditions;
import com.android.server.SystemService;
+import com.android.server.appsearch.impl.AppSearchImpl;
+import com.android.server.appsearch.impl.ImplInstanceManager;
+
+import com.google.android.icing.proto.SchemaProto;
/**
* TODO(b/142567528): add comments when implement this class
@@ -35,5 +43,32 @@
}
private class Stub extends IAppSearchManager.Stub {
+ @Override
+ public void setSchema(byte[] schemaBytes, AndroidFuture callback) {
+ Preconditions.checkNotNull(schemaBytes);
+ Preconditions.checkNotNull(callback);
+ int callingUid = Binder.getCallingUidOrThrow();
+ int callingUserId = UserHandle.getUserId(callingUid);
+ long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ SchemaProto schema = SchemaProto.parseFrom(schemaBytes);
+ AppSearchImpl impl = ImplInstanceManager.getInstance(getContext(), callingUserId);
+ impl.setSchema(callingUid, schema);
+ callback.complete(null);
+ } catch (Throwable t) {
+ callback.completeExceptionally(t);
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ }
+
+ @Override
+ public void put(byte[] documentBytes, AndroidFuture callback) {
+ try {
+ throw new UnsupportedOperationException("Put document not yet implemented");
+ } catch (Throwable t) {
+ callback.completeExceptionally(t);
+ }
+ }
}
}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/TEST_MAPPING b/apex/appsearch/service/java/com/android/server/appsearch/TEST_MAPPING
index 08811f8..ca5b884 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/TEST_MAPPING
+++ b/apex/appsearch/service/java/com/android/server/appsearch/TEST_MAPPING
@@ -10,6 +10,14 @@
"include-filter": "com.android.server.appsearch"
}
]
+ },
+ {
+ "name": "FrameworksCoreTests",
+ "options": [
+ {
+ "include-filter": "android.app.appsearch"
+ }
+ ]
}
]
}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/impl/AppSearchImpl.java b/apex/appsearch/service/java/com/android/server/appsearch/impl/AppSearchImpl.java
new file mode 100644
index 0000000..7c97b0b
--- /dev/null
+++ b/apex/appsearch/service/java/com/android/server/appsearch/impl/AppSearchImpl.java
@@ -0,0 +1,110 @@
+/*
+ * 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 com.android.server.appsearch.impl;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.Context;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import com.google.android.icing.proto.PropertyConfigProto;
+import com.google.android.icing.proto.SchemaProto;
+import com.google.android.icing.proto.SchemaTypeConfigProto;
+
+/**
+ * Manages interaction with {@link FakeIcing} and other components to implement AppSearch
+ * functionality.
+ */
+public final class AppSearchImpl {
+ private final Context mContext;
+ private final @UserIdInt int mUserId;
+ private final FakeIcing mFakeIcing = new FakeIcing();
+
+ AppSearchImpl(@NonNull Context context, @UserIdInt int userId) {
+ mContext = context;
+ mUserId = userId;
+ }
+
+ /**
+ * Updates the AppSearch schema for this app.
+ *
+ * @param callingUid The uid of the app calling AppSearch.
+ * @param origSchema The schema to set for this app.
+ */
+ public void setSchema(int callingUid, @NonNull SchemaProto origSchema) {
+ // Rewrite schema type names to include the calling app's package and uid.
+ String typePrefix = getTypePrefix(callingUid);
+ SchemaProto.Builder schemaBuilder = origSchema.toBuilder();
+ rewriteSchemaTypes(typePrefix, schemaBuilder);
+
+ // TODO(b/145635424): Save in schema type map
+ // TODO(b/145635424): Apply the schema to Icing and report results
+ }
+
+ /**
+ * Rewrites all types mentioned in the given {@code schemaBuilder} to prepend
+ * {@code typePrefix}.
+ *
+ * @param typePrefix The prefix to add
+ * @param schemaBuilder The schema to mutate
+ */
+ @VisibleForTesting
+ void rewriteSchemaTypes(
+ @NonNull String typePrefix, @NonNull SchemaProto.Builder schemaBuilder) {
+ for (int typeIdx = 0; typeIdx < schemaBuilder.getTypesCount(); typeIdx++) {
+ SchemaTypeConfigProto.Builder typeConfigBuilder =
+ schemaBuilder.getTypes(typeIdx).toBuilder();
+
+ // Rewrite SchemaProto.types.schema_type
+ String newSchemaType = typePrefix + typeConfigBuilder.getSchemaType();
+ typeConfigBuilder.setSchemaType(newSchemaType);
+
+ // Rewrite SchemaProto.types.properties.schema_type
+ for (int propertyIdx = 0;
+ propertyIdx < typeConfigBuilder.getPropertiesCount();
+ propertyIdx++) {
+ PropertyConfigProto.Builder propertyConfigBuilder =
+ typeConfigBuilder.getProperties(propertyIdx).toBuilder();
+ if (!propertyConfigBuilder.getSchemaType().isEmpty()) {
+ String newPropertySchemaType =
+ typePrefix + propertyConfigBuilder.getSchemaType();
+ propertyConfigBuilder.setSchemaType(newPropertySchemaType);
+ typeConfigBuilder.setProperties(propertyIdx, propertyConfigBuilder);
+ }
+ }
+
+ schemaBuilder.setTypes(typeIdx, typeConfigBuilder);
+ }
+ }
+
+ /**
+ * Returns a type prefix in a format like {@code com.example.package@1000/} or
+ * {@code com.example.sharedname:5678@1000/}.
+ */
+ @NonNull
+ private String getTypePrefix(int callingUid) {
+ // For regular apps, this call will return the package name. If callingUid is an
+ // android:sharedUserId, this value may be another type of name and have a :uid suffix.
+ String callingUidName = mContext.getPackageManager().getNameForUid(callingUid);
+ if (callingUidName == null) {
+ // Not sure how this is possible --- maybe app was uninstalled?
+ throw new IllegalStateException("Failed to look up package name for uid " + callingUid);
+ }
+ return callingUidName + "@" + mUserId + "/";
+ }
+}
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java b/apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java
index 3dbb5cf..02a79a1 100644
--- a/apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java
+++ b/apex/appsearch/service/java/com/android/server/appsearch/impl/FakeIcing.java
@@ -36,8 +36,6 @@
* <p>
* Currently, only queries by single exact term are supported. There is no support for persistence,
* namespaces, i18n tokenization, or schema.
- *
- * @hide
*/
public class FakeIcing {
private final AtomicInteger mNextDocId = new AtomicInteger();
diff --git a/apex/appsearch/service/java/com/android/server/appsearch/impl/ImplInstanceManager.java b/apex/appsearch/service/java/com/android/server/appsearch/impl/ImplInstanceManager.java
new file mode 100644
index 0000000..395e30e
--- /dev/null
+++ b/apex/appsearch/service/java/com/android/server/appsearch/impl/ImplInstanceManager.java
@@ -0,0 +1,56 @@
+/*
+ * 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 com.android.server.appsearch.impl;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.util.SparseArray;
+
+/**
+ * Manages the lifecycle of instances of {@link AppSearchImpl}.
+ *
+ * <p>These instances are managed per unique device-user.
+ */
+public final class ImplInstanceManager {
+ private static final SparseArray<AppSearchImpl> sInstances = new SparseArray<>();
+
+ /**
+ * Gets an instance of AppSearchImpl for the given user.
+ *
+ * <p>If no AppSearchImpl instance exists for this user, Icing will be initialized and one will
+ * be created.
+ *
+ * @param context The Android context
+ * @param userId The multi-user userId of the device user calling AppSearch
+ * @return An initialized {@link AppSearchImpl} for this user
+ */
+ @NonNull
+ public static AppSearchImpl getInstance(@NonNull Context context, @UserIdInt int userId) {
+ AppSearchImpl instance = sInstances.get(userId);
+ if (instance == null) {
+ synchronized (ImplInstanceManager.class) {
+ instance = sInstances.get(userId);
+ if (instance == null) {
+ instance = new AppSearchImpl(context, userId);
+ sInstances.put(userId, instance);
+ }
+ }
+ }
+ return instance;
+ }
+}
diff --git a/apex/blobstore/OWNERS b/apex/blobstore/OWNERS
new file mode 100644
index 0000000..8e04399
--- /dev/null
+++ b/apex/blobstore/OWNERS
@@ -0,0 +1,4 @@
+set noparent
+
+sudheersai@google.com
+yamasani@google.com
diff --git a/apex/blobstore/TEST_MAPPING b/apex/blobstore/TEST_MAPPING
new file mode 100644
index 0000000..4dc0c49
--- /dev/null
+++ b/apex/blobstore/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsBlobStoreTestCases"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/apex/jobscheduler/framework/Android.bp b/apex/jobscheduler/framework/Android.bp
index 98bbe82..ec07426 100644
--- a/apex/jobscheduler/framework/Android.bp
+++ b/apex/jobscheduler/framework/Android.bp
@@ -25,5 +25,6 @@
},
libs: [
"framework-minus-apex",
+ "unsupportedappusage",
],
}
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 8b3b3a2..0bb07ca 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -29,7 +29,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ClipData;
import android.content.ComponentName;
import android.net.NetworkRequest;
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
index 42cf17b..ef1351e 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobParameters.java
@@ -18,8 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
-import android.app.job.IJobCallback;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ClipData;
import android.net.Network;
import android.net.Uri;
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobWorkItem.java b/apex/jobscheduler/framework/java/android/app/job/JobWorkItem.java
index c6631fa..0c45cbf 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobWorkItem.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobWorkItem.java
@@ -19,7 +19,7 @@
import static android.app.job.JobInfo.NETWORK_BYTES_UNKNOWN;
import android.annotation.BytesLong;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Intent;
import android.os.Build;
import android.os.Parcel;
diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
index 041825c..6109b71 100644
--- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
@@ -1,5 +1,6 @@
package com.android.server.usage;
+import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.usage.AppStandbyInfo;
import android.app.usage.UsageEvents;
@@ -99,8 +100,18 @@
List<AppStandbyInfo> getAppStandbyBuckets(int userId);
- void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket,
- int reason, long elapsedRealtime, boolean resetTimeout);
+ /**
+ * Changes an app's standby bucket to the provided value. The caller can only set the standby
+ * bucket for a different app than itself.
+ */
+ void setAppStandbyBucket(@NonNull String packageName, int bucket, int userId, int callingUid,
+ int callingPid);
+
+ /**
+ * Changes the app standby bucket for multiple apps at once.
+ */
+ void setAppStandbyBuckets(@NonNull List<AppStandbyInfo> appBuckets, int userId, int callingUid,
+ int callingPid);
void addActiveDeviceAdmin(String adminPkg, int userId);
diff --git a/apex/jobscheduler/service/Android.bp b/apex/jobscheduler/service/Android.bp
index c9d9d6c..69a9fd8 100644
--- a/apex/jobscheduler/service/Android.bp
+++ b/apex/jobscheduler/service/Android.bp
@@ -9,6 +9,7 @@
],
libs: [
+ "app-compat-annotations",
"framework",
"services.core",
],
diff --git a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
index 3f58c72..f8b2f32 100644
--- a/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/DeviceIdleController.java
@@ -761,6 +761,7 @@
@Override
public void onTrigger(TriggerEvent event) {
synchronized (DeviceIdleController.this) {
+ // One_shot sensors (which call onTrigger) are unregistered when onTrigger is called
active = false;
motionLocked();
}
@@ -769,6 +770,9 @@
@Override
public void onSensorChanged(SensorEvent event) {
synchronized (DeviceIdleController.this) {
+ // Since one_shot sensors are unregistered when onTrigger is called, unregister
+ // listeners here so that the MotionListener is in a consistent state when it calls
+ // out to motionLocked.
mSensorManager.unregisterListener(this, mMotionSensor);
active = false;
motionLocked();
diff --git a/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING
new file mode 100644
index 0000000..8fbfb1d
--- /dev/null
+++ b/apex/jobscheduler/service/java/com/android/server/TEST_MAPPING
@@ -0,0 +1,22 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksMockingServicesTests",
+ "file_patterns": [
+ "DeviceIdleController\\.java"
+ ],
+ "options": [
+ {"include-filter": "com.android.server.DeviceIdleControllerTest"},
+ {"exclude-annotation": "androidx.test.filters.FlakyTest"}
+ ]
+ }
+ ],
+ "postsubmit": [
+ {
+ "name": "FrameworksMockingServicesTests",
+ "options": [
+ {"include-filter": "com.android.server"}
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING
new file mode 100644
index 0000000..bc7a7d3
--- /dev/null
+++ b/apex/jobscheduler/service/java/com/android/server/deviceidle/TEST_MAPPING
@@ -0,0 +1,19 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksMockingServicesTests",
+ "options": [
+ {"include-filter": "com.android.server.DeviceIdleControllerTest"},
+ {"exclude-annotation": "androidx.test.filters.FlakyTest"}
+ ]
+ }
+ ],
+ "postsubmit": [
+ {
+ "name": "FrameworksMockingServicesTests",
+ "options": [
+ {"include-filter": "com.android.server"}
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 9310762..102e848 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -37,12 +37,15 @@
import android.app.job.JobWorkItem;
import android.app.usage.UsageStatsManager;
import android.app.usage.UsageStatsManagerInternal;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -54,6 +57,7 @@
import android.os.BatteryStats;
import android.os.BatteryStatsInternal;
import android.os.Binder;
+import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -67,6 +71,7 @@
import android.os.WorkSource;
import android.provider.Settings;
import android.text.format.DateUtils;
+import android.util.ArrayMap;
import android.util.KeyValueListParser;
import android.util.Log;
import android.util.Slog;
@@ -85,6 +90,7 @@
import com.android.server.DeviceIdleInternal;
import com.android.server.FgThread;
import com.android.server.LocalServices;
+import com.android.server.compat.PlatformCompat;
import com.android.server.job.JobSchedulerServiceDumpProto.ActiveJob;
import com.android.server.job.JobSchedulerServiceDumpProto.PendingJob;
import com.android.server.job.controllers.BackgroundJobsController;
@@ -102,6 +108,9 @@
import com.android.server.job.restrictions.ThermalStatusRestriction;
import com.android.server.usage.AppStandbyInternal;
import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
+import com.android.server.utils.quota.Categorizer;
+import com.android.server.utils.quota.Category;
+import com.android.server.utils.quota.CountQuotaTracker;
import libcore.util.EmptyArray;
@@ -145,6 +154,16 @@
/** The maximum number of jobs that we allow an unprivileged app to schedule */
private static final int MAX_JOBS_PER_APP = 100;
+ /**
+ * {@link #schedule(JobInfo)}, {@link #scheduleAsPackage(JobInfo, String, int, String)}, and
+ * {@link #enqueue(JobInfo, JobWorkItem)} will throw a {@link IllegalStateException} if the app
+ * calls the APIs too frequently.
+ */
+ @ChangeId
+ // This means the change will be enabled for target SDK larger than 29 (Q), meaning R and up.
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+ protected static final long CRASH_ON_EXCEEDED_LIMIT = 144363383L;
+
@VisibleForTesting
public static Clock sSystemClock = Clock.systemUTC();
@@ -237,6 +256,10 @@
*/
private final List<JobRestriction> mJobRestrictions;
+ private final CountQuotaTracker mQuotaTracker;
+ private static final String QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG = ".schedulePersisted()";
+ private final PlatformCompat mPlatformCompat;
+
/**
* Queue of pending jobs. The JobServiceContext class will receive jobs from this list
* when ready to execute them.
@@ -276,6 +299,11 @@
final SparseIntArray mBackingUpUids = new SparseIntArray();
/**
+ * Cache of debuggable app status.
+ */
+ final ArrayMap<String, Boolean> mDebuggableApps = new ArrayMap<>();
+
+ /**
* Named indices into standby bucket arrays, for clarity in referring to
* specific buckets' bookkeeping.
*/
@@ -315,6 +343,10 @@
final StateController sc = mControllers.get(controller);
sc.onConstantsUpdatedLocked();
}
+ mQuotaTracker.setEnabled(mConstants.ENABLE_API_QUOTAS);
+ mQuotaTracker.setCountLimit(Category.SINGLE_CATEGORY,
+ mConstants.API_QUOTA_SCHEDULE_COUNT,
+ mConstants.API_QUOTA_SCHEDULE_WINDOW_MS);
} catch (IllegalArgumentException e) {
// Failed to parse the settings string, log this and move on
// with defaults.
@@ -466,6 +498,11 @@
private static final String KEY_CONN_CONGESTION_DELAY_FRAC = "conn_congestion_delay_frac";
private static final String KEY_CONN_PREFETCH_RELAX_FRAC = "conn_prefetch_relax_frac";
private static final String DEPRECATED_KEY_USE_HEARTBEATS = "use_heartbeats";
+ private static final String KEY_ENABLE_API_QUOTAS = "enable_api_quotas";
+ private static final String KEY_API_QUOTA_SCHEDULE_COUNT = "aq_schedule_count";
+ private static final String KEY_API_QUOTA_SCHEDULE_WINDOW_MS = "aq_schedule_window_ms";
+ private static final String KEY_API_QUOTA_SCHEDULE_THROW_EXCEPTION =
+ "aq_schedule_throw_exception";
private static final int DEFAULT_MIN_IDLE_COUNT = 1;
private static final int DEFAULT_MIN_CHARGING_COUNT = 1;
@@ -484,6 +521,10 @@
private static final long DEFAULT_MIN_EXP_BACKOFF_TIME = JobInfo.MIN_BACKOFF_MILLIS;
private static final float DEFAULT_CONN_CONGESTION_DELAY_FRAC = 0.5f;
private static final float DEFAULT_CONN_PREFETCH_RELAX_FRAC = 0.5f;
+ private static final boolean DEFAULT_ENABLE_API_QUOTAS = true;
+ private static final int DEFAULT_API_QUOTA_SCHEDULE_COUNT = 250;
+ private static final long DEFAULT_API_QUOTA_SCHEDULE_WINDOW_MS = MINUTE_IN_MILLIS;
+ private static final boolean DEFAULT_API_QUOTA_SCHEDULE_THROW_EXCEPTION = true;
/**
* Minimum # of idle jobs that must be ready in order to force the JMS to schedule things
@@ -618,6 +659,24 @@
*/
public float CONN_PREFETCH_RELAX_FRAC = DEFAULT_CONN_PREFETCH_RELAX_FRAC;
+ /**
+ * Whether to enable quota limits on APIs.
+ */
+ public boolean ENABLE_API_QUOTAS = DEFAULT_ENABLE_API_QUOTAS;
+ /**
+ * The maximum number of schedule() calls an app can make in a set amount of time.
+ */
+ public int API_QUOTA_SCHEDULE_COUNT = DEFAULT_API_QUOTA_SCHEDULE_COUNT;
+ /**
+ * The time window that {@link #API_QUOTA_SCHEDULE_COUNT} should be evaluated over.
+ */
+ public long API_QUOTA_SCHEDULE_WINDOW_MS = DEFAULT_API_QUOTA_SCHEDULE_WINDOW_MS;
+ /**
+ * Whether to throw an exception when an app hits its schedule quota limit.
+ */
+ public boolean API_QUOTA_SCHEDULE_THROW_EXCEPTION =
+ DEFAULT_API_QUOTA_SCHEDULE_THROW_EXCEPTION;
+
private final KeyValueListParser mParser = new KeyValueListParser(',');
void updateConstantsLocked(String value) {
@@ -678,6 +737,18 @@
DEFAULT_CONN_CONGESTION_DELAY_FRAC);
CONN_PREFETCH_RELAX_FRAC = mParser.getFloat(KEY_CONN_PREFETCH_RELAX_FRAC,
DEFAULT_CONN_PREFETCH_RELAX_FRAC);
+
+ ENABLE_API_QUOTAS = mParser.getBoolean(KEY_ENABLE_API_QUOTAS,
+ DEFAULT_ENABLE_API_QUOTAS);
+ // Set a minimum value on the quota limit so it's not so low that it interferes with
+ // legitimate use cases.
+ API_QUOTA_SCHEDULE_COUNT = Math.max(250,
+ mParser.getInt(KEY_API_QUOTA_SCHEDULE_COUNT, DEFAULT_API_QUOTA_SCHEDULE_COUNT));
+ API_QUOTA_SCHEDULE_WINDOW_MS = mParser.getDurationMillis(
+ KEY_API_QUOTA_SCHEDULE_WINDOW_MS, DEFAULT_API_QUOTA_SCHEDULE_WINDOW_MS);
+ API_QUOTA_SCHEDULE_THROW_EXCEPTION = mParser.getBoolean(
+ KEY_API_QUOTA_SCHEDULE_THROW_EXCEPTION,
+ DEFAULT_API_QUOTA_SCHEDULE_THROW_EXCEPTION);
}
void dump(IndentingPrintWriter pw) {
@@ -716,6 +787,12 @@
pw.printPair(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println();
pw.printPair(KEY_CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC).println();
+ pw.printPair(KEY_ENABLE_API_QUOTAS, ENABLE_API_QUOTAS).println();
+ pw.printPair(KEY_API_QUOTA_SCHEDULE_COUNT, API_QUOTA_SCHEDULE_COUNT).println();
+ pw.printPair(KEY_API_QUOTA_SCHEDULE_WINDOW_MS, API_QUOTA_SCHEDULE_WINDOW_MS).println();
+ pw.printPair(KEY_API_QUOTA_SCHEDULE_THROW_EXCEPTION,
+ API_QUOTA_SCHEDULE_THROW_EXCEPTION).println();
+
pw.decreaseIndent();
}
@@ -746,6 +823,12 @@
proto.write(ConstantsProto.MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME);
proto.write(ConstantsProto.CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC);
proto.write(ConstantsProto.CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC);
+
+ proto.write(ConstantsProto.ENABLE_API_QUOTAS, ENABLE_API_QUOTAS);
+ proto.write(ConstantsProto.API_QUOTA_SCHEDULE_COUNT, API_QUOTA_SCHEDULE_COUNT);
+ proto.write(ConstantsProto.API_QUOTA_SCHEDULE_WINDOW_MS, API_QUOTA_SCHEDULE_WINDOW_MS);
+ proto.write(ConstantsProto.API_QUOTA_SCHEDULE_THROW_EXCEPTION,
+ API_QUOTA_SCHEDULE_THROW_EXCEPTION);
}
}
@@ -847,6 +930,7 @@
for (int c = 0; c < mControllers.size(); ++c) {
mControllers.get(c).onAppRemovedLocked(pkgName, pkgUid);
}
+ mDebuggableApps.remove(pkgName);
}
}
} else if (Intent.ACTION_USER_REMOVED.equals(action)) {
@@ -972,6 +1056,49 @@
public int scheduleAsPackage(JobInfo job, JobWorkItem work, int uId, String packageName,
int userId, String tag) {
+ if (job.isPersisted()) {
+ // Only limit schedule calls for persisted jobs.
+ final String pkg =
+ packageName == null ? job.getService().getPackageName() : packageName;
+ if (!mQuotaTracker.isWithinQuota(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG)) {
+ Slog.e(TAG, userId + "-" + pkg + " has called schedule() too many times");
+ // TODO(b/145551233): attempt to restrict app
+ if (mConstants.API_QUOTA_SCHEDULE_THROW_EXCEPTION
+ && mPlatformCompat.isChangeEnabledByPackageName(
+ CRASH_ON_EXCEEDED_LIMIT, pkg, userId)) {
+ final boolean isDebuggable;
+ synchronized (mLock) {
+ if (!mDebuggableApps.containsKey(packageName)) {
+ try {
+ final ApplicationInfo appInfo = AppGlobals.getPackageManager()
+ .getApplicationInfo(pkg, 0, userId);
+ if (appInfo != null) {
+ mDebuggableApps.put(packageName,
+ (appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0);
+ } else {
+ return JobScheduler.RESULT_FAILURE;
+ }
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+ isDebuggable = mDebuggableApps.get(packageName);
+ }
+ if (isDebuggable) {
+ // Only throw the exception for debuggable apps.
+ throw new IllegalStateException(
+ "schedule()/enqueue() called more than "
+ + mQuotaTracker.getLimit(Category.SINGLE_CATEGORY)
+ + " times in the past "
+ + mQuotaTracker.getWindowSizeMs(Category.SINGLE_CATEGORY)
+ + "ms");
+ }
+ }
+ return JobScheduler.RESULT_FAILURE;
+ }
+ mQuotaTracker.noteEvent(userId, pkg, QUOTA_TRACKER_SCHEDULE_PERSISTED_TAG);
+ }
+
try {
if (ActivityManager.getService().isAppStartModeDisabled(uId,
job.getService().getPackageName())) {
@@ -1296,6 +1423,12 @@
// Set up the app standby bucketing tracker
mStandbyTracker = new StandbyTracker();
mUsageStats = LocalServices.getService(UsageStatsManagerInternal.class);
+ mPlatformCompat =
+ (PlatformCompat) ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE);
+ mQuotaTracker = new CountQuotaTracker(context, Categorizer.SINGLE_CATEGORIZER);
+ mQuotaTracker.setCountLimit(Category.SINGLE_CATEGORY,
+ mConstants.API_QUOTA_SCHEDULE_COUNT,
+ mConstants.API_QUOTA_SCHEDULE_WINDOW_MS);
AppStandbyInternal appStandby = LocalServices.getService(AppStandbyInternal.class);
appStandby.addListener(mStandbyTracker);
@@ -2745,7 +2878,7 @@
return new ParceledListSlice<>(snapshots);
}
}
- };
+ }
// Shell command infrastructure: run the given job immediately
int executeRunCommand(String pkgName, int userId, int jobId, boolean force) {
@@ -2968,6 +3101,10 @@
return 0;
}
+ void resetScheduleQuota() {
+ mQuotaTracker.clear();
+ }
+
void triggerDockState(boolean idleState) {
final Intent dockIntent;
if (idleState) {
@@ -3030,6 +3167,9 @@
}
pw.println();
+ mQuotaTracker.dump(pw);
+ pw.println();
+
pw.println("Started users: " + Arrays.toString(mStartedUsers));
pw.print("Registered ");
pw.print(mJobs.size());
@@ -3217,6 +3357,9 @@
for (int u : mStartedUsers) {
proto.write(JobSchedulerServiceDumpProto.STARTED_USERS, u);
}
+
+ mQuotaTracker.dump(proto, JobSchedulerServiceDumpProto.QUOTA_TRACKER);
+
if (mJobs.size() > 0) {
final List<JobStatus> jobs = mJobs.mJobSet.getAllJobs();
sortJobs(jobs);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
index a5c6c01..6becf04 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerShellCommand.java
@@ -66,6 +66,8 @@
return getJobState(pw);
case "heartbeat":
return doHeartbeat(pw);
+ case "reset-schedule-quota":
+ return resetScheduleQuota(pw);
case "trigger-dock-state":
return triggerDockState(pw);
default:
@@ -344,6 +346,18 @@
return -1;
}
+ private int resetScheduleQuota(PrintWriter pw) throws Exception {
+ checkPermission("reset schedule quota");
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ mInternal.resetScheduleQuota();
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ return 0;
+ }
+
private int triggerDockState(PrintWriter pw) throws Exception {
checkPermission("trigger wireless charging dock state");
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
index 82292cf..b9df30a 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java
@@ -17,7 +17,7 @@
package com.android.server.usage;
import static android.app.usage.UsageStatsManager.REASON_MAIN_DEFAULT;
-import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER;
import static android.app.usage.UsageStatsManager.REASON_MAIN_MASK;
import static android.app.usage.UsageStatsManager.REASON_MAIN_PREDICTED;
import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
@@ -441,7 +441,7 @@
elapsedRealtime, true);
if (idle) {
appUsageHistory.currentBucket = STANDBY_BUCKET_RARE;
- appUsageHistory.bucketingReason = REASON_MAIN_FORCED;
+ appUsageHistory.bucketingReason = REASON_MAIN_FORCED_BY_USER;
} else {
appUsageHistory.currentBucket = STANDBY_BUCKET_ACTIVE;
// This is to pretend that the app was just used, don't freeze the state anymore.
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 2f8b513..eb0b54b 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -17,7 +17,8 @@
package com.android.server.usage;
import static android.app.usage.UsageStatsManager.REASON_MAIN_DEFAULT;
-import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_SYSTEM;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER;
import static android.app.usage.UsageStatsManager.REASON_MAIN_MASK;
import static android.app.usage.UsageStatsManager.REASON_MAIN_PREDICTED;
import static android.app.usage.UsageStatsManager.REASON_MAIN_TIMEOUT;
@@ -47,6 +48,7 @@
import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
+import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.AppGlobals;
@@ -101,7 +103,6 @@
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalServices;
import com.android.server.usage.AppIdleHistory.AppUsageHistory;
-import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
import java.io.File;
import java.io.PrintWriter;
@@ -109,6 +110,7 @@
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
@@ -564,7 +566,7 @@
// If the bucket was forced by the user/developer, leave it alone.
// A usage event will be the only way to bring it out of this forced state
- if (oldMainReason == REASON_MAIN_FORCED) {
+ if (oldMainReason == REASON_MAIN_FORCED_BY_USER) {
return;
}
final int oldBucket = app.currentBucket;
@@ -782,7 +784,7 @@
// Inform listeners if necessary
if (previouslyIdle != stillIdle) {
maybeInformListeners(packageName, userId, elapsedRealtime, standbyBucket,
- REASON_MAIN_FORCED, false);
+ REASON_MAIN_FORCED_BY_USER, false);
if (!stillIdle) {
notifyBatteryStats(packageName, userId, idle);
}
@@ -1014,14 +1016,66 @@
}
}
- @VisibleForTesting
- void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket,
- int reason, long elapsedRealtime) {
- setAppStandbyBucket(packageName, userId, newBucket, reason, elapsedRealtime, false);
+ @Override
+ public void setAppStandbyBucket(@NonNull String packageName, int bucket, int userId,
+ int callingUid, int callingPid) {
+ setAppStandbyBuckets(
+ Collections.singletonList(new AppStandbyInfo(packageName, bucket)),
+ userId, callingUid, callingPid);
}
@Override
- public void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket,
+ public void setAppStandbyBuckets(@NonNull List<AppStandbyInfo> appBuckets, int userId,
+ int callingUid, int callingPid) {
+ userId = ActivityManager.handleIncomingUser(
+ callingPid, callingUid, userId, false, true, "setAppStandbyBucket", null);
+ final boolean shellCaller = callingUid == Process.ROOT_UID
+ || callingUid == Process.SHELL_UID;
+ final int reason;
+ // The Settings app runs in the system UID but in a separate process. Assume
+ // things coming from other processes are due to the user.
+ if ((UserHandle.isSameApp(callingUid, Process.SYSTEM_UID) && callingPid != Process.myPid())
+ || shellCaller) {
+ reason = REASON_MAIN_FORCED_BY_USER;
+ } else if (UserHandle.isCore(callingUid)) {
+ reason = REASON_MAIN_FORCED_BY_SYSTEM;
+ } else {
+ reason = REASON_MAIN_PREDICTED;
+ }
+ final int packageFlags = PackageManager.MATCH_ANY_USER
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE;
+ final int numApps = appBuckets.size();
+ final long elapsedRealtime = mInjector.elapsedRealtime();
+ for (int i = 0; i < numApps; ++i) {
+ final AppStandbyInfo bucketInfo = appBuckets.get(i);
+ final String packageName = bucketInfo.mPackageName;
+ final int bucket = bucketInfo.mStandbyBucket;
+ if (bucket < STANDBY_BUCKET_ACTIVE || bucket > STANDBY_BUCKET_NEVER) {
+ throw new IllegalArgumentException("Cannot set the standby bucket to " + bucket);
+ }
+ final int packageUid = mInjector.getPackageManagerInternal()
+ .getPackageUid(packageName, packageFlags, userId);
+ // Caller cannot set their own standby state
+ if (packageUid == callingUid) {
+ throw new IllegalArgumentException("Cannot set your own standby bucket");
+ }
+ if (packageUid < 0) {
+ throw new IllegalArgumentException(
+ "Cannot set standby bucket for non existent package (" + packageName + ")");
+ }
+ setAppStandbyBucket(packageName, userId, bucket, reason, elapsedRealtime, shellCaller);
+ }
+ }
+
+ @VisibleForTesting
+ void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket,
+ int reason) {
+ setAppStandbyBucket(
+ packageName, userId, newBucket, reason, mInjector.elapsedRealtime(), false);
+ }
+
+ private void setAppStandbyBucket(String packageName, int userId, @StandbyBuckets int newBucket,
int reason, long elapsedRealtime, boolean resetTimeout) {
synchronized (mAppIdleLock) {
// If the package is not installed, don't allow the bucket to be set.
@@ -1043,7 +1097,11 @@
}
// If the bucket was forced, don't allow prediction to override
- if ((app.bucketingReason & REASON_MAIN_MASK) == REASON_MAIN_FORCED && predicted) return;
+ if (predicted
+ && ((app.bucketingReason & REASON_MAIN_MASK) == REASON_MAIN_FORCED_BY_USER
+ || (app.bucketingReason & REASON_MAIN_MASK) == REASON_MAIN_FORCED_BY_SYSTEM)) {
+ return;
+ }
// If the bucket is required to stay in a higher state for a specified duration, don't
// override unless the duration has passed
@@ -1444,6 +1502,10 @@
mBatteryStats.noteEvent(event, packageName, uid);
}
+ PackageManagerInternal getPackageManagerInternal() {
+ return mPackageManagerInternal;
+ }
+
boolean isPackageEphemeral(int userId, String packageName) {
return mPackageManagerInternal.isPackageEphemeral(userId, packageName);
}
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
new file mode 100644
index 0000000..cf70878
--- /dev/null
+++ b/apex/jobscheduler/service/java/com/android/server/usage/TEST_MAPPING
@@ -0,0 +1,19 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {"include-filter": "com.android.server.usage"},
+ {"exclude-annotation": "androidx.test.filters.FlakyTest"}
+ ]
+ }
+ ],
+ "postsubmit": [
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {"include-filter": "com.android.server.usage"}
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/apex/media/Android.bp b/apex/media/framework/Android.bp
similarity index 94%
rename from apex/media/Android.bp
rename to apex/media/framework/Android.bp
index 6bd0086..18382a4 100644
--- a/apex/media/Android.bp
+++ b/apex/media/framework/Android.bp
@@ -55,6 +55,13 @@
jarjar_rules: "jarjar_rules.txt",
plugins: ["java_api_finder"],
+
+ hostdex: true, // for hiddenapi check
+ visibility: ["//frameworks/av/apex:__subpackages__"],
+ apex_available: [
+ "com.android.media",
+ "test_com.android.media",
+ ],
}
filegroup {
diff --git a/apex/media/jarjar_rules.txt b/apex/media/framework/jarjar_rules.txt
similarity index 100%
rename from apex/media/jarjar_rules.txt
rename to apex/media/framework/jarjar_rules.txt
diff --git a/apex/media/java/android/media/BufferingParams.java b/apex/media/framework/java/android/media/BufferingParams.java
similarity index 100%
rename from apex/media/java/android/media/BufferingParams.java
rename to apex/media/framework/java/android/media/BufferingParams.java
diff --git a/apex/media/java/android/media/Controller2Link.aidl b/apex/media/framework/java/android/media/Controller2Link.aidl
similarity index 100%
rename from apex/media/java/android/media/Controller2Link.aidl
rename to apex/media/framework/java/android/media/Controller2Link.aidl
diff --git a/apex/media/java/android/media/Controller2Link.java b/apex/media/framework/java/android/media/Controller2Link.java
similarity index 100%
rename from apex/media/java/android/media/Controller2Link.java
rename to apex/media/framework/java/android/media/Controller2Link.java
diff --git a/apex/media/java/android/media/DataSourceCallback.java b/apex/media/framework/java/android/media/DataSourceCallback.java
similarity index 100%
rename from apex/media/java/android/media/DataSourceCallback.java
rename to apex/media/framework/java/android/media/DataSourceCallback.java
diff --git a/apex/media/java/android/media/IMediaController2.aidl b/apex/media/framework/java/android/media/IMediaController2.aidl
similarity index 100%
rename from apex/media/java/android/media/IMediaController2.aidl
rename to apex/media/framework/java/android/media/IMediaController2.aidl
diff --git a/apex/media/java/android/media/IMediaSession2.aidl b/apex/media/framework/java/android/media/IMediaSession2.aidl
similarity index 100%
rename from apex/media/java/android/media/IMediaSession2.aidl
rename to apex/media/framework/java/android/media/IMediaSession2.aidl
diff --git a/apex/media/java/android/media/IMediaSession2Service.aidl b/apex/media/framework/java/android/media/IMediaSession2Service.aidl
similarity index 100%
rename from apex/media/java/android/media/IMediaSession2Service.aidl
rename to apex/media/framework/java/android/media/IMediaSession2Service.aidl
diff --git a/apex/media/java/android/media/MediaConstants.java b/apex/media/framework/java/android/media/MediaConstants.java
similarity index 100%
rename from apex/media/java/android/media/MediaConstants.java
rename to apex/media/framework/java/android/media/MediaConstants.java
diff --git a/apex/media/java/android/media/MediaController2.java b/apex/media/framework/java/android/media/MediaController2.java
similarity index 99%
rename from apex/media/java/android/media/MediaController2.java
rename to apex/media/framework/java/android/media/MediaController2.java
index c3dd3fe..d059c67 100644
--- a/apex/media/java/android/media/MediaController2.java
+++ b/apex/media/framework/java/android/media/MediaController2.java
@@ -141,6 +141,9 @@
// Note: unbindService() throws IllegalArgumentException when it's called twice.
return;
}
+ if (DEBUG) {
+ Log.d(TAG, "closing " + this);
+ }
mClosed = true;
if (mServiceConnection != null) {
// Note: This should be called even when the bindService() has returned false.
diff --git a/apex/media/java/android/media/MediaParser.java b/apex/media/framework/java/android/media/MediaParser.java
similarity index 100%
rename from apex/media/java/android/media/MediaParser.java
rename to apex/media/framework/java/android/media/MediaParser.java
diff --git a/apex/media/java/android/media/MediaSession2.java b/apex/media/framework/java/android/media/MediaSession2.java
similarity index 100%
rename from apex/media/java/android/media/MediaSession2.java
rename to apex/media/framework/java/android/media/MediaSession2.java
diff --git a/apex/media/java/android/media/MediaSession2Service.java b/apex/media/framework/java/android/media/MediaSession2Service.java
similarity index 100%
rename from apex/media/java/android/media/MediaSession2Service.java
rename to apex/media/framework/java/android/media/MediaSession2Service.java
diff --git a/apex/media/java/android/media/ProxyDataSourceCallback.java b/apex/media/framework/java/android/media/ProxyDataSourceCallback.java
similarity index 100%
rename from apex/media/java/android/media/ProxyDataSourceCallback.java
rename to apex/media/framework/java/android/media/ProxyDataSourceCallback.java
diff --git a/apex/media/java/android/media/RoutingDelegate.java b/apex/media/framework/java/android/media/RoutingDelegate.java
similarity index 100%
rename from apex/media/java/android/media/RoutingDelegate.java
rename to apex/media/framework/java/android/media/RoutingDelegate.java
diff --git a/apex/media/java/android/media/Session2Command.aidl b/apex/media/framework/java/android/media/Session2Command.aidl
similarity index 100%
rename from apex/media/java/android/media/Session2Command.aidl
rename to apex/media/framework/java/android/media/Session2Command.aidl
diff --git a/apex/media/java/android/media/Session2Command.java b/apex/media/framework/java/android/media/Session2Command.java
similarity index 100%
rename from apex/media/java/android/media/Session2Command.java
rename to apex/media/framework/java/android/media/Session2Command.java
diff --git a/apex/media/java/android/media/Session2CommandGroup.java b/apex/media/framework/java/android/media/Session2CommandGroup.java
similarity index 100%
rename from apex/media/java/android/media/Session2CommandGroup.java
rename to apex/media/framework/java/android/media/Session2CommandGroup.java
diff --git a/apex/media/java/android/media/Session2Link.java b/apex/media/framework/java/android/media/Session2Link.java
similarity index 100%
rename from apex/media/java/android/media/Session2Link.java
rename to apex/media/framework/java/android/media/Session2Link.java
diff --git a/apex/media/java/android/media/Session2Token.aidl b/apex/media/framework/java/android/media/Session2Token.aidl
similarity index 100%
rename from apex/media/java/android/media/Session2Token.aidl
rename to apex/media/framework/java/android/media/Session2Token.aidl
diff --git a/apex/media/java/android/media/Session2Token.java b/apex/media/framework/java/android/media/Session2Token.java
similarity index 100%
rename from apex/media/java/android/media/Session2Token.java
rename to apex/media/framework/java/android/media/Session2Token.java
diff --git a/apex/permission/Android.bp b/apex/permission/Android.bp
index 5945fb3..d746ea6 100644
--- a/apex/permission/Android.bp
+++ b/apex/permission/Android.bp
@@ -22,6 +22,11 @@
name: "com.android.permission-defaults",
key: "com.android.permission.key",
certificate: ":com.android.permission.certificate",
+ java_libs: [
+ "framework-permission",
+ "service-permission",
+ ],
+ apps: ["PermissionController"],
}
apex_key {
diff --git a/apex/permission/framework/Android.bp b/apex/permission/framework/Android.bp
new file mode 100644
index 0000000..8b03da3
--- /dev/null
+++ b/apex/permission/framework/Android.bp
@@ -0,0 +1,60 @@
+// 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.
+
+filegroup {
+ name: "framework-permission-sources",
+ srcs: [
+ "java/**/*.java",
+ "java/**/*.aidl",
+ ],
+ path: "java",
+}
+
+java_library {
+ name: "framework-permission",
+ srcs: [
+ ":framework-permission-sources",
+ ],
+ sdk_version: "system_current",
+ apex_available: [
+ "com.android.permission",
+ "test_com.android.permission",
+ ],
+ hostdex: true,
+ installable: true,
+ visibility: [
+ "//frameworks/base/apex/permission:__subpackages__",
+ ],
+}
+
+droidstubs {
+ name: "framework-permission-stubs-sources",
+ srcs: [
+ ":framework-annotations",
+ ":framework-permission-sources",
+ ],
+ sdk_version: "system_current",
+ defaults: [
+ "framework-module-stubs-defaults-systemapi",
+ ],
+}
+
+java_library {
+ name: "framework-permission-stubs",
+ srcs: [
+ ":framework-permission-stubs-sources",
+ ],
+ sdk_version: "system_current",
+ installable: false,
+}
diff --git a/media/java/android/media/RouteSessionInfo.aidl b/apex/permission/framework/java/android/permission/PermissionState.java
similarity index 80%
copy from media/java/android/media/RouteSessionInfo.aidl
copy to apex/permission/framework/java/android/permission/PermissionState.java
index fb5d836..e810db8 100644
--- a/media/java/android/media/RouteSessionInfo.aidl
+++ b/apex/permission/framework/java/android/permission/PermissionState.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 The Android Open Source Project
+ * 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.
@@ -14,6 +14,9 @@
* limitations under the License.
*/
-package android.media;
+package android.permission;
-parcelable RouteSessionInfo;
+/**
+ * @hide
+ */
+public class PermissionState {}
diff --git a/apex/permission/service/Android.bp b/apex/permission/service/Android.bp
new file mode 100644
index 0000000..972b362
--- /dev/null
+++ b/apex/permission/service/Android.bp
@@ -0,0 +1,29 @@
+// 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.
+
+java_library {
+ name: "service-permission",
+ srcs: [
+ "java/**/*.java",
+ ],
+ sdk_version: "system_current",
+ libs: [
+ "framework-permission",
+ ],
+ apex_available: [
+ "com.android.permission",
+ "test_com.android.permission",
+ ],
+ installable: true,
+}
diff --git a/media/java/android/media/RouteSessionInfo.aidl b/apex/permission/service/java/com/android/server/permission/RuntimePermissionPersistence.java
similarity index 74%
copy from media/java/android/media/RouteSessionInfo.aidl
copy to apex/permission/service/java/com/android/server/permission/RuntimePermissionPersistence.java
index fb5d836..a534e22 100644
--- a/media/java/android/media/RouteSessionInfo.aidl
+++ b/apex/permission/service/java/com/android/server/permission/RuntimePermissionPersistence.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 The Android Open Source Project
+ * 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.
@@ -14,6 +14,9 @@
* limitations under the License.
*/
-package android.media;
+package com.android.server.permission;
-parcelable RouteSessionInfo;
+/**
+ * Persistence for runtime permissions.
+ */
+public class RuntimePermissionPersistence {}
diff --git a/apex/sdkext/TEST_MAPPING b/apex/sdkext/TEST_MAPPING
deleted file mode 100644
index 91947f3..0000000
--- a/apex/sdkext/TEST_MAPPING
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "presubmit": [
- {
- "name": "CtsSdkExtTestCases"
- }
- ]
-}
diff --git a/apex/sdkext/framework/Android.bp b/apex/sdkext/framework/Android.bp
deleted file mode 100644
index a50dc3d..0000000
--- a/apex/sdkext/framework/Android.bp
+++ /dev/null
@@ -1,71 +0,0 @@
-// 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 {
- default_visibility: [ ":__pkg__" ]
-}
-
-filegroup {
- name: "framework-sdkext-sources",
- srcs: [
- "java/**/*.java",
- ],
- path: "java",
- visibility: [ "//frameworks/base:__pkg__" ] // For the "global" stubs.
-}
-
-java_library {
- name: "framework-sdkext",
- srcs: [ ":framework-sdkext-sources" ],
- sdk_version: "system_current",
- libs: [ "framework-annotations-lib" ],
- permitted_packages: [ "android.os.ext" ],
- installable: true,
- visibility: [ "//frameworks/base/apex/sdkext:__pkg__" ],
-}
-
-droidstubs {
- name: "framework-sdkext-droidstubs-publicapi",
- defaults: [
- "framework-sdkext-stubs-defaults",
- "framework-module-stubs-defaults-publicapi",
- ]
-}
-
-droidstubs {
- name: "framework-sdkext-droidstubs-systemapi",
- defaults: [
- "framework-sdkext-stubs-defaults",
- "framework-module-stubs-defaults-systemapi",
- ]
-}
-
-stubs_defaults {
- name: "framework-sdkext-stubs-defaults",
- srcs: [
- ":framework-sdkext-sources",
- ":framework-annotations",
- ],
- sdk_version: "system_current",
-}
-
-java_library {
- name: "framework-sdkext-stubs-systemapi",
- srcs: [":framework-sdkext-droidstubs-systemapi"],
- sdk_version: "system_current",
- visibility: [
- "//frameworks/base:__pkg__", // Framework
- "//frameworks/base/apex/sdkext:__pkg__", // sdkext SDK
- ]
-}
diff --git a/apex/sdkext/Android.bp b/apex/sdkextensions/Android.bp
similarity index 83%
rename from apex/sdkext/Android.bp
rename to apex/sdkextensions/Android.bp
index f62f167..4c5c2b2 100644
--- a/apex/sdkext/Android.bp
+++ b/apex/sdkextensions/Android.bp
@@ -18,21 +18,26 @@
apex {
name: "com.android.sdkext",
- manifest: "manifest.json",
+ defaults: [ "com.android.sdkext-defaults" ],
binaries: [ "derive_sdk" ],
- java_libs: [ "framework-sdkext" ],
+ prebuilts: [ "cur_sdkinfo" ],
+ manifest: "manifest.json",
+}
+
+apex_defaults {
+ name: "com.android.sdkext-defaults",
+ java_libs: [ "framework-sdkextensions" ],
prebuilts: [
- "com.android.sdkext.ldconfig",
- "cur_sdkinfo",
- "derive_sdk.rc",
+ "com.android.sdkext.ldconfig",
+ "derive_sdk.rc",
],
key: "com.android.sdkext.key",
certificate: ":com.android.sdkext.certificate",
}
sdk {
- name: "sdkext-sdk",
- java_header_libs: [ "framework-sdkext-stubs-systemapi" ],
+ name: "sdkextensions-sdk",
+ java_header_libs: [ "framework-sdkextensions-stubs-systemapi" ],
}
apex_key {
diff --git a/apex/sdkext/OWNERS b/apex/sdkextensions/OWNERS
similarity index 100%
rename from apex/sdkext/OWNERS
rename to apex/sdkextensions/OWNERS
diff --git a/apex/sdkextensions/TEST_MAPPING b/apex/sdkextensions/TEST_MAPPING
new file mode 100644
index 0000000..4e18833
--- /dev/null
+++ b/apex/sdkextensions/TEST_MAPPING
@@ -0,0 +1,10 @@
+{
+ "presubmit": [
+ {
+ "name": "CtsSdkExtensionsTestCases"
+ },
+ {
+ "name": "apiextensions_e2e_tests"
+ }
+ ]
+}
diff --git a/apex/sdkext/com.android.sdkext.avbpubkey b/apex/sdkextensions/com.android.sdkext.avbpubkey
similarity index 100%
rename from apex/sdkext/com.android.sdkext.avbpubkey
rename to apex/sdkextensions/com.android.sdkext.avbpubkey
Binary files differ
diff --git a/apex/sdkext/com.android.sdkext.pem b/apex/sdkextensions/com.android.sdkext.pem
similarity index 100%
rename from apex/sdkext/com.android.sdkext.pem
rename to apex/sdkextensions/com.android.sdkext.pem
diff --git a/apex/sdkext/com.android.sdkext.pk8 b/apex/sdkextensions/com.android.sdkext.pk8
similarity index 100%
rename from apex/sdkext/com.android.sdkext.pk8
rename to apex/sdkextensions/com.android.sdkext.pk8
Binary files differ
diff --git a/apex/sdkext/com.android.sdkext.x509.pem b/apex/sdkextensions/com.android.sdkext.x509.pem
similarity index 100%
rename from apex/sdkext/com.android.sdkext.x509.pem
rename to apex/sdkextensions/com.android.sdkext.x509.pem
diff --git a/apex/sdkext/derive_sdk/Android.bp b/apex/sdkextensions/derive_sdk/Android.bp
similarity index 63%
rename from apex/sdkext/derive_sdk/Android.bp
rename to apex/sdkextensions/derive_sdk/Android.bp
index c4e3c29..cf49902 100644
--- a/apex/sdkext/derive_sdk/Android.bp
+++ b/apex/sdkextensions/derive_sdk/Android.bp
@@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-cc_binary {
- name: "derive_sdk",
+cc_defaults {
+ name: "derive_sdk-defaults",
srcs: [
"derive_sdk.cpp",
"sdk.proto",
@@ -30,6 +30,24 @@
],
}
+cc_binary {
+ name: "derive_sdk",
+ defaults: [ "derive_sdk-defaults" ],
+ apex_available: [ "com.android.sdkext" ],
+ visibility: [ "//frameworks/base/apex/sdkextensions" ]
+}
+
+// Work around testing using a 64-bit test suite on 32-bit test device by
+// using a prefer32 version of derive_sdk in testing.
+cc_binary {
+ name: "derive_sdk_prefer32",
+ defaults: [ "derive_sdk-defaults" ],
+ compile_multilib: "prefer32",
+ stem: "derive_sdk",
+ apex_available: [ "test_com.android.sdkext" ],
+ visibility: [ "//frameworks/base/apex/sdkextensions/testing" ]
+}
+
prebuilt_etc {
name: "derive_sdk.rc",
src: "derive_sdk.rc",
diff --git a/apex/sdkext/derive_sdk/derive_sdk.cpp b/apex/sdkextensions/derive_sdk/derive_sdk.cpp
similarity index 97%
rename from apex/sdkext/derive_sdk/derive_sdk.cpp
rename to apex/sdkextensions/derive_sdk/derive_sdk.cpp
index 0a97116..6fb7ef4 100644
--- a/apex/sdkext/derive_sdk/derive_sdk.cpp
+++ b/apex/sdkextensions/derive_sdk/derive_sdk.cpp
@@ -26,7 +26,7 @@
#include <android-base/logging.h>
#include <android-base/properties.h>
-#include "frameworks/base/apex/sdkext/derive_sdk/sdk.pb.h"
+#include "frameworks/base/apex/sdkextensions/derive_sdk/sdk.pb.h"
using com::android::sdkext::proto::SdkVersion;
diff --git a/apex/sdkext/derive_sdk/derive_sdk.rc b/apex/sdkextensions/derive_sdk/derive_sdk.rc
similarity index 100%
rename from apex/sdkext/derive_sdk/derive_sdk.rc
rename to apex/sdkextensions/derive_sdk/derive_sdk.rc
diff --git a/apex/sdkext/derive_sdk/sdk.proto b/apex/sdkextensions/derive_sdk/sdk.proto
similarity index 100%
rename from apex/sdkext/derive_sdk/sdk.proto
rename to apex/sdkextensions/derive_sdk/sdk.proto
diff --git a/apex/sdkextensions/framework/Android.bp b/apex/sdkextensions/framework/Android.bp
new file mode 100644
index 0000000..dd17473
--- /dev/null
+++ b/apex/sdkextensions/framework/Android.bp
@@ -0,0 +1,79 @@
+// 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 {
+ default_visibility: [ ":__pkg__" ]
+}
+
+filegroup {
+ name: "framework-sdkextensions-sources",
+ srcs: [
+ "java/**/*.java",
+ ],
+ path: "java",
+ visibility: [ "//frameworks/base" ] // For the "global" stubs.
+}
+
+java_library {
+ name: "framework-sdkextensions",
+ srcs: [ ":framework-sdkextensions-sources" ],
+ sdk_version: "system_current",
+ libs: [ "framework-annotations-lib" ],
+ permitted_packages: [ "android.os.ext" ],
+ installable: true,
+ visibility: [
+ "//frameworks/base/apex/sdkextensions",
+ "//frameworks/base/apex/sdkextensions/testing",
+ ],
+ hostdex: true, // for hiddenapi check
+ apex_available: [
+ "com.android.sdkext",
+ "test_com.android.sdkext",
+ ],
+}
+
+droidstubs {
+ name: "framework-sdkextensions-droidstubs-publicapi",
+ defaults: [
+ "framework-sdkextensions-stubs-defaults",
+ "framework-module-stubs-defaults-publicapi",
+ ]
+}
+
+droidstubs {
+ name: "framework-sdkextensions-droidstubs-systemapi",
+ defaults: [
+ "framework-sdkextensions-stubs-defaults",
+ "framework-module-stubs-defaults-systemapi",
+ ]
+}
+
+stubs_defaults {
+ name: "framework-sdkextensions-stubs-defaults",
+ srcs: [
+ ":framework-sdkextensions-sources",
+ ":framework-annotations",
+ ],
+ sdk_version: "system_current",
+}
+
+java_library {
+ name: "framework-sdkextensions-stubs-systemapi",
+ srcs: [":framework-sdkextensions-droidstubs-systemapi"],
+ sdk_version: "system_current",
+ visibility: [
+ "//frameworks/base", // Framework
+ "//frameworks/base/apex/sdkextensions", // sdkextensions SDK
+ ]
+}
diff --git a/apex/sdkext/framework/java/android/os/ext/SdkExtensions.java b/apex/sdkextensions/framework/java/android/os/ext/SdkExtensions.java
similarity index 100%
rename from apex/sdkext/framework/java/android/os/ext/SdkExtensions.java
rename to apex/sdkextensions/framework/java/android/os/ext/SdkExtensions.java
diff --git a/apex/sdkext/framework/java/android/os/ext/package.html b/apex/sdkextensions/framework/java/android/os/ext/package.html
similarity index 100%
rename from apex/sdkext/framework/java/android/os/ext/package.html
rename to apex/sdkextensions/framework/java/android/os/ext/package.html
diff --git a/apex/sdkext/gen_sdkinfo.py b/apex/sdkextensions/gen_sdkinfo.py
similarity index 100%
rename from apex/sdkext/gen_sdkinfo.py
rename to apex/sdkextensions/gen_sdkinfo.py
diff --git a/apex/sdkext/ld.config.txt b/apex/sdkextensions/ld.config.txt
similarity index 91%
rename from apex/sdkext/ld.config.txt
rename to apex/sdkextensions/ld.config.txt
index b447068..dcc69b8 100644
--- a/apex/sdkext/ld.config.txt
+++ b/apex/sdkextensions/ld.config.txt
@@ -1,10 +1,10 @@
# Copyright (C) 2019 The Android Open Source Project
#
-# Bionic loader config file for the sdkext apex.
+# Bionic loader config file for the sdkextensions apex.
-dir.sdkext = /apex/com.android.sdkext/bin/
+dir.sdkextensions = /apex/com.android.sdkext/bin/
-[sdkext]
+[sdkextensions]
additional.namespaces = platform
namespace.default.isolated = true
diff --git a/apex/sdkext/manifest.json b/apex/sdkextensions/manifest.json
similarity index 100%
rename from apex/sdkext/manifest.json
rename to apex/sdkextensions/manifest.json
diff --git a/apex/sdkext/sdk.proto b/apex/sdkextensions/sdk.proto
similarity index 100%
rename from apex/sdkext/sdk.proto
rename to apex/sdkextensions/sdk.proto
diff --git a/apex/sdkextensions/testing/Android.bp b/apex/sdkextensions/testing/Android.bp
new file mode 100644
index 0000000..e6451cc
--- /dev/null
+++ b/apex/sdkextensions/testing/Android.bp
@@ -0,0 +1,46 @@
+// 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.
+
+apex {
+ name: "test_com.android.sdkext",
+ visibility: [ "//system/apex/tests" ],
+ defaults: ["com.android.sdkext-defaults"],
+ manifest: "test_manifest.json",
+ prebuilts: [ "sdkinfo_45" ],
+ file_contexts: ":com.android.sdkext-file_contexts",
+ installable: false, // Should never be installed on the systemimage
+ multilib: {
+ prefer32: {
+ binaries: ["derive_sdk_prefer32"],
+ },
+ },
+ // The automated test infra ends up building this apex for 64+32-bit and
+ // then installs it on a 32-bit-only device. Work around this weirdness
+ // by preferring 32-bit.
+ compile_multilib: "prefer32",
+}
+
+genrule {
+ name: "sdkinfo_45_src",
+ out: [ "sdkinfo.binarypb" ],
+ tools: [ "gen_sdkinfo" ],
+ cmd: "$(location) -v 45 -o $(out)",
+}
+
+prebuilt_etc {
+ name: "sdkinfo_45",
+ src: ":sdkinfo_45_src",
+ filename: "sdkinfo.binarypb",
+ installable: false,
+}
diff --git a/apex/sdkextensions/testing/test_manifest.json b/apex/sdkextensions/testing/test_manifest.json
new file mode 100644
index 0000000..1b4a2b0
--- /dev/null
+++ b/apex/sdkextensions/testing/test_manifest.json
@@ -0,0 +1,4 @@
+{
+ "name": "com.android.sdkext",
+ "version": 2147483647
+}
diff --git a/apex/statsd/aidl/Android.bp b/apex/statsd/aidl/Android.bp
index aed6ad9..f8325d4 100644
--- a/apex/statsd/aidl/Android.bp
+++ b/apex/statsd/aidl/Android.bp
@@ -18,6 +18,7 @@
filegroup {
name: "statsd_aidl",
srcs: [
+ "android/os/IPendingIntentRef.aidl",
"android/os/IPullAtomCallback.aidl",
"android/os/IPullAtomResultReceiver.aidl",
"android/os/IStatsCompanionService.aidl",
diff --git a/apex/statsd/aidl/android/os/IPendingIntentRef.aidl b/apex/statsd/aidl/android/os/IPendingIntentRef.aidl
new file mode 100644
index 0000000..6b9e467
--- /dev/null
+++ b/apex/statsd/aidl/android/os/IPendingIntentRef.aidl
@@ -0,0 +1,46 @@
+/*
+ * 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.os;
+
+import android.os.StatsDimensionsValue;
+
+/**
+ * Binder interface to hold a PendingIntent for StatsCompanionService.
+ * {@hide}
+ */
+interface IPendingIntentRef {
+
+ /**
+ * Sends a broadcast to the specified PendingIntent that it should getData now.
+ * This should be only called from StatsCompanionService.
+ */
+ oneway void sendDataBroadcast(long lastReportTimeNs);
+
+ /**
+ * Send a broadcast to the specified PendingIntent notifying it that the list of active configs
+ * has changed. This should be only called from StatsCompanionService.
+ */
+ oneway void sendActiveConfigsChangedBroadcast(in long[] configIds);
+
+ /**
+ * Send a broadcast to the specified PendingIntent, along with the other information
+ * specified. This should only be called from StatsCompanionService.
+ */
+ oneway void sendSubscriberBroadcast(long configUid, long configId, long subscriptionId,
+ long subscriptionRuleId, in String[] cookies,
+ in StatsDimensionsValue dimensionsValue);
+}
\ No newline at end of file
diff --git a/apex/statsd/aidl/android/os/IStatsCompanionService.aidl b/apex/statsd/aidl/android/os/IStatsCompanionService.aidl
index 5a6118e..99b9d39 100644
--- a/apex/statsd/aidl/android/os/IStatsCompanionService.aidl
+++ b/apex/statsd/aidl/android/os/IStatsCompanionService.aidl
@@ -17,7 +17,6 @@
package android.os;
import android.os.IPullAtomCallback;
-import android.os.StatsDimensionsValue;
import android.os.StatsLogEventWrapper;
/**
@@ -66,31 +65,6 @@
/** Pull the specified data. Results will be sent to statsd when complete. */
StatsLogEventWrapper[] pullData(int pullCode);
- /** Send a broadcast to the specified PendingIntent's as IBinder that it should getData now. */
- oneway void sendDataBroadcast(in IBinder intentSender, long lastReportTimeNs);
-
- /**
- * Send a broadcast to the specified PendingIntent's as IBinder notifying it that the list
- * of active configs has changed.
- */
- oneway void sendActiveConfigsChangedBroadcast(in IBinder intentSender, in long[] configIds);
-
- /**
- * Requests StatsCompanionService to send a broadcast using the given intentSender
- * (which should cast to an IIntentSender), along with the other information specified.
- */
- oneway void sendSubscriberBroadcast(in IBinder intentSender, long configUid, long configId,
- long subscriptionId, long subscriptionRuleId,
- in String[] cookies,
- in StatsDimensionsValue dimensionsValue);
-
/** Tells StatsCompaionService to grab the uid map snapshot and send it to statsd. */
oneway void triggerUidSnapshot();
-
- /** Tells StatsCompanionService to tell statsd to register a puller for the given atom id */
- oneway void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs,
- in int[] additiveFields, IPullAtomCallback pullerCallback);
-
- /** Tells StatsCompanionService to tell statsd to unregister a puller for the given atom id */
- oneway void unregisterPullAtomCallback(int atomTag);
}
diff --git a/apex/statsd/aidl/android/os/IStatsManagerService.aidl b/apex/statsd/aidl/android/os/IStatsManagerService.aidl
index 45ba3a2..4a259f5 100644
--- a/apex/statsd/aidl/android/os/IStatsManagerService.aidl
+++ b/apex/statsd/aidl/android/os/IStatsManagerService.aidl
@@ -17,6 +17,7 @@
package android.os;
import android.app.PendingIntent;
+import android.os.IPullAtomCallback;
/**
* Binder interface to communicate with the Java-based statistics service helper.
@@ -30,12 +31,19 @@
* memory consumed by the metrics for this configuration approach the pre-defined limits. There
* can be at most one listener per config key.
*
- * Requires Manifest.permission.DUMP.
+ * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
*/
- void setDataFetchOperation(long configKey, in PendingIntent pendingIntent,
+ void setDataFetchOperation(long configId, in PendingIntent pendingIntent,
in String packageName);
/**
+ * Removes the data fetch operation for the specified configuration.
+ *
+ * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
+ */
+ void removeDataFetchOperation(long configId, in String packageName);
+
+ /**
* Registers the given pending intent for this packagename. This intent is invoked when the
* active status of any of the configs sent by this package changes and will contain a list of
* config ids that are currently active. It also returns the list of configs that are currently
@@ -46,6 +54,13 @@
long[] setActiveConfigsChangedOperation(in PendingIntent pendingIntent, in String packageName);
/**
+ * Removes the active configs changed operation for the specified package name.
+ *
+ * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
+ */
+ void removeActiveConfigsChangedOperation(in String packageName);
+
+ /**
* Set the PendingIntent to be used when broadcasting subscriber
* information to the given subscriberId within the given config.
*
@@ -58,8 +73,64 @@
* This function can only be called by the owner (uid) of the config. It must be called each
* time statsd starts. Later calls overwrite previous calls; only one PendingIntent is stored.
*
- * Requires Manifest.permission.DUMP.
+ * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
*/
void setBroadcastSubscriber(long configKey, long subscriberId, in PendingIntent pendingIntent,
in String packageName);
-}
\ No newline at end of file
+
+ /**
+ * Undoes setBroadcastSubscriber() for the (configKey, subscriberId) pair.
+ * Any broadcasts associated with subscriberId will henceforth not be sent.
+ * No-op if this (configKey, subscriberId) pair was not associated with an PendingIntent.
+ *
+ * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
+ */
+ void unsetBroadcastSubscriber(long configKey, long subscriberId, in String packageName);
+
+ /**
+ * Returns the most recently registered experiment IDs.
+ *
+ * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
+ */
+ long[] getRegisteredExperimentIds();
+
+ /**
+ * Fetches metadata across statsd. Returns byte array representing wire-encoded proto.
+ *
+ * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
+ */
+ byte[] getMetadata(in String packageName);
+
+ /**
+ * Fetches data for the specified configuration key. Returns a byte array representing proto
+ * wire-encoded of ConfigMetricsReportList.
+ *
+ * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
+ */
+ byte[] getData(in long key, in String packageName);
+
+ /**
+ * Sets a configuration with the specified config id and subscribes to updates for this
+ * configuration id. Broadcasts will be sent if this configuration needs to be collected.
+ * The configuration must be a wire-encoded StatsdConfig. The receiver for this data is
+ * registered in a separate function.
+ *
+ * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
+ */
+ void addConfiguration(in long configId, in byte[] config, in String packageName);
+
+ /**
+ * Removes the configuration with the matching config id. No-op if this config id does not
+ * exist.
+ *
+ * Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
+ */
+ void removeConfiguration(in long configId, in String packageName);
+
+ /** Tell StatsManagerService to register a puller for the given atom tag with statsd. */
+ oneway void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs,
+ in int[] additiveFields, IPullAtomCallback pullerCallback);
+
+ /** Tell StatsManagerService to unregister the pulller for the given atom tag from statsd. */
+ oneway void unregisterPullAtomCallback(int atomTag);
+}
diff --git a/apex/statsd/aidl/android/os/IStatsd.aidl b/apex/statsd/aidl/android/os/IStatsd.aidl
index cce79fa..0ecf2f0 100644
--- a/apex/statsd/aidl/android/os/IStatsd.aidl
+++ b/apex/statsd/aidl/android/os/IStatsd.aidl
@@ -17,6 +17,7 @@
package android.os;
import android.os.IStatsPullerCallback;
+import android.os.IPendingIntentRef;
import android.os.IPullAtomCallback;
import android.os.ParcelFileDescriptor;
@@ -88,24 +89,24 @@
*
* Requires Manifest.permission.DUMP.
*/
- byte[] getData(in long key, in String packageName);
+ byte[] getData(in long key, int callingUid);
/**
* Fetches metadata across statsd. Returns byte array representing wire-encoded proto.
*
* Requires Manifest.permission.DUMP.
*/
- byte[] getMetadata(in String packageName);
+ byte[] getMetadata();
/**
- * Sets a configuration with the specified config key and subscribes to updates for this
+ * Sets a configuration with the specified config id and subscribes to updates for this
* configuration key. Broadcasts will be sent if this configuration needs to be collected.
* The configuration must be a wire-encoded StatsdConfig. The receiver for this data is
* registered in a separate function.
*
* Requires Manifest.permission.DUMP.
*/
- void addConfiguration(in long configKey, in byte[] config, in String packageName);
+ void addConfiguration(in long configId, in byte[] config, in int callingUid);
/**
* Registers the given pending intent for this config key. This intent is invoked when the
@@ -114,14 +115,15 @@
*
* Requires Manifest.permission.DUMP.
*/
- void setDataFetchOperation(long configKey, in IBinder intentSender, in String packageName);
+ void setDataFetchOperation(long configId, in IPendingIntentRef pendingIntentRef,
+ int callingUid);
/**
* Removes the data fetch operation for the specified configuration.
*
* Requires Manifest.permission.DUMP.
*/
- void removeDataFetchOperation(long configKey, in String packageName);
+ void removeDataFetchOperation(long configId, int callingUid);
/**
* Registers the given pending intent for this packagename. This intent is invoked when the
@@ -131,52 +133,49 @@
*
* Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
*/
- long[] setActiveConfigsChangedOperation(in IBinder intentSender, in String packageName);
+ long[] setActiveConfigsChangedOperation(in IPendingIntentRef pendingIntentRef, int callingUid);
/**
* Removes the active configs changed operation for the specified package name.
*
* Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS.
*/
- void removeActiveConfigsChangedOperation(in String packageName);
+ void removeActiveConfigsChangedOperation(int callingUid);
/**
- * Removes the configuration with the matching config key. No-op if this config key does not
+ * Removes the configuration with the matching config id. No-op if this config id does not
* exist.
*
* Requires Manifest.permission.DUMP.
*/
- void removeConfiguration(in long configKey, in String packageName);
+ void removeConfiguration(in long configId, in int callingUid);
/**
- * Set the IIntentSender (i.e. PendingIntent) to be used when broadcasting subscriber
+ * Set the PendingIntentRef to be used when broadcasting subscriber
* information to the given subscriberId within the given config.
*
- * Suppose that the calling uid has added a config with key configKey, and that in this config
+ * Suppose that the calling uid has added a config with key configId, and that in this config
* it is specified that when a particular anomaly is detected, a broadcast should be sent to
- * a BroadcastSubscriber with id subscriberId. This function links the given intentSender with
- * that subscriberId (for that config), so that this intentSender is used to send the broadcast
+ * a BroadcastSubscriber with id subscriberId. This function links the given pendingIntent with
+ * that subscriberId (for that config), so that this pendingIntent is used to send the broadcast
* when the anomaly is detected.
*
* This function can only be called by the owner (uid) of the config. It must be called each
- * time statsd starts. Later calls overwrite previous calls; only one intentSender is stored.
- *
- * intentSender must be convertible into an IntentSender using IntentSender(IBinder)
- * and cannot be null.
+ * time statsd starts. Later calls overwrite previous calls; only one pendingIntent is stored.
*
* Requires Manifest.permission.DUMP.
*/
- void setBroadcastSubscriber(long configKey, long subscriberId, in IBinder intentSender,
- in String packageName);
+ void setBroadcastSubscriber(long configId, long subscriberId, in IPendingIntentRef pir,
+ int callingUid);
/**
- * Undoes setBroadcastSubscriber() for the (configKey, subscriberId) pair.
+ * Undoes setBroadcastSubscriber() for the (configId, subscriberId) pair.
* Any broadcasts associated with subscriberId will henceforth not be sent.
- * No-op if this (configKey, subsriberId) pair was not associated with an IntentSender.
+ * No-op if this (configKey, subscriberId) pair was not associated with an PendingIntentRef.
*
* Requires Manifest.permission.DUMP.
*/
- void unsetBroadcastSubscriber(long configKey, long subscriberId, in String packageName);
+ void unsetBroadcastSubscriber(long configId, long subscriberId, int callingUid);
/**
* Apps can send an atom via this application breadcrumb with the specified label and state for
@@ -213,12 +212,17 @@
*
* Requires Manifest.permission.DUMP and Manifest.permission.PACKAGE_USAGE_STATS
*/
- oneway void unregisterPullerCallback(int atomTag, String packageName);
+ oneway void unregisterPullerCallback(int atomTag, String packageName);
- /**
- * Unregisters any pullAtomCallback for the given uid/atom.
- */
- oneway void unregisterPullAtomCallback(int uid, int atomTag);
+ /**
+ * Unregisters any pullAtomCallback for the given uid/atom.
+ */
+ oneway void unregisterPullAtomCallback(int uid, int atomTag);
+
+ /**
+ * Unregisters any pullAtomCallback for the given atom.
+ */
+ oneway void unregisterNativePullAtomCallback(int atomTag);
/**
* The install requires staging.
diff --git a/apex/statsd/framework/Android.bp b/apex/statsd/framework/Android.bp
index a2b0577..0b46645a 100644
--- a/apex/statsd/framework/Android.bp
+++ b/apex/statsd/framework/Android.bp
@@ -37,7 +37,16 @@
// TODO(b/146230220): Use framework-system-stubs instead.
"android_system_stubs_current",
],
- // TODO:(b/146210774): Add apex_available field.
+ hostdex: true, // for hiddenapi check
+ visibility: [
+ "//frameworks/base/apex/statsd:__subpackages__",
+ //TODO(b/146167933) remove this when framework is built with framework-statsd-stubs
+ "//frameworks/base",
+ ],
+ apex_available: [
+ "com.android.os.statsd",
+ "test_com.android.os.statsd",
+ ],
}
droidstubs {
diff --git a/apex/statsd/framework/java/android/util/StatsEvent.java b/apex/statsd/framework/java/android/util/StatsEvent.java
index c765945..1a45c4a 100644
--- a/apex/statsd/framework/java/android/util/StatsEvent.java
+++ b/apex/statsd/framework/java/android/util/StatsEvent.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.os.SystemClock;
import com.android.internal.annotations.GuardedBy;
@@ -51,6 +52,7 @@
* </pre>
* @hide
**/
+@SystemApi
public final class StatsEvent {
// Type Ids.
/**
@@ -270,6 +272,8 @@
/**
* Recycle resources used by this StatsEvent object.
* No actions should be taken on this StatsEvent after release() is called.
+ *
+ * @hide
**/
public void release() {
if (mBuffer != null) {
@@ -363,16 +367,6 @@
}
/**
- * Sets the timestamp in nanos for this StatsEvent.
- **/
- @VisibleForTesting
- @NonNull
- public Builder setTimestampNs(final long timestampNs) {
- mTimestampNs = timestampNs;
- return this;
- }
-
- /**
* Write a boolean field to this StatsEvent.
**/
@NonNull
@@ -500,14 +494,14 @@
**/
@NonNull
public Builder writeKeyValuePairs(
- @NonNull final SparseIntArray intMap,
- @NonNull final SparseLongArray longMap,
- @NonNull final SparseArray<String> stringMap,
- @NonNull final SparseArray<Float> floatMap) {
- final int intMapSize = intMap.size();
- final int longMapSize = longMap.size();
- final int stringMapSize = stringMap.size();
- final int floatMapSize = floatMap.size();
+ @Nullable final SparseIntArray intMap,
+ @Nullable final SparseLongArray longMap,
+ @Nullable final SparseArray<String> stringMap,
+ @Nullable final SparseArray<Float> floatMap) {
+ final int intMapSize = null == intMap ? 0 : intMap.size();
+ final int longMapSize = null == longMap ? 0 : longMap.size();
+ final int stringMapSize = null == stringMap ? 0 : stringMap.size();
+ final int floatMapSize = null == floatMap ? 0 : floatMap.size();
final int totalCount = intMapSize + longMapSize + stringMapSize + floatMapSize;
if (totalCount > MAX_KEY_VALUE_PAIRS) {
diff --git a/apex/statsd/service/Android.bp b/apex/statsd/service/Android.bp
index f3a8989..9103848 100644
--- a/apex/statsd/service/Android.bp
+++ b/apex/statsd/service/Android.bp
@@ -13,4 +13,8 @@
"framework-minus-apex",
"services.core",
],
+ apex_available: [
+ "com.android.os.statsd",
+ "test_com.android.os.statsd",
+ ],
}
diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java
index 71b52e2..4383b50 100644
--- a/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java
+++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java
@@ -16,11 +16,21 @@
package com.android.server.stats;
+import android.app.PendingIntent;
+import android.app.StatsManager;
import android.content.Context;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IPendingIntentRef;
+import android.os.Process;
+import android.os.StatsDimensionsValue;
import android.util.Slog;
import com.android.server.SystemService;
+import java.util.ArrayList;
+import java.util.Arrays;
+
/**
* @hide
*/
@@ -28,6 +38,13 @@
private static final String TAG = "StatsCompanion";
private static final boolean DEBUG = false;
+ static void enforceStatsCompanionPermission(Context context) {
+ if (Binder.getCallingPid() == Process.myPid()) {
+ return;
+ }
+ context.enforceCallingPermission(android.Manifest.permission.STATSCOMPANION, null);
+ }
+
/**
* Lifecycle class for both {@link StatsCompanionService} and {@link StatsManagerService}.
*/
@@ -63,7 +80,97 @@
super.onBootPhase(phase);
if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
mStatsCompanionService.systemReady();
- mStatsManagerService.systemReady();
+ }
+ }
+ }
+
+ /**
+ * Wrapper for {@link PendingIntent}. Allows Statsd to send PendingIntents.
+ */
+ public static class PendingIntentRef extends IPendingIntentRef.Stub {
+
+ private static final String TAG = "PendingIntentRef";
+
+ /**
+ * The last report time is provided with each intent registered to
+ * StatsManager#setFetchReportsOperation. This allows easy de-duping in the receiver if
+ * statsd is requesting the client to retrieve the same statsd data. The last report time
+ * corresponds to the last_report_elapsed_nanos that will provided in the current
+ * ConfigMetricsReport, and this timestamp also corresponds to the
+ * current_report_elapsed_nanos of the most recently obtained ConfigMetricsReport.
+ */
+ private static final String EXTRA_LAST_REPORT_TIME = "android.app.extra.LAST_REPORT_TIME";
+ private static final int CODE_DATA_BROADCAST = 1;
+ private static final int CODE_ACTIVE_CONFIGS_BROADCAST = 1;
+ private static final int CODE_SUBSCRIBER_BROADCAST = 1;
+
+ private final PendingIntent mPendingIntent;
+ private final Context mContext;
+
+ public PendingIntentRef(PendingIntent pendingIntent, Context context) {
+ mPendingIntent = pendingIntent;
+ mContext = context;
+ }
+
+ @Override
+ public void sendDataBroadcast(long lastReportTimeNs) {
+ enforceStatsCompanionPermission(mContext);
+ Intent intent = new Intent();
+ intent.putExtra(EXTRA_LAST_REPORT_TIME, lastReportTimeNs);
+ try {
+ mPendingIntent.send(mContext, CODE_DATA_BROADCAST, intent, null, null);
+ } catch (PendingIntent.CanceledException e) {
+ Slog.w(TAG, "Unable to send PendingIntent");
+ }
+ }
+
+ @Override
+ public void sendActiveConfigsChangedBroadcast(long[] configIds) {
+ enforceStatsCompanionPermission(mContext);
+ Intent intent = new Intent();
+ intent.putExtra(StatsManager.EXTRA_STATS_ACTIVE_CONFIG_KEYS, configIds);
+ try {
+ mPendingIntent.send(mContext, CODE_ACTIVE_CONFIGS_BROADCAST, intent, null, null);
+ if (DEBUG) {
+ Slog.d(TAG, "Sent broadcast with config ids " + Arrays.toString(configIds));
+ }
+ } catch (PendingIntent.CanceledException e) {
+ Slog.w(TAG, "Unable to send active configs changed broadcast using PendingIntent");
+ }
+ }
+
+ @Override
+ public void sendSubscriberBroadcast(long configUid, long configId, long subscriptionId,
+ long subscriptionRuleId, String[] cookies, StatsDimensionsValue dimensionsValue) {
+ enforceStatsCompanionPermission(mContext);
+ Intent intent =
+ new Intent()
+ .putExtra(StatsManager.EXTRA_STATS_CONFIG_UID, configUid)
+ .putExtra(StatsManager.EXTRA_STATS_CONFIG_KEY, configId)
+ .putExtra(StatsManager.EXTRA_STATS_SUBSCRIPTION_ID, subscriptionId)
+ .putExtra(StatsManager.EXTRA_STATS_SUBSCRIPTION_RULE_ID,
+ subscriptionRuleId)
+ .putExtra(StatsManager.EXTRA_STATS_DIMENSIONS_VALUE, dimensionsValue);
+
+ ArrayList<String> cookieList = new ArrayList<>(cookies.length);
+ cookieList.addAll(Arrays.asList(cookies));
+ intent.putStringArrayListExtra(
+ StatsManager.EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES, cookieList);
+
+ if (DEBUG) {
+ Slog.d(TAG,
+ String.format(
+ "Statsd sendSubscriberBroadcast with params {%d %d %d %d %s %s}",
+ configUid, configId, subscriptionId, subscriptionRuleId,
+ Arrays.toString(cookies),
+ dimensionsValue));
+ }
+ try {
+ mPendingIntent.send(mContext, CODE_SUBSCRIBER_BROADCAST, intent, null, null);
+ } catch (PendingIntent.CanceledException e) {
+ Slog.w(TAG,
+ "Unable to send using PendingIntent from uid " + configUid
+ + "; presumably it had been cancelled.");
}
}
}
diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
index e4f7300..8bc14d7 100644
--- a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
+++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
@@ -50,7 +50,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
@@ -76,7 +75,6 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
-import android.os.IPullAtomCallback;
import android.os.IStatsCompanionService;
import android.os.IStatsd;
import android.os.IStoraged;
@@ -85,11 +83,9 @@
import android.os.Looper;
import android.os.ParcelFileDescriptor;
import android.os.Parcelable;
-import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StatFs;
-import android.os.StatsDimensionsValue;
import android.os.StatsLogEventWrapper;
import android.os.SynchronousResultReceiver;
import android.os.SystemClock;
@@ -203,18 +199,6 @@
private static final int PACKAGE_NAME_FIELD_ID = 4;
private static final int INSTALLER_FIELD_ID = 5;
- public static final int CODE_DATA_BROADCAST = 1;
- public static final int CODE_SUBSCRIBER_BROADCAST = 1;
- public static final int CODE_ACTIVE_CONFIGS_BROADCAST = 1;
- /**
- * The last report time is provided with each intent registered to
- * StatsManager#setFetchReportsOperation. This allows easy de-duping in the receiver if
- * statsd is requesting the client to retrieve the same statsd data. The last report time
- * corresponds to the last_report_elapsed_nanos that will provided in the current
- * ConfigMetricsReport, and this timestamp also corresponds to the
- * current_report_elapsed_nanos of the most recently obtained ConfigMetricsReport.
- */
- public static final String EXTRA_LAST_REPORT_TIME = "android.app.extra.LAST_REPORT_TIME";
public static final int DEATH_THRESHOLD = 10;
/**
* Which native processes to snapshot memory for.
@@ -278,71 +262,6 @@
private StatsManagerService mStatsManagerService;
- private static final class PullerKey {
- private final int mUid;
- private final int mAtomTag;
-
- PullerKey(int uid, int atom) {
- mUid = uid;
- mAtomTag = atom;
- }
-
- public int getUid() {
- return mUid;
- }
-
- public int getAtom() {
- return mAtomTag;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mUid, mAtomTag);
- }
-
- @Override
- public boolean equals(Object obj) {
- if (obj instanceof PullerKey) {
- PullerKey other = (PullerKey) obj;
- return this.mUid == other.getUid() && this.mAtomTag == other.getAtom();
- }
- return false;
- }
- }
-
- private static final class PullerValue {
- private final long mCoolDownNs;
- private final long mTimeoutNs;
- private int[] mAdditiveFields;
- private IPullAtomCallback mCallback;
-
- PullerValue(long coolDownNs, long timeoutNs, int[] additiveFields,
- IPullAtomCallback callback) {
- mCoolDownNs = coolDownNs;
- mTimeoutNs = timeoutNs;
- mAdditiveFields = additiveFields;
- mCallback = callback;
- }
-
- public long getCoolDownNs() {
- return mCoolDownNs;
- }
-
- public long getTimeoutNs() {
- return mTimeoutNs;
- }
-
- public int[] getAdditiveFields() {
- return mAdditiveFields;
- }
-
- public IPullAtomCallback getCallback() {
- return mCallback;
- }
- }
-
- private final HashMap<PullerKey, PullerValue> mPullers = new HashMap<>();
-
private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
private WifiManager mWifiManager = null;
@@ -454,72 +373,6 @@
KernelCpuThreadReaderSettingsObserver.getSettingsModifiedReader(mContext);
}
- @Override
- public void sendDataBroadcast(IBinder intentSenderBinder, long lastReportTimeNs) {
- enforceCallingPermission();
- IntentSender intentSender = new IntentSender(intentSenderBinder);
- Intent intent = new Intent();
- intent.putExtra(EXTRA_LAST_REPORT_TIME, lastReportTimeNs);
- try {
- intentSender.sendIntent(mContext, CODE_DATA_BROADCAST, intent, null, null);
- } catch (IntentSender.SendIntentException e) {
- Slog.w(TAG, "Unable to send using IntentSender");
- }
- }
-
- @Override
- public void sendActiveConfigsChangedBroadcast(IBinder intentSenderBinder, long[] configIds) {
- enforceCallingPermission();
- IntentSender intentSender = new IntentSender(intentSenderBinder);
- Intent intent = new Intent();
- intent.putExtra(StatsManager.EXTRA_STATS_ACTIVE_CONFIG_KEYS, configIds);
- try {
- intentSender.sendIntent(mContext, CODE_ACTIVE_CONFIGS_BROADCAST, intent, null, null);
- if (DEBUG) {
- Slog.d(TAG, "Sent broadcast with config ids " + Arrays.toString(configIds));
- }
- } catch (IntentSender.SendIntentException e) {
- Slog.w(TAG, "Unable to send active configs changed broadcast using IntentSender");
- }
- }
-
- @Override
- public void sendSubscriberBroadcast(IBinder intentSenderBinder, long configUid, long configKey,
- long subscriptionId, long subscriptionRuleId, String[] cookies,
- StatsDimensionsValue dimensionsValue) {
- enforceCallingPermission();
- IntentSender intentSender = new IntentSender(intentSenderBinder);
- Intent intent =
- new Intent()
- .putExtra(StatsManager.EXTRA_STATS_CONFIG_UID, configUid)
- .putExtra(StatsManager.EXTRA_STATS_CONFIG_KEY, configKey)
- .putExtra(StatsManager.EXTRA_STATS_SUBSCRIPTION_ID, subscriptionId)
- .putExtra(StatsManager.EXTRA_STATS_SUBSCRIPTION_RULE_ID, subscriptionRuleId)
- .putExtra(StatsManager.EXTRA_STATS_DIMENSIONS_VALUE, dimensionsValue);
-
- ArrayList<String> cookieList = new ArrayList<>(cookies.length);
- for (String cookie : cookies) {
- cookieList.add(cookie);
- }
- intent.putStringArrayListExtra(
- StatsManager.EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES, cookieList);
-
- if (DEBUG) {
- Slog.d(TAG,
- String.format("Statsd sendSubscriberBroadcast with params {%d %d %d %d %s %s}",
- configUid, configKey, subscriptionId, subscriptionRuleId,
- Arrays.toString(cookies),
- dimensionsValue));
- }
- try {
- intentSender.sendIntent(mContext, CODE_SUBSCRIBER_BROADCAST, intent, null, null);
- } catch (IntentSender.SendIntentException e) {
- Slog.w(TAG,
- "Unable to send using IntentSender from uid " + configUid
- + "; presumably it had been cancelled.");
- }
- }
-
private final static int[] toIntArray(List<Integer> list) {
int[] ret = new int[list.size()];
for (int i = 0; i < ret.length; i++) {
@@ -770,7 +623,7 @@
@Override // Binder call
public void setAnomalyAlarm(long timestampMs) {
- enforceCallingPermission();
+ StatsCompanion.enforceStatsCompanionPermission(mContext);
if (DEBUG) Slog.d(TAG, "Setting anomaly alarm for " + timestampMs);
final long callingToken = Binder.clearCallingIdentity();
try {
@@ -786,7 +639,7 @@
@Override // Binder call
public void cancelAnomalyAlarm() {
- enforceCallingPermission();
+ StatsCompanion.enforceStatsCompanionPermission(mContext);
if (DEBUG) Slog.d(TAG, "Cancelling anomaly alarm");
final long callingToken = Binder.clearCallingIdentity();
try {
@@ -798,7 +651,7 @@
@Override // Binder call
public void setAlarmForSubscriberTriggering(long timestampMs) {
- enforceCallingPermission();
+ StatsCompanion.enforceStatsCompanionPermission(mContext);
if (DEBUG) {
Slog.d(TAG,
"Setting periodic alarm in about " + (timestampMs
@@ -817,7 +670,7 @@
@Override // Binder call
public void cancelAlarmForSubscriberTriggering() {
- enforceCallingPermission();
+ StatsCompanion.enforceStatsCompanionPermission(mContext);
if (DEBUG) {
Slog.d(TAG, "Cancelling periodic alarm");
}
@@ -831,7 +684,7 @@
@Override // Binder call
public void setPullingAlarm(long nextPullTimeMs) {
- enforceCallingPermission();
+ StatsCompanion.enforceStatsCompanionPermission(mContext);
if (DEBUG) {
Slog.d(TAG, "Setting pulling alarm in about "
+ (nextPullTimeMs - SystemClock.elapsedRealtime()));
@@ -849,7 +702,7 @@
@Override // Binder call
public void cancelPullingAlarm() {
- enforceCallingPermission();
+ StatsCompanion.enforceStatsCompanionPermission(mContext);
if (DEBUG) {
Slog.d(TAG, "Cancelling pulling alarm");
}
@@ -861,396 +714,6 @@
}
}
- private void addNetworkStats(
- int tag, List<StatsLogEventWrapper> ret, NetworkStats stats, boolean withFGBG) {
- int size = stats.size();
- long elapsedNanos = SystemClock.elapsedRealtimeNanos();
- long wallClockNanos = SystemClock.currentTimeMicro() * 1000L;
- NetworkStats.Entry entry = new NetworkStats.Entry(); // For recycling
- for (int j = 0; j < size; j++) {
- stats.getValues(j, entry);
- StatsLogEventWrapper e = new StatsLogEventWrapper(tag, elapsedNanos, wallClockNanos);
- e.writeInt(entry.uid);
- if (withFGBG) {
- e.writeInt(entry.set);
- }
- e.writeLong(entry.rxBytes);
- e.writeLong(entry.rxPackets);
- e.writeLong(entry.txBytes);
- e.writeLong(entry.txPackets);
- ret.add(e);
- }
- }
-
- /**
- * Allows rollups per UID but keeping the set (foreground/background) slicing.
- * Adapted from groupedByUid in frameworks/base/core/java/android/net/NetworkStats.java
- */
- private NetworkStats rollupNetworkStatsByFGBG(NetworkStats stats) {
- final NetworkStats ret = new NetworkStats(stats.getElapsedRealtime(), 1);
-
- final NetworkStats.Entry entry = new NetworkStats.Entry();
- entry.iface = NetworkStats.IFACE_ALL;
- entry.tag = NetworkStats.TAG_NONE;
- entry.metered = NetworkStats.METERED_ALL;
- entry.roaming = NetworkStats.ROAMING_ALL;
-
- int size = stats.size();
- NetworkStats.Entry recycle = new NetworkStats.Entry(); // Used for retrieving values
- for (int i = 0; i < size; i++) {
- stats.getValues(i, recycle);
-
- // Skip specific tags, since already counted in TAG_NONE
- if (recycle.tag != NetworkStats.TAG_NONE) continue;
-
- entry.set = recycle.set; // Allows slicing by background/foreground
- entry.uid = recycle.uid;
- entry.rxBytes = recycle.rxBytes;
- entry.rxPackets = recycle.rxPackets;
- entry.txBytes = recycle.txBytes;
- entry.txPackets = recycle.txPackets;
- // Operations purposefully omitted since we don't use them for statsd.
- ret.combineValues(entry);
- }
- return ret;
- }
-
- /**
- * Helper method to extract the Parcelable controller info from a
- * SynchronousResultReceiver.
- */
- private static <T extends Parcelable> T awaitControllerInfo(
- @Nullable SynchronousResultReceiver receiver) {
- if (receiver == null) {
- return null;
- }
-
- try {
- final SynchronousResultReceiver.Result result =
- receiver.awaitResult(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS);
- if (result.bundle != null) {
- // This is the final destination for the Bundle.
- result.bundle.setDefusable(true);
-
- final T data = result.bundle.getParcelable(
- RESULT_RECEIVER_CONTROLLER_KEY);
- if (data != null) {
- return data;
- }
- }
- Slog.e(TAG, "no controller energy info supplied for " + receiver.getName());
- } catch (TimeoutException e) {
- Slog.w(TAG, "timeout reading " + receiver.getName() + " stats");
- }
- return null;
- }
-
- private void pullKernelWakelock(
- int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- final KernelWakelockStats wakelockStats =
- mKernelWakelockReader.readKernelWakelockStats(mTmpWakelockStats);
- for (Map.Entry<String, KernelWakelockStats.Entry> ent : wakelockStats.entrySet()) {
- String name = ent.getKey();
- KernelWakelockStats.Entry kws = ent.getValue();
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
- e.writeString(name);
- e.writeInt(kws.mCount);
- e.writeInt(kws.mVersion);
- e.writeLong(kws.mTotalTime);
- pulledData.add(e);
- }
- }
-
- private void pullWifiBytesTransfer(
- int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- long token = Binder.clearCallingIdentity();
- try {
- // TODO: Consider caching the following call to get BatteryStatsInternal.
- BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
- String[] ifaces = bs.getWifiIfaces();
- if (ifaces.length == 0) {
- return;
- }
- if (mNetworkStatsService == null) {
- Slog.e(TAG, "NetworkStats Service is not available!");
- return;
- }
- // Combine all the metrics per Uid into one record.
- NetworkStats stats = mNetworkStatsService.getDetailedUidStats(ifaces).groupedByUid();
- addNetworkStats(tagId, pulledData, stats, false);
- } catch (RemoteException e) {
- Slog.e(TAG, "Pulling netstats for wifi bytes has error", e);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- private void pullWifiBytesTransferByFgBg(
- int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- long token = Binder.clearCallingIdentity();
- try {
- BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
- String[] ifaces = bs.getWifiIfaces();
- if (ifaces.length == 0) {
- return;
- }
- if (mNetworkStatsService == null) {
- Slog.e(TAG, "NetworkStats Service is not available!");
- return;
- }
- NetworkStats stats = rollupNetworkStatsByFGBG(
- mNetworkStatsService.getDetailedUidStats(ifaces));
- addNetworkStats(tagId, pulledData, stats, true);
- } catch (RemoteException e) {
- Slog.e(TAG, "Pulling netstats for wifi bytes w/ fg/bg has error", e);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- private void pullMobileBytesTransfer(
- int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- long token = Binder.clearCallingIdentity();
- try {
- BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
- String[] ifaces = bs.getMobileIfaces();
- if (ifaces.length == 0) {
- return;
- }
- if (mNetworkStatsService == null) {
- Slog.e(TAG, "NetworkStats Service is not available!");
- return;
- }
- // Combine all the metrics per Uid into one record.
- NetworkStats stats = mNetworkStatsService.getDetailedUidStats(ifaces).groupedByUid();
- addNetworkStats(tagId, pulledData, stats, false);
- } catch (RemoteException e) {
- Slog.e(TAG, "Pulling netstats for mobile bytes has error", e);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- private void pullBluetoothBytesTransfer(
- int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- BluetoothActivityEnergyInfo info = fetchBluetoothData();
- if (info.getUidTraffic() != null) {
- for (UidTraffic traffic : info.getUidTraffic()) {
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos,
- wallClockNanos);
- e.writeInt(traffic.getUid());
- e.writeLong(traffic.getRxBytes());
- e.writeLong(traffic.getTxBytes());
- pulledData.add(e);
- }
- }
- }
-
- private void pullMobileBytesTransferByFgBg(
- int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- long token = Binder.clearCallingIdentity();
- try {
- BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
- String[] ifaces = bs.getMobileIfaces();
- if (ifaces.length == 0) {
- return;
- }
- if (mNetworkStatsService == null) {
- Slog.e(TAG, "NetworkStats Service is not available!");
- return;
- }
- NetworkStats stats = rollupNetworkStatsByFGBG(
- mNetworkStatsService.getDetailedUidStats(ifaces));
- addNetworkStats(tagId, pulledData, stats, true);
- } catch (RemoteException e) {
- Slog.e(TAG, "Pulling netstats for mobile bytes w/ fg/bg has error", e);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- private void pullCpuTimePerFreq(
- int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- for (int cluster = 0; cluster < mKernelCpuSpeedReaders.length; cluster++) {
- long[] clusterTimeMs = mKernelCpuSpeedReaders[cluster].readAbsolute();
- if (clusterTimeMs != null) {
- for (int speed = clusterTimeMs.length - 1; speed >= 0; --speed) {
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos,
- wallClockNanos);
- e.writeInt(cluster);
- e.writeInt(speed);
- e.writeLong(clusterTimeMs[speed]);
- pulledData.add(e);
- }
- }
- }
- }
-
- private void pullKernelUidCpuTime(
- int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- mCpuUidUserSysTimeReader.readAbsolute((uid, timesUs) -> {
- long userTimeUs = timesUs[0], systemTimeUs = timesUs[1];
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
- e.writeInt(uid);
- e.writeLong(userTimeUs);
- e.writeLong(systemTimeUs);
- pulledData.add(e);
- });
- }
-
- private void pullKernelUidCpuFreqTime(
- int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- mCpuUidFreqTimeReader.readAbsolute((uid, cpuFreqTimeMs) -> {
- for (int freqIndex = 0; freqIndex < cpuFreqTimeMs.length; ++freqIndex) {
- if (cpuFreqTimeMs[freqIndex] != 0) {
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos,
- wallClockNanos);
- e.writeInt(uid);
- e.writeInt(freqIndex);
- e.writeLong(cpuFreqTimeMs[freqIndex]);
- pulledData.add(e);
- }
- }
- });
- }
-
- private void pullKernelUidCpuClusterTime(
- int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- mCpuUidClusterTimeReader.readAbsolute((uid, cpuClusterTimesMs) -> {
- for (int i = 0; i < cpuClusterTimesMs.length; i++) {
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos,
- wallClockNanos);
- e.writeInt(uid);
- e.writeInt(i);
- e.writeLong(cpuClusterTimesMs[i]);
- pulledData.add(e);
- }
- });
- }
-
- private void pullKernelUidCpuActiveTime(
- int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- mCpuUidActiveTimeReader.readAbsolute((uid, cpuActiveTimesMs) -> {
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
- e.writeInt(uid);
- e.writeLong((long) cpuActiveTimesMs);
- pulledData.add(e);
- });
- }
-
- private void pullWifiActivityInfo(
- int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- WifiManager wifiManager;
- synchronized (this) {
- if (mWifiManager == null) {
- mWifiManager = mContext.getSystemService(WifiManager.class);
- }
- wifiManager = mWifiManager;
- }
- if (wifiManager == null) {
- return;
- }
- long token = Binder.clearCallingIdentity();
- try {
- SynchronousResultReceiver wifiReceiver = new SynchronousResultReceiver("wifi");
- wifiManager.getWifiActivityEnergyInfoAsync(
- new Executor() {
- @Override
- public void execute(Runnable runnable) {
- // run the listener on the binder thread, if it was run on the main
- // thread it would deadlock since we would be waiting on ourselves
- runnable.run();
- }
- },
- info -> {
- Bundle bundle = new Bundle();
- bundle.putParcelable(BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY, info);
- wifiReceiver.send(0, bundle);
- }
- );
- final WifiActivityEnergyInfo wifiInfo = awaitControllerInfo(wifiReceiver);
- if (wifiInfo == null) {
- return;
- }
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
- e.writeLong(wifiInfo.getTimeSinceBootMillis());
- e.writeInt(wifiInfo.getStackState());
- e.writeLong(wifiInfo.getControllerTxDurationMillis());
- e.writeLong(wifiInfo.getControllerRxDurationMillis());
- e.writeLong(wifiInfo.getControllerIdleDurationMillis());
- e.writeLong(wifiInfo.getControllerEnergyUsedMicroJoules());
- pulledData.add(e);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
-
- private void pullModemActivityInfo(
- int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- long token = Binder.clearCallingIdentity();
- synchronized (this) {
- if (mTelephony == null) {
- mTelephony = mContext.getSystemService(TelephonyManager.class);
- }
- }
- if (mTelephony != null) {
- SynchronousResultReceiver modemReceiver = new SynchronousResultReceiver("telephony");
- mTelephony.requestModemActivityInfo(modemReceiver);
- final ModemActivityInfo modemInfo = awaitControllerInfo(modemReceiver);
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
- e.writeLong(modemInfo.getTimestamp());
- e.writeLong(modemInfo.getSleepTimeMillis());
- e.writeLong(modemInfo.getIdleTimeMillis());
- e.writeLong(modemInfo.getTransmitPowerInfo().get(0).getTimeInMillis());
- e.writeLong(modemInfo.getTransmitPowerInfo().get(1).getTimeInMillis());
- e.writeLong(modemInfo.getTransmitPowerInfo().get(2).getTimeInMillis());
- e.writeLong(modemInfo.getTransmitPowerInfo().get(3).getTimeInMillis());
- e.writeLong(modemInfo.getTransmitPowerInfo().get(4).getTimeInMillis());
- e.writeLong(modemInfo.getReceiveTimeMillis());
- pulledData.add(e);
- }
- }
-
- private void pullBluetoothActivityInfo(
- int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- BluetoothActivityEnergyInfo info = fetchBluetoothData();
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
- e.writeLong(info.getTimeStamp());
- e.writeInt(info.getBluetoothStackState());
- e.writeLong(info.getControllerTxTimeMillis());
- e.writeLong(info.getControllerRxTimeMillis());
- e.writeLong(info.getControllerIdleTimeMillis());
- e.writeLong(info.getControllerEnergyUsed());
- pulledData.add(e);
- }
-
- private synchronized BluetoothActivityEnergyInfo fetchBluetoothData() {
- final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
- if (adapter != null) {
- SynchronousResultReceiver bluetoothReceiver = new SynchronousResultReceiver(
- "bluetooth");
- adapter.requestControllerActivityEnergyInfo(bluetoothReceiver);
- return awaitControllerInfo(bluetoothReceiver);
- } else {
- Slog.e(TAG, "Failed to get bluetooth adapter!");
- return null;
- }
- }
-
private void pullSystemElapsedRealtime(
int tagId, long elapsedNanos, long wallClockNanos,
List<StatsLogEventWrapper> pulledData) {
@@ -1259,13 +722,6 @@
pulledData.add(e);
}
- private void pullSystemUpTime(int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
- e.writeLong(SystemClock.uptimeMillis());
- pulledData.add(e);
- }
-
private void pullProcessMemoryState(
int tagId, long elapsedNanos, long wallClockNanos,
List<StatsLogEventWrapper> pulledData) {
@@ -1843,108 +1299,6 @@
}
}
- private void pullPowerProfile(
- int tagId, long elapsedNanos, long wallClockNanos,
- List<StatsLogEventWrapper> pulledData) {
- PowerProfile powerProfile = new PowerProfile(mContext);
- Objects.requireNonNull(powerProfile);
-
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos,
- wallClockNanos);
- ProtoOutputStream proto = new ProtoOutputStream();
- powerProfile.dumpDebug(proto);
- proto.flush();
- e.writeStorage(proto.getBytes());
- pulledData.add(e);
- }
-
- private void pullBuildInformation(int tagId,
- long elapsedNanos, long wallClockNanos, List<StatsLogEventWrapper> pulledData) {
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
- e.writeString(Build.FINGERPRINT);
- e.writeString(Build.BRAND);
- e.writeString(Build.PRODUCT);
- e.writeString(Build.DEVICE);
- e.writeString(Build.VERSION.RELEASE);
- e.writeString(Build.ID);
- e.writeString(Build.VERSION.INCREMENTAL);
- e.writeString(Build.TYPE);
- e.writeString(Build.TAGS);
- pulledData.add(e);
- }
-
- private BatteryStatsHelper getBatteryStatsHelper() {
- if (mBatteryStatsHelper == null) {
- final long callingToken = Binder.clearCallingIdentity();
- try {
- // clearCallingIdentity required for BatteryStatsHelper.checkWifiOnly().
- mBatteryStatsHelper = new BatteryStatsHelper(mContext, false);
- } finally {
- Binder.restoreCallingIdentity(callingToken);
- }
- mBatteryStatsHelper.create((Bundle) null);
- }
- long currentTime = SystemClock.elapsedRealtime();
- if (currentTime - mBatteryStatsHelperTimestampMs >= MAX_BATTERY_STATS_HELPER_FREQUENCY_MS) {
- // Load BatteryStats and do all the calculations.
- mBatteryStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, UserHandle.USER_ALL);
- // Calculations are done so we don't need to save the raw BatteryStats data in RAM.
- mBatteryStatsHelper.clearStats();
- mBatteryStatsHelperTimestampMs = currentTime;
- }
- return mBatteryStatsHelper;
- }
-
- private long milliAmpHrsToNanoAmpSecs(double mAh) {
- final long MILLI_AMP_HR_TO_NANO_AMP_SECS = 1_000_000L * 3600L;
- return (long) (mAh * MILLI_AMP_HR_TO_NANO_AMP_SECS + 0.5);
- }
-
- private void pullDeviceCalculatedPowerUse(int tagId,
- long elapsedNanos, final long wallClockNanos, List<StatsLogEventWrapper> pulledData) {
- BatteryStatsHelper bsHelper = getBatteryStatsHelper();
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
- e.writeLong(milliAmpHrsToNanoAmpSecs(bsHelper.getComputedPower()));
- pulledData.add(e);
- }
-
- private void pullDeviceCalculatedPowerBlameUid(int tagId,
- long elapsedNanos, final long wallClockNanos, List<StatsLogEventWrapper> pulledData) {
- final List<BatterySipper> sippers = getBatteryStatsHelper().getUsageList();
- if (sippers == null) {
- return;
- }
- for (BatterySipper bs : sippers) {
- if (bs.drainType != bs.drainType.APP) {
- continue;
- }
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
- e.writeInt(bs.uidObj.getUid());
- e.writeLong(milliAmpHrsToNanoAmpSecs(bs.totalPowerMah));
- pulledData.add(e);
- }
- }
-
- private void pullDeviceCalculatedPowerBlameOther(int tagId,
- long elapsedNanos, final long wallClockNanos, List<StatsLogEventWrapper> pulledData) {
- final List<BatterySipper> sippers = getBatteryStatsHelper().getUsageList();
- if (sippers == null) {
- return;
- }
- for (BatterySipper bs : sippers) {
- if (bs.drainType == bs.drainType.APP) {
- continue; // This is a separate atom; see pullDeviceCalculatedPowerBlameUid().
- }
- if (bs.drainType == bs.drainType.USER) {
- continue; // This is not supported. We purposefully calculate over USER_ALL.
- }
- StatsLogEventWrapper e = new StatsLogEventWrapper(tagId, elapsedNanos, wallClockNanos);
- e.writeInt(bs.drainType.ordinal());
- e.writeLong(milliAmpHrsToNanoAmpSecs(bs.totalPowerMah));
- pulledData.add(e);
- }
- }
-
private void pullDiskIo(int tagId, long elapsedNanos, final long wallClockNanos,
List<StatsLogEventWrapper> pulledData) {
mStoragedUidIoStatsReader.readAbsolute((uid, fgCharsRead, fgCharsWrite, fgBytesRead,
@@ -2455,7 +1809,7 @@
*/
@Override // Binder call
public StatsLogEventWrapper[] pullData(int tagId) {
- enforceCallingPermission();
+ StatsCompanion.enforceStatsCompanionPermission(mContext);
if (DEBUG) {
Slog.d(TAG, "Pulling " + tagId);
}
@@ -2463,149 +1817,100 @@
long elapsedNanos = SystemClock.elapsedRealtimeNanos();
long wallClockNanos = SystemClock.currentTimeMicro() * 1000L;
switch (tagId) {
- case StatsLog.WIFI_BYTES_TRANSFER: {
- pullWifiBytesTransfer(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
- case StatsLog.MOBILE_BYTES_TRANSFER: {
- pullMobileBytesTransfer(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
- case StatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG: {
- pullWifiBytesTransferByFgBg(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
- case StatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG: {
- pullMobileBytesTransferByFgBg(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
- case StatsLog.BLUETOOTH_BYTES_TRANSFER: {
- pullBluetoothBytesTransfer(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
- case StatsLog.KERNEL_WAKELOCK: {
- pullKernelWakelock(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
- case StatsLog.CPU_TIME_PER_FREQ: {
- pullCpuTimePerFreq(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
- case StatsLog.CPU_TIME_PER_UID: {
- pullKernelUidCpuTime(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
- case StatsLog.CPU_TIME_PER_UID_FREQ: {
- pullKernelUidCpuFreqTime(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
- case StatsLog.CPU_CLUSTER_TIME: {
- pullKernelUidCpuClusterTime(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
- case StatsLog.CPU_ACTIVE_TIME: {
- pullKernelUidCpuActiveTime(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
- case StatsLog.WIFI_ACTIVITY_INFO: {
- pullWifiActivityInfo(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
- case StatsLog.MODEM_ACTIVITY_INFO: {
- pullModemActivityInfo(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
- case StatsLog.BLUETOOTH_ACTIVITY_INFO: {
- pullBluetoothActivityInfo(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
- case StatsLog.SYSTEM_UPTIME: {
- pullSystemUpTime(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
+
case StatsLog.SYSTEM_ELAPSED_REALTIME: {
pullSystemElapsedRealtime(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.PROCESS_MEMORY_STATE: {
pullProcessMemoryState(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.PROCESS_MEMORY_HIGH_WATER_MARK: {
pullProcessMemoryHighWaterMark(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.PROCESS_MEMORY_SNAPSHOT: {
pullProcessMemorySnapshot(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.SYSTEM_ION_HEAP_SIZE: {
pullSystemIonHeapSize(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.PROCESS_SYSTEM_ION_HEAP_SIZE: {
pullProcessSystemIonHeapSize(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.BINDER_CALLS: {
pullBinderCallsStats(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.BINDER_CALLS_EXCEPTIONS: {
pullBinderCallsStatsExceptions(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.LOOPER_STATS: {
pullLooperStats(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.DISK_STATS: {
pullDiskStats(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.DIRECTORY_USAGE: {
pullDirectoryUsage(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.APP_SIZE: {
pullAppSize(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.CATEGORY_SIZE: {
pullCategorySize(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.NUM_FINGERPRINTS_ENROLLED: {
pullNumBiometricsEnrolled(BiometricsProtoEnums.MODALITY_FINGERPRINT, tagId,
elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.NUM_FACES_ENROLLED: {
pullNumBiometricsEnrolled(BiometricsProtoEnums.MODALITY_FACE, tagId, elapsedNanos,
wallClockNanos, ret);
break;
}
+
case StatsLog.PROC_STATS: {
pullProcessStats(ProcessStats.REPORT_ALL, tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.PROC_STATS_PKG_PROC: {
pullProcessStats(ProcessStats.REPORT_PKG_PROC_STATS, tagId, elapsedNanos,
wallClockNanos, ret);
break;
}
+
case StatsLog.DISK_IO: {
pullDiskIo(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
- case StatsLog.POWER_PROFILE: {
- pullPowerProfile(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
- case StatsLog.BUILD_INFORMATION: {
- pullBuildInformation(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
+
case StatsLog.PROCESS_CPU_TIME: {
pullProcessCpuTime(tagId, elapsedNanos, wallClockNanos, ret);
break;
@@ -2614,73 +1919,75 @@
pullCpuTimePerThreadFreq(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
- case StatsLog.DEVICE_CALCULATED_POWER_USE: {
- pullDeviceCalculatedPowerUse(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
- case StatsLog.DEVICE_CALCULATED_POWER_BLAME_UID: {
- pullDeviceCalculatedPowerBlameUid(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
- case StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER: {
- pullDeviceCalculatedPowerBlameOther(tagId, elapsedNanos, wallClockNanos, ret);
- break;
- }
+
case StatsLog.TEMPERATURE: {
pullTemperature(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.COOLING_DEVICE: {
pullCoolingDevices(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.DEBUG_ELAPSED_CLOCK: {
pullDebugElapsedClock(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.DEBUG_FAILING_ELAPSED_CLOCK: {
pullDebugFailingElapsedClock(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.ROLE_HOLDER: {
pullRoleHolders(elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.DANGEROUS_PERMISSION_STATE: {
pullDangerousPermissionState(StatsLog.DANGEROUS_PERMISSION_STATE, elapsedNanos,
wallClockNanos, ret);
break;
}
+
case StatsLog.DANGEROUS_PERMISSION_STATE_SAMPLED: {
pullDangerousPermissionState(StatsLog.DANGEROUS_PERMISSION_STATE_SAMPLED,
elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.TIME_ZONE_DATA_INFO: {
pullTimeZoneDataInfo(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.EXTERNAL_STORAGE_INFO: {
pullExternalStorageInfo(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.APPS_ON_EXTERNAL_STORAGE_INFO: {
pullAppsOnExternalStorageInfo(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.FACE_SETTINGS: {
pullFaceSettings(tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.APP_OPS: {
pullAppOps(elapsedNanos, wallClockNanos, ret);
break;
}
+
case StatsLog.NOTIFICATION_REMOTE_VIEWS: {
pullNotificationStats(NotificationManagerService.REPORT_REMOTE_VIEWS,
tagId, elapsedNanos, wallClockNanos, ret);
break;
}
+
default:
Slog.w(TAG, "No such tagId data as " + tagId);
return null;
@@ -2690,12 +1997,11 @@
@Override // Binder call
public void statsdReady() {
- enforceCallingPermission();
+ StatsCompanion.enforceStatsCompanionPermission(mContext);
if (DEBUG) {
Slog.d(TAG, "learned that statsdReady");
}
sayHiToStatsd(); // tell statsd that we're ready too and link to it
- mStatsManagerService.systemReady();
mContext.sendBroadcastAsUser(new Intent(StatsManager.ACTION_STATSD_STARTED)
.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND),
UserHandle.SYSTEM, android.Manifest.permission.DUMP);
@@ -2703,7 +2009,7 @@
@Override
public void triggerUidSnapshot() {
- enforceCallingPermission();
+ StatsCompanion.enforceStatsCompanionPermission(mContext);
synchronized (sStatsdLock) {
final long token = Binder.clearCallingIdentity();
try {
@@ -2716,64 +2022,6 @@
}
}
- private void enforceCallingPermission() {
- if (Binder.getCallingPid() == Process.myPid()) {
- return;
- }
- mContext.enforceCallingPermission(android.Manifest.permission.STATSCOMPANION, null);
- }
-
- @Override
- public void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs,
- int[] additiveFields, IPullAtomCallback pullerCallback) {
- synchronized (sStatsdLock) {
- // Always cache the puller in SCS.
- // If statsd is down, we will register it when it comes back up.
- int callingUid = Binder.getCallingUid();
- final long token = Binder.clearCallingIdentity();
- PullerKey key = new PullerKey(callingUid, atomTag);
- PullerValue val = new PullerValue(
- coolDownNs, timeoutNs, additiveFields, pullerCallback);
- mPullers.put(key, val);
-
- if (sStatsd == null) {
- Slog.w(TAG, "Could not access statsd for registering puller for atom " + atomTag);
- return;
- }
- try {
- sStatsd.registerPullAtomCallback(
- callingUid, atomTag, coolDownNs, timeoutNs, additiveFields, pullerCallback);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to access statsd to register puller for atom " + atomTag);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
- }
-
- @Override
- public void unregisterPullAtomCallback(int atomTag) {
- synchronized (sStatsdLock) {
- // Always remove the puller in SCS.
- // If statsd is down, we will not register it when it comes back up.
- int callingUid = Binder.getCallingUid();
- final long token = Binder.clearCallingIdentity();
- PullerKey key = new PullerKey(callingUid, atomTag);
- mPullers.remove(key);
-
- if (sStatsd == null) {
- Slog.w(TAG, "Could not access statsd for registering puller for atom " + atomTag);
- return;
- }
- try {
- sStatsd.unregisterPullAtomCallback(callingUid, atomTag);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to access statsd to register puller for atom " + atomTag);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
- }
- }
// Statsd related code
@@ -2817,6 +2065,7 @@
+ "alive.");
return;
}
+ mStatsManagerService.statsdReady(sStatsd);
if (DEBUG) Slog.d(TAG, "Saying hi to statsd");
try {
sStatsd.statsCompanionReady();
@@ -2851,8 +2100,6 @@
// Pull the latest state of UID->app name, version mapping when
// statsd starts.
informAllUidsLocked(mContext);
- // Register all pullers. If SCS has just started, this should be empty.
- registerAllPullersLocked();
} finally {
restoreCallingIdentity(token);
}
@@ -2864,17 +2111,6 @@
}
}
- @GuardedBy("sStatsdLock")
- private void registerAllPullersLocked() throws RemoteException {
- // TODO: pass in one call, using a file descriptor (similar to uidmap).
- for (Map.Entry<PullerKey, PullerValue> entry : mPullers.entrySet()) {
- PullerKey key = entry.getKey();
- PullerValue val = entry.getValue();
- sStatsd.registerPullAtomCallback(key.getUid(), key.getAtom(), val.getCoolDownNs(),
- val.getTimeoutNs(), val.getAdditiveFields(), val.getCallback());
- }
- }
-
private class StatsdDeathRecipient implements IBinder.DeathRecipient {
@Override
public void binderDied() {
@@ -2928,6 +2164,7 @@
if (looperStats != null) {
looperStats.reset();
}
+ mStatsManagerService.statsdNotReady();
}
@Override
diff --git a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java
index 24b7978..04d8b00 100644
--- a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java
+++ b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java
@@ -16,18 +16,30 @@
package com.android.server.stats;
+import static com.android.server.stats.StatsCompanion.PendingIntentRef;
+
+import android.Manifest;
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
import android.app.PendingIntent;
import android.content.Context;
-import android.os.IBinder;
+import android.os.Binder;
+import android.os.IPullAtomCallback;
import android.os.IStatsManagerService;
import android.os.IStatsd;
+import android.os.Process;
import android.os.RemoteException;
-import android.os.ServiceManager;
+import android.util.ArrayMap;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
+import java.util.Map;
+import java.util.Objects;
+
/**
+ * Service for {@link android.app.StatsManager}.
+ *
* @hide
*/
public class StatsManagerService extends IStatsManagerService.Stub {
@@ -35,79 +47,599 @@
private static final String TAG = "StatsManagerService";
private static final boolean DEBUG = false;
- @GuardedBy("sStatsdLock")
- private static IStatsd sStatsd;
- private static final Object sStatsdLock = new Object();
+ private static final int STATSD_TIMEOUT_MILLIS = 5000;
+
+ private static final String USAGE_STATS_PERMISSION_OPS = "android:get_usage_stats";
+
+ @GuardedBy("mLock")
+ private IStatsd mStatsd;
+ private final Object mLock = new Object();
private StatsCompanionService mStatsCompanionService;
+ private Context mContext;
+
+ @GuardedBy("mLock")
+ private ArrayMap<ConfigKey, PendingIntentRef> mDataFetchPirMap = new ArrayMap<>();
+ @GuardedBy("mLock")
+ private ArrayMap<Integer, PendingIntentRef> mActiveConfigsPirMap = new ArrayMap<>();
+ @GuardedBy("mLock")
+ private ArrayMap<ConfigKey, ArrayMap<Long, PendingIntentRef>> mBroadcastSubscriberPirMap =
+ new ArrayMap<>();
public StatsManagerService(Context context) {
super();
+ mContext = context;
+ }
+
+ private static class ConfigKey {
+ private final int mUid;
+ private final long mConfigId;
+
+ ConfigKey(int uid, long configId) {
+ mUid = uid;
+ mConfigId = configId;
+ }
+
+ public int getUid() {
+ return mUid;
+ }
+
+ public long getConfigId() {
+ return mConfigId;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mUid, mConfigId);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof ConfigKey) {
+ ConfigKey other = (ConfigKey) obj;
+ return this.mUid == other.getUid() && this.mConfigId == other.getConfigId();
+ }
+ return false;
+ }
+ }
+
+ private static class PullerKey {
+ private final int mUid;
+ private final int mAtomTag;
+
+ PullerKey(int uid, int atom) {
+ mUid = uid;
+ mAtomTag = atom;
+ }
+
+ public int getUid() {
+ return mUid;
+ }
+
+ public int getAtom() {
+ return mAtomTag;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mUid, mAtomTag);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof PullerKey) {
+ PullerKey other = (PullerKey) obj;
+ return this.mUid == other.getUid() && this.mAtomTag == other.getAtom();
+ }
+ return false;
+ }
+ }
+
+ private static class PullerValue {
+ private final long mCoolDownNs;
+ private final long mTimeoutNs;
+ private final int[] mAdditiveFields;
+ private final IPullAtomCallback mCallback;
+
+ PullerValue(long coolDownNs, long timeoutNs, int[] additiveFields,
+ IPullAtomCallback callback) {
+ mCoolDownNs = coolDownNs;
+ mTimeoutNs = timeoutNs;
+ mAdditiveFields = additiveFields;
+ mCallback = callback;
+ }
+
+ public long getCoolDownNs() {
+ return mCoolDownNs;
+ }
+
+ public long getTimeoutNs() {
+ return mTimeoutNs;
+ }
+
+ public int[] getAdditiveFields() {
+ return mAdditiveFields;
+ }
+
+ public IPullAtomCallback getCallback() {
+ return mCallback;
+ }
+ }
+
+ private final ArrayMap<PullerKey, PullerValue> mPullers = new ArrayMap<>();
+
+ @Override
+ public void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs,
+ int[] additiveFields, IPullAtomCallback pullerCallback) {
+ int callingUid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+ PullerKey key = new PullerKey(callingUid, atomTag);
+ PullerValue val = new PullerValue(coolDownNs, timeoutNs, additiveFields, pullerCallback);
+
+ // Always cache the puller in StatsManagerService. If statsd is down, we will register the
+ // puller when statsd comes back up.
+ synchronized (mLock) {
+ mPullers.put(key, val);
+ }
+
+ IStatsd statsd = getStatsdNonblocking();
+ if (statsd == null) {
+ return;
+ }
+
+ try {
+ statsd.registerPullAtomCallback(
+ callingUid, atomTag, coolDownNs, timeoutNs, additiveFields, pullerCallback);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to access statsd to register puller for atom " + atomTag);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
@Override
- public void setDataFetchOperation(long configKey, PendingIntent pendingIntent,
+ public void unregisterPullAtomCallback(int atomTag) {
+ int callingUid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+ PullerKey key = new PullerKey(callingUid, atomTag);
+
+ // Always remove the puller from StatsManagerService even if statsd is down. When statsd
+ // comes back up, we will not re-register the removed puller.
+ synchronized (mLock) {
+ mPullers.remove(key);
+ }
+
+ IStatsd statsd = getStatsdNonblocking();
+ if (statsd == null) {
+ return;
+ }
+
+ try {
+ statsd.unregisterPullAtomCallback(callingUid, atomTag);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to access statsd to unregister puller for atom " + atomTag);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void setDataFetchOperation(long configId, PendingIntent pendingIntent,
String packageName) {
- // no-op
- if (DEBUG) {
- Slog.d(TAG, "setDataFetchOperation");
+ enforceDumpAndUsageStatsPermission(packageName);
+ int callingUid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+ PendingIntentRef pir = new PendingIntentRef(pendingIntent, mContext);
+ ConfigKey key = new ConfigKey(callingUid, configId);
+ // We add the PIR to a map so we can reregister if statsd is unavailable.
+ synchronized (mLock) {
+ mDataFetchPirMap.put(key, pir);
+ }
+ try {
+ IStatsd statsd = getStatsdNonblocking();
+ if (statsd != null) {
+ statsd.setDataFetchOperation(configId, pir, callingUid);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to setDataFetchOperation with statsd");
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void removeDataFetchOperation(long configId, String packageName) {
+ enforceDumpAndUsageStatsPermission(packageName);
+ int callingUid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+ ConfigKey key = new ConfigKey(callingUid, configId);
+ synchronized (mLock) {
+ mDataFetchPirMap.remove(key);
+ }
+ try {
+ IStatsd statsd = getStatsdNonblocking();
+ if (statsd != null) {
+ statsd.removeDataFetchOperation(configId, callingUid);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to removeDataFetchOperation with statsd");
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
}
@Override
public long[] setActiveConfigsChangedOperation(PendingIntent pendingIntent,
String packageName) {
- // no-op
- if (DEBUG) {
- Slog.d(TAG, "setActiveConfigsChangedOperation");
+ enforceDumpAndUsageStatsPermission(packageName);
+ int callingUid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+ PendingIntentRef pir = new PendingIntentRef(pendingIntent, mContext);
+ // We add the PIR to a map so we can reregister if statsd is unavailable.
+ synchronized (mLock) {
+ mActiveConfigsPirMap.put(callingUid, pir);
}
- return new long[]{};
+ try {
+ IStatsd statsd = getStatsdNonblocking();
+ if (statsd != null) {
+ return statsd.setActiveConfigsChangedOperation(pir, callingUid);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to setActiveConfigsChangedOperation with statsd");
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return new long[] {};
}
@Override
- public void setBroadcastSubscriber(long configKey, long subscriberId,
- PendingIntent pendingIntent, String packageName) {
- //no-op
- if (DEBUG) {
- Slog.d(TAG, "setBroadcastSubscriber");
+ public void removeActiveConfigsChangedOperation(String packageName) {
+ enforceDumpAndUsageStatsPermission(packageName);
+ int callingUid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+ synchronized (mLock) {
+ mActiveConfigsPirMap.remove(callingUid);
}
+ try {
+ IStatsd statsd = getStatsdNonblocking();
+ if (statsd != null) {
+ statsd.removeActiveConfigsChangedOperation(callingUid);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to removeActiveConfigsChangedOperation with statsd");
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void setBroadcastSubscriber(long configId, long subscriberId,
+ PendingIntent pendingIntent, String packageName) {
+ enforceDumpAndUsageStatsPermission(packageName);
+ int callingUid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+ PendingIntentRef pir = new PendingIntentRef(pendingIntent, mContext);
+ ConfigKey key = new ConfigKey(callingUid, configId);
+ // We add the PIR to a map so we can reregister if statsd is unavailable.
+ synchronized (mLock) {
+ ArrayMap<Long, PendingIntentRef> innerMap = mBroadcastSubscriberPirMap
+ .getOrDefault(key, new ArrayMap<>());
+ innerMap.put(subscriberId, pir);
+ mBroadcastSubscriberPirMap.put(key, innerMap);
+ }
+ try {
+ IStatsd statsd = getStatsdNonblocking();
+ if (statsd != null) {
+ statsd.setBroadcastSubscriber(
+ configId, subscriberId, pir, callingUid);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to setBroadcastSubscriber with statsd");
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public void unsetBroadcastSubscriber(long configId, long subscriberId, String packageName) {
+ enforceDumpAndUsageStatsPermission(packageName);
+ int callingUid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+ ConfigKey key = new ConfigKey(callingUid, configId);
+ synchronized (mLock) {
+ ArrayMap<Long, PendingIntentRef> innerMap = mBroadcastSubscriberPirMap
+ .getOrDefault(key, new ArrayMap<>());
+ innerMap.remove(subscriberId);
+ if (innerMap.isEmpty()) {
+ mBroadcastSubscriberPirMap.remove(key);
+ }
+ }
+ try {
+ IStatsd statsd = getStatsdNonblocking();
+ if (statsd != null) {
+ statsd.unsetBroadcastSubscriber(configId, subscriberId, callingUid);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to unsetBroadcastSubscriber with statsd");
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ @Override
+ public long[] getRegisteredExperimentIds() throws IllegalStateException {
+ enforceDumpAndUsageStatsPermission(null);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ IStatsd statsd = waitForStatsd();
+ if (statsd != null) {
+ return statsd.getRegisteredExperimentIds();
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to getRegisteredExperimentIds with statsd");
+ throw new IllegalStateException(e.getMessage(), e);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ throw new IllegalStateException("Failed to connect to statsd to registerExperimentIds");
+ }
+
+ @Override
+ public byte[] getMetadata(String packageName) throws IllegalStateException {
+ enforceDumpAndUsageStatsPermission(packageName);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ IStatsd statsd = waitForStatsd();
+ if (statsd != null) {
+ return statsd.getMetadata();
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to getMetadata with statsd");
+ throw new IllegalStateException(e.getMessage(), e);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ throw new IllegalStateException("Failed to connect to statsd to getMetadata");
+ }
+
+ @Override
+ public byte[] getData(long key, String packageName) throws IllegalStateException {
+ enforceDumpAndUsageStatsPermission(packageName);
+ int callingUid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ IStatsd statsd = waitForStatsd();
+ if (statsd != null) {
+ return statsd.getData(key, callingUid);
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to getData with statsd");
+ throw new IllegalStateException(e.getMessage(), e);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ throw new IllegalStateException("Failed to connect to statsd to getData");
+ }
+
+ @Override
+ public void addConfiguration(long configId, byte[] config, String packageName)
+ throws IllegalStateException {
+ enforceDumpAndUsageStatsPermission(packageName);
+ int callingUid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ IStatsd statsd = waitForStatsd();
+ if (statsd != null) {
+ statsd.addConfiguration(configId, config, callingUid);
+ return;
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to addConfiguration with statsd");
+ throw new IllegalStateException(e.getMessage(), e);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ throw new IllegalStateException("Failed to connect to statsd to addConfig");
+ }
+
+ @Override
+ public void removeConfiguration(long configId, String packageName)
+ throws IllegalStateException {
+ enforceDumpAndUsageStatsPermission(packageName);
+ int callingUid = Binder.getCallingUid();
+ final long token = Binder.clearCallingIdentity();
+ try {
+ IStatsd statsd = waitForStatsd();
+ if (statsd != null) {
+ statsd.removeConfiguration(configId, callingUid);
+ return;
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to removeConfiguration with statsd");
+ throw new IllegalStateException(e.getMessage(), e);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ throw new IllegalStateException("Failed to connect to statsd to removeConfig");
}
void setStatsCompanionService(StatsCompanionService statsCompanionService) {
mStatsCompanionService = statsCompanionService;
}
- void systemReady() {
- if (DEBUG) {
- Slog.d(TAG, "statsdReady");
+ /**
+ * Checks that the caller has both DUMP and PACKAGE_USAGE_STATS permissions. Also checks that
+ * the caller has USAGE_STATS_PERMISSION_OPS for the specified packageName if it is not null.
+ *
+ * @param packageName The packageName to check USAGE_STATS_PERMISSION_OPS.
+ */
+ private void enforceDumpAndUsageStatsPermission(@Nullable String packageName) {
+ int callingUid = Binder.getCallingUid();
+ int callingPid = Binder.getCallingPid();
+
+ if (callingPid == Process.myPid()) {
+ return;
}
- setupStatsManagerService();
+
+ mContext.enforceCallingPermission(Manifest.permission.DUMP, null);
+ mContext.enforceCallingPermission(Manifest.permission.PACKAGE_USAGE_STATS, null);
+
+ if (packageName == null) {
+ return;
+ }
+ AppOpsManager appOpsManager = (AppOpsManager) mContext
+ .getSystemService(Context.APP_OPS_SERVICE);
+ switch (appOpsManager.noteOp(USAGE_STATS_PERMISSION_OPS,
+ Binder.getCallingUid(), packageName, null, null)) {
+ case AppOpsManager.MODE_ALLOWED:
+ case AppOpsManager.MODE_DEFAULT:
+ break;
+ default:
+ throw new SecurityException(
+ String.format("UID %d / PID %d lacks app-op %s",
+ callingUid, callingPid, USAGE_STATS_PERMISSION_OPS)
+ );
+ }
}
- private void setupStatsManagerService() {
- synchronized (sStatsdLock) {
- if (sStatsd != null) {
- if (DEBUG) {
- Slog.e(TAG, "Trying to fetch statsd, but it was already fetched",
- new IllegalStateException(
- "sStatsd is not null when being fetched"));
+ /**
+ * Clients should call this if blocking until statsd to be ready is desired
+ *
+ * @return IStatsd object if statsd becomes ready within the timeout, null otherwise.
+ */
+ private IStatsd waitForStatsd() {
+ synchronized (mLock) {
+ if (mStatsd == null) {
+ try {
+ mLock.wait(STATSD_TIMEOUT_MILLIS);
+ } catch (InterruptedException e) {
+ Slog.e(TAG, "wait for statsd interrupted");
}
- return;
}
- sStatsd = IStatsd.Stub.asInterface(ServiceManager.getService("stats"));
- if (sStatsd == null) {
- if (DEBUG) {
- Slog.d(TAG, "Failed to get stats service.");
- }
- return;
+ return mStatsd;
+ }
+ }
+
+ /**
+ * Clients should call this to receive a reference to statsd.
+ *
+ * @return IStatsd object if statsd is ready, null otherwise.
+ */
+ private IStatsd getStatsdNonblocking() {
+ synchronized (mLock) {
+ return mStatsd;
+ }
+ }
+
+ /**
+ * Called from {@link StatsCompanionService}.
+ *
+ * Tells StatsManagerService that Statsd is ready and updates
+ * Statsd with the contents of our local cache.
+ */
+ void statsdReady(IStatsd statsd) {
+ synchronized (mLock) {
+ mStatsd = statsd;
+ mLock.notify();
+ }
+ sayHiToStatsd(statsd);
+ }
+
+ /**
+ * Called from {@link StatsCompanionService}.
+ *
+ * Tells StatsManagerService that Statsd is no longer ready
+ * and we should no longer make binder calls with statsd.
+ */
+ void statsdNotReady() {
+ synchronized (mLock) {
+ mStatsd = null;
+ }
+ }
+
+ private void sayHiToStatsd(IStatsd statsd) {
+ if (statsd == null) {
+ return;
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ registerAllPullers(statsd);
+ registerAllDataFetchOperations(statsd);
+ registerAllActiveConfigsChangedOperations(statsd);
+ registerAllBroadcastSubscribers(statsd);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "StatsManager failed to (re-)register data with statsd");
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ // Pre-condition: the Binder calling identity has already been cleared
+ private void registerAllPullers(IStatsd statsd) throws RemoteException {
+ // Since we do not want to make an IPC with the lock held, we first create a copy of the
+ // data with the lock held before iterating through the map.
+ ArrayMap<PullerKey, PullerValue> pullersCopy;
+ synchronized (mLock) {
+ pullersCopy = new ArrayMap<>(mPullers);
+ }
+
+ for (Map.Entry<PullerKey, PullerValue> entry : pullersCopy.entrySet()) {
+ PullerKey key = entry.getKey();
+ PullerValue value = entry.getValue();
+ statsd.registerPullAtomCallback(key.getUid(), key.getAtom(), value.getCoolDownNs(),
+ value.getTimeoutNs(), value.getAdditiveFields(), value.getCallback());
+ }
+ }
+
+ // Pre-condition: the Binder calling identity has already been cleared
+ private void registerAllDataFetchOperations(IStatsd statsd) throws RemoteException {
+ // Since we do not want to make an IPC with the lock held, we first create a copy of the
+ // data with the lock held before iterating through the map.
+ ArrayMap<ConfigKey, PendingIntentRef> dataFetchCopy;
+ synchronized (mLock) {
+ dataFetchCopy = new ArrayMap<>(mDataFetchPirMap);
+ }
+
+ for (Map.Entry<ConfigKey, PendingIntentRef> entry : dataFetchCopy.entrySet()) {
+ ConfigKey key = entry.getKey();
+ statsd.setDataFetchOperation(key.getConfigId(), entry.getValue(), key.getUid());
+ }
+ }
+
+ // Pre-condition: the Binder calling identity has already been cleared
+ private void registerAllActiveConfigsChangedOperations(IStatsd statsd) throws RemoteException {
+ // Since we do not want to make an IPC with the lock held, we first create a copy of the
+ // data with the lock held before iterating through the map.
+ ArrayMap<Integer, PendingIntentRef> activeConfigsChangedCopy;
+ synchronized (mLock) {
+ activeConfigsChangedCopy = new ArrayMap<>(mActiveConfigsPirMap);
+ }
+
+ for (Map.Entry<Integer, PendingIntentRef> entry : activeConfigsChangedCopy.entrySet()) {
+ statsd.setActiveConfigsChangedOperation(entry.getValue(), entry.getKey());
+ }
+ }
+
+ // Pre-condition: the Binder calling identity has already been cleared
+ private void registerAllBroadcastSubscribers(IStatsd statsd) throws RemoteException {
+ // Since we do not want to make an IPC with the lock held, we first create a deep copy of
+ // the data with the lock held before iterating through the map.
+ ArrayMap<ConfigKey, ArrayMap<Long, PendingIntentRef>> broadcastSubscriberCopy =
+ new ArrayMap<>();
+ synchronized (mLock) {
+ for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry :
+ mBroadcastSubscriberPirMap.entrySet()) {
+ broadcastSubscriberCopy.put(entry.getKey(), new ArrayMap(entry.getValue()));
}
- // Assume statsd is ready since this is called form statscompanion, link to statsd.
- try {
- sStatsd.asBinder().linkToDeath((IBinder.DeathRecipient) () -> {
- sStatsd = null;
- }, 0);
- } catch (RemoteException e) {
- Slog.e(TAG, "linkToDeath(StatsdDeathRecipient) failed", e);
+ }
+
+ for (Map.Entry<ConfigKey, ArrayMap<Long, PendingIntentRef>> entry :
+ mBroadcastSubscriberPirMap.entrySet()) {
+ ConfigKey configKey = entry.getKey();
+ for (Map.Entry<Long, PendingIntentRef> subscriberEntry : entry.getValue().entrySet()) {
+ statsd.setBroadcastSubscriber(configKey.getConfigId(), subscriberEntry.getKey(),
+ subscriberEntry.getValue(), configKey.getUid());
}
}
}
diff --git a/api/current.txt b/api/current.txt
index 9425b81..e27c318 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -87,6 +87,7 @@
field public static final String INSTALL_PACKAGES = "android.permission.INSTALL_PACKAGES";
field public static final String INSTALL_SHORTCUT = "com.android.launcher.permission.INSTALL_SHORTCUT";
field public static final String INSTANT_APP_FOREGROUND_SERVICE = "android.permission.INSTANT_APP_FOREGROUND_SERVICE";
+ field public static final String INTERACT_ACROSS_PROFILES = "android.permission.INTERACT_ACROSS_PROFILES";
field public static final String INTERNET = "android.permission.INTERNET";
field public static final String KILL_BACKGROUND_PROCESSES = "android.permission.KILL_BACKGROUND_PROCESSES";
field public static final String LOCATION_HARDWARE = "android.permission.LOCATION_HARDWARE";
@@ -114,6 +115,7 @@
field public static final String READ_LOGS = "android.permission.READ_LOGS";
field public static final String READ_PHONE_NUMBERS = "android.permission.READ_PHONE_NUMBERS";
field public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
+ field public static final String READ_PRECISE_PHONE_STATE = "android.permission.READ_PRECISE_PHONE_STATE";
field public static final String READ_SMS = "android.permission.READ_SMS";
field public static final String READ_SYNC_SETTINGS = "android.permission.READ_SYNC_SETTINGS";
field public static final String READ_SYNC_STATS = "android.permission.READ_SYNC_STATS";
@@ -384,6 +386,7 @@
field public static final int canRequestFingerprintGestures = 16844109; // 0x101054d
field public static final int canRequestTouchExplorationMode = 16843735; // 0x10103d7
field public static final int canRetrieveWindowContent = 16843653; // 0x1010385
+ field public static final int canTakeScreenshot = 16844304; // 0x1010610
field public static final int candidatesTextStyleSpans = 16843312; // 0x1010230
field public static final int cantSaveState = 16844142; // 0x101056e
field @Deprecated public static final int capitalize = 16843113; // 0x1010169
@@ -478,6 +481,7 @@
field public static final int countDown = 16844059; // 0x101051b
field public static final int country = 16843962; // 0x10104ba
field public static final int cropToPadding = 16843043; // 0x1010123
+ field public static final int crossProfile = 16844303; // 0x101060f
field public static final int cursorVisible = 16843090; // 0x1010152
field public static final int customNavigationLayout = 16843474; // 0x10102d2
field public static final int customTokens = 16843579; // 0x101033b
@@ -1142,6 +1146,7 @@
field public static final int resizeable = 16843405; // 0x101028d
field public static final int resizeableActivity = 16844022; // 0x10104f6
field public static final int resource = 16842789; // 0x1010025
+ field public static final int resourcesMap = 16844297; // 0x1010609
field public static final int restoreAnyVersion = 16843450; // 0x10102ba
field @Deprecated public static final int restoreNeedsApplication = 16843421; // 0x101029d
field public static final int restrictedAccountType = 16843733; // 0x10103d5
@@ -2860,6 +2865,7 @@
method protected void onServiceConnected();
method public final boolean performGlobalAction(int);
method public final void setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo);
+ method public boolean takeScreenshot(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.graphics.Bitmap>);
field public static final int GESTURE_SWIPE_DOWN = 2; // 0x2
field public static final int GESTURE_SWIPE_DOWN_AND_LEFT = 15; // 0xf
field public static final int GESTURE_SWIPE_DOWN_AND_RIGHT = 16; // 0x10
@@ -2921,6 +2927,7 @@
method public int getShowMode();
method public boolean removeOnShowModeChangedListener(@NonNull android.accessibilityservice.AccessibilityService.SoftKeyboardController.OnShowModeChangedListener);
method public boolean setShowMode(int);
+ method public boolean switchToInputMethod(@NonNull String);
}
public static interface AccessibilityService.SoftKeyboardController.OnShowModeChangedListener {
@@ -2955,6 +2962,7 @@
field public static final int CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES = 64; // 0x40
field public static final int CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION = 2; // 0x2
field public static final int CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT = 1; // 0x1
+ field public static final int CAPABILITY_CAN_TAKE_SCREENSHOT = 128; // 0x80
field @NonNull public static final android.os.Parcelable.Creator<android.accessibilityservice.AccessibilityServiceInfo> CREATOR;
field public static final int DEFAULT = 1; // 0x1
field public static final int FEEDBACK_ALL_MASK = -1; // 0xffffffff
@@ -5835,6 +5843,7 @@
method public void enableLights(boolean);
method public void enableVibration(boolean);
method public android.media.AudioAttributes getAudioAttributes();
+ method @Nullable public String getConversationId();
method public String getDescription();
method public String getGroup();
method public String getId();
@@ -5842,12 +5851,14 @@
method public int getLightColor();
method public int getLockscreenVisibility();
method public CharSequence getName();
+ method @Nullable public String getParentChannelId();
method public android.net.Uri getSound();
method public long[] getVibrationPattern();
method public boolean hasUserSetImportance();
method public boolean hasUserSetSound();
method public void setAllowBubbles(boolean);
method public void setBypassDnd(boolean);
+ method public void setConversationId(@Nullable String, @Nullable String);
method public void setDescription(String);
method public void setGroup(String);
method public void setImportance(int);
@@ -5860,6 +5871,7 @@
method public boolean shouldShowLights();
method public boolean shouldVibrate();
method public void writeToParcel(android.os.Parcel, int);
+ field public static final String CONVERSATION_CHANNEL_ID_FORMAT = "%1$s : %2$s";
field @NonNull public static final android.os.Parcelable.Creator<android.app.NotificationChannel> CREATOR;
field public static final String DEFAULT_CHANNEL_ID = "miscellaneous";
}
@@ -5901,6 +5913,7 @@
method public final int getCurrentInterruptionFilter();
method public int getImportance();
method public android.app.NotificationChannel getNotificationChannel(String);
+ method @Nullable public android.app.NotificationChannel getNotificationChannel(@NonNull String, @NonNull String);
method public android.app.NotificationChannelGroup getNotificationChannelGroup(String);
method public java.util.List<android.app.NotificationChannelGroup> getNotificationChannelGroups();
method public java.util.List<android.app.NotificationChannel> getNotificationChannels();
@@ -6754,6 +6767,7 @@
method @NonNull public java.util.List<java.lang.String> getDelegatedScopes(@Nullable android.content.ComponentName, @NonNull String);
method public CharSequence getDeviceOwnerLockScreenInfo();
method public CharSequence getEndUserSessionMessage(@NonNull android.content.ComponentName);
+ method @Nullable public android.app.admin.FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(@Nullable android.content.ComponentName);
method @Nullable public String getGlobalPrivateDnsHost(@NonNull android.content.ComponentName);
method public int getGlobalPrivateDnsMode(@NonNull android.content.ComponentName);
method @NonNull public java.util.List<byte[]> getInstalledCaCerts(@Nullable android.content.ComponentName);
@@ -6788,6 +6802,7 @@
method @Nullable public java.util.List<java.lang.String> getPermittedAccessibilityServices(@NonNull android.content.ComponentName);
method @Nullable public java.util.List<java.lang.String> getPermittedCrossProfileNotificationListeners(@NonNull android.content.ComponentName);
method @Nullable public java.util.List<java.lang.String> getPermittedInputMethods(@NonNull android.content.ComponentName);
+ method @NonNull public java.util.List<java.lang.String> getProtectedPackages(@NonNull android.content.ComponentName);
method public long getRequiredStrongAuthTimeout(@Nullable android.content.ComponentName);
method public boolean getScreenCaptureDisabled(@Nullable android.content.ComponentName);
method public java.util.List<android.os.UserHandle> getSecondaryUsers(@NonNull android.content.ComponentName);
@@ -6824,6 +6839,7 @@
method public boolean isManagedProfile(@NonNull android.content.ComponentName);
method public boolean isMasterVolumeMuted(@NonNull android.content.ComponentName);
method public boolean isNetworkLoggingEnabled(@Nullable android.content.ComponentName);
+ method public boolean isOrganizationOwnedDeviceWithManagedProfile();
method public boolean isOverrideApnEnabled(@NonNull android.content.ComponentName);
method public boolean isPackageSuspended(@NonNull android.content.ComponentName, String) throws android.content.pm.PackageManager.NameNotFoundException;
method public boolean isProfileOwnerApp(String);
@@ -6871,6 +6887,7 @@
method public void setDelegatedScopes(@NonNull android.content.ComponentName, @NonNull String, @NonNull java.util.List<java.lang.String>);
method public void setDeviceOwnerLockScreenInfo(@NonNull android.content.ComponentName, CharSequence);
method public void setEndUserSessionMessage(@NonNull android.content.ComponentName, @Nullable CharSequence);
+ method public void setFactoryResetProtectionPolicy(@NonNull android.content.ComponentName, @Nullable android.app.admin.FactoryResetProtectionPolicy);
method public int setGlobalPrivateDnsModeOpportunistic(@NonNull android.content.ComponentName);
method @WorkerThread public int setGlobalPrivateDnsModeSpecifiedHost(@NonNull android.content.ComponentName, @NonNull String);
method public void setGlobalSetting(@NonNull android.content.ComponentName, String, String);
@@ -6909,6 +6926,7 @@
method public boolean setPermittedInputMethods(@NonNull android.content.ComponentName, java.util.List<java.lang.String>);
method public void setProfileEnabled(@NonNull android.content.ComponentName);
method public void setProfileName(@NonNull android.content.ComponentName, String);
+ method public void setProtectedPackages(@NonNull android.content.ComponentName, @NonNull java.util.List<java.lang.String>);
method public void setRecommendedGlobalProxy(@NonNull android.content.ComponentName, @Nullable android.net.ProxyInfo);
method public void setRequiredStrongAuthTimeout(@NonNull android.content.ComponentName, long);
method public boolean setResetPasswordToken(android.content.ComponentName, byte[]);
@@ -7104,6 +7122,21 @@
field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.DnsEvent> CREATOR;
}
+ public final class FactoryResetProtectionPolicy implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.List<java.lang.String> getFactoryResetProtectionAccounts();
+ method public boolean isFactoryResetProtectionDisabled();
+ method public void writeToParcel(@NonNull android.os.Parcel, @Nullable int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.FactoryResetProtectionPolicy> CREATOR;
+ }
+
+ public static class FactoryResetProtectionPolicy.Builder {
+ ctor public FactoryResetProtectionPolicy.Builder();
+ method @NonNull public android.app.admin.FactoryResetProtectionPolicy build();
+ method @NonNull public android.app.admin.FactoryResetProtectionPolicy.Builder setFactoryResetProtectionAccounts(@NonNull java.util.List<java.lang.String>);
+ method @NonNull public android.app.admin.FactoryResetProtectionPolicy.Builder setFactoryResetProtectionDisabled(boolean);
+ }
+
public class FreezePeriod {
ctor public FreezePeriod(java.time.MonthDay, java.time.MonthDay);
method public java.time.MonthDay getEnd();
@@ -7126,6 +7159,7 @@
field public static final int TAG_ADB_SHELL_CMD = 210002; // 0x33452
field public static final int TAG_ADB_SHELL_INTERACTIVE = 210001; // 0x33451
field public static final int TAG_APP_PROCESS_START = 210005; // 0x33455
+ field public static final int TAG_CAMERA_POLICY_SET = 210034; // 0x33472
field public static final int TAG_CERT_AUTHORITY_INSTALLED = 210029; // 0x3346d
field public static final int TAG_CERT_AUTHORITY_REMOVED = 210030; // 0x3346e
field public static final int TAG_CERT_VALIDATION_FAILURE = 210033; // 0x33471
@@ -9499,6 +9533,7 @@
method public void dump(java.io.FileDescriptor, java.io.PrintWriter, String[]);
method @Nullable public final String getCallingFeatureId();
method @Nullable public final String getCallingPackage();
+ method @Nullable public final String getCallingPackageUnchecked();
method @Nullable public final android.content.Context getContext();
method @Nullable public final android.content.pm.PathPermission[] getPathPermissions();
method @Nullable public final String getReadPermission();
@@ -9508,6 +9543,7 @@
method @Nullable public abstract android.net.Uri insert(@NonNull android.net.Uri, @Nullable android.content.ContentValues);
method @Nullable public android.net.Uri insert(@NonNull android.net.Uri, @Nullable android.content.ContentValues, @Nullable android.os.Bundle);
method protected boolean isTemporary();
+ method public void onCallingPackageChanged();
method public void onConfigurationChanged(android.content.res.Configuration);
method public abstract boolean onCreate();
method public void onLowMemory();
@@ -9622,13 +9658,13 @@
ctor public ContentProviderResult(@NonNull android.net.Uri);
ctor public ContentProviderResult(int);
ctor public ContentProviderResult(@NonNull android.os.Bundle);
- ctor public ContentProviderResult(@NonNull Exception);
+ ctor public ContentProviderResult(@NonNull Throwable);
ctor public ContentProviderResult(android.os.Parcel);
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.content.ContentProviderResult> CREATOR;
field @Nullable public final Integer count;
- field @Nullable public final Exception exception;
+ field @Nullable public final Throwable exception;
field @Nullable public final android.os.Bundle extras;
field @Nullable public final android.net.Uri uri;
}
@@ -9816,6 +9852,7 @@
method public boolean bindIsolatedService(@NonNull @RequiresPermission android.content.Intent, int, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.content.ServiceConnection);
method public abstract boolean bindService(@RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int);
method public boolean bindService(@NonNull @RequiresPermission android.content.Intent, int, @NonNull java.util.concurrent.Executor, @NonNull android.content.ServiceConnection);
+ method @RequiresPermission(anyOf={"android.permission.INTERACT_ACROSS_USERS", android.Manifest.permission.INTERACT_ACROSS_PROFILES}) public boolean bindServiceAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.content.ServiceConnection, int, @NonNull android.os.UserHandle);
method @CheckResult(suggest="#enforceCallingOrSelfPermission(String,String)") public abstract int checkCallingOrSelfPermission(@NonNull String);
method @CheckResult(suggest="#enforceCallingOrSelfUriPermission(Uri,int,String)") public abstract int checkCallingOrSelfUriPermission(android.net.Uri, int);
method @CheckResult(suggest="#enforceCallingPermission(String,String)") public abstract int checkCallingPermission(@NonNull String);
@@ -9993,6 +10030,7 @@
field public static final String MEDIA_ROUTER_SERVICE = "media_router";
field public static final String MEDIA_SESSION_SERVICE = "media_session";
field public static final String MIDI_SERVICE = "midi";
+ field public static final String MMS_SERVICE = "mms";
field public static final int MODE_APPEND = 32768; // 0x8000
field public static final int MODE_ENABLE_WRITE_AHEAD_LOGGING = 8; // 0x8
field @Deprecated public static final int MODE_MULTI_PROCESS = 4; // 0x4
@@ -10367,6 +10405,7 @@
field public static final String ACTION_CLOSE_SYSTEM_DIALOGS = "android.intent.action.CLOSE_SYSTEM_DIALOGS";
field public static final String ACTION_CONFIGURATION_CHANGED = "android.intent.action.CONFIGURATION_CHANGED";
field public static final String ACTION_CREATE_DOCUMENT = "android.intent.action.CREATE_DOCUMENT";
+ field public static final String ACTION_CREATE_REMINDER = "android.intent.action.CREATE_REMINDER";
field public static final String ACTION_CREATE_SHORTCUT = "android.intent.action.CREATE_SHORTCUT";
field public static final String ACTION_DATE_CHANGED = "android.intent.action.DATE_CHANGED";
field public static final String ACTION_DEFAULT = "android.intent.action.VIEW";
@@ -10592,6 +10631,7 @@
field public static final String EXTRA_SUSPENDED_PACKAGE_EXTRAS = "android.intent.extra.SUSPENDED_PACKAGE_EXTRAS";
field public static final String EXTRA_TEMPLATE = "android.intent.extra.TEMPLATE";
field public static final String EXTRA_TEXT = "android.intent.extra.TEXT";
+ field public static final String EXTRA_TIME = "android.intent.extra.TIME";
field public static final String EXTRA_TITLE = "android.intent.extra.TITLE";
field public static final String EXTRA_UID = "android.intent.extra.UID";
field public static final String EXTRA_USER = "android.intent.extra.USER";
@@ -11348,6 +11388,9 @@
}
public class CrossProfileApps {
+ method public boolean canInteractAcrossProfiles();
+ method public boolean canRequestInteractAcrossProfiles();
+ method @Nullable public android.content.Intent createRequestInteractAcrossProfilesIntent();
method @NonNull public android.graphics.drawable.Drawable getProfileSwitchingIconDrawable(@NonNull android.os.UserHandle);
method @NonNull public CharSequence getProfileSwitchingLabel(@NonNull android.os.UserHandle);
method @NonNull public java.util.List<android.os.UserHandle> getTargetUserProfiles();
@@ -11381,6 +11424,7 @@
public final class InstallSourceInfo implements android.os.Parcelable {
method public int describeContents();
method @Nullable public String getInitiatingPackageName();
+ method @Nullable public android.content.pm.SigningInfo getInitiatingPackageSigningInfo();
method @Nullable public String getInstallingPackageName();
method @Nullable public String getOriginatingPackageName();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -11747,6 +11791,7 @@
method @NonNull public abstract CharSequence getApplicationLabel(@NonNull android.content.pm.ApplicationInfo);
method @Nullable public abstract android.graphics.drawable.Drawable getApplicationLogo(@NonNull android.content.pm.ApplicationInfo);
method @Nullable public abstract android.graphics.drawable.Drawable getApplicationLogo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
+ method @NonNull public CharSequence getBackgroundPermissionButtonLabel();
method @Nullable public abstract android.content.pm.ChangedPackages getChangedPackages(@IntRange(from=0) int);
method public abstract int getComponentEnabledSetting(@NonNull android.content.ComponentName);
method @NonNull public abstract android.graphics.drawable.Drawable getDefaultActivityIcon();
@@ -11920,6 +11965,7 @@
field public static final String FEATURE_STRONGBOX_KEYSTORE = "android.hardware.strongbox_keystore";
field public static final String FEATURE_TELEPHONY = "android.hardware.telephony";
field public static final String FEATURE_TELEPHONY_CDMA = "android.hardware.telephony.cdma";
+ field public static final String FEATURE_TELEPHONY_DATA = "android.hardware.telephony.data";
field public static final String FEATURE_TELEPHONY_EUICC = "android.hardware.telephony.euicc";
field public static final String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm";
field public static final String FEATURE_TELEPHONY_IMS = "android.hardware.telephony.ims";
@@ -13318,6 +13364,7 @@
method @Deprecated public String buildUnionSubQuery(String, String[], java.util.Set<java.lang.String>, int, String, String, String[], String, String);
method public int delete(@NonNull android.database.sqlite.SQLiteDatabase, @Nullable String, @Nullable String[]);
method @Nullable public android.database.sqlite.SQLiteDatabase.CursorFactory getCursorFactory();
+ method @Nullable public java.util.Collection<java.util.regex.Pattern> getProjectionGreylist();
method @Nullable public java.util.Map<java.lang.String,java.lang.String> getProjectionMap();
method @Nullable public String getTables();
method public long insert(@NonNull android.database.sqlite.SQLiteDatabase, @NonNull android.content.ContentValues);
@@ -13330,6 +13377,7 @@
method public android.database.Cursor query(android.database.sqlite.SQLiteDatabase, String[], String, String[], String, String, String, String, android.os.CancellationSignal);
method public void setCursorFactory(@Nullable android.database.sqlite.SQLiteDatabase.CursorFactory);
method public void setDistinct(boolean);
+ method public void setProjectionGreylist(@Nullable java.util.Collection<java.util.regex.Pattern>);
method public void setProjectionMap(@Nullable java.util.Map<java.lang.String,java.lang.String>);
method public void setStrict(boolean);
method public void setStrictColumns(boolean);
@@ -16812,6 +16860,8 @@
method @Nullable public CharSequence getSubtitle();
method @NonNull public CharSequence getTitle();
method public boolean isConfirmationRequired();
+ field public static final int AUTHENTICATION_RESULT_TYPE_BIOMETRIC = 2; // 0x2
+ field public static final int AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL = 1; // 0x1
field public static final int BIOMETRIC_ACQUIRED_GOOD = 0; // 0x0
field public static final int BIOMETRIC_ACQUIRED_IMAGER_DIRTY = 3; // 0x3
field public static final int BIOMETRIC_ACQUIRED_INSUFFICIENT = 2; // 0x2
@@ -16841,6 +16891,7 @@
}
public static class BiometricPrompt.AuthenticationResult {
+ method public int getAuthenticationType();
method public android.hardware.biometrics.BiometricPrompt.CryptoObject getCryptoObject();
}
@@ -17040,13 +17091,13 @@
method public abstract void close();
method @NonNull public abstract android.hardware.camera2.CaptureRequest.Builder createCaptureRequest(int) throws android.hardware.camera2.CameraAccessException;
method @NonNull public android.hardware.camera2.CaptureRequest.Builder createCaptureRequest(int, java.util.Set<java.lang.String>) throws android.hardware.camera2.CameraAccessException;
- method public abstract void createCaptureSession(@NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+ method @Deprecated public abstract void createCaptureSession(@NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method public void createCaptureSession(android.hardware.camera2.params.SessionConfiguration) throws android.hardware.camera2.CameraAccessException;
- method public abstract void createCaptureSessionByOutputConfigurations(java.util.List<android.hardware.camera2.params.OutputConfiguration>, android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
- method public abstract void createConstrainedHighSpeedCaptureSession(@NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+ method @Deprecated public abstract void createCaptureSessionByOutputConfigurations(java.util.List<android.hardware.camera2.params.OutputConfiguration>, android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+ method @Deprecated public abstract void createConstrainedHighSpeedCaptureSession(@NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method @NonNull public abstract android.hardware.camera2.CaptureRequest.Builder createReprocessCaptureRequest(@NonNull android.hardware.camera2.TotalCaptureResult) throws android.hardware.camera2.CameraAccessException;
- method public abstract void createReprocessableCaptureSession(@NonNull android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
- method public abstract void createReprocessableCaptureSessionByConfigurations(@NonNull android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+ method @Deprecated public abstract void createReprocessableCaptureSession(@NonNull android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.view.Surface>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+ method @Deprecated public abstract void createReprocessableCaptureSessionByConfigurations(@NonNull android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
method public int getCameraAudioRestriction() throws android.hardware.camera2.CameraAccessException;
method @NonNull public abstract String getId();
method public boolean isSessionConfigurationSupported(@NonNull android.hardware.camera2.params.SessionConfiguration) throws android.hardware.camera2.CameraAccessException;
@@ -17238,6 +17289,7 @@
field public static final int LENS_OPTICAL_STABILIZATION_MODE_ON = 1; // 0x1
field public static final int LENS_POSE_REFERENCE_GYROSCOPE = 1; // 0x1
field public static final int LENS_POSE_REFERENCE_PRIMARY_CAMERA = 0; // 0x0
+ field public static final int LENS_POSE_REFERENCE_UNDEFINED = 2; // 0x2
field public static final int LENS_STATE_MOVING = 1; // 0x1
field public static final int LENS_STATE_STATIONARY = 0; // 0x0
field public static final int LOGICAL_MULTI_CAMERA_SENSOR_SYNC_TYPE_APPROXIMATE = 0; // 0x0
@@ -23642,6 +23694,7 @@
field public static final int TYPE_BUILTIN_EARPIECE = 1; // 0x1
field public static final int TYPE_BUILTIN_MIC = 15; // 0xf
field public static final int TYPE_BUILTIN_SPEAKER = 2; // 0x2
+ field public static final int TYPE_BUILTIN_SPEAKER_SAFE = 24; // 0x18
field public static final int TYPE_BUS = 21; // 0x15
field public static final int TYPE_DOCK = 13; // 0xd
field public static final int TYPE_FM = 14; // 0xe
@@ -25776,8 +25829,8 @@
method @Nullable public android.graphics.Bitmap getImageAtIndex(int);
method @Nullable public android.graphics.Bitmap getPrimaryImage(@NonNull android.media.MediaMetadataRetriever.BitmapParams);
method @Nullable public android.graphics.Bitmap getPrimaryImage();
- method @Nullable public android.graphics.Bitmap getScaledFrameAtTime(long, int, int, int);
- method @Nullable public android.graphics.Bitmap getScaledFrameAtTime(long, int, int, int, @NonNull android.media.MediaMetadataRetriever.BitmapParams);
+ method @Nullable public android.graphics.Bitmap getScaledFrameAtTime(long, int, @IntRange(from=1) int, @IntRange(from=1) int);
+ method @Nullable public android.graphics.Bitmap getScaledFrameAtTime(long, int, @IntRange(from=1) int, @IntRange(from=1) int, @NonNull android.media.MediaMetadataRetriever.BitmapParams);
method public void release();
method public void setDataSource(String) throws java.lang.IllegalArgumentException;
method public void setDataSource(String, java.util.Map<java.lang.String,java.lang.String>) throws java.lang.IllegalArgumentException;
@@ -26275,6 +26328,59 @@
field public static final int SURFACE = 2; // 0x2
}
+ public final class MediaRoute2Info implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getConnectionState();
+ method @Nullable public CharSequence getDescription();
+ method public int getDeviceType();
+ method @Nullable public android.os.Bundle getExtras();
+ method @NonNull public java.util.List<java.lang.String> getFeatures();
+ method @Nullable public android.net.Uri getIconUri();
+ method @NonNull public String getId();
+ method @NonNull public CharSequence getName();
+ method public int getVolume();
+ method public int getVolumeHandling();
+ method public int getVolumeMax();
+ method public boolean hasAnyFeatures(@NonNull java.util.Collection<java.lang.String>);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int CONNECTION_STATE_CONNECTED = 2; // 0x2
+ field public static final int CONNECTION_STATE_CONNECTING = 1; // 0x1
+ field public static final int CONNECTION_STATE_DISCONNECTED = 0; // 0x0
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.MediaRoute2Info> CREATOR;
+ field public static final int DEVICE_TYPE_BLUETOOTH = 3; // 0x3
+ field public static final int DEVICE_TYPE_REMOTE_SPEAKER = 2; // 0x2
+ field public static final int DEVICE_TYPE_REMOTE_TV = 1; // 0x1
+ field public static final int DEVICE_TYPE_UNKNOWN = 0; // 0x0
+ field public static final int PLAYBACK_VOLUME_FIXED = 0; // 0x0
+ field public static final int PLAYBACK_VOLUME_VARIABLE = 1; // 0x1
+ }
+
+ public static final class MediaRoute2Info.Builder {
+ ctor public MediaRoute2Info.Builder(@NonNull String, @NonNull CharSequence);
+ ctor public MediaRoute2Info.Builder(@NonNull android.media.MediaRoute2Info);
+ method @NonNull public android.media.MediaRoute2Info.Builder addFeature(@NonNull String);
+ method @NonNull public android.media.MediaRoute2Info.Builder addFeatures(@NonNull java.util.Collection<java.lang.String>);
+ method @NonNull public android.media.MediaRoute2Info build();
+ method @NonNull public android.media.MediaRoute2Info.Builder clearFeatures();
+ method @NonNull public android.media.MediaRoute2Info.Builder setClientPackageName(@Nullable String);
+ method @NonNull public android.media.MediaRoute2Info.Builder setConnectionState(int);
+ method @NonNull public android.media.MediaRoute2Info.Builder setDescription(@Nullable CharSequence);
+ method @NonNull public android.media.MediaRoute2Info.Builder setDeviceType(int);
+ method @NonNull public android.media.MediaRoute2Info.Builder setExtras(@Nullable android.os.Bundle);
+ method @NonNull public android.media.MediaRoute2Info.Builder setIconUri(@Nullable android.net.Uri);
+ method @NonNull public android.media.MediaRoute2Info.Builder setVolume(int);
+ method @NonNull public android.media.MediaRoute2Info.Builder setVolumeHandling(int);
+ method @NonNull public android.media.MediaRoute2Info.Builder setVolumeMax(int);
+ }
+
+ public abstract class MediaRoute2ProviderService extends android.app.Service {
+ ctor public MediaRoute2ProviderService();
+ method public final void notifyRoutes(@NonNull java.util.Collection<android.media.MediaRoute2Info>);
+ method @NonNull public android.os.IBinder onBind(@NonNull android.content.Intent);
+ method public void onDiscoveryPreferenceChanged(@NonNull android.media.RouteDiscoveryPreference);
+ field public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService";
+ }
+
public class MediaRouter {
method public void addCallback(int, android.media.MediaRouter.Callback);
method public void addCallback(int, android.media.MediaRouter.Callback, int);
@@ -26398,6 +26504,20 @@
method public abstract void onVolumeUpdateRequest(android.media.MediaRouter.RouteInfo, int);
}
+ public class MediaRouter2 {
+ method @NonNull public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context);
+ method @NonNull public java.util.List<android.media.MediaRoute2Info> getRoutes();
+ method public void registerRouteCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.RouteCallback, @NonNull android.media.RouteDiscoveryPreference);
+ method public void unregisterRouteCallback(@NonNull android.media.MediaRouter2.RouteCallback);
+ }
+
+ public static class MediaRouter2.RouteCallback {
+ ctor public MediaRouter2.RouteCallback();
+ method public void onRoutesAdded(@NonNull java.util.List<android.media.MediaRoute2Info>);
+ method public void onRoutesChanged(@NonNull java.util.List<android.media.MediaRoute2Info>);
+ method public void onRoutesRemoved(@NonNull java.util.List<android.media.MediaRoute2Info>);
+ }
+
public class MediaScannerConnection implements android.content.ServiceConnection {
ctor public MediaScannerConnection(android.content.Context, android.media.MediaScannerConnection.MediaScannerConnectionClient);
method public void connect();
@@ -26756,6 +26876,22 @@
field public static final int URI_COLUMN_INDEX = 2; // 0x2
}
+ public final class RouteDiscoveryPreference implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.List<java.lang.String> getPreferredFeatures();
+ method public boolean isActiveScan();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.RouteDiscoveryPreference> CREATOR;
+ }
+
+ public static final class RouteDiscoveryPreference.Builder {
+ ctor public RouteDiscoveryPreference.Builder(@NonNull java.util.List<java.lang.String>, boolean);
+ ctor public RouteDiscoveryPreference.Builder(@NonNull android.media.RouteDiscoveryPreference);
+ method @NonNull public android.media.RouteDiscoveryPreference build();
+ method @NonNull public android.media.RouteDiscoveryPreference.Builder setActiveScan(boolean);
+ method @NonNull public android.media.RouteDiscoveryPreference.Builder setPreferredFeatures(@NonNull java.util.List<java.lang.String>);
+ }
+
public final class Session2Command implements android.os.Parcelable {
ctor public Session2Command(int);
ctor public Session2Command(@NonNull String, @Nullable android.os.Bundle);
@@ -27014,9 +27150,11 @@
public abstract class VolumeProvider {
ctor public VolumeProvider(int, int, int);
+ ctor public VolumeProvider(int, int, int, @Nullable String);
method public final int getCurrentVolume();
method public final int getMaxVolume();
method public final int getVolumeControl();
+ method @Nullable public final String getVolumeControlId();
method public void onAdjustVolume(int);
method public void onSetVolumeTo(int);
method public final void setCurrentVolume(int);
@@ -27876,6 +28014,7 @@
method public int getMaxVolume();
method public int getPlaybackType();
method public int getVolumeControl();
+ method @Nullable public String getVolumeControlId();
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.media.session.MediaController.PlaybackInfo> CREATOR;
field public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1
@@ -28577,7 +28716,9 @@
ctor public TvInputService();
method public final android.os.IBinder onBind(android.content.Intent);
method @Nullable public android.media.tv.TvInputService.RecordingSession onCreateRecordingSession(String);
+ method @Nullable public android.media.tv.TvInputService.RecordingSession onCreateRecordingSession(@NonNull String, @NonNull String);
method @Nullable public abstract android.media.tv.TvInputService.Session onCreateSession(String);
+ method @Nullable public android.media.tv.TvInputService.Session onCreateSession(@NonNull String, @NonNull String);
field public static final String SERVICE_INTERFACE = "android.media.tv.TvInputService";
field public static final String SERVICE_META_DATA = "android.media.tv.input";
}
@@ -28676,8 +28817,11 @@
method public int getVideoHeight();
method public float getVideoPixelAspectRatio();
method public int getVideoWidth();
+ method public boolean isAudioDescription();
method public boolean isEncrypted();
- method public void writeToParcel(android.os.Parcel, int);
+ method public boolean isHardOfHearing();
+ method public boolean isSpokenSubtitle();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.TvTrackInfo> CREATOR;
field public static final int TYPE_AUDIO = 0; // 0x0
field public static final int TYPE_SUBTITLE = 2; // 0x2
@@ -28686,18 +28830,21 @@
public static final class TvTrackInfo.Builder {
ctor public TvTrackInfo.Builder(int, @NonNull String);
- method public android.media.tv.TvTrackInfo build();
- method public android.media.tv.TvTrackInfo.Builder setAudioChannelCount(int);
- method public android.media.tv.TvTrackInfo.Builder setAudioSampleRate(int);
- method public android.media.tv.TvTrackInfo.Builder setDescription(CharSequence);
+ method @NonNull public android.media.tv.TvTrackInfo build();
+ method @NonNull public android.media.tv.TvTrackInfo.Builder setAudioChannelCount(int);
+ method @NonNull public android.media.tv.TvTrackInfo.Builder setAudioDescription(boolean);
+ method @NonNull public android.media.tv.TvTrackInfo.Builder setAudioSampleRate(int);
+ method @NonNull public android.media.tv.TvTrackInfo.Builder setDescription(@NonNull CharSequence);
method @NonNull public android.media.tv.TvTrackInfo.Builder setEncrypted(boolean);
- method public android.media.tv.TvTrackInfo.Builder setExtra(android.os.Bundle);
- method public android.media.tv.TvTrackInfo.Builder setLanguage(String);
- method public android.media.tv.TvTrackInfo.Builder setVideoActiveFormatDescription(byte);
- method public android.media.tv.TvTrackInfo.Builder setVideoFrameRate(float);
- method public android.media.tv.TvTrackInfo.Builder setVideoHeight(int);
- method public android.media.tv.TvTrackInfo.Builder setVideoPixelAspectRatio(float);
- method public android.media.tv.TvTrackInfo.Builder setVideoWidth(int);
+ method @NonNull public android.media.tv.TvTrackInfo.Builder setExtra(@NonNull android.os.Bundle);
+ method @NonNull public android.media.tv.TvTrackInfo.Builder setHardOfHearing(boolean);
+ method @NonNull public android.media.tv.TvTrackInfo.Builder setLanguage(@NonNull String);
+ method @NonNull public android.media.tv.TvTrackInfo.Builder setSpokenSubtitle(boolean);
+ method @NonNull public android.media.tv.TvTrackInfo.Builder setVideoActiveFormatDescription(byte);
+ method @NonNull public android.media.tv.TvTrackInfo.Builder setVideoFrameRate(float);
+ method @NonNull public android.media.tv.TvTrackInfo.Builder setVideoHeight(int);
+ method @NonNull public android.media.tv.TvTrackInfo.Builder setVideoPixelAspectRatio(float);
+ method @NonNull public android.media.tv.TvTrackInfo.Builder setVideoWidth(int);
}
public class TvView extends android.view.ViewGroup {
@@ -29004,6 +29151,37 @@
field @NonNull public static final android.os.Parcelable.Creator<android.net.CaptivePortal> CREATOR;
}
+ public class ConnectivityDiagnosticsManager {
+ method public void registerConnectivityDiagnosticsCallback(@NonNull android.net.NetworkRequest, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback);
+ method public void unregisterConnectivityDiagnosticsCallback(@NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback);
+ field public static final int DETECTION_METHOD_DNS_EVENTS = 1; // 0x1
+ field public static final int DETECTION_METHOD_TCP_METRICS = 2; // 0x2
+ }
+
+ public abstract static class ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback {
+ ctor public ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback();
+ method public void onConnectivityReport(@NonNull android.net.ConnectivityDiagnosticsManager.ConnectivityReport);
+ method public void onDataStallSuspected(@NonNull android.net.ConnectivityDiagnosticsManager.DataStallReport);
+ method public void onNetworkConnectivityReported(@NonNull android.net.Network, boolean);
+ }
+
+ public static class ConnectivityDiagnosticsManager.ConnectivityReport {
+ ctor public ConnectivityDiagnosticsManager.ConnectivityReport(@NonNull android.net.Network, long, @NonNull android.net.LinkProperties, @NonNull android.net.NetworkCapabilities, @NonNull android.os.PersistableBundle);
+ field @NonNull public final android.os.PersistableBundle additionalInfo;
+ field @NonNull public final android.net.LinkProperties linkProperties;
+ field @NonNull public final android.net.Network network;
+ field @NonNull public final android.net.NetworkCapabilities networkCapabilities;
+ field public final long reportTimestamp;
+ }
+
+ public static class ConnectivityDiagnosticsManager.DataStallReport {
+ ctor public ConnectivityDiagnosticsManager.DataStallReport(@NonNull android.net.Network, long, int, @NonNull android.os.PersistableBundle);
+ field public final int detectionMethod;
+ field @NonNull public final android.net.Network network;
+ field public final long reportTimestamp;
+ field @NonNull public final android.os.PersistableBundle stallDetails;
+ }
+
public class ConnectivityManager {
method public void addDefaultNetworkActiveListener(android.net.ConnectivityManager.OnNetworkActiveListener);
method public boolean bindProcessToNetwork(@Nullable android.net.Network);
@@ -29108,6 +29286,7 @@
ctor public DhcpInfo();
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.DhcpInfo> CREATOR;
field public int dns1;
field public int dns2;
field public int gateway;
@@ -29237,6 +29416,7 @@
method public boolean addRoute(@NonNull android.net.RouteInfo);
method public void clear();
method public int describeContents();
+ method @Nullable public java.net.Inet4Address getDhcpServerAddress();
method @NonNull public java.util.List<java.net.InetAddress> getDnsServers();
method @Nullable public String getDomains();
method @Nullable public android.net.ProxyInfo getHttpProxy();
@@ -29248,6 +29428,7 @@
method @NonNull public java.util.List<android.net.RouteInfo> getRoutes();
method public boolean isPrivateDnsActive();
method public boolean isWakeOnLanSupported();
+ method public void setDhcpServerAddress(@Nullable java.net.Inet4Address);
method public void setDnsServers(@NonNull java.util.Collection<java.net.InetAddress>);
method public void setDomains(@Nullable String);
method public void setHttpProxy(@Nullable android.net.ProxyInfo);
@@ -29472,9 +29653,10 @@
method public android.net.NetworkRequest.Builder addCapability(int);
method public android.net.NetworkRequest.Builder addTransportType(int);
method public android.net.NetworkRequest build();
+ method @NonNull public android.net.NetworkRequest.Builder clearCapabilities();
method public android.net.NetworkRequest.Builder removeCapability(int);
method public android.net.NetworkRequest.Builder removeTransportType(int);
- method public android.net.NetworkRequest.Builder setNetworkSpecifier(String);
+ method @Deprecated public android.net.NetworkRequest.Builder setNetworkSpecifier(String);
method public android.net.NetworkRequest.Builder setNetworkSpecifier(android.net.NetworkSpecifier);
}
@@ -29573,6 +29755,19 @@
method public void onStopped();
}
+ public final class TelephonyNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getSubscriptionId();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.TelephonyNetworkSpecifier> CREATOR;
+ }
+
+ public static final class TelephonyNetworkSpecifier.Builder {
+ ctor public TelephonyNetworkSpecifier.Builder();
+ method @NonNull public android.net.TelephonyNetworkSpecifier build();
+ method @NonNull public android.net.TelephonyNetworkSpecifier.Builder setSubscriptionId(int);
+ }
+
public class TrafficStats {
ctor public TrafficStats();
method public static void clearThreadStatsTag();
@@ -30236,6 +30431,7 @@
@Deprecated public class WifiConfiguration implements android.os.Parcelable {
ctor @Deprecated public WifiConfiguration();
+ ctor @Deprecated public WifiConfiguration(@NonNull android.net.wifi.WifiConfiguration);
method public int describeContents();
method @Deprecated public android.net.ProxyInfo getHttpProxy();
method @Deprecated @NonNull public String getKey();
@@ -30269,6 +30465,7 @@
@Deprecated public static class WifiConfiguration.AuthAlgorithm {
field @Deprecated public static final int LEAP = 2; // 0x2
field @Deprecated public static final int OPEN = 0; // 0x0
+ field @Deprecated public static final int SAE = 3; // 0x3
field @Deprecated public static final int SHARED = 1; // 0x1
field @Deprecated public static final String[] strings;
field @Deprecated public static final String varName = "auth_alg";
@@ -30328,6 +30525,11 @@
field @Deprecated public static final String[] strings;
}
+ @Deprecated public static class WifiConfiguration.SuiteBCipher {
+ field @Deprecated public static final int ECDHE_ECDSA = 0; // 0x0
+ field @Deprecated public static final int ECDHE_RSA = 1; // 0x1
+ }
+
public class WifiEnterpriseConfig implements android.os.Parcelable {
ctor public WifiEnterpriseConfig();
ctor public WifiEnterpriseConfig(android.net.wifi.WifiEnterpriseConfig);
@@ -30338,6 +30540,7 @@
method @Nullable public java.security.cert.X509Certificate[] getCaCertificates();
method public java.security.cert.X509Certificate getClientCertificate();
method @Nullable public java.security.cert.X509Certificate[] getClientCertificateChain();
+ method @Nullable public java.security.PrivateKey getClientPrivateKey();
method public String getDomainSuffixMatch();
method public int getEapMethod();
method public String getIdentity();
@@ -30346,6 +30549,7 @@
method public String getPlmn();
method public String getRealm();
method @Deprecated public String getSubjectMatch();
+ method public boolean isAuthenticationSimBased();
method public void setAltSubjectMatch(String);
method public void setAnonymousIdentity(String);
method public void setCaCertificate(@Nullable java.security.cert.X509Certificate);
@@ -30451,6 +30655,7 @@
method public boolean isP2pSupported();
method public boolean isPreferredNetworkOffloadSupported();
method @Deprecated public boolean isScanAlwaysAvailable();
+ method public boolean isStaApConcurrencySupported();
method public boolean isTdlsSupported();
method public boolean isWapiSupported();
method public boolean isWifiEnabled();
@@ -30476,6 +30681,7 @@
field public static final String ACTION_PICK_WIFI_NETWORK = "android.net.wifi.PICK_WIFI_NETWORK";
field public static final String ACTION_REQUEST_SCAN_ALWAYS_AVAILABLE = "android.net.wifi.action.REQUEST_SCAN_ALWAYS_AVAILABLE";
field public static final String ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION = "android.net.wifi.action.WIFI_NETWORK_SUGGESTION_POST_CONNECTION";
+ field public static final String ACTION_WIFI_SCAN_AVAILABLE = "android.net.wifi.action.WIFI_SCAN_AVAILABLE";
field @Deprecated public static final int ERROR_AUTHENTICATING = 1; // 0x1
field @Deprecated public static final String EXTRA_BSSID = "bssid";
field public static final String EXTRA_NETWORK_INFO = "networkInfo";
@@ -30484,6 +30690,7 @@
field @Deprecated public static final String EXTRA_NEW_STATE = "newState";
field public static final String EXTRA_PREVIOUS_WIFI_STATE = "previous_wifi_state";
field public static final String EXTRA_RESULTS_UPDATED = "resultsUpdated";
+ field public static final String EXTRA_SCAN_AVAILABLE = "android.net.wifi.extra.SCAN_AVAILABLE";
field @Deprecated public static final String EXTRA_SUPPLICANT_CONNECTED = "connected";
field @Deprecated public static final String EXTRA_SUPPLICANT_ERROR = "supplicantError";
field @Deprecated public static final String EXTRA_WIFI_INFO = "wifiInfo";
@@ -30600,11 +30807,12 @@
ctor public WifiNetworkSuggestion.Builder();
method @NonNull public android.net.wifi.WifiNetworkSuggestion build();
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setBssid(@NonNull android.net.MacAddress);
+ method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setCredentialSharedWithUser(boolean);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsAppInteractionRequired(boolean);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsEnhancedOpen(boolean);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsHiddenSsid(boolean);
+ method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsInitialAutoJoinEnabled(boolean);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsMetered(boolean);
- method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsUserAllowedToManuallyConnect(boolean);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setIsUserInteractionRequired(boolean);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setPasspointConfig(@NonNull android.net.wifi.hotspot2.PasspointConfiguration);
method @NonNull public android.net.wifi.WifiNetworkSuggestion.Builder setPriority(@IntRange(from=0) int);
@@ -35670,7 +35878,9 @@
method public int describeContents();
method @Nullable public android.os.PersistableBundle getPersistableBundle(@Nullable String);
method public void putPersistableBundle(@Nullable String, @Nullable android.os.PersistableBundle);
+ method @NonNull public static android.os.PersistableBundle readFromStream(@NonNull java.io.InputStream) throws java.io.IOException;
method public void writeToParcel(android.os.Parcel, int);
+ method public void writeToStream(@NonNull java.io.OutputStream) throws java.io.IOException;
field @NonNull public static final android.os.Parcelable.Creator<android.os.PersistableBundle> CREATOR;
field public static final android.os.PersistableBundle EMPTY;
}
@@ -35772,6 +35982,7 @@
field public static final int THREAD_PRIORITY_URGENT_AUDIO = -19; // 0xffffffed
field public static final int THREAD_PRIORITY_URGENT_DISPLAY = -8; // 0xfffffff8
field public static final int THREAD_PRIORITY_VIDEO = -10; // 0xfffffff6
+ field public static final int WIFI_UID = 1010; // 0x3f2
}
public abstract class ProxyFileDescriptorCallback {
@@ -36006,6 +36217,7 @@
method @RequiresPermission(anyOf={"android.permission.MANAGE_USERS", "android.permission.INTERACT_ACROSS_USERS"}, conditional=true) public android.os.Bundle getUserRestrictions(android.os.UserHandle);
method public boolean hasUserRestriction(String);
method public boolean isDemoUser();
+ method public boolean isManagedProfile();
method public boolean isQuietModeEnabled(android.os.UserHandle);
method public boolean isSystemUser();
method public boolean isUserAGoat();
@@ -36090,6 +36302,37 @@
method public int getUserOperationResult();
}
+ public final class VibrationAttributes implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getFlags();
+ method public int getUsage();
+ method public int getUsageClass();
+ method public boolean isFlagSet(int);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationAttributes> CREATOR;
+ field public static final int FLAG_BYPASS_INTERRUPTION_POLICY = 1; // 0x1
+ field public static final int USAGE_ALARM = 17; // 0x11
+ field public static final int USAGE_CLASS_ALARM = 1; // 0x1
+ field public static final int USAGE_CLASS_FEEDBACK = 2; // 0x2
+ field public static final int USAGE_CLASS_MASK = 15; // 0xf
+ field public static final int USAGE_CLASS_UNKNOWN = 0; // 0x0
+ field public static final int USAGE_COMMUNICATION_REQUEST = 65; // 0x41
+ field public static final int USAGE_HARDWARE_FEEDBACK = 50; // 0x32
+ field public static final int USAGE_NOTIFICATION = 49; // 0x31
+ field public static final int USAGE_PHYSICAL_EMULATION = 34; // 0x22
+ field public static final int USAGE_RINGTONE = 33; // 0x21
+ field public static final int USAGE_TOUCH = 18; // 0x12
+ field public static final int USAGE_UNKNOWN = 0; // 0x0
+ }
+
+ public static final class VibrationAttributes.Builder {
+ ctor public VibrationAttributes.Builder();
+ ctor public VibrationAttributes.Builder(@Nullable android.os.VibrationAttributes);
+ method @NonNull public android.os.VibrationAttributes build();
+ method @NonNull public android.os.VibrationAttributes.Builder replaceFlags(int);
+ method @NonNull public android.os.VibrationAttributes.Builder setUsage(int);
+ }
+
public abstract class VibrationEffect implements android.os.Parcelable {
method public static android.os.VibrationEffect createOneShot(long, int);
method @NonNull public static android.os.VibrationEffect createPredefined(int);
@@ -36307,15 +36550,22 @@
method public boolean isObbMounted(String);
method public boolean mountObb(String, String, android.os.storage.OnObbStateChangeListener);
method @NonNull public android.os.ParcelFileDescriptor openProxyFileDescriptor(int, android.os.ProxyFileDescriptorCallback, android.os.Handler) throws java.io.IOException;
+ method public void registerStorageVolumeCallback(@NonNull java.util.concurrent.Executor, @NonNull android.os.storage.StorageManager.StorageVolumeCallback);
method public void setCacheBehaviorGroup(java.io.File, boolean) throws java.io.IOException;
method public void setCacheBehaviorTombstone(java.io.File, boolean) throws java.io.IOException;
method public boolean unmountObb(String, boolean, android.os.storage.OnObbStateChangeListener);
+ method public void unregisterStorageVolumeCallback(@NonNull android.os.storage.StorageManager.StorageVolumeCallback);
field public static final String ACTION_MANAGE_STORAGE = "android.os.storage.action.MANAGE_STORAGE";
field public static final String EXTRA_REQUESTED_BYTES = "android.os.storage.extra.REQUESTED_BYTES";
field public static final String EXTRA_UUID = "android.os.storage.extra.UUID";
field public static final java.util.UUID UUID_DEFAULT;
}
+ public static class StorageManager.StorageVolumeCallback {
+ ctor public StorageManager.StorageVolumeCallback();
+ method public void onStateChanged(@NonNull android.os.storage.StorageVolume);
+ }
+
public final class StorageVolume implements android.os.Parcelable {
method @Deprecated @Nullable public android.content.Intent createAccessIntent(String);
method @NonNull public android.content.Intent createOpenDocumentTreeIntent();
@@ -38819,7 +39069,7 @@
field public static final String COLUMN_MIME_TYPE = "mime_type";
field public static final String COLUMN_SIZE = "_size";
field public static final String COLUMN_SUMMARY = "summary";
- field public static final int FLAG_DIR_BLOCKS_TREE = 32768; // 0x8000
+ field public static final int FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE = 32768; // 0x8000
field public static final int FLAG_DIR_PREFERS_GRID = 16; // 0x10
field public static final int FLAG_DIR_PREFERS_LAST_MODIFIED = 32; // 0x20
field public static final int FLAG_DIR_SUPPORTS_CREATE = 8; // 0x8
@@ -38991,6 +39241,7 @@
method @NonNull public static android.app.PendingIntent createWriteRequest(@NonNull android.content.ContentResolver, @NonNull java.util.Collection<android.net.Uri>);
method @Nullable public static android.net.Uri getDocumentUri(@NonNull android.content.Context, @NonNull android.net.Uri);
method @NonNull public static java.util.Set<java.lang.String> getExternalVolumeNames(@NonNull android.content.Context);
+ method public static long getGeneration(@NonNull android.content.Context, @NonNull String);
method public static android.net.Uri getMediaScannerUri();
method @Nullable public static android.net.Uri getMediaUri(@NonNull android.content.Context, @NonNull android.net.Uri);
method @NonNull public static java.util.Set<java.lang.String> getRecentExternalVolumeNames(@NonNull android.content.Context);
@@ -39236,6 +39487,7 @@
field @Deprecated public static final String LONGITUDE = "longitude";
field @Deprecated public static final String MINI_THUMB_MAGIC = "mini_thumb_magic";
field @Deprecated public static final String PICASA_ID = "picasa_id";
+ field public static final String SCENE_CAPTURE_TYPE = "scene_capture_type";
}
public static final class MediaStore.Images.Media implements android.provider.MediaStore.Images.ImageColumns {
@@ -39300,6 +39552,8 @@
field public static final String DISPLAY_NAME = "_display_name";
field public static final String DOCUMENT_ID = "document_id";
field public static final String DURATION = "duration";
+ field public static final String GENERATION_ADDED = "generation_added";
+ field public static final String GENERATION_MODIFIED = "generation_modified";
field public static final String GENRE = "genre";
field public static final String HEIGHT = "height";
field public static final String INSTANCE_ID = "instance_id";
@@ -39431,7 +39685,9 @@
field public static final String ACTION_LOCALE_SETTINGS = "android.settings.LOCALE_SETTINGS";
field public static final String ACTION_LOCATION_SOURCE_SETTINGS = "android.settings.LOCATION_SOURCE_SETTINGS";
field public static final String ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS = "android.settings.MANAGE_ALL_APPLICATIONS_SETTINGS";
+ field public static final String ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION = "android.settings.MANAGE_ALL_FILES_ACCESS_PERMISSION";
field public static final String ACTION_MANAGE_APPLICATIONS_SETTINGS = "android.settings.MANAGE_APPLICATIONS_SETTINGS";
+ field public static final String ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION = "android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION";
field public static final String ACTION_MANAGE_DEFAULT_APPS_SETTINGS = "android.settings.MANAGE_DEFAULT_APPS_SETTINGS";
field public static final String ACTION_MANAGE_OVERLAY_PERMISSION = "android.settings.action.MANAGE_OVERLAY_PERMISSION";
field public static final String ACTION_MANAGE_UNKNOWN_APP_SOURCES = "android.settings.MANAGE_UNKNOWN_APP_SOURCES";
@@ -41783,6 +42039,7 @@
}
public static final class Dataset.Builder {
+ ctor public Dataset.Builder(@NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation);
ctor public Dataset.Builder(@NonNull android.widget.RemoteViews);
ctor public Dataset.Builder();
method @NonNull public android.service.autofill.Dataset build();
@@ -41792,6 +42049,8 @@
method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @NonNull android.widget.RemoteViews);
method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern);
method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.widget.RemoteViews);
+ method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation);
+ method @NonNull public android.service.autofill.Dataset.Builder setValue(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.widget.RemoteViews, @NonNull android.service.autofill.InlinePresentation);
}
public final class DateTransformation implements android.os.Parcelable android.service.autofill.Transformation {
@@ -41880,7 +42139,6 @@
public static final class FillResponse.Builder {
ctor public FillResponse.Builder();
method @NonNull public android.service.autofill.FillResponse.Builder addDataset(@Nullable android.service.autofill.Dataset);
- method @NonNull public android.service.autofill.FillResponse.Builder addInlineSuggestionSlice(@NonNull android.app.slice.Slice);
method @NonNull public android.service.autofill.FillResponse build();
method @NonNull public android.service.autofill.FillResponse.Builder disableAutofill(long);
method @NonNull public android.service.autofill.FillResponse.Builder setAuthentication(@NonNull android.view.autofill.AutofillId[], @Nullable android.content.IntentSender, @Nullable android.widget.RemoteViews);
@@ -41909,6 +42167,15 @@
method public android.service.autofill.ImageTransformation build();
}
+ public final class InlinePresentation implements android.os.Parcelable {
+ ctor public InlinePresentation(@NonNull android.app.slice.Slice, @NonNull android.view.inline.InlinePresentationSpec);
+ method public int describeContents();
+ method @NonNull public android.view.inline.InlinePresentationSpec getInlinePresentationSpec();
+ method @NonNull public android.app.slice.Slice getSlice();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.service.autofill.InlinePresentation> CREATOR;
+ }
+
public final class LuhnChecksumValidator implements android.os.Parcelable android.service.autofill.Validator {
ctor public LuhnChecksumValidator(@NonNull android.view.autofill.AutofillId...);
method public int describeContents();
@@ -42564,11 +42831,20 @@
method public android.content.Intent createEnrollIntent();
method public android.content.Intent createReEnrollIntent();
method public android.content.Intent createUnEnrollIntent();
+ method public int getParameter(int);
+ method public int getSupportedAudioCapabilities();
method public int getSupportedRecognitionModes();
+ method @Nullable public android.service.voice.AlwaysOnHotwordDetector.ModelParamRange queryParameter(int);
+ method public int setParameter(int, int);
method public boolean startRecognition(int);
method public boolean stopRecognition();
+ field public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION = 1; // 0x1
+ field public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION = 2; // 0x2
+ field public static final int MODEL_PARAM_THRESHOLD_FACTOR = 0; // 0x0
field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2
field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1
+ field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 4; // 0x4
+ field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 8; // 0x8
field public static final int RECOGNITION_MODE_USER_IDENTIFICATION = 2; // 0x2
field public static final int RECOGNITION_MODE_VOICE_TRIGGER = 1; // 0x1
field public static final int STATE_HARDWARE_UNAVAILABLE = -2; // 0xfffffffe
@@ -42591,6 +42867,11 @@
method @Nullable public byte[] getTriggerAudio();
}
+ public static final class AlwaysOnHotwordDetector.ModelParamRange {
+ method public int end();
+ method public int start();
+ }
+
public class VoiceInteractionService extends android.app.Service {
ctor public VoiceInteractionService();
method public final android.service.voice.AlwaysOnHotwordDetector createAlwaysOnHotwordDetector(String, java.util.Locale, android.service.voice.AlwaysOnHotwordDetector.Callback);
@@ -43124,6 +43405,7 @@
method public static void execve(String, String[], String[]) throws android.system.ErrnoException;
method public static void fchmod(java.io.FileDescriptor, int) throws android.system.ErrnoException;
method public static void fchown(java.io.FileDescriptor, int, int) throws android.system.ErrnoException;
+ method public static int fcntlInt(@NonNull java.io.FileDescriptor, int, int) throws android.system.ErrnoException;
method public static void fdatasync(java.io.FileDescriptor) throws android.system.ErrnoException;
method public static android.system.StructStat fstat(java.io.FileDescriptor) throws android.system.ErrnoException;
method public static android.system.StructStatVfs fstatvfs(java.io.FileDescriptor) throws android.system.ErrnoException;
@@ -43804,6 +44086,7 @@
method public java.util.List<android.telecom.Call> getChildren();
method public java.util.List<android.telecom.Call> getConferenceableCalls();
method public android.telecom.Call.Details getDetails();
+ method @Nullable public android.telecom.Call getGenericConferenceActiveChildCall();
method public android.telecom.Call getParent();
method public String getRemainingPostDialSequence();
method @Nullable public android.telecom.Call.RttCall getRttCall();
@@ -43887,6 +44170,7 @@
method public int getCallerDisplayNamePresentation();
method public int getCallerNumberVerificationStatus();
method public final long getConnectTimeMillis();
+ method @Nullable public String getContactDisplayName();
method public long getCreationTimeMillis();
method public android.telecom.DisconnectCause getDisconnectCause();
method public android.os.Bundle getExtras();
@@ -44896,6 +45180,39 @@
field public static final int PRIORITY_MED = 2; // 0x2
}
+ public final class BarringInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public android.telephony.BarringInfo.BarringServiceInfo getBarringServiceInfo(int);
+ method public boolean isServiceBarred(int);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int BARRING_SERVICE_TYPE_CS_FALLBACK = 5; // 0x5
+ field public static final int BARRING_SERVICE_TYPE_CS_SERVICE = 0; // 0x0
+ field public static final int BARRING_SERVICE_TYPE_CS_VOICE = 2; // 0x2
+ field public static final int BARRING_SERVICE_TYPE_EMERGENCY = 8; // 0x8
+ field public static final int BARRING_SERVICE_TYPE_MMTEL_VIDEO = 7; // 0x7
+ field public static final int BARRING_SERVICE_TYPE_MMTEL_VOICE = 6; // 0x6
+ field public static final int BARRING_SERVICE_TYPE_MO_DATA = 4; // 0x4
+ field public static final int BARRING_SERVICE_TYPE_MO_SIGNALLING = 3; // 0x3
+ field public static final int BARRING_SERVICE_TYPE_PS_SERVICE = 1; // 0x1
+ field public static final int BARRING_SERVICE_TYPE_SMS = 9; // 0x9
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.BarringInfo> CREATOR;
+ }
+
+ public static final class BarringInfo.BarringServiceInfo implements android.os.Parcelable {
+ method public int describeContents();
+ method public int getBarringType();
+ method public int getConditionalBarringFactor();
+ method public int getConditionalBarringTimeSeconds();
+ method public boolean isBarred();
+ method public boolean isConditionallyBarred();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int BARRING_TYPE_CONDITIONAL = 1; // 0x1
+ field public static final int BARRING_TYPE_NONE = 0; // 0x0
+ field public static final int BARRING_TYPE_UNCONDITIONAL = 2; // 0x2
+ field public static final int BARRING_TYPE_UNKNOWN = -1; // 0xffffffff
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.BarringInfo.BarringServiceInfo> CREATOR;
+ }
+
public class CarrierConfigManager {
method @Nullable public android.os.PersistableBundle getConfig();
method @Nullable public android.os.PersistableBundle getConfigByComponentForSubId(@NonNull String, int);
@@ -44905,8 +45222,12 @@
field public static final String ACTION_CARRIER_CONFIG_CHANGED = "android.telephony.action.CARRIER_CONFIG_CHANGED";
field public static final int DATA_CYCLE_THRESHOLD_DISABLED = -2; // 0xfffffffe
field public static final int DATA_CYCLE_USE_PLATFORM_DEFAULT = -1; // 0xffffffff
+ field public static final String ENABLE_EAP_METHOD_PREFIX_BOOL = "enable_eap_method_prefix_bool";
field public static final String EXTRA_SLOT_INDEX = "android.telephony.extra.SLOT_INDEX";
field public static final String EXTRA_SUBSCRIPTION_INDEX = "android.telephony.extra.SUBSCRIPTION_INDEX";
+ field public static final String IMSI_KEY_AVAILABILITY_INT = "imsi_key_availability_int";
+ field public static final String KEY_5G_ICON_CONFIGURATION_STRING = "5g_icon_configuration_string";
+ field public static final String KEY_5G_ICON_DISPLAY_GRACE_PERIOD_SEC_INT = "5g_icon_display_grace_period_sec_int";
field public static final String KEY_5G_NR_SSRSRP_THRESHOLDS_INT_ARRAY = "5g_nr_ssrsrp_thresholds_int_array";
field public static final String KEY_5G_NR_SSRSRQ_THRESHOLDS_INT_ARRAY = "5g_nr_ssrsrq_thresholds_int_array";
field public static final String KEY_5G_NR_SSSINR_THRESHOLDS_INT_ARRAY = "5g_nr_sssinr_thresholds_int_array";
@@ -44920,18 +45241,32 @@
field public static final String KEY_ALLOW_LOCAL_DTMF_TONES_BOOL = "allow_local_dtmf_tones_bool";
field public static final String KEY_ALLOW_MERGE_WIFI_CALLS_WHEN_VOWIFI_OFF_BOOL = "allow_merge_wifi_calls_when_vowifi_off_bool";
field public static final String KEY_ALLOW_NON_EMERGENCY_CALLS_IN_ECM_BOOL = "allow_non_emergency_calls_in_ecm_bool";
- field public static final String KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL = "always_show_emergency_alert_onoff_bool";
+ field public static final String KEY_ALLOW_VIDEO_CALLING_FALLBACK_BOOL = "allow_video_calling_fallback_bool";
+ field public static final String KEY_ALWAYS_SHOW_DATA_RAT_ICON_BOOL = "always_show_data_rat_icon_bool";
+ field @Deprecated public static final String KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL = "always_show_emergency_alert_onoff_bool";
+ field public static final String KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN = "always_show_primary_signal_bar_in_opportunistic_network_boolean";
field public static final String KEY_APN_EXPAND_BOOL = "apn_expand_bool";
+ field public static final String KEY_APN_SETTINGS_DEFAULT_APN_TYPES_STRING_ARRAY = "apn_settings_default_apn_types_string_array";
field public static final String KEY_AUTO_RETRY_ENABLED_BOOL = "auto_retry_enabled_bool";
field public static final String KEY_CALL_BARRING_SUPPORTS_DEACTIVATE_ALL_BOOL = "call_barring_supports_deactivate_all_bool";
field public static final String KEY_CALL_BARRING_SUPPORTS_PASSWORD_CHANGE_BOOL = "call_barring_supports_password_change_bool";
field public static final String KEY_CALL_BARRING_VISIBILITY_BOOL = "call_barring_visibility_bool";
field public static final String KEY_CALL_FORWARDING_BLOCKS_WHILE_ROAMING_STRING_ARRAY = "call_forwarding_blocks_while_roaming_string_array";
+ field public static final String KEY_CALL_REDIRECTION_SERVICE_COMPONENT_NAME_STRING = "call_redirection_service_component_name_string";
+ field public static final String KEY_CARRIER_ALLOW_DEFLECT_IMS_CALL_BOOL = "carrier_allow_deflect_ims_call_bool";
field public static final String KEY_CARRIER_ALLOW_TURNOFF_IMS_BOOL = "carrier_allow_turnoff_ims_bool";
field public static final String KEY_CARRIER_APP_REQUIRED_DURING_SIM_SETUP_BOOL = "carrier_app_required_during_setup_bool";
field public static final String KEY_CARRIER_CALL_SCREENING_APP_STRING = "call_screening_app";
+ field public static final String KEY_CARRIER_CERTIFICATE_STRING_ARRAY = "carrier_certificate_string_array";
+ field public static final String KEY_CARRIER_CONFIG_APPLIED_BOOL = "carrier_config_applied_bool";
field public static final String KEY_CARRIER_CONFIG_VERSION_STRING = "carrier_config_version_string";
field public static final String KEY_CARRIER_DATA_CALL_PERMANENT_FAILURE_STRINGS = "carrier_data_call_permanent_failure_strings";
+ field public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_DCFAILURE_STRING_ARRAY = "carrier_default_actions_on_dcfailure_string_array";
+ field public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_DEFAULT_NETWORK_AVAILABLE = "carrier_default_actions_on_default_network_available_string_array";
+ field public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_REDIRECTION_STRING_ARRAY = "carrier_default_actions_on_redirection_string_array";
+ field public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_RESET = "carrier_default_actions_on_reset_string_array";
+ field public static final String KEY_CARRIER_DEFAULT_REDIRECTION_URL_STRING_ARRAY = "carrier_default_redirection_url_string_array";
+ field public static final String KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL = "carrier_default_wfc_ims_enabled_bool";
field public static final String KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT = "carrier_default_wfc_ims_mode_int";
field public static final String KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_MODE_INT = "carrier_default_wfc_ims_roaming_mode_int";
field @Deprecated public static final String KEY_CARRIER_FORCE_DISABLE_ETWS_CMAS_TEST_BOOL = "carrier_force_disable_etws_cmas_test_bool";
@@ -44943,11 +45278,14 @@
field public static final String KEY_CARRIER_INSTANT_LETTERING_LENGTH_LIMIT_INT = "carrier_instant_lettering_length_limit_int";
field public static final String KEY_CARRIER_NAME_OVERRIDE_BOOL = "carrier_name_override_bool";
field public static final String KEY_CARRIER_NAME_STRING = "carrier_name_string";
+ field public static final String KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL = "carrier_rcs_provisioning_required_bool";
+ field public static final String KEY_CARRIER_SETTINGS_ACTIVITY_COMPONENT_NAME_STRING = "carrier_settings_activity_component_name_string";
field public static final String KEY_CARRIER_SETTINGS_ENABLE_BOOL = "carrier_settings_enable_bool";
field public static final String KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL = "carrier_supports_ss_over_ut_bool";
field public static final String KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL = "carrier_use_ims_first_for_emergency_bool";
field public static final String KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL = "carrier_ut_provisioning_required_bool";
field public static final String KEY_CARRIER_VOLTE_AVAILABLE_BOOL = "carrier_volte_available_bool";
+ field public static final String KEY_CARRIER_VOLTE_OVERRIDE_WFC_PROVISIONING_BOOL = "carrier_volte_override_wfc_provisioning_bool";
field public static final String KEY_CARRIER_VOLTE_PROVISIONED_BOOL = "carrier_volte_provisioned_bool";
field public static final String KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL = "carrier_volte_provisioning_required_bool";
field public static final String KEY_CARRIER_VOLTE_TTY_SUPPORTED_BOOL = "carrier_volte_tty_supported_bool";
@@ -44961,6 +45299,7 @@
field public static final String KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY = "cdma_nonroaming_networks_string_array";
field public static final String KEY_CDMA_ROAMING_MODE_INT = "cdma_roaming_mode_int";
field public static final String KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY = "cdma_roaming_networks_string_array";
+ field public static final String KEY_CHECK_PRICING_WITH_CARRIER_FOR_DATA_ROAMING_BOOL = "check_pricing_with_carrier_data_roaming_bool";
field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_BOOL = "ci_action_on_sys_update_bool";
field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_STRING = "ci_action_on_sys_update_extra_string";
field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_EXTRA_VAL_STRING = "ci_action_on_sys_update_extra_val_string";
@@ -44970,6 +45309,7 @@
field public static final String KEY_CONFIG_IMS_RCS_PACKAGE_OVERRIDE_STRING = "config_ims_rcs_package_override_string";
field public static final String KEY_CONFIG_PLANS_PACKAGE_OVERRIDE_STRING = "config_plans_package_override_string";
field public static final String KEY_CONFIG_TELEPHONY_USE_OWN_NUMBER_FOR_VOICEMAIL_BOOL = "config_telephony_use_own_number_for_voicemail_bool";
+ field public static final String KEY_CONFIG_WIFI_DISABLE_IN_ECBM = "config_wifi_disable_in_ecbm";
field public static final String KEY_CSP_ENABLED_BOOL = "csp_enabled_bool";
field public static final String KEY_DATA_LIMIT_NOTIFICATION_BOOL = "data_limit_notification_bool";
field public static final String KEY_DATA_LIMIT_THRESHOLD_BYTES_LONG = "data_limit_threshold_bytes_long";
@@ -44981,6 +45321,7 @@
field public static final String KEY_DEFAULT_VM_NUMBER_STRING = "default_vm_number_string";
field public static final String KEY_DIAL_STRING_REPLACE_STRING_ARRAY = "dial_string_replace_string_array";
field public static final String KEY_DISABLE_CDMA_ACTIVATION_CODE_BOOL = "disable_cdma_activation_code_bool";
+ field public static final String KEY_DISABLE_CHARGE_INDICATION_BOOL = "disable_charge_indication_bool";
field public static final String KEY_DISABLE_SUPPLEMENTARY_SERVICES_IN_AIRPLANE_MODE_BOOL = "disable_supplementary_services_in_airplane_mode_bool";
field public static final String KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY = "disconnect_cause_play_busytone_int_array";
field public static final String KEY_DISPLAY_HD_AUDIO_PROPERTY_BOOL = "display_hd_audio_property_bool";
@@ -44990,9 +45331,13 @@
field public static final String KEY_EDITABLE_ENHANCED_4G_LTE_BOOL = "editable_enhanced_4g_lte_bool";
field public static final String KEY_EDITABLE_VOICEMAIL_NUMBER_BOOL = "editable_voicemail_number_bool";
field public static final String KEY_EDITABLE_VOICEMAIL_NUMBER_SETTING_BOOL = "editable_voicemail_number_setting_bool";
+ field public static final String KEY_EDITABLE_WFC_MODE_BOOL = "editable_wfc_mode_bool";
+ field public static final String KEY_EDITABLE_WFC_ROAMING_MODE_BOOL = "editable_wfc_roaming_mode_bool";
+ field public static final String KEY_EMERGENCY_NOTIFICATION_DELAY_INT = "emergency_notification_delay_int";
field public static final String KEY_EMERGENCY_NUMBER_PREFIX_STRING_ARRAY = "emergency_number_prefix_string_array";
field public static final String KEY_ENABLE_DIALER_KEY_VIBRATION_BOOL = "enable_dialer_key_vibration_bool";
field public static final String KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL = "enhanced_4g_lte_on_by_default_bool";
+ field public static final String KEY_ENHANCED_4G_LTE_TITLE_VARIANT_INT = "enhanced_4g_lte_title_variant_int";
field public static final String KEY_FORCE_HOME_NETWORK_BOOL = "force_home_network_bool";
field public static final String KEY_GSM_DTMF_TONE_DELAY_INT = "gsm_dtmf_tone_delay_int";
field public static final String KEY_GSM_NONROAMING_NETWORKS_STRING_ARRAY = "gsm_nonroaming_networks_string_array";
@@ -45001,13 +45346,17 @@
field public static final String KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL = "hide_carrier_network_settings_bool";
field public static final String KEY_HIDE_ENHANCED_4G_LTE_BOOL = "hide_enhanced_4g_lte_bool";
field public static final String KEY_HIDE_IMS_APN_BOOL = "hide_ims_apn_bool";
+ field public static final String KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL = "hide_lte_plus_data_icon_bool";
field public static final String KEY_HIDE_PREFERRED_NETWORK_TYPE_BOOL = "hide_preferred_network_type_bool";
field public static final String KEY_HIDE_PRESET_APN_DETAILS_BOOL = "hide_preset_apn_details_bool";
field public static final String KEY_HIDE_SIM_LOCK_SETTINGS_BOOL = "hide_sim_lock_settings_bool";
+ field public static final String KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS = "ignore_data_enabled_changed_for_video_calls";
+ field public static final String KEY_IGNORE_RTT_MODE_SETTING_BOOL = "ignore_rtt_mode_setting_bool";
field public static final String KEY_IGNORE_SIM_NETWORK_LOCKED_EVENTS_BOOL = "ignore_sim_network_locked_events_bool";
field public static final String KEY_IMS_CONFERENCE_SIZE_LIMIT_INT = "ims_conference_size_limit_int";
field public static final String KEY_IMS_DTMF_TONE_DELAY_INT = "ims_dtmf_tone_delay_int";
field public static final String KEY_IS_IMS_CONFERENCE_SIZE_ENFORCED_BOOL = "is_ims_conference_size_enforced_bool";
+ field public static final String KEY_LTE_ENABLED_BOOL = "lte_enabled_bool";
field public static final String KEY_LTE_RSRQ_THRESHOLDS_INT_ARRAY = "lte_rsrq_thresholds_int_array";
field public static final String KEY_LTE_RSSNR_THRESHOLDS_INT_ARRAY = "lte_rssnr_thresholds_int_array";
field public static final String KEY_MDN_IS_ADDITIONAL_VOICEMAIL_NUMBER_BOOL = "mdn_is_additional_voicemail_number_bool";
@@ -45043,6 +45392,7 @@
field public static final String KEY_MMS_UA_PROF_URL_STRING = "uaProfUrl";
field public static final String KEY_MMS_USER_AGENT_STRING = "userAgent";
field public static final String KEY_MONTHLY_DATA_CYCLE_DAY_INT = "monthly_data_cycle_day_int";
+ field public static final String KEY_ONLY_AUTO_SELECT_IN_HOME_NETWORK_BOOL = "only_auto_select_in_home_network";
field public static final String KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY = "only_single_dc_allowed_int_array";
field public static final String KEY_OPERATOR_SELECTION_EXPAND_BOOL = "operator_selection_expand_bool";
field public static final String KEY_OPPORTUNISTIC_NETWORK_DATA_SWITCH_HYSTERESIS_TIME_LONG = "opportunistic_network_data_switch_hysteresis_time_long";
@@ -45056,15 +45406,24 @@
field public static final String KEY_PREVENT_CLIR_ACTIVATION_AND_DEACTIVATION_CODE_BOOL = "prevent_clir_activation_and_deactivation_code_bool";
field public static final String KEY_RADIO_RESTART_FAILURE_CAUSES_INT_ARRAY = "radio_restart_failure_causes_int_array";
field public static final String KEY_RCS_CONFIG_SERVER_URL_STRING = "rcs_config_server_url_string";
+ field public static final String KEY_READ_ONLY_APN_FIELDS_STRING_ARRAY = "read_only_apn_fields_string_array";
+ field public static final String KEY_READ_ONLY_APN_TYPES_STRING_ARRAY = "read_only_apn_types_string_array";
field public static final String KEY_REQUIRE_ENTITLEMENT_CHECKS_BOOL = "require_entitlement_checks_bool";
field @Deprecated public static final String KEY_RESTART_RADIO_ON_PDP_FAIL_REGULAR_DEACTIVATION_BOOL = "restart_radio_on_pdp_fail_regular_deactivation_bool";
field public static final String KEY_RTT_SUPPORTED_BOOL = "rtt_supported_bool";
+ field public static final String KEY_SHOW_4G_FOR_3G_DATA_ICON_BOOL = "show_4g_for_3g_data_icon_bool";
+ field public static final String KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL = "show_4g_for_lte_data_icon_bool";
field public static final String KEY_SHOW_APN_SETTING_CDMA_BOOL = "show_apn_setting_cdma_bool";
+ field public static final String KEY_SHOW_BLOCKING_PAY_PHONE_OPTION_BOOL = "show_blocking_pay_phone_option_bool";
field public static final String KEY_SHOW_CALL_BLOCKING_DISABLED_NOTIFICATION_ALWAYS_BOOL = "show_call_blocking_disabled_notification_always_bool";
+ field public static final String KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING = "show_carrier_data_icon_pattern_string";
field public static final String KEY_SHOW_CDMA_CHOICES_BOOL = "show_cdma_choices_bool";
field public static final String KEY_SHOW_ICCID_IN_SIM_STATUS_BOOL = "show_iccid_in_sim_status_bool";
+ field public static final String KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL = "show_ims_registration_status_bool";
field public static final String KEY_SHOW_ONSCREEN_DIAL_BUTTON_BOOL = "show_onscreen_dial_button_bool";
field public static final String KEY_SHOW_SIGNAL_STRENGTH_IN_SIM_STATUS_BOOL = "show_signal_strength_in_sim_status_bool";
+ field public static final String KEY_SHOW_VIDEO_CALL_CHARGES_ALERT_DIALOG_BOOL = "show_video_call_charges_alert_dialog_bool";
+ field public static final String KEY_SHOW_WFC_LOCATION_PRIVACY_POLICY_BOOL = "show_wfc_location_privacy_policy_bool";
field public static final String KEY_SIMPLIFIED_NETWORK_SETTINGS_BOOL = "simplified_network_settings_bool";
field public static final String KEY_SIM_NETWORK_UNLOCK_ALLOW_DISMISS_BOOL = "sim_network_unlock_allow_dismiss_bool";
field public static final String KEY_SMS_REQUIRES_DESTINATION_NUMBER_CONVERSION_BOOL = "sms_requires_destination_number_conversion_bool";
@@ -45072,14 +45431,20 @@
field public static final String KEY_SUPPORT_CLIR_NETWORK_DEFAULT_BOOL = "support_clir_network_default_bool";
field public static final String KEY_SUPPORT_CONFERENCE_CALL_BOOL = "support_conference_call_bool";
field public static final String KEY_SUPPORT_EMERGENCY_SMS_OVER_IMS_BOOL = "support_emergency_sms_over_ims_bool";
+ field public static final String KEY_SUPPORT_ENHANCED_CALL_BLOCKING_BOOL = "support_enhanced_call_blocking_bool";
+ field public static final String KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_BOOL = "support_ims_conference_event_package_bool";
field public static final String KEY_SUPPORT_PAUSE_IMS_VIDEO_CALLS_BOOL = "support_pause_ims_video_calls_bool";
field public static final String KEY_SUPPORT_SWAP_AFTER_MERGE_BOOL = "support_swap_after_merge_bool";
+ field public static final String KEY_SUPPORT_TDSCDMA_BOOL = "support_tdscdma_bool";
+ field public static final String KEY_SUPPORT_TDSCDMA_ROAMING_NETWORKS_STRING_ARRAY = "support_tdscdma_roaming_networks_string_array";
field public static final String KEY_TREAT_DOWNGRADED_VIDEO_CALLS_AS_VIDEO_CALLS_BOOL = "treat_downgraded_video_calls_as_video_calls_bool";
field public static final String KEY_TTY_SUPPORTED_BOOL = "tty_supported_bool";
+ field public static final String KEY_UNLOGGABLE_NUMBERS_STRING_ARRAY = "unloggable_numbers_string_array";
field public static final String KEY_USE_HFA_FOR_PROVISIONING_BOOL = "use_hfa_for_provisioning_bool";
field public static final String KEY_USE_OTASP_FOR_PROVISIONING_BOOL = "use_otasp_for_provisioning_bool";
field public static final String KEY_USE_RCS_PRESENCE_BOOL = "use_rcs_presence_bool";
field public static final String KEY_USE_RCS_SIP_OPTIONS_BOOL = "use_rcs_sip_options_bool";
+ field public static final String KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL = "use_wfc_home_network_mode_in_roaming_network_bool";
field public static final String KEY_VOICEMAIL_NOTIFICATION_PERSISTENT_BOOL = "voicemail_notification_persistent_bool";
field public static final String KEY_VOICE_PRIVACY_DISABLE_UI_BOOL = "voice_privacy_disable_ui_bool";
field public static final String KEY_VOLTE_REPLACEMENT_RAT_INT = "volte_replacement_rat_int";
@@ -45092,6 +45457,8 @@
field public static final String KEY_VVM_PREFETCH_BOOL = "vvm_prefetch_bool";
field public static final String KEY_VVM_SSL_ENABLED_BOOL = "vvm_ssl_enabled_bool";
field public static final String KEY_VVM_TYPE_STRING = "vvm_type_string";
+ field public static final String KEY_WFC_EMERGENCY_ADDRESS_CARRIER_APP_STRING = "wfc_emergency_address_carrier_app_string";
+ field public static final String KEY_WORLD_MODE_ENABLED_BOOL = "world_mode_enabled_bool";
field public static final String KEY_WORLD_PHONE_BOOL = "world_phone_bool";
}
@@ -45102,6 +45469,7 @@
public static final class CarrierConfigManager.Ims {
field public static final String KEY_PREFIX = "ims.";
+ field public static final String KEY_WIFI_OFF_DEFERRING_TIME_INT = "ims.wifi_off_deferring_time_int";
}
public abstract class CellIdentity implements android.os.Parcelable {
@@ -45410,6 +45778,11 @@
method @Nullable public android.telephony.mbms.StreamingService startStreaming(android.telephony.mbms.StreamingServiceInfo, @NonNull java.util.concurrent.Executor, android.telephony.mbms.StreamingServiceCallback);
}
+ public class MmsManager {
+ method public void downloadMultimediaMessage(int, @NonNull String, @NonNull android.net.Uri, @Nullable android.os.Bundle, @Nullable android.app.PendingIntent);
+ method public void sendMultimediaMessage(int, @NonNull android.net.Uri, @Nullable String, @Nullable android.os.Bundle, @Nullable android.app.PendingIntent);
+ }
+
@Deprecated public class NeighboringCellInfo implements android.os.Parcelable {
ctor @Deprecated public NeighboringCellInfo();
ctor @Deprecated public NeighboringCellInfo(int, int);
@@ -45533,6 +45906,7 @@
ctor public PhoneStateListener();
ctor public PhoneStateListener(@NonNull java.util.concurrent.Executor);
method public void onActiveDataSubscriptionIdChanged(int);
+ method public void onBarringInfoChanged(@NonNull android.telephony.BarringInfo);
method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onCallDisconnectCauseChanged(int, int);
method public void onCallForwardingIndicatorChanged(boolean);
method public void onCallStateChanged(int, String);
@@ -45544,11 +45918,13 @@
method @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public void onImsCallDisconnectCauseChanged(@NonNull android.telephony.ims.ImsReasonInfo);
method public void onMessageWaitingIndicatorChanged(boolean);
method @RequiresPermission("android.permission.MODIFY_PHONE_STATE") public void onPreciseDataConnectionStateChanged(@NonNull android.telephony.PreciseDataConnectionState);
+ method public void onRegistrationFailed(@NonNull android.telephony.CellIdentity, @NonNull String, int, int, int);
method public void onServiceStateChanged(android.telephony.ServiceState);
method @Deprecated public void onSignalStrengthChanged(int);
method public void onSignalStrengthsChanged(android.telephony.SignalStrength);
method public void onUserMobileDataStateChanged(boolean);
field public static final int LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE = 4194304; // 0x400000
+ field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int LISTEN_BARRING_INFO = -2147483648; // 0x80000000
field @RequiresPermission("android.permission.READ_PRECISE_PHONE_STATE") public static final int LISTEN_CALL_DISCONNECT_CAUSES = 33554432; // 0x2000000
field public static final int LISTEN_CALL_FORWARDING_INDICATOR = 8; // 0x8
field public static final int LISTEN_CALL_STATE = 32; // 0x20
@@ -45561,6 +45937,7 @@
field public static final int LISTEN_MESSAGE_WAITING_INDICATOR = 4; // 0x4
field public static final int LISTEN_NONE = 0; // 0x0
field @RequiresPermission("android.permission.MODIFY_PHONE_STATE") public static final int LISTEN_PRECISE_DATA_CONNECTION_STATE = 4096; // 0x1000
+ field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final int LISTEN_REGISTRATION_FAILURE = 1073741824; // 0x40000000
field public static final int LISTEN_SERVICE_STATE = 1; // 0x1
field @Deprecated public static final int LISTEN_SIGNAL_STRENGTH = 2; // 0x2
field public static final int LISTEN_SIGNAL_STRENGTHS = 256; // 0x100
@@ -45604,6 +45981,7 @@
method public String getOperatorNumeric();
method public boolean getRoaming();
method public int getState();
+ method public boolean isSearching();
method public void setIsManualSelection(boolean);
method public void setOperatorName(String, String, String);
method public void setRoaming(boolean);
@@ -45636,6 +46014,7 @@
method public int getLevel();
method @Deprecated public boolean isGsm();
method public void writeToParcel(android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.SignalStrength> CREATOR;
field public static final int INVALID = 2147483647; // 0x7fffffff
}
@@ -45643,8 +46022,8 @@
method public String createAppSpecificSmsToken(android.app.PendingIntent);
method @Nullable public String createAppSpecificSmsTokenWithPackageInfo(@Nullable String, @NonNull android.app.PendingIntent);
method public java.util.ArrayList<java.lang.String> divideMessage(String);
- method public void downloadMultimediaMessage(android.content.Context, String, android.net.Uri, android.os.Bundle, android.app.PendingIntent);
- method public android.os.Bundle getCarrierConfigValues();
+ method @Deprecated public void downloadMultimediaMessage(android.content.Context, String, android.net.Uri, android.os.Bundle, android.app.PendingIntent);
+ method @NonNull public android.os.Bundle getCarrierConfigValues();
method public static android.telephony.SmsManager getDefault();
method public static int getDefaultSmsSubscriptionId();
method public static android.telephony.SmsManager getSmsManagerForSubscriptionId(int);
@@ -45653,7 +46032,7 @@
method public int getSubscriptionId();
method public void injectSmsPdu(byte[], String, android.app.PendingIntent);
method public void sendDataMessage(String, String, short, byte[], android.app.PendingIntent, android.app.PendingIntent);
- method public void sendMultimediaMessage(android.content.Context, android.net.Uri, String, android.os.Bundle, android.app.PendingIntent);
+ method @Deprecated public void sendMultimediaMessage(android.content.Context, android.net.Uri, String, android.os.Bundle, android.app.PendingIntent);
method public void sendMultipartTextMessage(String, String, java.util.ArrayList<java.lang.String>, java.util.ArrayList<android.app.PendingIntent>, java.util.ArrayList<android.app.PendingIntent>);
method public void sendTextMessage(String, String, String, android.app.PendingIntent, android.app.PendingIntent);
method @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.SEND_SMS}) public void sendTextMessageWithoutPersisting(String, String, String, android.app.PendingIntent, android.app.PendingIntent);
@@ -45861,6 +46240,7 @@
public class SubscriptionManager {
method public void addOnOpportunisticSubscriptionsChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener);
method public void addOnSubscriptionsChangedListener(android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
+ method public void addOnSubscriptionsChangedListener(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void addSubscriptionsIntoGroup(@NonNull java.util.List<java.lang.Integer>, @NonNull android.os.ParcelUuid);
method public boolean canManageSubscription(android.telephony.SubscriptionInfo);
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.os.ParcelUuid createSubscriptionGroup(@NonNull java.util.List<java.lang.Integer>);
@@ -46019,12 +46399,12 @@
method public android.net.Uri getVoicemailRingtoneUri(android.telecom.PhoneAccountHandle);
method public boolean hasCarrierPrivileges();
method public boolean hasIccCard();
- method public boolean iccCloseLogicalChannel(int);
- method public byte[] iccExchangeSimIO(int, int, int, int, int, String);
+ method @Deprecated public boolean iccCloseLogicalChannel(int);
+ method @Deprecated public byte[] iccExchangeSimIO(int, int, int, int, int, String);
method @Deprecated public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(String);
- method public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(String, int);
- method public String iccTransmitApduBasicChannel(int, int, int, int, int, String);
- method public String iccTransmitApduLogicalChannel(int, int, int, int, int, int, String);
+ method @Deprecated public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannel(String, int);
+ method @Deprecated public String iccTransmitApduBasicChannel(int, int, int, int, int, String);
+ method @Deprecated public String iccTransmitApduLogicalChannel(int, int, int, int, int, int, String);
method public boolean isConcurrentVoiceAndDataSupported();
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.MODIFY_PHONE_STATE}) public boolean isDataEnabled();
method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isDataRoamingEnabled();
@@ -46042,7 +46422,7 @@
method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestCellInfoUpdate(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback);
method @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public android.telephony.NetworkScan requestNetworkScan(android.telephony.NetworkScanRequest, java.util.concurrent.Executor, android.telephony.TelephonyScanManager.NetworkScanCallback);
method public void sendDialerSpecialCode(String);
- method public String sendEnvelopeWithStatus(String);
+ method @Deprecated public String sendEnvelopeWithStatus(String);
method @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void sendUssdRequest(String, android.telephony.TelephonyManager.UssdResponseCallback, android.os.Handler);
method public void sendVisualVoicemailSms(String, int, String, android.app.PendingIntent);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(boolean);
@@ -46143,6 +46523,7 @@
field public static final int NETWORK_TYPE_UNKNOWN = 0; // 0x0
field public static final int PHONE_TYPE_CDMA = 2; // 0x2
field public static final int PHONE_TYPE_GSM = 1; // 0x1
+ field public static final int PHONE_TYPE_IMS = 5; // 0x5
field public static final int PHONE_TYPE_NONE = 0; // 0x0
field public static final int PHONE_TYPE_SIP = 3; // 0x3
field public static final int SET_OPPORTUNISTIC_SUB_INACTIVE_SUBSCRIPTION = 2; // 0x2
@@ -51484,9 +51865,10 @@
method public boolean dispatchUnhandledMove(android.view.View, int);
method protected void dispatchVisibilityChanged(@NonNull android.view.View, int);
method public void dispatchWindowFocusChanged(boolean);
- method public void dispatchWindowInsetsAnimationFinished(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
+ method public void dispatchWindowInsetsAnimationFinish(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
+ method public void dispatchWindowInsetsAnimationPrepare(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
method @NonNull public android.view.WindowInsets dispatchWindowInsetsAnimationProgress(@NonNull android.view.WindowInsets);
- method @NonNull public android.view.WindowInsetsAnimationCallback.AnimationBounds dispatchWindowInsetsAnimationStarted(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds);
+ method @NonNull public android.view.WindowInsetsAnimationCallback.AnimationBounds dispatchWindowInsetsAnimationStart(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds);
method public void dispatchWindowSystemUiVisiblityChanged(int);
method public void dispatchWindowVisibilityChanged(int);
method @CallSuper public void draw(android.graphics.Canvas);
@@ -53166,9 +53548,10 @@
}
public interface WindowInsetsAnimationCallback {
- method public default void onFinished(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
+ method public default void onFinish(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
+ method public default void onPrepare(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation);
method @NonNull public android.view.WindowInsets onProgress(@NonNull android.view.WindowInsets);
- method @NonNull public default android.view.WindowInsetsAnimationCallback.AnimationBounds onStarted(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds);
+ method @NonNull public default android.view.WindowInsetsAnimationCallback.AnimationBounds onStart(@NonNull android.view.WindowInsetsAnimationCallback.InsetsAnimation, @NonNull android.view.WindowInsetsAnimationCallback.AnimationBounds);
}
public static final class WindowInsetsAnimationCallback.AnimationBounds {
@@ -54564,6 +54947,7 @@
public final class InlineSuggestionsRequest implements android.os.Parcelable {
method public int describeContents();
+ method @NonNull public String getHostPackageName();
method public int getMaxSuggestionCount();
method @NonNull public java.util.List<android.view.inline.InlinePresentationSpec> getPresentationSpecs();
method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -58587,7 +58971,7 @@
method @android.view.ViewDebug.ExportedProperty public CharSequence getFormat24Hour();
method public String getTimeZone();
method public boolean is24HourModeEnabled();
- method public void refresh();
+ method public void refreshTime();
method public void setFormat12Hour(CharSequence);
method public void setFormat24Hour(CharSequence);
method public void setTimeZone(String);
@@ -58902,6 +59286,7 @@
public class Toast {
ctor public Toast(android.content.Context);
+ method public void addCallback(@NonNull android.widget.Toast.Callback);
method public void cancel();
method public int getDuration();
method public int getGravity();
@@ -58912,6 +59297,7 @@
method public int getYOffset();
method public static android.widget.Toast makeText(android.content.Context, CharSequence, int);
method public static android.widget.Toast makeText(android.content.Context, @StringRes int, int) throws android.content.res.Resources.NotFoundException;
+ method public void removeCallback(@NonNull android.widget.Toast.Callback);
method public void setDuration(int);
method public void setGravity(int, int, int);
method public void setMargin(float, float);
@@ -58923,6 +59309,12 @@
field public static final int LENGTH_SHORT = 0; // 0x0
}
+ public abstract static class Toast.Callback {
+ ctor public Toast.Callback();
+ method public void onToastHidden();
+ method public void onToastShown();
+ }
+
public class ToggleButton extends android.widget.CompoundButton {
ctor public ToggleButton(android.content.Context, android.util.AttributeSet, int, int);
ctor public ToggleButton(android.content.Context, android.util.AttributeSet, int);
diff --git a/api/module-app-current.txt b/api/module-app-current.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/api/module-app-current.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/api/module-app-removed.txt b/api/module-app-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/api/module-app-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/api/module-lib-current.txt b/api/module-lib-current.txt
new file mode 100644
index 0000000..c8253a0
--- /dev/null
+++ b/api/module-lib-current.txt
@@ -0,0 +1,126 @@
+// Signature format: 2.0
+package android.app.timedetector {
+
+ public final class PhoneTimeSuggestion implements android.os.Parcelable {
+ method public void addDebugInfo(@NonNull String);
+ method public void addDebugInfo(@NonNull java.util.List<java.lang.String>);
+ method public int describeContents();
+ method @NonNull public java.util.List<java.lang.String> getDebugInfo();
+ method public int getPhoneId();
+ method @Nullable public android.os.TimestampedValue<java.lang.Long> getUtcTime();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.timedetector.PhoneTimeSuggestion> CREATOR;
+ }
+
+ public static final class PhoneTimeSuggestion.Builder {
+ ctor public PhoneTimeSuggestion.Builder(int);
+ method @NonNull public android.app.timedetector.PhoneTimeSuggestion.Builder addDebugInfo(@NonNull String);
+ method @NonNull public android.app.timedetector.PhoneTimeSuggestion build();
+ method @NonNull public android.app.timedetector.PhoneTimeSuggestion.Builder setUtcTime(@Nullable android.os.TimestampedValue<java.lang.Long>);
+ }
+
+ public class TimeDetector {
+ method @RequiresPermission("android.permission.SUGGEST_PHONE_TIME_AND_ZONE") public void suggestPhoneTime(@NonNull android.app.timedetector.PhoneTimeSuggestion);
+ }
+
+}
+
+package android.app.timezonedetector {
+
+ public final class PhoneTimeZoneSuggestion implements android.os.Parcelable {
+ method public void addDebugInfo(@NonNull String);
+ method public void addDebugInfo(@NonNull java.util.List<java.lang.String>);
+ method @NonNull public static android.app.timezonedetector.PhoneTimeZoneSuggestion createEmptySuggestion(int, @NonNull String);
+ method public int describeContents();
+ method @NonNull public java.util.List<java.lang.String> getDebugInfo();
+ method public int getMatchType();
+ method public int getPhoneId();
+ method public int getQuality();
+ method @Nullable public String getZoneId();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.timezonedetector.PhoneTimeZoneSuggestion> CREATOR;
+ field public static final int MATCH_TYPE_EMULATOR_ZONE_ID = 4; // 0x4
+ field public static final int MATCH_TYPE_NA = 0; // 0x0
+ field public static final int MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET = 3; // 0x3
+ field public static final int MATCH_TYPE_NETWORK_COUNTRY_ONLY = 2; // 0x2
+ field public static final int MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY = 5; // 0x5
+ field public static final int QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS = 3; // 0x3
+ field public static final int QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET = 2; // 0x2
+ field public static final int QUALITY_NA = 0; // 0x0
+ field public static final int QUALITY_SINGLE_ZONE = 1; // 0x1
+ }
+
+ public static final class PhoneTimeZoneSuggestion.Builder {
+ ctor public PhoneTimeZoneSuggestion.Builder(int);
+ method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion.Builder addDebugInfo(@NonNull String);
+ method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion build();
+ method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion.Builder setMatchType(int);
+ method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion.Builder setQuality(int);
+ method @NonNull public android.app.timezonedetector.PhoneTimeZoneSuggestion.Builder setZoneId(@Nullable String);
+ }
+
+ public class TimeZoneDetector {
+ method @RequiresPermission("android.permission.SUGGEST_PHONE_TIME_AND_ZONE") public void suggestPhoneTimeZone(@NonNull android.app.timezonedetector.PhoneTimeZoneSuggestion);
+ }
+
+}
+
+package android.os {
+
+ public final class TimestampedValue<T> implements android.os.Parcelable {
+ ctor public TimestampedValue(long, @Nullable T);
+ method public int describeContents();
+ method public long getReferenceTimeMillis();
+ method @Nullable public T getValue();
+ method public static long referenceTimeDifference(@NonNull android.os.TimestampedValue<?>, @NonNull android.os.TimestampedValue<?>);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.os.TimestampedValue<?>> CREATOR;
+ }
+
+}
+
+package android.timezone {
+
+ public final class CountryTimeZones {
+ method @Nullable public android.icu.util.TimeZone getDefaultTimeZone();
+ method @Nullable public String getDefaultTimeZoneId();
+ method @NonNull public java.util.List<android.timezone.CountryTimeZones.TimeZoneMapping> getEffectiveTimeZoneMappingsAt(long);
+ method public boolean hasUtcZone(long);
+ method public boolean isDefaultTimeZoneBoosted();
+ method public boolean isForCountryCode(@NonNull String);
+ method @Nullable public android.timezone.CountryTimeZones.OffsetResult lookupByOffsetWithBias(int, @Nullable Boolean, @Nullable Integer, long, @Nullable android.icu.util.TimeZone);
+ }
+
+ public static final class CountryTimeZones.OffsetResult {
+ ctor public CountryTimeZones.OffsetResult(@NonNull android.icu.util.TimeZone, boolean);
+ method @NonNull public android.icu.util.TimeZone getTimeZone();
+ method public boolean isOnlyMatch();
+ }
+
+ public static final class CountryTimeZones.TimeZoneMapping {
+ method @Nullable public android.icu.util.TimeZone getTimeZone();
+ method @NonNull public String getTimeZoneId();
+ }
+
+ public class TelephonyLookup {
+ method @NonNull public static android.timezone.TelephonyLookup getInstance();
+ method @Nullable public android.timezone.TelephonyNetworkFinder getTelephonyNetworkFinder();
+ }
+
+ public class TelephonyNetwork {
+ method @NonNull public String getCountryIsoCode();
+ method @NonNull public String getMcc();
+ method @NonNull public String getMnc();
+ }
+
+ public class TelephonyNetworkFinder {
+ method @Nullable public android.timezone.TelephonyNetwork findNetworkByMccMnc(@NonNull String, @NonNull String);
+ }
+
+ public final class TimeZoneFinder {
+ method @NonNull public static android.timezone.TimeZoneFinder getInstance();
+ method @Nullable public android.timezone.CountryTimeZones lookupCountryTimeZones(@NonNull String);
+ }
+
+}
+
diff --git a/api/module-lib-removed.txt b/api/module-lib-removed.txt
new file mode 100644
index 0000000..d802177
--- /dev/null
+++ b/api/module-lib-removed.txt
@@ -0,0 +1 @@
+// Signature format: 2.0
diff --git a/api/system-current.txt b/api/system-current.txt
index 6b2aea5..d00bd84 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -90,7 +90,6 @@
field public static final String INSTALL_PACKAGE_UPDATES = "android.permission.INSTALL_PACKAGE_UPDATES";
field public static final String INSTALL_SELF_UPDATES = "android.permission.INSTALL_SELF_UPDATES";
field public static final String INTENT_FILTER_VERIFICATION_AGENT = "android.permission.INTENT_FILTER_VERIFICATION_AGENT";
- field public static final String INTERACT_ACROSS_PROFILES = "android.permission.INTERACT_ACROSS_PROFILES";
field public static final String INTERACT_ACROSS_USERS = "android.permission.INTERACT_ACROSS_USERS";
field public static final String INTERACT_ACROSS_USERS_FULL = "android.permission.INTERACT_ACROSS_USERS_FULL";
field public static final String INTERNAL_SYSTEM_WINDOW = "android.permission.INTERNAL_SYSTEM_WINDOW";
@@ -127,6 +126,7 @@
field @Deprecated public static final String MODIFY_NETWORK_ACCOUNTING = "android.permission.MODIFY_NETWORK_ACCOUNTING";
field public static final String MODIFY_PARENTAL_CONTROLS = "android.permission.MODIFY_PARENTAL_CONTROLS";
field public static final String MODIFY_QUIET_MODE = "android.permission.MODIFY_QUIET_MODE";
+ field public static final String MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE = "android.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE";
field public static final String MOVE_PACKAGE = "android.permission.MOVE_PACKAGE";
field public static final String NETWORK_AIRPLANE_MODE = "android.permission.NETWORK_AIRPLANE_MODE";
field public static final String NETWORK_CARRIER_PROVISIONING = "android.permission.NETWORK_CARRIER_PROVISIONING";
@@ -153,6 +153,7 @@
field public static final String PROVIDE_RESOLVER_RANKER_SERVICE = "android.permission.PROVIDE_RESOLVER_RANKER_SERVICE";
field public static final String PROVIDE_TRUST_AGENT = "android.permission.PROVIDE_TRUST_AGENT";
field public static final String QUERY_TIME_ZONE_RULES = "android.permission.QUERY_TIME_ZONE_RULES";
+ field public static final String RADIO_SCAN_WITHOUT_LOCATION = "android.permission.RADIO_SCAN_WITHOUT_LOCATION";
field public static final String READ_ACTIVE_EMERGENCY_SESSION = "android.permission.READ_ACTIVE_EMERGENCY_SESSION";
field public static final String READ_CELL_BROADCASTS = "android.permission.READ_CELL_BROADCASTS";
field public static final String READ_CONTENT_RATING_SYSTEMS = "android.permission.READ_CONTENT_RATING_SYSTEMS";
@@ -190,6 +191,7 @@
field public static final String REVIEW_ACCESSIBILITY_SERVICES = "android.permission.REVIEW_ACCESSIBILITY_SERVICES";
field public static final String REVOKE_RUNTIME_PERMISSIONS = "android.permission.REVOKE_RUNTIME_PERMISSIONS";
field public static final String SCORE_NETWORKS = "android.permission.SCORE_NETWORKS";
+ field public static final String SECURE_ELEMENT_PRIVILEGED = "android.permission.SECURE_ELEMENT_PRIVILEGED";
field public static final String SEND_DEVICE_CUSTOMIZATION_READY = "android.permission.SEND_DEVICE_CUSTOMIZATION_READY";
field public static final String SEND_SHOW_SUSPENDED_APP_DETAILS = "android.permission.SEND_SHOW_SUSPENDED_APP_DETAILS";
field public static final String SEND_SMS_NO_CONFIRMATION = "android.permission.SEND_SMS_NO_CONFIRMATION";
@@ -207,9 +209,11 @@
field public static final String STOP_APP_SWITCHES = "android.permission.STOP_APP_SWITCHES";
field public static final String SUBSTITUTE_NOTIFICATION_APP_NAME = "android.permission.SUBSTITUTE_NOTIFICATION_APP_NAME";
field public static final String SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON = "android.permission.SUBSTITUTE_SHARE_TARGET_APP_NAME_AND_ICON";
+ field public static final String SUGGEST_PHONE_TIME_AND_ZONE = "android.permission.SUGGEST_PHONE_TIME_AND_ZONE";
field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS";
field public static final String SYSTEM_CAMERA = "android.permission.SYSTEM_CAMERA";
field public static final String TETHER_PRIVILEGED = "android.permission.TETHER_PRIVILEGED";
+ field public static final String TUNER_RESOURCE_ACCESS = "android.permission.TUNER_RESOURCE_ACCESS";
field public static final String TV_INPUT_HARDWARE = "android.permission.TV_INPUT_HARDWARE";
field public static final String TV_VIRTUAL_REMOTE_CONTROLLER = "android.permission.TV_VIRTUAL_REMOTE_CONTROLLER";
field public static final String UNLIMITED_SHORTCUTS_API_CALLS = "android.permission.UNLIMITED_SHORTCUTS_API_CALLS";
@@ -239,9 +243,10 @@
public static final class R.attr {
field public static final int allowClearUserDataOnFailedRestore = 16844288; // 0x1010600
field public static final int isVrOnly = 16844152; // 0x1010578
+ field public static final int minExtensionVersion = 16844306; // 0x1010612
field public static final int requiredSystemPropertyName = 16844133; // 0x1010565
field public static final int requiredSystemPropertyValue = 16844134; // 0x1010566
- field public static final int resourcesMap = 16844297; // 0x1010609
+ field public static final int sdkVersion = 16844305; // 0x1010611
field public static final int supportsAmbientMode = 16844173; // 0x101058d
field public static final int userRestriction = 16844164; // 0x1010584
}
@@ -283,6 +288,7 @@
field public static final int config_helpIntentNameKey = 17039390; // 0x104001e
field public static final int config_helpPackageNameKey = 17039387; // 0x104001b
field public static final int config_helpPackageNameValue = 17039388; // 0x104001c
+ field public static final int config_systemGallery = 17039402; // 0x104002a
}
public static final class R.style {
@@ -325,6 +331,7 @@
method public void setDeviceLocales(@NonNull android.os.LocaleList);
method @RequiresPermission(android.Manifest.permission.RESTRICTED_VR_ACCESS) public static void setPersistentVrThread(int);
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean switchUser(@NonNull android.os.UserHandle);
+ method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public boolean updateMccMncConfiguration(@NonNull String, @NonNull String);
}
public static interface ActivityManager.OnUidImportanceListener {
@@ -364,6 +371,7 @@
field public static final String OPSTR_GET_ACCOUNTS = "android:get_accounts";
field public static final String OPSTR_GPS = "android:gps";
field public static final String OPSTR_INSTANT_APP_START_FOREGROUND = "android:instant_app_start_foreground";
+ field public static final String OPSTR_INTERACT_ACROSS_PROFILES = "android:interact_across_profiles";
field public static final String OPSTR_LEGACY_STORAGE = "android:legacy_storage";
field public static final String OPSTR_MANAGE_EXTERNAL_STORAGE = "android:manage_external_storage";
field public static final String OPSTR_MANAGE_IPSEC_TUNNELS = "android:manage_ipsec_tunnels";
@@ -413,6 +421,16 @@
field public static final int UID_STATE_TOP = 200; // 0xc8
}
+ public static final class AppOpsManager.HistoricalFeatureOps implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public String getFeatureId();
+ method @Nullable public android.app.AppOpsManager.HistoricalOp getOp(@NonNull String);
+ method @NonNull public android.app.AppOpsManager.HistoricalOp getOpAt(@IntRange(from=0) int);
+ method @IntRange(from=0) public int getOpCount();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalFeatureOps> CREATOR;
+ }
+
public static final class AppOpsManager.HistoricalOp implements android.os.Parcelable {
method public int describeContents();
method public long getAccessCount(int, int, int);
@@ -446,6 +464,7 @@
public static final class AppOpsManager.HistoricalOpsRequest.Builder {
ctor public AppOpsManager.HistoricalOpsRequest.Builder(long, long);
method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest build();
+ method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setFeatureId(@Nullable String);
method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setFlags(int);
method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setOpNames(@Nullable java.util.List<java.lang.String>);
method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setPackageName(@Nullable String);
@@ -454,6 +473,9 @@
public static final class AppOpsManager.HistoricalPackageOps implements android.os.Parcelable {
method public int describeContents();
+ method @IntRange(from=0) public int getFeatureCount();
+ method @Nullable public android.app.AppOpsManager.HistoricalFeatureOps getFeatureOps(@NonNull String);
+ method @NonNull public android.app.AppOpsManager.HistoricalFeatureOps getFeatureOpsAt(@IntRange(from=0) int);
method @Nullable public android.app.AppOpsManager.HistoricalOp getOp(@NonNull String);
method @NonNull public android.app.AppOpsManager.HistoricalOp getOpAt(@IntRange(from=0) int);
method @IntRange(from=0) public int getOpCount();
@@ -553,6 +575,7 @@
}
public class DownloadManager {
+ method @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE) public void onMediaStoreDownloadsDeleted(@NonNull android.util.LongSparseArray<java.lang.String>);
field public static final String ACTION_DOWNLOAD_COMPLETED = "android.intent.action.DOWNLOAD_COMPLETED";
}
@@ -663,6 +686,7 @@
public class StatusBarManager {
method @NonNull @RequiresPermission(android.Manifest.permission.STATUS_BAR) public android.app.StatusBarManager.DisableInfo getDisableInfo();
method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSetup(boolean);
+ method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSimNetworkLock(boolean);
}
public static final class StatusBarManager.DisableInfo {
@@ -770,6 +794,7 @@
package android.app.admin {
public class DevicePolicyManager {
+ method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean getBluetoothContactSharingDisabled(@NonNull android.os.UserHandle);
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwner();
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.content.ComponentName getDeviceOwnerComponentOnAnyUser();
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public String getDeviceOwnerNameOnAnyUser();
@@ -795,7 +820,9 @@
field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_ALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_ALLOWED";
field public static final String ACCOUNT_FEATURE_DEVICE_OR_PROFILE_OWNER_DISALLOWED = "android.account.DEVICE_OR_PROFILE_OWNER_DISALLOWED";
field public static final String ACTION_PROVISION_FINALIZATION = "android.app.action.PROVISION_FINALIZATION";
+ field public static final String ACTION_PROVISION_FINANCED_DEVICE = "android.app.action.PROVISION_FINANCED_DEVICE";
field public static final String ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE = "android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
+ field public static final String ACTION_RESET_PROTECTION_POLICY_CHANGED = "android.app.action.RESET_PROTECTION_POLICY_CHANGED";
field public static final String ACTION_SET_PROFILE_OWNER = "android.app.action.SET_PROFILE_OWNER";
field public static final String ACTION_STATE_USER_SETUP_COMPLETE = "android.app.action.STATE_USER_SETUP_COMPLETE";
field public static final String EXTRA_PROFILE_OWNER_NAME = "android.app.extra.PROFILE_OWNER_NAME";
@@ -1013,6 +1040,7 @@
field public static final int AGENT_ERROR = -1003; // 0xfffffc15
field public static final int AGENT_UNKNOWN = -1004; // 0xfffffc14
field public static final String EXTRA_TRANSPORT_REGISTRATION = "android.app.backup.extra.TRANSPORT_REGISTRATION";
+ field public static final int FLAG_DATA_NOT_CHANGED = 8; // 0x8
field public static final int FLAG_INCREMENTAL = 2; // 0x2
field public static final int FLAG_NON_INCREMENTAL = 4; // 0x4
field public static final int FLAG_USER_INITIATED = 1; // 0x1
@@ -1268,6 +1296,7 @@
method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void addRoleHolderAsUser(@NonNull String, @NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public boolean addRoleHolderFromController(@NonNull String, @NonNull String);
method @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public void clearRoleHoldersAsUser(@NonNull String, int, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+ method @Nullable public String getDefaultSmsPackage(int);
method @NonNull @RequiresPermission("com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER") public java.util.List<java.lang.String> getHeldRolesFromController(@NonNull String);
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public java.util.List<java.lang.String> getRoleHolders(@NonNull String);
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public java.util.List<java.lang.String> getRoleHoldersAsUser(@NonNull String, @NonNull android.os.UserHandle);
@@ -1311,6 +1340,10 @@
field public static final String SERVICE_INTERFACE = "android.app.usage.CacheQuotaService";
}
+ public class NetworkStatsManager {
+ method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public android.net.netstats.provider.NetworkStatsProviderCallback registerNetworkStatsProvider(@NonNull String, @NonNull android.net.netstats.provider.AbstractNetworkStatsProvider);
+ }
+
public static final class UsageEvents.Event {
method public int getInstanceId();
method @Nullable public String getNotificationChannelId();
@@ -1517,12 +1550,32 @@
method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
}
+ public final class BluetoothHidDevice implements android.bluetooth.BluetoothProfile {
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
+ }
+
+ public final class BluetoothHidHost implements android.bluetooth.BluetoothProfile {
+ method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice);
+ method public int getConnectionState(@Nullable android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice, int);
+ field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED";
+ }
+
+ public final class BluetoothMap implements android.bluetooth.BluetoothProfile {
+ method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH) public int getConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@Nullable android.bluetooth.BluetoothDevice, int);
+ field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED";
+ }
+
public final class BluetoothPan implements android.bluetooth.BluetoothProfile {
method protected void finalize();
method @NonNull public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
method public int getConnectionState(@Nullable android.bluetooth.BluetoothDevice);
method public boolean isTetheringOn();
method public void setBluetoothTethering(boolean);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED";
field public static final String EXTRA_LOCAL_ROLE = "android.bluetooth.pan.extra.LOCAL_ROLE";
field public static final int LOCAL_NAP_ROLE = 1; // 0x1
@@ -1534,6 +1587,7 @@
public class BluetoothPbap implements android.bluetooth.BluetoothProfile {
method public int getConnectionState(@Nullable android.bluetooth.BluetoothDevice);
+ method @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
field public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED";
}
@@ -1628,17 +1682,22 @@
method @NonNull public final android.os.UserHandle getSendingUser();
}
+ public abstract class ContentProvider implements android.content.ComponentCallbacks2 {
+ method public int checkUriPermission(@NonNull android.net.Uri, int, int);
+ }
+
public class ContentProviderClient implements java.lang.AutoCloseable {
method @RequiresPermission(android.Manifest.permission.REMOVE_TASKS) public void setDetectNotResponding(long);
}
public abstract class ContentResolver {
+ method @NonNull public static android.net.Uri decodeFromFile(@NonNull java.io.File);
+ method @NonNull public static java.io.File encodeToFile(@NonNull android.net.Uri);
method @Nullable @RequiresPermission("android.permission.CACHE_CONTENT") public android.os.Bundle getCache(@NonNull android.net.Uri);
method @RequiresPermission("android.permission.CACHE_CONTENT") public void putCache(@NonNull android.net.Uri, @Nullable android.os.Bundle);
}
public abstract class Context {
- method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public boolean bindServiceAsUser(@RequiresPermission android.content.Intent, android.content.ServiceConnection, int, android.os.UserHandle);
method @NonNull public android.content.Context createContextAsUser(@NonNull android.os.UserHandle, int);
method public abstract android.content.Context createCredentialProtectedStorageContext();
method @NonNull public android.content.Context createPackageContextAsUser(@NonNull String, int, @NonNull android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -1648,7 +1707,7 @@
method public abstract void sendBroadcast(android.content.Intent, @Nullable String, @Nullable android.os.Bundle);
method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public abstract void sendBroadcastAsUser(@RequiresPermission android.content.Intent, android.os.UserHandle, @Nullable String, @Nullable android.os.Bundle);
method public abstract void sendOrderedBroadcast(@NonNull android.content.Intent, @Nullable String, @Nullable android.os.Bundle, @Nullable android.content.BroadcastReceiver, @Nullable android.os.Handler, int, @Nullable String, @Nullable android.os.Bundle);
- method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.os.UserHandle);
+ method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS) public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.os.UserHandle);
field public static final String APP_INTEGRITY_SERVICE = "app_integrity";
field public static final String APP_PREDICTION_SERVICE = "app_prediction";
field public static final String BACKUP_SERVICE = "backup";
@@ -1659,7 +1718,9 @@
field public static final String EUICC_CARD_SERVICE = "euicc_card";
field public static final String HDMI_CONTROL_SERVICE = "hdmi_control";
field public static final String NETD_SERVICE = "netd";
+ field public static final String NETWORK_POLICY_SERVICE = "netpolicy";
field public static final String NETWORK_SCORE_SERVICE = "network_score";
+ field public static final String NETWORK_STACK_SERVICE = "network_stack";
field public static final String OEM_LOCK_SERVICE = "oem_lock";
field public static final String PERMISSION_SERVICE = "permission";
field public static final String PERSISTENT_DATA_BLOCK_SERVICE = "persistent_data_block";
@@ -1672,6 +1733,7 @@
field public static final String TELEPHONY_REGISTRY_SERVICE = "telephony_registry";
field public static final String TETHERING_SERVICE = "tethering";
field public static final String VR_SERVICE = "vrmanager";
+ field public static final String WIFI_COND_SERVICE = "wificond";
field @Deprecated public static final String WIFI_RTT_SERVICE = "rttmanager";
field public static final String WIFI_SCANNING_SERVICE = "wifiscanner";
}
@@ -1697,6 +1759,7 @@
field public static final String ACTION_INSTALL_INSTANT_APP_PACKAGE = "android.intent.action.INSTALL_INSTANT_APP_PACKAGE";
field public static final String ACTION_INSTANT_APP_RESOLVER_SETTINGS = "android.intent.action.INSTANT_APP_RESOLVER_SETTINGS";
field public static final String ACTION_INTENT_FILTER_NEEDS_VERIFICATION = "android.intent.action.INTENT_FILTER_NEEDS_VERIFICATION";
+ field public static final String ACTION_LOAD_DATA = "android.intent.action.LOAD_DATA";
field @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS) public static final String ACTION_MANAGE_APP_PERMISSION = "android.intent.action.MANAGE_APP_PERMISSION";
field public static final String ACTION_MANAGE_APP_PERMISSIONS = "android.intent.action.MANAGE_APP_PERMISSIONS";
field @RequiresPermission(android.Manifest.permission.MANAGE_ROLE_HOLDERS) public static final String ACTION_MANAGE_DEFAULT_APP = "android.intent.action.MANAGE_DEFAULT_APP";
@@ -1744,6 +1807,7 @@
field @Deprecated public static final String EXTRA_SIM_STATE = "ss";
field public static final String EXTRA_UNKNOWN_INSTANT_APP = "android.intent.extra.UNKNOWN_INSTANT_APP";
field public static final String EXTRA_VERIFICATION_BUNDLE = "android.intent.extra.VERIFICATION_BUNDLE";
+ field public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 67108864; // 0x4000000
field public static final String METADATA_SETUP_VERSION = "android.SETUP_VERSION";
field @Deprecated public static final String SIM_ABSENT_ON_PERM_DISABLED = "PERM_DISABLED";
field @Deprecated public static final String SIM_LOCKED_NETWORK = "NETWORK";
@@ -2115,6 +2179,7 @@
field public static final String EXTRA_REQUEST_PERMISSIONS_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_NAMES";
field public static final String EXTRA_REQUEST_PERMISSIONS_RESULTS = "android.content.pm.extra.REQUEST_PERMISSIONS_RESULTS";
field public static final String FEATURE_BROADCAST_RADIO = "android.hardware.broadcastradio";
+ field public static final String FEATURE_REBOOT_ESCROW = "android.hardware.reboot_escrow";
field public static final String FEATURE_TELEPHONY_CARRIERLOCK = "android.hardware.telephony.carrierlock";
field public static final int FLAG_PERMISSION_APPLY_RESTRICTION = 16384; // 0x4000
field public static final int FLAG_PERMISSION_GRANTED_BY_DEFAULT = 32; // 0x20
@@ -2358,7 +2423,7 @@
package android.hardware.camera2 {
public abstract class CameraDevice implements java.lang.AutoCloseable {
- method public abstract void createCustomCaptureSession(android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, int, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+ method @Deprecated public abstract void createCustomCaptureSession(android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, int, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
field public static final int SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED = 1; // 0x1
field public static final int SESSION_OPERATION_MODE_NORMAL = 0; // 0x0
field public static final int SESSION_OPERATION_MODE_VENDOR_START = 32768; // 0x8000
@@ -3508,7 +3573,6 @@
}
public static final class SoundTrigger.ModelParamRange implements android.os.Parcelable {
- method public int describeContents();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.ModelParamRange> CREATOR;
field public final int end;
@@ -3518,7 +3582,10 @@
public static final class SoundTrigger.ModuleProperties implements android.os.Parcelable {
method public int describeContents();
method public void writeToParcel(android.os.Parcel, int);
+ field public static final int CAPABILITY_ECHO_CANCELLATION = 1; // 0x1
+ field public static final int CAPABILITY_NOISE_SUPPRESSION = 2; // 0x2
field @NonNull public static final android.os.Parcelable.Creator<android.hardware.soundtrigger.SoundTrigger.ModuleProperties> CREATOR;
+ field public final int audioCapabilities;
field @NonNull public final String description;
field public final int id;
field @NonNull public final String implementor;
@@ -3529,6 +3596,7 @@
field public final int powerConsumptionMw;
field public final int recognitionModes;
field public final boolean returnsTriggerInEvent;
+ field @NonNull public final String supportedModelArch;
field public final boolean supportsCaptureTransition;
field public final boolean supportsConcurrentCapture;
field @NonNull public final java.util.UUID uuid;
@@ -3551,9 +3619,17 @@
}
public class UsbManager {
+ method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public long getCurrentFunctions();
method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_USB) public java.util.List<android.hardware.usb.UsbPort> getPorts();
method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void grantPermission(android.hardware.usb.UsbDevice, String);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_USB) public void setCurrentFunctions(long);
field @RequiresPermission(android.Manifest.permission.MANAGE_USB) public static final String ACTION_USB_PORT_CHANGED = "android.hardware.usb.action.USB_PORT_CHANGED";
+ field public static final String ACTION_USB_STATE = "android.hardware.usb.action.USB_STATE";
+ field public static final long FUNCTION_NONE = 0L; // 0x0L
+ field public static final long FUNCTION_RNDIS = 32L; // 0x20L
+ field public static final String USB_CONFIGURED = "configured";
+ field public static final String USB_CONNECTED = "connected";
+ field public static final String USB_FUNCTION_RNDIS = "rndis";
}
public final class UsbPort {
@@ -4006,6 +4082,10 @@
field public static final int ROLE_OUTPUT = 2; // 0x2
}
+ public final class AudioDeviceInfo {
+ field public static final int TYPE_REMOTE_SUBMIX = 25; // 0x19
+ }
+
public final class AudioFocusInfo implements android.os.Parcelable {
method public int describeContents();
method @NonNull public android.media.AudioAttributes getAttributes();
@@ -4033,6 +4113,7 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int dispatchAudioFocusChange(@NonNull android.media.AudioFocusInfo, int, @NonNull android.media.audiopolicy.AudioPolicy);
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioProductStrategy> getAudioProductStrategies();
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static java.util.List<android.media.audiopolicy.AudioVolumeGroup> getAudioVolumeGroups();
+ method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioDeviceAddress> getDevicesForAttributes(@NonNull android.media.AudioAttributes);
method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMaxVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMinVolumeIndexForAttributes(@NonNull android.media.AudioAttributes);
method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public android.media.AudioDeviceAddress getPreferredDeviceForStrategy(@NonNull android.media.audiopolicy.AudioProductStrategy);
@@ -4139,6 +4220,14 @@
}
+package android.media.audiofx {
+
+ public class AudioEffect {
+ ctor @RequiresPermission("android.permission.MODIFY_DEFAULT_AUDIO_EFFECTS") public AudioEffect(@NonNull java.util.UUID, @NonNull android.media.AudioDeviceAddress);
+ }
+
+}
+
package android.media.audiopolicy {
public class AudioMix {
@@ -4258,7 +4347,7 @@
}
public static interface MediaSessionManager.OnMediaKeyEventDispatchedListener {
- method public default void onMediaKeyEventDispatched(@NonNull android.view.KeyEvent, @NonNull String, @NonNull android.media.session.MediaSession.Token);
+ method public default void onMediaKeyEventDispatched(@NonNull android.view.KeyEvent, @NonNull String, @Nullable android.media.session.MediaSession.Token);
}
public static interface MediaSessionManager.OnMediaKeyEventSessionChangedListener {
@@ -4292,6 +4381,8 @@
method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public boolean stopRecognition();
field public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 2; // 0x2
field public static final int RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO = 1; // 0x1
+ field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 4; // 0x4
+ field public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 8; // 0x8
}
public abstract static class SoundTriggerDetector.Callback {
@@ -4314,23 +4405,34 @@
method public int getDetectionServiceOperationsTimeout();
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.media.soundtrigger.SoundTriggerManager.Model getModel(java.util.UUID);
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.hardware.soundtrigger.SoundTrigger.ModuleProperties getModuleProperties();
- method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int getParameter(@NonNull java.util.UUID, int) throws java.lang.IllegalArgumentException, java.lang.UnsupportedOperationException;
+ method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int getParameter(@NonNull java.util.UUID, int);
method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public android.hardware.soundtrigger.SoundTrigger.ModelParamRange queryParameter(@Nullable java.util.UUID, int);
- method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int setParameter(@Nullable java.util.UUID, int, int) throws java.lang.IllegalArgumentException, java.lang.UnsupportedOperationException;
+ method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public int setParameter(@Nullable java.util.UUID, int, int);
method @RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER) public void updateModel(android.media.soundtrigger.SoundTriggerManager.Model);
}
public static class SoundTriggerManager.Model {
- method public static android.media.soundtrigger.SoundTriggerManager.Model create(java.util.UUID, java.util.UUID, byte[]);
- method public byte[] getModelData();
- method public java.util.UUID getModelUuid();
- method public java.util.UUID getVendorUuid();
+ method @NonNull public static android.media.soundtrigger.SoundTriggerManager.Model create(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[], int);
+ method @NonNull public static android.media.soundtrigger.SoundTriggerManager.Model create(@NonNull java.util.UUID, @NonNull java.util.UUID, @Nullable byte[]);
+ method @Nullable public byte[] getModelData();
+ method @NonNull public java.util.UUID getModelUuid();
+ method @NonNull public java.util.UUID getVendorUuid();
+ method public int getVersion();
}
}
package android.media.tv {
+ public final class DvbDeviceInfo implements android.os.Parcelable {
+ ctor public DvbDeviceInfo(int, int);
+ method public int describeContents();
+ method public int getAdapterId();
+ method public int getDeviceId();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.media.tv.DvbDeviceInfo> CREATOR;
+ }
+
public final class TvContentRatingSystemInfo implements android.os.Parcelable {
method public static android.media.tv.TvContentRatingSystemInfo createTvContentRatingSystemInfo(int, android.content.pm.ApplicationInfo);
method public int describeContents();
@@ -4445,15 +4547,19 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_PARENTAL_CONTROLS) public void addBlockedRating(@NonNull android.media.tv.TvContentRating);
method @RequiresPermission(android.Manifest.permission.CAPTURE_TV_INPUT) public boolean captureFrame(String, android.view.Surface, android.media.tv.TvStreamConfig);
method @RequiresPermission(android.Manifest.permission.CAPTURE_TV_INPUT) public java.util.List<android.media.tv.TvStreamConfig> getAvailableTvStreamConfigList(String);
+ method @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS) public int getClientPid(@NonNull String);
+ method @NonNull @RequiresPermission("android.permission.DVB_DEVICE") public java.util.List<android.media.tv.DvbDeviceInfo> getDvbDeviceList();
method @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE) public java.util.List<android.media.tv.TvInputHardwareInfo> getHardwareList();
method @RequiresPermission(android.Manifest.permission.READ_CONTENT_RATING_SYSTEMS) public java.util.List<android.media.tv.TvContentRatingSystemInfo> getTvContentRatingSystemList();
method @RequiresPermission(android.Manifest.permission.CAPTURE_TV_INPUT) public boolean isSingleSessionActive();
method @RequiresPermission(android.Manifest.permission.NOTIFY_TV_INPUTS) public void notifyPreviewProgramAddedToWatchNext(String, long, long);
method @RequiresPermission(android.Manifest.permission.NOTIFY_TV_INPUTS) public void notifyPreviewProgramBrowsableDisabled(String, long);
method @RequiresPermission(android.Manifest.permission.NOTIFY_TV_INPUTS) public void notifyWatchNextProgramBrowsableDisabled(String, long);
+ method @Nullable @RequiresPermission("android.permission.DVB_DEVICE") public android.os.ParcelFileDescriptor openDvbDevice(@NonNull android.media.tv.DvbDeviceInfo, int);
method @RequiresPermission(android.Manifest.permission.TV_INPUT_HARDWARE) public void releaseTvInputHardware(int, android.media.tv.TvInputManager.Hardware);
method @RequiresPermission(android.Manifest.permission.MODIFY_PARENTAL_CONTROLS) public void removeBlockedRating(@NonNull android.media.tv.TvContentRating);
method @RequiresPermission(android.Manifest.permission.MODIFY_PARENTAL_CONTROLS) public void setParentalControlsEnabled(boolean);
+ field public static final int UNKNOWN_CLIENT_PID = -1; // 0xffffffff
}
public static final class TvInputManager.Hardware {
@@ -4530,6 +4636,28 @@
method public abstract int getType();
}
+ public class Lnb implements java.lang.AutoCloseable {
+ method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public void close();
+ method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int sendDiseqcMessage(@NonNull byte[]);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int setSatellitePosition(int);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int setTone(int);
+ method @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public int setVoltage(int);
+ field public static final int POSITION_A = 1; // 0x1
+ field public static final int POSITION_B = 2; // 0x2
+ field public static final int POSITION_UNDEFINED = 0; // 0x0
+ field public static final int TONE_CONTINUOUS = 1; // 0x1
+ field public static final int TONE_NONE = 0; // 0x0
+ field public static final int VOLTAGE_11V = 2; // 0x2
+ field public static final int VOLTAGE_12V = 3; // 0x3
+ field public static final int VOLTAGE_13V = 4; // 0x4
+ field public static final int VOLTAGE_14V = 5; // 0x5
+ field public static final int VOLTAGE_15V = 6; // 0x6
+ field public static final int VOLTAGE_18V = 7; // 0x7
+ field public static final int VOLTAGE_19V = 8; // 0x8
+ field public static final int VOLTAGE_5V = 1; // 0x1
+ field public static final int VOLTAGE_NONE = 0; // 0x0
+ }
+
public final class Tuner implements java.lang.AutoCloseable {
ctor public Tuner(@NonNull android.content.Context);
method @Nullable @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public android.media.tv.tuner.Tuner.Descrambler openDescrambler();
@@ -4539,6 +4667,79 @@
public class Tuner.Descrambler {
}
+ public class Tuner.Filter {
+ }
+
+ public static interface Tuner.FilterCallback {
+ method public void onFilterEvent(@NonNull android.media.tv.tuner.Tuner.Filter, @NonNull android.media.tv.tuner.filter.FilterEvent[]);
+ method public void onFilterStatusChanged(@NonNull android.media.tv.tuner.Tuner.Filter, int);
+ }
+
+ public final class TunerConstants {
+ field public static final int FILTER_STATUS_DATA_READY = 1; // 0x1
+ field public static final int FILTER_STATUS_HIGH_WATER = 4; // 0x4
+ field public static final int FILTER_STATUS_LOW_WATER = 2; // 0x2
+ field public static final int FILTER_STATUS_OVERFLOW = 8; // 0x8
+ field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4
+ field public static final int RESULT_INVALID_STATE = 3; // 0x3
+ field public static final int RESULT_NOT_INITIALIZED = 2; // 0x2
+ field public static final int RESULT_OUT_OF_MEMORY = 5; // 0x5
+ field public static final int RESULT_SUCCESS = 0; // 0x0
+ field public static final int RESULT_UNAVAILABLE = 1; // 0x1
+ field public static final int RESULT_UNKNOWN_ERROR = 6; // 0x6
+ }
+
+}
+
+package android.media.tv.tuner.filter {
+
+ public abstract class FilterConfiguration {
+ field public static final int FILTER_TYPE_ALP = 16; // 0x10
+ field public static final int FILTER_TYPE_IP = 4; // 0x4
+ field public static final int FILTER_TYPE_MMTP = 2; // 0x2
+ field public static final int FILTER_TYPE_TLV = 8; // 0x8
+ field public static final int FILTER_TYPE_TS = 1; // 0x1
+ }
+
+ public abstract class FilterEvent {
+ ctor public FilterEvent();
+ }
+
+ public class PesSettings extends android.media.tv.tuner.filter.Settings {
+ method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public static android.media.tv.tuner.filter.PesSettings.Builder builder(@NonNull android.content.Context, int);
+ method public int getStreamId();
+ method public boolean isRaw();
+ }
+
+ public static class PesSettings.Builder {
+ method @NonNull public android.media.tv.tuner.filter.PesSettings build();
+ method @NonNull public android.media.tv.tuner.filter.PesSettings.Builder setRaw(boolean);
+ method @NonNull public android.media.tv.tuner.filter.PesSettings.Builder setStreamId(int);
+ }
+
+ public class SectionEvent extends android.media.tv.tuner.filter.FilterEvent {
+ method public int getDataLength();
+ method public int getSectionNumber();
+ method public int getTableId();
+ method public int getVersion();
+ }
+
+ public abstract class Settings {
+ }
+
+ public class TsFilterConfiguration extends android.media.tv.tuner.filter.FilterConfiguration {
+ method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER) public static android.media.tv.tuner.filter.TsFilterConfiguration.Builder builder(@NonNull android.content.Context);
+ method @Nullable public android.media.tv.tuner.filter.Settings getSettings();
+ method public int getTpid();
+ method public int getType();
+ }
+
+ public static class TsFilterConfiguration.Builder {
+ method @NonNull public android.media.tv.tuner.filter.TsFilterConfiguration build();
+ method @NonNull public android.media.tv.tuner.filter.TsFilterConfiguration.Builder setSettings(@NonNull android.media.tv.tuner.filter.Settings);
+ method @NonNull public android.media.tv.tuner.filter.TsFilterConfiguration.Builder setTpid(int);
+ }
+
}
package android.metrics {
@@ -4589,7 +4790,9 @@
public class CaptivePortal implements android.os.Parcelable {
method public void logEvent(int, @NonNull String);
+ method public void reevaluateNetwork();
method public void useNetwork();
+ field public static final int APP_REQUEST_REEVALUATION_REQUIRED = 100; // 0x64
field public static final int APP_RETURN_DISMISSED = 0; // 0x0
field public static final int APP_RETURN_UNWANTED = 1; // 0x1
field public static final int APP_RETURN_WANTED_AS_IS = 2; // 0x2
@@ -4601,13 +4804,16 @@
method @Deprecated @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public String getCaptivePortalServerUrl();
method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void getLatestTetheringEntitlementResult(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEntitlementResultListener);
method @RequiresPermission(anyOf={android.Manifest.permission.TETHER_PRIVILEGED, android.Manifest.permission.WRITE_SETTINGS}) public boolean isTetheringSupported();
+ method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public int registerNetworkProvider(@NonNull android.net.NetworkProvider);
method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void registerTetheringEventCallback(@NonNull java.util.concurrent.Executor, @NonNull android.net.ConnectivityManager.OnTetheringEventCallback);
+ method @Deprecated public void requestNetwork(@NonNull android.net.NetworkRequest, @NonNull android.net.ConnectivityManager.NetworkCallback, int, int, @NonNull android.os.Handler);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_AIRPLANE_MODE, android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void setAirplaneMode(boolean);
method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK}) public boolean shouldAvoidBadWifi();
method @RequiresPermission(android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK) public void startCaptivePortalApp(@NonNull android.net.Network, @NonNull android.os.Bundle);
method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback);
method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void startTethering(int, boolean, android.net.ConnectivityManager.OnStartTetheringCallback, android.os.Handler);
method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void stopTethering(int);
+ method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void unregisterNetworkProvider(@NonNull android.net.NetworkProvider);
method @RequiresPermission(android.Manifest.permission.TETHER_PRIVILEGED) public void unregisterTetheringEventCallback(@NonNull android.net.ConnectivityManager.OnTetheringEventCallback);
field public static final String EXTRA_CAPTIVE_PORTAL_PROBE_SPEC = "android.net.extra.CAPTIVE_PORTAL_PROBE_SPEC";
field public static final String EXTRA_CAPTIVE_PORTAL_USER_AGENT = "android.net.extra.CAPTIVE_PORTAL_USER_AGENT";
@@ -4617,6 +4823,8 @@
field public static final int TETHER_ERROR_ENTITLEMENT_UNKONWN = 13; // 0xd
field public static final int TETHER_ERROR_NO_ERROR = 0; // 0x0
field public static final int TETHER_ERROR_PROVISION_FAILED = 11; // 0xb
+ field public static final int TYPE_NONE = -1; // 0xffffffff
+ field @Deprecated public static final int TYPE_WIFI_P2P = 13; // 0xd
}
public abstract static class ConnectivityManager.OnStartTetheringCallback {
@@ -4747,17 +4955,64 @@
public final class MatchAllNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
ctor public MatchAllNetworkSpecifier();
method public int describeContents();
+ method public boolean satisfiedBy(android.net.NetworkSpecifier);
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.MatchAllNetworkSpecifier> CREATOR;
}
+ public final class NattKeepalivePacketData extends android.net.KeepalivePacketData implements android.os.Parcelable {
+ ctor public NattKeepalivePacketData(@NonNull java.net.InetAddress, int, @NonNull java.net.InetAddress, int, @NonNull byte[]) throws android.net.InvalidPacketException;
+ method public int describeContents();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.NattKeepalivePacketData> CREATOR;
+ }
+
public class Network implements android.os.Parcelable {
ctor public Network(@NonNull android.net.Network);
method @NonNull public android.net.Network getPrivateDnsBypassingCopy();
field public final int netId;
}
+ public abstract class NetworkAgent {
+ method public void onAddKeepalivePacketFilter(int, @NonNull android.net.KeepalivePacketData);
+ method public void onAutomaticReconnectDisabled();
+ method public void onBandwidthUpdateRequested();
+ method public void onNetworkUnwanted();
+ method public void onRemoveKeepalivePacketFilter(int);
+ method public void onSaveAcceptUnvalidated(boolean);
+ method public void onSignalStrengthThresholdsUpdated(@NonNull int[]);
+ method public void onStartSocketKeepalive(int, int, @NonNull android.net.KeepalivePacketData);
+ method public void onStopSocketKeepalive(int);
+ method public void onValidationStatus(int, @Nullable String);
+ method public void sendLinkProperties(@NonNull android.net.LinkProperties);
+ method public void sendNetworkCapabilities(@NonNull android.net.NetworkCapabilities);
+ method public void sendNetworkScore(int);
+ method public void sendSocketKeepaliveEvent(int, int);
+ field public static final int VALIDATION_STATUS_NOT_VALID = 2; // 0x2
+ field public static final int VALIDATION_STATUS_VALID = 1; // 0x1
+ field @NonNull public final android.net.Network network;
+ field public final int providerId;
+ }
+
+ public final class NetworkAgentConfig implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public String getSubscriberId();
+ method public boolean isNat64DetectionEnabled();
+ method public boolean isProvisioningNotificationEnabled();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkAgentConfig> CREATOR;
+ }
+
+ public static class NetworkAgentConfig.Builder {
+ ctor public NetworkAgentConfig.Builder();
+ method @NonNull public android.net.NetworkAgentConfig build();
+ method @NonNull public android.net.NetworkAgentConfig.Builder disableNat64Detection();
+ method @NonNull public android.net.NetworkAgentConfig.Builder disableProvisioningNotification();
+ method @NonNull public android.net.NetworkAgentConfig.Builder setSubscriberId(@Nullable String);
+ }
+
public final class NetworkCapabilities implements android.os.Parcelable {
+ method public boolean deduceRestrictedCapability();
method @NonNull public int[] getTransportTypes();
method public boolean satisfiedByNetworkCapabilities(@Nullable android.net.NetworkCapabilities);
method @NonNull public android.net.NetworkCapabilities setSSID(@Nullable String);
@@ -4777,12 +5032,27 @@
field public final android.net.WifiKey wifiKey;
}
+ public class NetworkProvider {
+ ctor public NetworkProvider(@NonNull android.content.Context, @NonNull android.os.Looper, @NonNull String);
+ method @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) public void declareNetworkRequestUnfulfillable(@NonNull android.net.NetworkRequest);
+ method @Nullable public android.os.Messenger getMessenger();
+ method @NonNull public String getName();
+ method public int getProviderId();
+ method public void onNetworkRequested(@NonNull android.net.NetworkRequest, int, int);
+ method public void onRequestWithdrawn(@NonNull android.net.NetworkRequest);
+ field public static final int ID_NONE = -1; // 0xffffffff
+ }
+
public abstract class NetworkRecommendationProvider {
ctor public NetworkRecommendationProvider(android.content.Context, java.util.concurrent.Executor);
method public final android.os.IBinder getBinder();
method public abstract void onRequestScores(android.net.NetworkKey[]);
}
+ public class NetworkRequest implements android.os.Parcelable {
+ method public boolean satisfiedBy(@Nullable android.net.NetworkCapabilities);
+ }
+
public static class NetworkRequest.Builder {
method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP) public android.net.NetworkRequest.Builder setSignalStrength(int);
}
@@ -4803,6 +5073,9 @@
field @Deprecated public static final String EXTRA_NETWORKS_TO_SCORE = "networksToScore";
field public static final String EXTRA_NEW_SCORER = "newScorer";
field @Deprecated public static final String EXTRA_PACKAGE_NAME = "packageName";
+ field public static final int SCORE_FILTER_CURRENT_NETWORK = 1; // 0x1
+ field public static final int SCORE_FILTER_NONE = 0; // 0x0
+ field public static final int SCORE_FILTER_SCAN_RESULTS = 2; // 0x2
}
public static interface NetworkScoreManager.NetworkScoreCallback {
@@ -4810,10 +5083,43 @@
method public void updateScores(@NonNull java.util.List<android.net.ScoredNetwork>);
}
+ public abstract class NetworkSpecifier {
+ method public void assertValidFromUid(int);
+ method @Nullable public android.net.NetworkSpecifier redact();
+ method public abstract boolean satisfiedBy(@Nullable android.net.NetworkSpecifier);
+ }
+
public class NetworkStack {
field public static final String PERMISSION_MAINLINE_NETWORK_STACK = "android.permission.MAINLINE_NETWORK_STACK";
}
+ public final class NetworkStats implements android.os.Parcelable {
+ ctor public NetworkStats(long, int);
+ method @NonNull public android.net.NetworkStats add(@NonNull android.net.NetworkStats);
+ method @NonNull public android.net.NetworkStats addValues(@NonNull android.net.NetworkStats.Entry);
+ method public int describeContents();
+ method @NonNull public android.net.NetworkStats subtract(@NonNull android.net.NetworkStats);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.NetworkStats> CREATOR;
+ field public static final int DEFAULT_NETWORK_NO = 0; // 0x0
+ field public static final int DEFAULT_NETWORK_YES = 1; // 0x1
+ field @Nullable public static final String IFACE_ALL;
+ field public static final String IFACE_VT = "vt_data0";
+ field public static final int METERED_NO = 0; // 0x0
+ field public static final int METERED_YES = 1; // 0x1
+ field public static final int ROAMING_NO = 0; // 0x0
+ field public static final int ROAMING_YES = 1; // 0x1
+ field public static final int SET_DEFAULT = 0; // 0x0
+ field public static final int SET_FOREGROUND = 1; // 0x1
+ field public static final int TAG_NONE = 0; // 0x0
+ field public static final int UID_ALL = -1; // 0xffffffff
+ field public static final int UID_TETHERING = -5; // 0xfffffffb
+ }
+
+ public static class NetworkStats.Entry {
+ ctor public NetworkStats.Entry(@Nullable String, int, int, int, int, int, int, long, long, long, long, long);
+ }
+
public final class RouteInfo implements android.os.Parcelable {
ctor public RouteInfo(@Nullable android.net.IpPrefix, @Nullable java.net.InetAddress, @Nullable String, int);
method public int getType();
@@ -4853,6 +5159,10 @@
field public final android.net.RssiCurve rssiCurve;
}
+ public abstract class SocketKeepalive implements java.lang.AutoCloseable {
+ field public static final int SUCCESS = 0; // 0x0
+ }
+
public final class StaticIpConfiguration implements android.os.Parcelable {
ctor public StaticIpConfiguration();
ctor public StaticIpConfiguration(@Nullable android.net.StaticIpConfiguration);
@@ -4880,11 +5190,16 @@
public final class StringNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
ctor public StringNetworkSpecifier(@NonNull String);
method public int describeContents();
+ method public boolean satisfiedBy(android.net.NetworkSpecifier);
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.net.StringNetworkSpecifier> CREATOR;
field @NonNull public final String specifier;
}
+ public final class TelephonyNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
+ method public boolean satisfiedBy(android.net.NetworkSpecifier);
+ }
+
public class TrafficStats {
method public static void setThreadStatsTagApp();
method public static void setThreadStatsTagBackup();
@@ -5404,6 +5719,25 @@
}
+package android.net.netstats.provider {
+
+ public abstract class AbstractNetworkStatsProvider {
+ ctor public AbstractNetworkStatsProvider();
+ method public abstract void requestStatsUpdate(int);
+ method public abstract void setAlert(long);
+ method public abstract void setLimit(@NonNull String, long);
+ field public static final int QUOTA_UNLIMITED = -1; // 0xffffffff
+ }
+
+ public class NetworkStatsProviderCallback {
+ method public void onAlertReached();
+ method public void onLimitReached();
+ method public void onStatsUpdated(int, @NonNull android.net.NetworkStats, @NonNull android.net.NetworkStats);
+ method public void unregister();
+ }
+
+}
+
package android.net.util {
public final class SocketUtils {
@@ -5627,8 +5961,33 @@
}
public class ScanResult implements android.os.Parcelable {
+ ctor public ScanResult();
+ field public static final int CIPHER_CCMP = 3; // 0x3
+ field public static final int CIPHER_GCMP_256 = 4; // 0x4
+ field public static final int CIPHER_NONE = 0; // 0x0
+ field public static final int CIPHER_NO_GROUP_ADDRESSED = 1; // 0x1
+ field public static final int CIPHER_SMS4 = 5; // 0x5
+ field public static final int CIPHER_TKIP = 2; // 0x2
+ field public static final int KEY_MGMT_EAP = 2; // 0x2
+ field public static final int KEY_MGMT_EAP_SHA256 = 6; // 0x6
+ field public static final int KEY_MGMT_EAP_SUITE_B_192 = 10; // 0xa
+ field public static final int KEY_MGMT_FT_EAP = 4; // 0x4
+ field public static final int KEY_MGMT_FT_PSK = 3; // 0x3
+ field public static final int KEY_MGMT_FT_SAE = 11; // 0xb
+ field public static final int KEY_MGMT_NONE = 0; // 0x0
+ field public static final int KEY_MGMT_OSEN = 7; // 0x7
+ field public static final int KEY_MGMT_OWE = 9; // 0x9
+ field public static final int KEY_MGMT_OWE_TRANSITION = 12; // 0xc
+ field public static final int KEY_MGMT_PSK = 1; // 0x1
+ field public static final int KEY_MGMT_PSK_SHA256 = 5; // 0x5
+ field public static final int KEY_MGMT_SAE = 8; // 0x8
field public static final int KEY_MGMT_WAPI_CERT = 14; // 0xe
field public static final int KEY_MGMT_WAPI_PSK = 13; // 0xd
+ field public static final int PROTOCOL_NONE = 0; // 0x0
+ field public static final int PROTOCOL_OSEN = 3; // 0x3
+ field public static final int PROTOCOL_RSN = 2; // 0x2
+ field public static final int PROTOCOL_WAPI = 4; // 0x4
+ field public static final int PROTOCOL_WPA = 1; // 0x1
}
public final class SoftApCapability implements android.os.Parcelable {
@@ -5639,17 +5998,22 @@
field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.SoftApCapability> CREATOR;
field public static final int SOFTAP_FEATURE_ACS_OFFLOAD = 1; // 0x1
field public static final int SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT = 2; // 0x2
+ field public static final int SOFTAP_FEATURE_WPA3_SAE = 4; // 0x4
}
public final class SoftApConfiguration implements android.os.Parcelable {
method public int describeContents();
+ method @NonNull public java.util.List<android.net.MacAddress> getAllowedClientList();
method public int getBand();
+ method @NonNull public java.util.List<android.net.MacAddress> getBlockedClientList();
method @Nullable public android.net.MacAddress getBssid();
method public int getChannel();
method public int getMaxNumberOfClients();
+ method @Nullable public String getPassphrase();
method public int getSecurityType();
+ method public int getShutdownTimeoutMillis();
method @Nullable public String getSsid();
- method @Nullable public String getWpa2Passphrase();
+ method public boolean isClientControlByUserEnabled();
method public boolean isHiddenSsid();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field public static final int BAND_2GHZ = 1; // 0x1
@@ -5659,19 +6023,24 @@
field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.SoftApConfiguration> CREATOR;
field public static final int SECURITY_TYPE_OPEN = 0; // 0x0
field public static final int SECURITY_TYPE_WPA2_PSK = 1; // 0x1
+ field public static final int SECURITY_TYPE_WPA3_SAE = 3; // 0x3
+ field public static final int SECURITY_TYPE_WPA3_SAE_TRANSITION = 2; // 0x2
}
public static final class SoftApConfiguration.Builder {
ctor public SoftApConfiguration.Builder();
ctor public SoftApConfiguration.Builder(@NonNull android.net.wifi.SoftApConfiguration);
method @NonNull public android.net.wifi.SoftApConfiguration build();
+ method @NonNull public android.net.wifi.SoftApConfiguration.Builder enableClientControlByUser(boolean);
method @NonNull public android.net.wifi.SoftApConfiguration.Builder setBand(int);
method @NonNull public android.net.wifi.SoftApConfiguration.Builder setBssid(@Nullable android.net.MacAddress);
method @NonNull public android.net.wifi.SoftApConfiguration.Builder setChannel(int, int);
+ method @NonNull public android.net.wifi.SoftApConfiguration.Builder setClientList(@NonNull java.util.List<android.net.MacAddress>, @NonNull java.util.List<android.net.MacAddress>);
method @NonNull public android.net.wifi.SoftApConfiguration.Builder setHiddenSsid(boolean);
method @NonNull public android.net.wifi.SoftApConfiguration.Builder setMaxNumberOfClients(int);
+ method @NonNull public android.net.wifi.SoftApConfiguration.Builder setPassphrase(@Nullable String, int);
+ method @NonNull public android.net.wifi.SoftApConfiguration.Builder setShutdownTimeoutMillis(int);
method @NonNull public android.net.wifi.SoftApConfiguration.Builder setSsid(@Nullable String);
- method @NonNull public android.net.wifi.SoftApConfiguration.Builder setWpa2Passphrase(@Nullable String);
}
public final class SoftApInfo implements android.os.Parcelable {
@@ -5709,6 +6078,7 @@
method @Deprecated public static boolean isMetered(@Nullable android.net.wifi.WifiConfiguration, @Nullable android.net.wifi.WifiInfo);
method @Deprecated public boolean isNoInternetAccessExpected();
method @Deprecated public void setIpConfiguration(@Nullable android.net.IpConfiguration);
+ method @Deprecated public void setNetworkSelectionStatus(@NonNull android.net.wifi.WifiConfiguration.NetworkSelectionStatus);
method @Deprecated public void setProxy(@NonNull android.net.IpConfiguration.ProxySettings, @NonNull android.net.ProxyInfo);
field @Deprecated public static final int AP_BAND_2GHZ = 0; // 0x0
field @Deprecated public static final int AP_BAND_5GHZ = 1; // 0x1
@@ -5753,6 +6123,7 @@
method @Deprecated public boolean getHasEverConnected();
method @Deprecated @Nullable public static String getNetworkDisableReasonString(int);
method @Deprecated public int getNetworkSelectionDisableReason();
+ method @Deprecated public int getNetworkSelectionStatus();
method @Deprecated @NonNull public String getNetworkStatusString();
method @Deprecated public boolean isNetworkEnabled();
method @Deprecated public boolean isNetworkPermanentlyDisabled();
@@ -5767,6 +6138,16 @@
field @Deprecated public static final int DISABLED_NO_INTERNET_TEMPORARY = 4; // 0x4
field @Deprecated public static final int NETWORK_SELECTION_DISABLED_MAX = 10; // 0xa
field @Deprecated public static final int NETWORK_SELECTION_ENABLE = 0; // 0x0
+ field @Deprecated public static final int NETWORK_SELECTION_ENABLED = 0; // 0x0
+ field @Deprecated public static final int NETWORK_SELECTION_PERMANENTLY_DISABLED = 2; // 0x2
+ field @Deprecated public static final int NETWORK_SELECTION_TEMPORARY_DISABLED = 1; // 0x1
+ }
+
+ @Deprecated public static final class WifiConfiguration.NetworkSelectionStatus.Builder {
+ ctor @Deprecated public WifiConfiguration.NetworkSelectionStatus.Builder();
+ method @Deprecated @NonNull public android.net.wifi.WifiConfiguration.NetworkSelectionStatus build();
+ method @Deprecated @NonNull public android.net.wifi.WifiConfiguration.NetworkSelectionStatus.Builder setNetworkSelectionDisableReason(int);
+ method @Deprecated @NonNull public android.net.wifi.WifiConfiguration.NetworkSelectionStatus.Builder setNetworkSelectionStatus(int);
}
@Deprecated public static class WifiConfiguration.RecentFailure {
@@ -5811,9 +6192,19 @@
field public static final int INVALID_RSSI = -127; // 0xffffff81
}
+ public static final class WifiInfo.Builder {
+ ctor public WifiInfo.Builder();
+ method @NonNull public android.net.wifi.WifiInfo build();
+ method @NonNull public android.net.wifi.WifiInfo.Builder setBssid(@NonNull String);
+ method @NonNull public android.net.wifi.WifiInfo.Builder setNetworkId(int);
+ method @NonNull public android.net.wifi.WifiInfo.Builder setRssi(int);
+ method @NonNull public android.net.wifi.WifiInfo.Builder setSsid(@NonNull byte[]);
+ }
+
public class WifiManager {
method @RequiresPermission(android.Manifest.permission.WIFI_UPDATE_USABILITY_STATS_SCORE) public void addOnWifiUsabilityStatsListener(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.OnWifiUsabilityStatsListener);
method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void allowAutojoin(int, boolean);
+ method @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS) public void allowAutojoinPasspoint(@NonNull String, boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void connect(@NonNull android.net.wifi.WifiConfiguration, @Nullable android.net.wifi.WifiManager.ActionListener);
method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void connect(int, @Nullable android.net.wifi.WifiManager.ActionListener);
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD, android.Manifest.permission.NETWORK_STACK}) public void disable(int, @Nullable android.net.wifi.WifiManager.ActionListener);
@@ -5833,6 +6224,7 @@
method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public void getWifiActivityEnergyInfoAsync(@NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.WifiManager.OnWifiActivityEnergyInfoListener);
method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public android.net.wifi.WifiConfiguration getWifiApConfiguration();
method @RequiresPermission(android.Manifest.permission.ACCESS_WIFI_STATE) public int getWifiApState();
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_SETUP_WIZARD}) public java.util.List<android.net.wifi.WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(@NonNull java.util.List<android.net.wifi.ScanResult>);
method public boolean isApMacRandomizationSupported();
method public boolean isConnectedMacRandomizationSupported();
method @Deprecated public boolean isDeviceToDeviceRttSupported();
@@ -5890,6 +6282,7 @@
field public static final String EXTRA_OSU_NETWORK = "android.net.wifi.extra.OSU_NETWORK";
field public static final String EXTRA_PREVIOUS_WIFI_AP_STATE = "previous_wifi_state";
field public static final String EXTRA_URL = "android.net.wifi.extra.URL";
+ field public static final String EXTRA_WIFI_AP_FAILURE_REASON = "android.net.wifi.extra.WIFI_AP_FAILURE_REASON";
field public static final String EXTRA_WIFI_AP_INTERFACE_NAME = "android.net.wifi.extra.WIFI_AP_INTERFACE_NAME";
field public static final String EXTRA_WIFI_AP_MODE = "android.net.wifi.extra.WIFI_AP_MODE";
field public static final String EXTRA_WIFI_AP_STATE = "wifi_state";
@@ -5902,6 +6295,8 @@
field public static final int IFACE_IP_MODE_UNSPECIFIED = -1; // 0xffffffff
field public static final int PASSPOINT_HOME_NETWORK = 0; // 0x0
field public static final int PASSPOINT_ROAMING_NETWORK = 1; // 0x1
+ field public static final int SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER = 0; // 0x0
+ field public static final int SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS = 1; // 0x1
field public static final int SAP_START_FAILURE_GENERAL = 0; // 0x0
field public static final int SAP_START_FAILURE_NO_CHANNEL = 1; // 0x1
field public static final int SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION = 2; // 0x2
@@ -5943,6 +6338,7 @@
}
public static interface WifiManager.SoftApCallback {
+ method public default void onBlockedClientConnecting(@NonNull android.net.wifi.WifiClient, int);
method public default void onCapabilityChanged(@NonNull android.net.wifi.SoftApCapability);
method public default void onConnectedClientsChanged(@NonNull java.util.List<android.net.wifi.WifiClient>);
method public default void onInfoChanged(@NonNull android.net.wifi.SoftApInfo);
@@ -5968,10 +6364,33 @@
field public int numUsage;
}
+ public final class WifiNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
+ method public boolean satisfiedBy(android.net.NetworkSpecifier);
+ }
+
public static final class WifiNetworkSuggestion.Builder {
method @NonNull @RequiresPermission(android.Manifest.permission.NETWORK_CARRIER_PROVISIONING) public android.net.wifi.WifiNetworkSuggestion.Builder setCarrierId(int);
}
+ public final class WifiOemConfigStoreMigrationHook {
+ method @Nullable public static android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData load();
+ }
+
+ public static final class WifiOemConfigStoreMigrationHook.MigrationData implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public java.util.List<android.net.wifi.WifiConfiguration> getUserSavedNetworkConfigurations();
+ method @Nullable public android.net.wifi.SoftApConfiguration getUserSoftApConfiguration();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData> CREATOR;
+ }
+
+ public static final class WifiOemConfigStoreMigrationHook.MigrationData.Builder {
+ ctor public WifiOemConfigStoreMigrationHook.MigrationData.Builder();
+ method @NonNull public android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData build();
+ method @NonNull public android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData.Builder setUserSavedNetworkConfigurations(@NonNull java.util.List<android.net.wifi.WifiConfiguration>);
+ method @NonNull public android.net.wifi.WifiOemConfigStoreMigrationHook.MigrationData.Builder setUserSoftApConfiguration(@NonNull android.net.wifi.SoftApConfiguration);
+ }
+
public class WifiScanner {
method @Deprecated public void configureWifiChange(int, int, int, int, int, android.net.wifi.WifiScanner.BssidInfo[]);
method @Deprecated public void configureWifiChange(android.net.wifi.WifiScanner.WifiChangeSettings);
@@ -6154,6 +6573,10 @@
method @Deprecated public android.net.NetworkSpecifier createNetworkSpecifierPmk(@NonNull android.net.wifi.aware.PeerHandle, @NonNull byte[]);
}
+ public final class WifiAwareNetworkSpecifier extends android.net.NetworkSpecifier implements android.os.Parcelable {
+ method public boolean satisfiedBy(android.net.NetworkSpecifier);
+ }
+
public class WifiAwareSession implements java.lang.AutoCloseable {
method public android.net.NetworkSpecifier createNetworkSpecifierPmk(int, @NonNull byte[], @NonNull byte[]);
}
@@ -6170,6 +6593,10 @@
field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.hotspot2.OsuProvider> CREATOR;
}
+ public final class PasspointConfiguration implements android.os.Parcelable {
+ method public boolean isAutoJoinEnabled();
+ }
+
public abstract class ProvisioningCallback {
ctor public ProvisioningCallback();
method public abstract void onProvisioningComplete();
@@ -6368,6 +6795,7 @@
method @NonNull public java.util.List<android.net.wifi.wificond.NativeScanResult> getScanResults(@NonNull String, int);
method @Nullable public android.net.wifi.wificond.WifiCondManager.TxPacketCounters getTxPacketCounters(@NonNull String);
method public boolean initialize(@NonNull Runnable);
+ method @Nullable public static android.net.wifi.wificond.WifiCondManager.OemSecurityType parseOemSecurityTypeElement(int, int, @NonNull byte[]);
method public boolean registerApCallback(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiCondManager.SoftApCallback);
method public void sendMgmtFrame(@NonNull String, @NonNull byte[], int, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiCondManager.SendMgmtFrameCallback);
method public boolean setupInterfaceForClientMode(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiCondManager.ScanEventCallback, @NonNull android.net.wifi.wificond.WifiCondManager.ScanEventCallback);
@@ -6388,6 +6816,14 @@
field public static final int SEND_MGMT_FRAME_ERROR_UNKNOWN = 1; // 0x1
}
+ public static class WifiCondManager.OemSecurityType {
+ ctor public WifiCondManager.OemSecurityType(int, @NonNull java.util.List<java.lang.Integer>, @NonNull java.util.List<java.lang.Integer>, int);
+ field public final int groupCipher;
+ field @NonNull public final java.util.List<java.lang.Integer> keyManagement;
+ field @NonNull public final java.util.List<java.lang.Integer> pairwiseCipher;
+ field public final int protocol;
+ }
+
public static interface WifiCondManager.PnoScanRequestCallback {
method public void onPnoRequestFailed();
method public void onPnoRequestSucceeded();
@@ -6827,6 +7263,10 @@
method public boolean hasSingleFileDescriptor();
}
+ public class ParcelFileDescriptor implements java.io.Closeable android.os.Parcelable {
+ method @NonNull public static android.os.ParcelFileDescriptor wrap(@NonNull android.os.ParcelFileDescriptor, @NonNull android.os.Handler, @NonNull android.os.ParcelFileDescriptor.OnCloseListener) throws java.io.IOException;
+ }
+
public final class PowerManager {
method @RequiresPermission(allOf={android.Manifest.permission.READ_DREAM_STATE, android.Manifest.permission.WRITE_DREAM_STATE}) public void dream(long);
method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public boolean forceSuspend();
@@ -6859,9 +7299,12 @@
public class RecoverySystem {
method @RequiresPermission(android.Manifest.permission.RECOVERY) public static void cancelScheduledUpdate(android.content.Context) throws java.io.IOException;
+ method @RequiresPermission(android.Manifest.permission.RECOVERY) public static boolean clearPrepareForUnattendedUpdate(@NonNull android.content.Context) throws java.io.IOException;
method @RequiresPermission(android.Manifest.permission.RECOVERY) public static void installPackage(android.content.Context, java.io.File, boolean) throws java.io.IOException;
+ method @RequiresPermission(android.Manifest.permission.RECOVERY) public static void prepareForUnattendedUpdate(@NonNull android.content.Context, @NonNull String, @Nullable android.content.IntentSender) throws java.io.IOException;
method @RequiresPermission(android.Manifest.permission.RECOVERY) public static void processPackage(android.content.Context, java.io.File, android.os.RecoverySystem.ProgressListener, android.os.Handler) throws java.io.IOException;
method @RequiresPermission(android.Manifest.permission.RECOVERY) public static void processPackage(android.content.Context, java.io.File, android.os.RecoverySystem.ProgressListener) throws java.io.IOException;
+ method @RequiresPermission(android.Manifest.permission.RECOVERY) public static boolean rebootAndApply(@NonNull android.content.Context, @NonNull String, @NonNull String) throws java.io.IOException;
method @RequiresPermission(allOf={android.Manifest.permission.RECOVERY, android.Manifest.permission.REBOOT}) public static void rebootWipeAb(android.content.Context, java.io.File, String) throws java.io.IOException;
method @RequiresPermission(android.Manifest.permission.RECOVERY) public static void scheduleUpdateOnBoot(android.content.Context, java.io.File) throws java.io.IOException;
method public static boolean verifyPackageCompatibility(java.io.File) throws java.io.IOException;
@@ -6932,7 +7375,22 @@
}
public class TelephonyServiceManager {
+ method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getCarrierConfigServiceRegisterer();
+ method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getEuiccCardControllerServiceRegisterer();
+ method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getEuiccControllerService();
+ method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getIccPhoneBookServiceRegisterer();
+ method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getNetworkPolicyServiceRegisterer();
+ method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getOpportunisticNetworkServiceRegisterer();
+ method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getPackageManagerServiceRegisterer();
+ method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getPermissionManagerServiceRegisterer();
+ method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getPhoneSubServiceRegisterer();
+ method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getSmsServiceRegisterer();
+ method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getSubscriptionServiceRegisterer();
+ method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getTelephonyImsServiceRegisterer();
+ method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getTelephonyRcsMessageServiceRegisterer();
+ method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getTelephonyRegistryServiceRegisterer();
method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getTelephonyServiceRegisterer();
+ method @NonNull public android.os.TelephonyServiceManager.ServiceRegisterer getWindowServiceRegisterer();
}
public static class TelephonyServiceManager.ServiceNotFoundException extends java.lang.Exception {
@@ -6948,11 +7406,13 @@
public class UpdateEngine {
ctor public UpdateEngine();
+ method @NonNull public android.os.UpdateEngine.AllocateSpaceResult allocateSpace(@NonNull String, @NonNull String[]);
method public void applyPayload(String, long, long, String[]);
- method public void applyPayload(@NonNull android.os.ParcelFileDescriptor, long, long, @NonNull String[]);
+ method public void applyPayload(@NonNull android.content.res.AssetFileDescriptor, @NonNull String[]);
method public boolean bind(android.os.UpdateEngineCallback, android.os.Handler);
method public boolean bind(android.os.UpdateEngineCallback);
method public void cancel();
+ method public int cleanupAppliedPayload();
method public void resetStatus();
method public void resume();
method public void suspend();
@@ -6960,14 +7420,21 @@
method public boolean verifyPayloadMetadata(String);
}
+ public static final class UpdateEngine.AllocateSpaceResult {
+ method public int errorCode();
+ method public long freeSpaceRequired();
+ }
+
public static final class UpdateEngine.ErrorCodeConstants {
ctor public UpdateEngine.ErrorCodeConstants();
+ field public static final int DEVICE_CORRUPTED = 61; // 0x3d
field public static final int DOWNLOAD_PAYLOAD_VERIFICATION_ERROR = 12; // 0xc
field public static final int DOWNLOAD_TRANSFER_ERROR = 9; // 0x9
field public static final int ERROR = 1; // 0x1
field public static final int FILESYSTEM_COPIER_ERROR = 4; // 0x4
field public static final int INSTALL_DEVICE_OPEN_ERROR = 7; // 0x7
field public static final int KERNEL_DEVICE_OPEN_ERROR = 8; // 0x8
+ field public static final int NOT_ENOUGH_SPACE = 60; // 0x3c
field public static final int PAYLOAD_HASH_MISMATCH_ERROR = 10; // 0xa
field public static final int PAYLOAD_MISMATCHED_TYPE_ERROR = 6; // 0x6
field public static final int PAYLOAD_SIZE_MISMATCH_ERROR = 11; // 0xb
@@ -7026,7 +7493,6 @@
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional=true) public boolean hasUserRestrictionForUser(@NonNull String, @NonNull android.os.UserHandle);
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isAdminUser();
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isGuestUser();
- method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isManagedProfile();
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isManagedProfile(int);
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isPrimaryUser();
method @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public boolean isRestrictedProfile();
@@ -7203,6 +7669,10 @@
field @RequiresPermission(android.Manifest.permission.ALLOCATE_AGGRESSIVE) public static final int FLAG_ALLOCATE_AGGRESSIVE = 1; // 0x1
}
+ public final class StorageVolume implements android.os.Parcelable {
+ method @NonNull public String getId();
+ }
+
}
package android.permission {
@@ -7246,7 +7716,10 @@
public final class PermissionManager {
method @IntRange(from=0) @RequiresPermission(android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY) public int getRuntimePermissionsVersion();
method @NonNull public java.util.List<android.permission.PermissionManager.SplitPermissionInfo> getSplitPermissions();
+ method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS) public void grantDefaultPermissionsToEnabledImsServices(@NonNull String[], @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+ method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS) public void grantDefaultPermissionsToEnabledTelephonyDataServices(@NonNull String[], @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS) public void grantDefaultPermissionsToLuiApp(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
+ method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS) public void revokeDefaultPermissionsFromDisabledTelephonyDataServices(@NonNull String[], @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @RequiresPermission(android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS) public void revokeDefaultPermissionsFromLuiApps(@NonNull String[], @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>);
method @RequiresPermission(android.Manifest.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY) public void setRuntimePermissionsVersion(@IntRange(from=0) int);
method @RequiresPermission(android.Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS) public void startOneTimePermissionSession(@NonNull String, long, int, int);
@@ -7547,6 +8020,8 @@
field public static final int COLUMN_INDEX_XML_RES_INTENT_TARGET_PACKAGE = 5; // 0x5
field public static final int COLUMN_INDEX_XML_RES_RANK = 0; // 0x0
field public static final int COLUMN_INDEX_XML_RES_RESID = 1; // 0x1
+ field public static final String DYNAMIC_INDEXABLES_RAW = "dynamic_indexables_raw";
+ field public static final String DYNAMIC_INDEXABLES_RAW_PATH = "settings/dynamic_indexables_raw";
field public static final String INDEXABLES_RAW = "indexables_raw";
field public static final String[] INDEXABLES_RAW_COLUMNS;
field public static final String INDEXABLES_RAW_PATH = "settings/indexables_raw";
@@ -7604,6 +8079,7 @@
method public String getType(android.net.Uri);
method public final android.net.Uri insert(android.net.Uri, android.content.ContentValues);
method public android.database.Cursor query(android.net.Uri, String[], String, String[], String);
+ method @Nullable public android.database.Cursor queryDynamicRawData(@Nullable String[]);
method public abstract android.database.Cursor queryNonIndexableKeys(String[]);
method public abstract android.database.Cursor queryRawData(String[]);
method @Nullable public android.database.Cursor querySliceUriPairs();
@@ -7612,6 +8088,7 @@
}
public final class Settings {
+ method public static boolean checkAndNoteWriteSettingsOperation(@NonNull android.content.Context, int, @NonNull String, boolean);
field public static final String ACTION_ACCESSIBILITY_DETAILS_SETTINGS = "android.settings.ACCESSIBILITY_DETAILS_SETTINGS";
field public static final String ACTION_BUGREPORT_HANDLER_SETTINGS = "android.settings.BUGREPORT_HANDLER_SETTINGS";
field public static final String ACTION_ENTERPRISE_PRIVACY_SETTINGS = "android.settings.ENTERPRISE_PRIVACY_SETTINGS";
@@ -7622,11 +8099,13 @@
field public static final String ACTION_NOTIFICATION_POLICY_ACCESS_DETAIL_SETTINGS = "android.settings.NOTIFICATION_POLICY_ACCESS_DETAIL_SETTINGS";
field public static final String ACTION_REQUEST_ENABLE_CONTENT_CAPTURE = "android.settings.REQUEST_ENABLE_CONTENT_CAPTURE";
field public static final String ACTION_SHOW_ADMIN_SUPPORT_DETAILS = "android.settings.SHOW_ADMIN_SUPPORT_DETAILS";
+ field public static final String ACTION_TETHER_PROVISIONING_UI = "android.settings.TETHER_PROVISIONING_UI";
}
public static final class Settings.Global extends android.provider.Settings.NameValueTable {
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static boolean putString(@NonNull android.content.ContentResolver, @NonNull String, @Nullable String, @Nullable String, boolean);
method @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static void resetToDefaults(@NonNull android.content.ContentResolver, @Nullable String);
+ field public static final String AIRPLANE_MODE_TOGGLEABLE_RADIOS = "airplane_mode_toggleable_radios";
field public static final String APP_STANDBY_ENABLED = "app_standby_enabled";
field public static final String AUTOFILL_COMPAT_MODE_ALLOWED_PACKAGES = "autofill_compat_mode_allowed_packages";
field public static final String CARRIER_APP_NAMES = "carrier_app_names";
@@ -7639,12 +8118,21 @@
field public static final String EUICC_UNSUPPORTED_COUNTRIES = "euicc_unsupported_countries";
field public static final String INSTALL_CARRIER_APP_NOTIFICATION_PERSISTENT = "install_carrier_app_notification_persistent";
field public static final String INSTALL_CARRIER_APP_NOTIFICATION_SLEEP_MILLIS = "install_carrier_app_notification_sleep_millis";
+ field public static final String NETWORK_RECOMMENDATIONS_ENABLED = "network_recommendations_enabled";
field public static final String OTA_DISABLE_AUTOMATIC_UPDATE = "ota_disable_automatic_update";
field public static final String REQUIRE_PASSWORD_TO_DECRYPT = "require_password_to_decrypt";
+ field public static final String SOFT_AP_TIMEOUT_ENABLED = "soft_ap_timeout_enabled";
+ field public static final String TETHER_OFFLOAD_DISABLED = "tether_offload_disabled";
field public static final String TETHER_SUPPORTED = "tether_supported";
field public static final String THEATER_MODE_ON = "theater_mode_on";
field public static final String WEBVIEW_MULTIPROCESS = "webview_multiprocess";
field public static final String WIFI_BADGING_THRESHOLDS = "wifi_badging_thresholds";
+ field public static final String WIFI_P2P_DEVICE_NAME = "wifi_p2p_device_name";
+ field public static final String WIFI_P2P_PENDING_FACTORY_RESET = "wifi_p2p_pending_factory_reset";
+ field public static final String WIFI_SCAN_ALWAYS_AVAILABLE = "wifi_scan_always_enabled";
+ field public static final String WIFI_SCAN_THROTTLE_ENABLED = "wifi_scan_throttle_enabled";
+ field public static final String WIFI_SCORE_PARAMS = "wifi_score_params";
+ field public static final String WIFI_VERBOSE_LOGGING_ENABLED = "wifi_verbose_logging_enabled";
field public static final String WIFI_WAKEUP_ENABLED = "wifi_wakeup_enabled";
}
@@ -7685,6 +8173,10 @@
field public static final int VOLUME_HUSH_VIBRATE = 1; // 0x1
}
+ public static final class Settings.System extends android.provider.Settings.NameValueTable {
+ method @RequiresPermission(android.Manifest.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE) public static boolean putString(@NonNull android.content.ContentResolver, @NonNull String, @Nullable String, boolean);
+ }
+
public static interface Telephony.CarrierColumns extends android.provider.BaseColumns {
field @NonNull public static final android.net.Uri CONTENT_URI;
field public static final String EXPIRATION_TIME = "expiration_time";
@@ -7731,6 +8223,9 @@
}
public static final class Telephony.CellBroadcasts implements android.provider.BaseColumns {
+ field @NonNull public static final String AUTHORITY_LEGACY = "cellbroadcast-legacy";
+ field @NonNull public static final android.net.Uri AUTHORITY_LEGACY_URI;
+ field @NonNull public static final String CALL_METHOD_GET_PREFERENCE = "get_preference";
field public static final String CID = "cid";
field public static final String CMAS_CATEGORY = "cmas_category";
field public static final String CMAS_CERTAINTY = "cmas_certainty";
@@ -7758,11 +8253,89 @@
field public static final String SERIAL_NUMBER = "serial_number";
field public static final String SERVICE_CATEGORY = "service_category";
field public static final String SLOT_INDEX = "slot_index";
- field public static final String SUB_ID = "sub_id";
+ field public static final String SUBSCRIPTION_ID = "sub_id";
+ }
+
+ public static final class Telephony.CellBroadcasts.Preference {
+ field @NonNull public static final String ENABLE_ALERT_VIBRATION_PREF = "enable_alert_vibrate";
+ field @NonNull public static final String ENABLE_AREA_UPDATE_INFO_PREF = "enable_area_update_info_alerts";
+ field @NonNull public static final String ENABLE_CMAS_AMBER_PREF = "enable_cmas_amber_alerts";
+ field @NonNull public static final String ENABLE_CMAS_EXTREME_THREAT_PREF = "enable_cmas_extreme_threat_alerts";
+ field @NonNull public static final String ENABLE_CMAS_IN_SECOND_LANGUAGE_PREF = "receive_cmas_in_second_language";
+ field @NonNull public static final String ENABLE_CMAS_PRESIDENTIAL_PREF = "enable_cmas_presidential_alerts";
+ field @NonNull public static final String ENABLE_CMAS_SEVERE_THREAT_PREF = "enable_cmas_severe_threat_alerts";
+ field @NonNull public static final String ENABLE_EMERGENCY_PERF = "enable_emergency_alerts";
+ field @NonNull public static final String ENABLE_PUBLIC_SAFETY_PREF = "enable_public_safety_messages";
+ field @NonNull public static final String ENABLE_STATE_LOCAL_TEST_PREF = "enable_state_local_test_alerts";
+ field @NonNull public static final String ENABLE_TEST_ALERT_PREF = "enable_test_alerts";
}
public static final class Telephony.SimInfo {
+ field public static final String ACCESS_RULES = "access_rules";
+ field public static final String ACCESS_RULES_FROM_CARRIER_CONFIGS = "access_rules_from_carrier_configs";
+ field public static final String CARD_ID = "card_id";
+ field public static final String CARRIER_ID = "carrier_id";
+ field public static final String CARRIER_NAME = "carrier_name";
+ field public static final String CB_ALERT_REMINDER_INTERVAL = "alert_reminder_interval";
+ field public static final String CB_ALERT_SOUND_DURATION = "alert_sound_duration";
+ field public static final String CB_ALERT_SPEECH = "enable_alert_speech";
+ field public static final String CB_ALERT_VIBRATE = "enable_alert_vibrate";
+ field public static final String CB_AMBER_ALERT = "enable_cmas_amber_alerts";
+ field public static final String CB_CHANNEL_50_ALERT = "enable_channel_50_alerts";
+ field public static final String CB_CMAS_TEST_ALERT = "enable_cmas_test_alerts";
+ field public static final String CB_EMERGENCY_ALERT = "enable_emergency_alerts";
+ field public static final String CB_ETWS_TEST_ALERT = "enable_etws_test_alerts";
+ field public static final String CB_EXTREME_THREAT_ALERT = "enable_cmas_extreme_threat_alerts";
+ field public static final String CB_OPT_OUT_DIALOG = "show_cmas_opt_out_dialog";
+ field public static final String CB_SEVERE_THREAT_ALERT = "enable_cmas_severe_threat_alerts";
+ field public static final String COLOR = "color";
field @NonNull public static final android.net.Uri CONTENT_URI;
+ field public static final String DATA_ENABLED_OVERRIDE_RULES = "data_enabled_override_rules";
+ field public static final String DATA_ROAMING = "data_roaming";
+ field public static final int DATA_ROAMING_DEFAULT = 0; // 0x0
+ field public static final int DATA_ROAMING_DISABLE = 0; // 0x0
+ field public static final int DATA_ROAMING_ENABLE = 1; // 0x1
+ field public static final String DISPLAY_NAME = "display_name";
+ field public static final String EHPLMNS = "ehplmns";
+ field public static final String ENHANCED_4G_MODE_ENABLED = "volte_vt_enabled";
+ field public static final String GROUP_OWNER = "group_owner";
+ field public static final String GROUP_UUID = "group_uuid";
+ field public static final String HPLMNS = "hplmns";
+ field public static final String ICC_ID = "icc_id";
+ field public static final String IMSI = "imsi";
+ field public static final String ISO_COUNTRY_CODE = "iso_country_code";
+ field public static final String IS_EMBEDDED = "is_embedded";
+ field public static final String IS_OPPORTUNISTIC = "is_opportunistic";
+ field public static final String IS_REMOVABLE = "is_removable";
+ field public static final String MCC = "mcc";
+ field public static final String MCC_STRING = "mcc_string";
+ field public static final String MNC = "mnc";
+ field public static final String MNC_STRING = "mnc_string";
+ field public static final String NAME_SOURCE = "name_source";
+ field public static final int NAME_SOURCE_CARRIER = 3; // 0x3
+ field public static final int NAME_SOURCE_DEFAULT = 0; // 0x0
+ field public static final int NAME_SOURCE_SIM_PNN = 4; // 0x4
+ field public static final int NAME_SOURCE_SIM_SPN = 1; // 0x1
+ field public static final int NAME_SOURCE_USER_INPUT = 2; // 0x2
+ field public static final String NUMBER = "number";
+ field public static final String PROFILE_CLASS = "profile_class";
+ field public static final int PROFILE_CLASS_DEFAULT = -1; // 0xffffffff
+ field public static final int PROFILE_CLASS_OPERATIONAL = 2; // 0x2
+ field public static final int PROFILE_CLASS_PROVISIONING = 1; // 0x1
+ field public static final int PROFILE_CLASS_TESTING = 0; // 0x0
+ field public static final int PROFILE_CLASS_UNSET = -1; // 0xffffffff
+ field public static final int SIM_NOT_INSERTED = -1; // 0xffffffff
+ field public static final String SIM_SLOT_INDEX = "sim_id";
+ field public static final String SUBSCRIPTION_TYPE = "subscription_type";
+ field public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0; // 0x0
+ field public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1; // 0x1
+ field public static final String UICC_APPLICATIONS_ENABLED = "uicc_applications_enabled";
+ field public static final String UNIQUE_KEY_SUBSCRIPTION_ID = "_id";
+ field public static final String VT_IMS_ENABLED = "vt_ims_enabled";
+ field public static final String WFC_IMS_ENABLED = "wfc_ims_enabled";
+ field public static final String WFC_IMS_MODE = "wfc_ims_mode";
+ field public static final String WFC_IMS_ROAMING_ENABLED = "wfc_ims_roaming_enabled";
+ field public static final String WFC_IMS_ROAMING_MODE = "wfc_ims_roaming_mode";
}
public static final class Telephony.Sms.Intents {
@@ -7989,6 +8562,11 @@
field public static final String SERVICE_META_DATA_KEY_DEFAULT_ALGORITHM = "android.autofill.field_classification.default_algorithm";
}
+ public static final class Dataset.Builder {
+ ctor public Dataset.Builder(@NonNull android.service.autofill.InlinePresentation);
+ method @NonNull public android.service.autofill.Dataset.Builder setInlinePresentation(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.service.autofill.InlinePresentation);
+ }
+
}
package android.service.autofill.augmented {
@@ -8321,7 +8899,10 @@
method @Nullable public android.service.notification.Adjustment onNotificationEnqueued(@NonNull android.service.notification.StatusBarNotification, @NonNull android.app.NotificationChannel);
method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean);
method public abstract void onNotificationSnoozedUntilContext(@NonNull android.service.notification.StatusBarNotification, @NonNull String);
+ method public void onNotificationVisibilityChanged(@NonNull String, boolean);
method public void onNotificationsSeen(@NonNull java.util.List<java.lang.String>);
+ method public void onPanelHidden();
+ method public void onPanelRevealed(int);
method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int);
method public final void unsnoozeNotification(@NonNull String);
field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
@@ -8907,6 +9488,11 @@
field public static final int TRANSPORT_TYPE_WWAN = 1; // 0x1
}
+ public final class BarringInfo implements android.os.Parcelable {
+ ctor public BarringInfo();
+ method @NonNull public android.telephony.BarringInfo createLocationInfoSanitizedCopy();
+ }
+
public final class CallAttributes implements android.os.Parcelable {
ctor public CallAttributes(@NonNull android.telephony.PreciseCallState, int, @NonNull android.telephony.CallQuality);
method public int describeContents();
@@ -8919,6 +9505,7 @@
public final class CallQuality implements android.os.Parcelable {
ctor public CallQuality(int, int, int, int, int, int, int, int, int, int, int);
+ ctor public CallQuality(int, int, int, int, int, int, int, int, int, int, int, boolean, boolean, boolean);
method public int describeContents();
method public int getAverageRelativeJitter();
method public int getAverageRoundTripTime();
@@ -8931,6 +9518,9 @@
method public int getNumRtpPacketsTransmitted();
method public int getNumRtpPacketsTransmittedLost();
method public int getUplinkCallQualityLevel();
+ method public boolean isIncomingSilenceDetected();
+ method public boolean isOutgoingSilenceDetected();
+ method public boolean isRtpInactivityDetected();
method public void writeToParcel(android.os.Parcel, int);
field public static final int CALL_QUALITY_BAD = 4; // 0x4
field public static final int CALL_QUALITY_EXCELLENT = 0; // 0x0
@@ -9024,30 +9614,37 @@
public abstract class CellIdentity implements android.os.Parcelable {
method @NonNull public abstract android.telephony.CellLocation asCellLocation();
+ method @NonNull public abstract android.telephony.CellIdentity sanitizeLocationInfo();
}
public final class CellIdentityCdma extends android.telephony.CellIdentity {
method @NonNull public android.telephony.cdma.CdmaCellLocation asCellLocation();
+ method @NonNull public android.telephony.CellIdentityCdma sanitizeLocationInfo();
}
public final class CellIdentityGsm extends android.telephony.CellIdentity {
method @NonNull public android.telephony.gsm.GsmCellLocation asCellLocation();
+ method @NonNull public android.telephony.CellIdentityGsm sanitizeLocationInfo();
}
public final class CellIdentityLte extends android.telephony.CellIdentity {
method @NonNull public android.telephony.gsm.GsmCellLocation asCellLocation();
+ method @NonNull public android.telephony.CellIdentityLte sanitizeLocationInfo();
}
public final class CellIdentityNr extends android.telephony.CellIdentity {
method @NonNull public android.telephony.CellLocation asCellLocation();
+ method @NonNull public android.telephony.CellIdentityNr sanitizeLocationInfo();
}
public final class CellIdentityTdscdma extends android.telephony.CellIdentity {
method @NonNull public android.telephony.gsm.GsmCellLocation asCellLocation();
+ method @NonNull public android.telephony.CellIdentityTdscdma sanitizeLocationInfo();
}
public final class CellIdentityWcdma extends android.telephony.CellIdentity {
method @NonNull public android.telephony.gsm.GsmCellLocation asCellLocation();
+ method @NonNull public android.telephony.CellIdentityWcdma sanitizeLocationInfo();
}
public final class DataFailCause {
@@ -9447,6 +10044,7 @@
field public static final int IMS_ACCESS_BLOCKED = 60; // 0x3c
field public static final int IMS_MERGED_SUCCESSFULLY = 45; // 0x2d
field public static final int IMS_SIP_ALTERNATE_EMERGENCY_CALL = 71; // 0x47
+ field public static final int INCOMING_AUTO_REJECTED = 81; // 0x51
field public static final int INCOMING_MISSED = 1; // 0x1
field public static final int INCOMING_REJECTED = 16; // 0x10
field public static final int INVALID_CREDENTIALS = 10; // 0xa
@@ -9548,7 +10146,9 @@
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.NetworkRegistrationInfo> CREATOR;
field public static final int DOMAIN_CS = 1; // 0x1
+ field public static final int DOMAIN_CS_PS = 3; // 0x3
field public static final int DOMAIN_PS = 2; // 0x2
+ field public static final int DOMAIN_UNKNOWN = 0; // 0x0
field public static final int REGISTRATION_STATE_DENIED = 3; // 0x3
field public static final int REGISTRATION_STATE_HOME = 1; // 0x1
field public static final int REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING = 0; // 0x0
@@ -9774,7 +10374,9 @@
public class ServiceState implements android.os.Parcelable {
method @NonNull public android.telephony.ServiceState createLocationInfoSanitizedCopy(boolean);
method public void fillInNotifierBundle(@NonNull android.os.Bundle);
+ method public int getDataNetworkType();
method public int getDataRegistrationState();
+ method public boolean getDataRoamingFromRegistration();
method @Nullable public android.telephony.NetworkRegistrationInfo getNetworkRegistrationInfo(int, int);
method @NonNull public java.util.List<android.telephony.NetworkRegistrationInfo> getNetworkRegistrationInfoList();
method @NonNull public java.util.List<android.telephony.NetworkRegistrationInfo> getNetworkRegistrationInfoListForDomain(int);
@@ -9789,6 +10391,10 @@
field public static final int ROAMING_TYPE_UNKNOWN = 1; // 0x1
}
+ public class SignalStrength implements android.os.Parcelable {
+ ctor public SignalStrength(@NonNull android.telephony.SignalStrength);
+ }
+
public final class SmsCbCmasInfo implements android.os.Parcelable {
ctor public SmsCbCmasInfo(int, int, int, int, int, int);
method public int describeContents();
@@ -9916,9 +10522,20 @@
method public boolean disableCellBroadcastRange(int, int, int);
method public boolean enableCellBroadcastRange(int, int, int);
method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_MESSAGES_ON_ICC) public java.util.List<android.telephony.SmsMessage> getMessagesFromIcc();
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getPremiumSmsConsent(@NonNull String);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getSmsCapacityOnIcc();
method public void sendMultipartTextMessage(@NonNull String, @NonNull String, @NonNull java.util.List<java.lang.String>, @Nullable java.util.List<android.app.PendingIntent>, @Nullable java.util.List<android.app.PendingIntent>, @NonNull String);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void sendMultipartTextMessageWithoutPersisting(String, String, java.util.List<java.lang.String>, java.util.List<android.app.PendingIntent>, java.util.List<android.app.PendingIntent>);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setPremiumSmsConsent(@NonNull String, int);
+ field public static final int PREMIUM_SMS_CONSENT_ALWAYS_ALLOW = 3; // 0x3
+ field public static final int PREMIUM_SMS_CONSENT_ASK_USER = 1; // 0x1
+ field public static final int PREMIUM_SMS_CONSENT_NEVER_ALLOW = 2; // 0x2
+ field public static final int PREMIUM_SMS_CONSENT_UNKNOWN = 0; // 0x0
+ }
+
+ public class SmsMessage {
+ method @Nullable public static android.telephony.SmsMessage createFromNativeSmsSubmitPdu(@NonNull byte[], boolean);
+ method @NonNull @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public static byte[] getSubmitPduEncodedMessage(boolean, @NonNull String, @NonNull String, int, int, int, int, int, int);
}
public class SubscriptionInfo implements android.os.Parcelable {
@@ -9931,6 +10548,9 @@
public class SubscriptionManager {
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean canDisablePhysicalSubscription();
method public boolean canManageSubscription(@Nullable android.telephony.SubscriptionInfo, @Nullable String);
+ method @NonNull public int[] getActiveAndHiddenSubscriptionIdList();
+ method @NonNull public int[] getActiveSubscriptionIdList();
+ method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public android.telephony.SubscriptionInfo getActiveSubscriptionInfoForIcc(@NonNull String);
method public java.util.List<android.telephony.SubscriptionInfo> getAvailableSubscriptionInfoList();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getEnabledSubscriptionId(int);
method @NonNull public static android.content.res.Resources getResourcesForSubId(@NonNull android.content.Context, int);
@@ -10013,6 +10633,7 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String getCdmaMin();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String getCdmaMin(int);
method public String getCdmaPrlVersion();
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getCdmaRoamingMode();
method public int getCurrentPhoneType();
method public int getCurrentPhoneType(int);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getDataActivationState();
@@ -10025,6 +10646,7 @@
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean getEmergencyCallbackMode();
method public int getEmergencyNumberDbVersion();
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimDomain();
+ method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String[] getIsimImpu();
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public String getIsimIst();
method @NonNull @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public java.util.Map<java.lang.Integer,java.lang.Integer> getLogicalToPhysicalSlotMapping();
method public int getMaxNumberOfSimultaneouslyActiveSims();
@@ -10045,26 +10667,28 @@
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public int getVoiceActivationState();
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handlePinMmi(String);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean handlePinMmiForSubscriber(int, String);
- method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean iccCloseLogicalChannelBySlot(int, int);
+ method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean iccCloseLogicalChannelBySlot(int, int);
method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.IccOpenLogicalChannelResponse iccOpenLogicalChannelBySlot(int, @Nullable String, int);
- method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduBasicChannelBySlot(int, int, int, int, int, int, @Nullable String);
- method @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduLogicalChannelBySlot(int, int, int, int, int, int, int, @Nullable String);
+ method @Deprecated @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduBasicChannelBySlot(int, int, int, int, int, int, @Nullable String);
+ method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public String iccTransmitApduLogicalChannelBySlot(int, int, int, int, int, int, int, @Nullable String);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isAnyRadioPoweredOn();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApnMetered(int);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isApplicationOnUicc(int);
method public boolean isCurrentSimOperator(@NonNull String, int, @Nullable String);
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isDataConnectionEnabled();
method public boolean isDataConnectivityPossible();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isDataEnabledForApn(int);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isEmergencyAssistanceEnabled();
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isIdle();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isInEmergencySmsMode();
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isManualNetworkSelectionAllowed();
method public boolean isModemEnabledForSlot(int);
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isOffhook();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isOpportunisticNetworkEnabled();
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isPotentialEmergencyNumber(@NonNull String);
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isRadioOn();
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isRinging();
- method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public boolean isTetheringApnRequired();
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean isTetheringApnRequired();
method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE}) public boolean isVideoCallingEnabled();
method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isVisualVoicemailEnabled(android.telecom.PhoneAccountHandle);
method public boolean modifyDevicePolicyOverrideApn(@NonNull android.content.Context, int, @NonNull android.telephony.data.ApnSetting);
@@ -10080,13 +10704,17 @@
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean resetRadioConfig();
method @RequiresPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL) public void resetSettings();
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setAlwaysAllowMmsData(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setCarrierDataEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setCarrierRestrictionRules(@NonNull android.telephony.CarrierRestrictionRules);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setCdmaRoamingMode(int);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setCdmaSubscriptionMode(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataActivationState(int);
method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataEnabled(int, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setDataRoamingEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setMultiSimCarrierRestriction(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setOpportunisticNetworkState(boolean);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setPolicyDataEnabled(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setPreferredNetworkTypeBitmask(long);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean setRadio(boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void setRadioEnabled(boolean);
@@ -10105,10 +10733,17 @@
method public void updateServiceLocation();
method @RequiresPermission(android.Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION) public void updateTestOtaEmergencyNumberDbFilePath(@NonNull String);
field @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public static final String ACTION_ANOMALY_REPORTED = "android.telephony.action.ANOMALY_REPORTED";
+ field public static final String ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE = "com.android.internal.telephony.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE";
+ field public static final String ACTION_CARRIER_SIGNAL_PCO_VALUE = "com.android.internal.telephony.CARRIER_SIGNAL_PCO_VALUE";
+ field public static final String ACTION_CARRIER_SIGNAL_REDIRECTED = "com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED";
+ field public static final String ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED = "com.android.internal.telephony.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED";
+ field public static final String ACTION_CARRIER_SIGNAL_RESET = "com.android.internal.telephony.CARRIER_SIGNAL_RESET";
+ field public static final String ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED = "android.intent.action.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED";
+ field public static final String ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED = "android.intent.action.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED";
field public static final String ACTION_EMERGENCY_ASSISTANCE = "android.telephony.action.EMERGENCY_ASSISTANCE";
field public static final String ACTION_EMERGENCY_CALLBACK_MODE_CHANGED = "android.intent.action.EMERGENCY_CALLBACK_MODE_CHANGED";
field public static final String ACTION_EMERGENCY_CALL_STATE_CHANGED = "android.intent.action.EMERGENCY_CALL_STATE_CHANGED";
- field public static final String ACTION_NETWORK_SET_TIME = "android.telephony.action.NETWORK_SET_TIME";
+ field public static final String ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE = "com.android.omadm.service.CONFIGURATION_UPDATE";
field public static final String ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS = "android.telephony.action.SHOW_NOTICE_ECM_BLOCK_OTHERS";
field public static final String ACTION_SIM_APPLICATION_STATE_CHANGED = "android.telephony.action.SIM_APPLICATION_STATE_CHANGED";
field public static final String ACTION_SIM_CARD_STATE_CHANGED = "android.telephony.action.SIM_CARD_STATE_CHANGED";
@@ -10117,8 +10752,23 @@
field public static final int CARRIER_PRIVILEGE_STATUS_HAS_ACCESS = 1; // 0x1
field public static final int CARRIER_PRIVILEGE_STATUS_NO_ACCESS = 0; // 0x0
field public static final int CARRIER_PRIVILEGE_STATUS_RULES_NOT_LOADED = -1; // 0xffffffff
+ field public static final int CDMA_SUBSCRIPTION_NV = 1; // 0x1
+ field public static final int CDMA_SUBSCRIPTION_RUIM_SIM = 0; // 0x0
+ field public static final int CDMA_SUBSCRIPTION_UNKNOWN = -1; // 0xffffffff
+ field public static final int DEFAULT_PREFERRED_NETWORK_MODE = 0; // 0x0
field public static final String EXTRA_ANOMALY_DESCRIPTION = "android.telephony.extra.ANOMALY_DESCRIPTION";
field public static final String EXTRA_ANOMALY_ID = "android.telephony.extra.ANOMALY_ID";
+ field @Deprecated public static final String EXTRA_APN_PROTOCOL = "apnProto";
+ field public static final String EXTRA_APN_PROTOCOL_INT = "apnProtoInt";
+ field @Deprecated public static final String EXTRA_APN_TYPE = "apnType";
+ field public static final String EXTRA_APN_TYPE_INT = "apnTypeInt";
+ field public static final String EXTRA_DEFAULT_NETWORK_AVAILABLE = "defaultNetworkAvailable";
+ field public static final String EXTRA_ERROR_CODE = "errorCode";
+ field public static final String EXTRA_PCO_ID = "pcoId";
+ field public static final String EXTRA_PCO_VALUE = "pcoValue";
+ field public static final String EXTRA_PHONE_IN_ECM_STATE = "android.telephony.extra.PHONE_IN_ECM_STATE";
+ field public static final String EXTRA_PHONE_IN_EMERGENCY_CALL = "android.telephony.extra.PHONE_IN_EMERGENCY_CALL";
+ field public static final String EXTRA_REDIRECTION_URL = "redirectionUrl";
field public static final String EXTRA_SIM_STATE = "android.telephony.extra.SIM_STATE";
field public static final String EXTRA_VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL = "android.telephony.extra.VISUAL_VOICEMAIL_ENABLED_BY_USER_BOOL";
field public static final String EXTRA_VOICEMAIL_SCRAMBLED_PIN_STRING = "android.telephony.extra.VOICEMAIL_SCRAMBLED_PIN_STRING";
@@ -10145,6 +10795,7 @@
field public static final long NETWORK_TYPE_BITMASK_TD_SCDMA = 65536L; // 0x10000L
field public static final long NETWORK_TYPE_BITMASK_UMTS = 4L; // 0x4L
field public static final long NETWORK_TYPE_BITMASK_UNKNOWN = 0L; // 0x0L
+ field public static final int PHONE_TYPE_THIRD_PARTY = 4; // 0x4
field public static final int RADIO_POWER_OFF = 0; // 0x0
field public static final int RADIO_POWER_ON = 1; // 0x1
field public static final int RADIO_POWER_UNAVAILABLE = 2; // 0x2
@@ -10168,8 +10819,10 @@
public class TelephonyRegistryManager {
method public void addOnOpportunisticSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener, @NonNull java.util.concurrent.Executor);
method public void addOnSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnSubscriptionsChangedListener, @NonNull java.util.concurrent.Executor);
+ method public void notifyBarringInfoChanged(int, int, @NonNull android.telephony.BarringInfo);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyCallStateChangedForAllSubscriptions(int, @Nullable String);
method public void notifyCarrierNetworkChange(boolean);
+ method public void notifyRegistrationFailed(int, int, @NonNull android.telephony.CellIdentity, @NonNull String, int, int, int);
method public void removeOnOpportunisticSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnOpportunisticSubscriptionsChangedListener);
method public void removeOnSubscriptionsChangedListener(@NonNull android.telephony.SubscriptionManager.OnSubscriptionsChangedListener);
}
@@ -10208,6 +10861,20 @@
method public static final void setSmsFilterSettings(android.content.Context, android.telecom.PhoneAccountHandle, android.telephony.VisualVoicemailSmsFilterSettings);
}
+ public final class WapPushManagerConnector {
+ ctor public WapPushManagerConnector(@NonNull android.content.Context);
+ method public boolean bindToWapPushManagerService();
+ method @Nullable public String getConnectedWapPushManagerServicePackage();
+ method public int processMessage(@NonNull String, @NonNull String, @NonNull android.content.Intent);
+ method public void unbindWapPushManagerService();
+ field public static final int RESULT_APP_QUERY_FAILED = 2; // 0x2
+ field public static final int RESULT_EXCEPTION_CAUGHT = 16; // 0x10
+ field public static final int RESULT_FURTHER_PROCESSING = 32768; // 0x8000
+ field public static final int RESULT_INVALID_RECEIVER_NAME = 8; // 0x8
+ field public static final int RESULT_MESSAGE_HANDLED = 1; // 0x1
+ field public static final int RESULT_SIGNATURE_NO_MATCH = 4; // 0x4
+ }
+
}
package android.telephony.cdma {
@@ -10588,6 +11255,7 @@
field public static final int DIALSTRING_USSD = 2; // 0x2
field public static final String EXTRA_ADDITIONAL_CALL_INFO = "AdditionalCallInfo";
field public static final String EXTRA_ADDITIONAL_SIP_INVITE_FIELDS = "android.telephony.ims.extra.ADDITIONAL_SIP_INVITE_FIELDS";
+ field public static final String EXTRA_CALL_DISCONNECT_CAUSE = "android.telephony.ims.extra.CALL_DISCONNECT_CAUSE";
field public static final String EXTRA_CALL_RAT_TYPE = "CallRadioTech";
field public static final String EXTRA_CHILD_NUMBER = "ChildNum";
field public static final String EXTRA_CNA = "cna";
@@ -10707,6 +11375,7 @@
public class ImsManager {
method @NonNull public android.telephony.ims.ImsMmTelManager getImsMmTelManager(int);
method @NonNull public android.telephony.ims.ImsRcsManager getImsRcsManager(int);
+ field public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION = "com.android.internal.intent.action.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION";
}
public class ImsMmTelManager implements android.telephony.ims.RegistrationManager {
@@ -11001,13 +11670,30 @@
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public int getProvisioningIntValue(int);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean getProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int);
method @Nullable @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public String getProvisioningStringValue(int);
+ method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) @WorkerThread public boolean getRcsProvisioningStatusForCapability(int);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyRcsAutoConfigurationReceived(@NonNull byte[], boolean);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void registerProvisioningChangedCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.Callback) throws android.telephony.ims.ImsException;
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningIntValue(int, int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, @NonNull String);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setRcsProvisioningStatusForCapability(int, boolean);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback);
+ field public static final int KEY_EAB_PROVISIONING_STATUS = 25; // 0x19
+ field public static final int KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC = 19; // 0x13
+ field public static final int KEY_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC = 18; // 0x12
+ field public static final int KEY_RCS_CAPABILITIES_POLL_INTERVAL_SEC = 20; // 0x14
+ field public static final int KEY_RCS_CAPABILITY_DISCOVERY_ENABLED = 17; // 0x11
+ field public static final int KEY_RCS_CAPABILITY_POLL_LIST_SUB_EXP_SEC = 23; // 0x17
+ field public static final int KEY_RCS_MAX_NUM_ENTRIES_IN_RCL = 22; // 0x16
+ field public static final int KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS = 21; // 0x15
+ field public static final int KEY_RCS_PUBLISH_TIMER_EXTENDED_SEC = 16; // 0x10
+ field public static final int KEY_RCS_PUBLISH_TIMER_SEC = 15; // 0xf
+ field public static final int KEY_T1_TIMER_VALUE_MS = 7; // 0x7
field public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; // 0x1b
field public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; // 0x1a
+ field public static final int KEY_VOLTE_PROVISIONING_STATUS = 10; // 0xa
+ field public static final int KEY_VT_PROVISIONING_STATUS = 11; // 0xb
+ field public static final int PROVISIONING_RESULT_UNKNOWN = -1; // 0xffffffff
field public static final int PROVISIONING_VALUE_DISABLED = 0; // 0x0
field public static final int PROVISIONING_VALUE_ENABLED = 1; // 0x1
field public static final String STRING_QUERY_RESULT_ERROR_GENERIC = "STRING_QUERY_RESULT_ERROR_GENERIC";
@@ -11020,6 +11706,47 @@
method public void onProvisioningStringChanged(int, @NonNull String);
}
+ public final class RcsContactUceCapability implements android.os.Parcelable {
+ method public int describeContents();
+ method @NonNull public java.util.List<java.lang.String> getCapableExtensionTags();
+ method @NonNull public android.net.Uri getContactUri();
+ method @Nullable public android.net.Uri getServiceUri(int);
+ method public boolean isCapable(int);
+ method public boolean isCapable(@NonNull String);
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field public static final int CAPABILITY_CHAT_SESSION = 2; // 0x2
+ field public static final int CAPABILITY_CHAT_SESSION_STORE_FORWARD = 4; // 0x4
+ field public static final int CAPABILITY_CHAT_STANDALONE = 1; // 0x1
+ field public static final int CAPABILITY_DISCOVERY_VIA_PRESENCE = 4096; // 0x1000
+ field public static final int CAPABILITY_FILE_TRANSFER = 8; // 0x8
+ field public static final int CAPABILITY_FILE_TRANSFER_HTTP = 64; // 0x40
+ field public static final int CAPABILITY_FILE_TRANSFER_SMS = 128; // 0x80
+ field public static final int CAPABILITY_FILE_TRANSFER_STORE_FORWARD = 32; // 0x20
+ field public static final int CAPABILITY_FILE_TRANSFER_THUMBNAIL = 16; // 0x10
+ field public static final int CAPABILITY_GEOLOCATION_PULL = 131072; // 0x20000
+ field public static final int CAPABILITY_GEOLOCATION_PULL_FILE_TRANSFER = 262144; // 0x40000
+ field public static final int CAPABILITY_GEOLOCATION_PUSH = 32768; // 0x8000
+ field public static final int CAPABILITY_GEOLOCATION_PUSH_SMS = 65536; // 0x10000
+ field public static final int CAPABILITY_IMAGE_SHARE = 256; // 0x100
+ field public static final int CAPABILITY_IP_VIDEO_CALL = 16384; // 0x4000
+ field public static final int CAPABILITY_IP_VOICE_CALL = 8192; // 0x2000
+ field public static final int CAPABILITY_RCS_VIDEO_CALL = 1048576; // 0x100000
+ field public static final int CAPABILITY_RCS_VIDEO_ONLY_CALL = 2097152; // 0x200000
+ field public static final int CAPABILITY_RCS_VOICE_CALL = 524288; // 0x80000
+ field public static final int CAPABILITY_SOCIAL_PRESENCE = 2048; // 0x800
+ field public static final int CAPABILITY_VIDEO_SHARE = 1024; // 0x400
+ field public static final int CAPABILITY_VIDEO_SHARE_DURING_CS_CALL = 512; // 0x200
+ field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.RcsContactUceCapability> CREATOR;
+ }
+
+ public static class RcsContactUceCapability.Builder {
+ ctor public RcsContactUceCapability.Builder(@NonNull android.net.Uri);
+ method @NonNull public android.telephony.ims.RcsContactUceCapability.Builder add(int, @NonNull android.net.Uri);
+ method @NonNull public android.telephony.ims.RcsContactUceCapability.Builder add(int);
+ method @NonNull public android.telephony.ims.RcsContactUceCapability.Builder add(@NonNull String);
+ method @NonNull public android.telephony.ims.RcsContactUceCapability build();
+ }
+
public interface RegistrationManager {
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getRegistrationState(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
method @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE) public void getRegistrationTransportType(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
@@ -11209,6 +11936,7 @@
method public String getConfigString(int);
method public final void notifyProvisionedValueChanged(int, int);
method public final void notifyProvisionedValueChanged(int, String);
+ method public void notifyRcsAutoConfigurationReceived(@NonNull byte[], boolean);
method public int setConfig(int, int);
method public int setConfig(int, String);
field public static final int CONFIG_RESULT_FAILED = 1; // 0x1
@@ -11424,7 +12152,28 @@
method public int getUid();
}
+ public final class StatsEvent {
+ method @NonNull public static android.util.StatsEvent.Builder newBuilder();
+ }
+
+ public static final class StatsEvent.Builder {
+ method @NonNull public android.util.StatsEvent.Builder addBooleanAnnotation(byte, boolean);
+ method @NonNull public android.util.StatsEvent.Builder addIntAnnotation(byte, int);
+ method @NonNull public android.util.StatsEvent build();
+ method @NonNull public android.util.StatsEvent.Builder setAtomId(int);
+ method @NonNull public android.util.StatsEvent.Builder usePooledBuffer();
+ method @NonNull public android.util.StatsEvent.Builder writeAttributionChain(@NonNull int[], @NonNull String[]);
+ method @NonNull public android.util.StatsEvent.Builder writeBoolean(boolean);
+ method @NonNull public android.util.StatsEvent.Builder writeByteArray(@NonNull byte[]);
+ method @NonNull public android.util.StatsEvent.Builder writeFloat(float);
+ method @NonNull public android.util.StatsEvent.Builder writeInt(int);
+ method @NonNull public android.util.StatsEvent.Builder writeKeyValuePairs(@Nullable android.util.SparseIntArray, @Nullable android.util.SparseLongArray, @Nullable android.util.SparseArray<java.lang.String>, @Nullable android.util.SparseArray<java.lang.Float>);
+ method @NonNull public android.util.StatsEvent.Builder writeLong(long);
+ method @NonNull public android.util.StatsEvent.Builder writeString(@NonNull String);
+ }
+
public final class StatsLog {
+ method public static void write(@NonNull android.util.StatsEvent);
method public static void writeRaw(@NonNull byte[], int);
}
@@ -11457,6 +12206,8 @@
public final class AccessibilityManager {
method public int getAccessibilityWindowId(@Nullable android.os.IBinder);
method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void performAccessibilityShortcut();
+ method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void registerSystemAction(@NonNull android.app.RemoteAction, int);
+ method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void unregisterSystemAction(int);
}
}
@@ -11584,6 +12335,12 @@
method public void onJsResultComplete(android.webkit.JsResult);
}
+ public interface PacProcessor {
+ method @NonNull public static android.webkit.PacProcessor getInstance();
+ method @Nullable public String makeProxyRequest(@NonNull String);
+ method public boolean setProxyScript(@NonNull String);
+ }
+
public class SslErrorHandler extends android.os.Handler {
ctor public SslErrorHandler();
}
@@ -11718,6 +12475,7 @@
method public android.webkit.WebViewProvider createWebView(android.webkit.WebView, android.webkit.WebView.PrivateAccess);
method public android.webkit.CookieManager getCookieManager();
method public android.webkit.GeolocationPermissions getGeolocationPermissions();
+ method @NonNull public default android.webkit.PacProcessor getPacProcessor();
method public android.webkit.ServiceWorkerController getServiceWorkerController();
method public android.webkit.WebViewFactoryProvider.Statics getStatics();
method @Deprecated public android.webkit.TokenBindingService getTokenBindingService();
diff --git a/api/test-current.txt b/api/test-current.txt
index c7cdb16..28119e3 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -34,6 +34,7 @@
public static final class R.string {
field public static final int config_defaultAssistant = 17039393; // 0x1040021
field public static final int config_defaultDialer = 17039395; // 0x1040023
+ field public static final int config_systemGallery = 17039402; // 0x104002a
}
}
@@ -72,6 +73,7 @@
method @RequiresPermission(android.Manifest.permission.PACKAGE_USAGE_STATS) public void removeOnUidImportanceListener(android.app.ActivityManager.OnUidImportanceListener);
method public static void resumeAppSwitches() throws android.os.RemoteException;
method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public void scheduleApplicationInfoChanged(java.util.List<java.lang.String>, int);
+ method @RequiresPermission("android.permission.MANAGE_USERS") public boolean switchUser(@NonNull android.os.UserHandle);
field public static final int PROCESS_CAPABILITY_ALL = 1; // 0x1
field public static final int PROCESS_CAPABILITY_FOREGROUND_LOCATION = 1; // 0x1
field public static final int PROCESS_CAPABILITY_NONE = 0; // 0x0
@@ -244,6 +246,16 @@
field public static final int UID_STATE_TOP = 200; // 0xc8
}
+ public static final class AppOpsManager.HistoricalFeatureOps implements android.os.Parcelable {
+ method public int describeContents();
+ method @Nullable public String getFeatureId();
+ method @Nullable public android.app.AppOpsManager.HistoricalOp getOp(@NonNull String);
+ method @NonNull public android.app.AppOpsManager.HistoricalOp getOpAt(@IntRange(from=0) int);
+ method @IntRange(from=0) public int getOpCount();
+ method public void writeToParcel(@NonNull android.os.Parcel, int);
+ field @NonNull public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalFeatureOps> CREATOR;
+ }
+
public static final class AppOpsManager.HistoricalOp implements android.os.Parcelable {
method public int describeContents();
method public long getAccessCount(int, int, int);
@@ -268,9 +280,9 @@
method @IntRange(from=0) public int getUidCount();
method @Nullable public android.app.AppOpsManager.HistoricalUidOps getUidOps(int);
method @NonNull public android.app.AppOpsManager.HistoricalUidOps getUidOpsAt(@IntRange(from=0) int);
- method public void increaseAccessCount(int, int, @NonNull String, int, int, long);
- method public void increaseAccessDuration(int, int, @NonNull String, int, int, long);
- method public void increaseRejectCount(int, int, @NonNull String, int, int, long);
+ method public void increaseAccessCount(int, int, @NonNull String, @Nullable String, int, int, long);
+ method public void increaseAccessDuration(int, int, @NonNull String, @Nullable String, int, int, long);
+ method public void increaseRejectCount(int, int, @NonNull String, @Nullable String, int, int, long);
method public void offsetBeginAndEndTime(long);
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.app.AppOpsManager.HistoricalOps> CREATOR;
@@ -282,6 +294,7 @@
public static final class AppOpsManager.HistoricalOpsRequest.Builder {
ctor public AppOpsManager.HistoricalOpsRequest.Builder(long, long);
method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest build();
+ method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setFeatureId(@Nullable String);
method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setFlags(int);
method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setOpNames(@Nullable java.util.List<java.lang.String>);
method @NonNull public android.app.AppOpsManager.HistoricalOpsRequest.Builder setPackageName(@Nullable String);
@@ -290,6 +303,9 @@
public static final class AppOpsManager.HistoricalPackageOps implements android.os.Parcelable {
method public int describeContents();
+ method @IntRange(from=0) public int getFeatureCount();
+ method @Nullable public android.app.AppOpsManager.HistoricalFeatureOps getFeatureOps(@NonNull String);
+ method @NonNull public android.app.AppOpsManager.HistoricalFeatureOps getFeatureOpsAt(@IntRange(from=0) int);
method @Nullable public android.app.AppOpsManager.HistoricalOp getOp(@NonNull String);
method @NonNull public android.app.AppOpsManager.HistoricalOp getOpAt(@IntRange(from=0) int);
method @IntRange(from=0) public int getOpCount();
@@ -421,8 +437,11 @@
}
public class StatusBarManager {
+ method public void collapsePanels();
+ method public void expandNotificationsPanel();
method @NonNull @RequiresPermission(android.Manifest.permission.STATUS_BAR) public android.app.StatusBarManager.DisableInfo getDisableInfo();
method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSetup(boolean);
+ method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void setDisabledForSimNetworkLock(boolean);
}
public static final class StatusBarManager.DisableInfo {
@@ -453,7 +472,10 @@
}
public class WallpaperManager {
+ method @Nullable public android.graphics.Bitmap getBitmap();
method @RequiresPermission("android.permission.SET_WALLPAPER_COMPONENT") public boolean setWallpaperComponent(android.content.ComponentName);
+ method public boolean shouldEnableWideColorGamut();
+ method @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public boolean wallpaperSupportsWcg(int);
}
public class WindowConfiguration implements java.lang.Comparable<android.app.WindowConfiguration> android.os.Parcelable {
@@ -735,10 +757,12 @@
method public int getUserId();
method public void setAutofillOptions(@Nullable android.content.AutofillOptions);
method public void setContentCaptureOptions(@Nullable android.content.ContentCaptureOptions);
+ method @RequiresPermission("android.permission.INTERACT_ACROSS_USERS") public void startActivityAsUser(@NonNull @RequiresPermission android.content.Intent, @NonNull android.os.UserHandle);
field public static final String BLOB_STORE_SERVICE = "blob_store";
field public static final String BUGREPORT_SERVICE = "bugreport";
field public static final String CONTENT_CAPTURE_MANAGER_SERVICE = "content_capture";
field public static final String DEVICE_IDLE_CONTROLLER = "deviceidle";
+ field public static final String NETWORK_STACK_SERVICE = "network_stack";
field public static final String PERMISSION_SERVICE = "permission";
field public static final String POWER_WHITELIST_MANAGER = "power_whitelist";
field public static final String ROLLBACK_SERVICE = "rollback";
@@ -1003,7 +1027,7 @@
package android.hardware.camera2 {
public abstract class CameraDevice implements java.lang.AutoCloseable {
- method public abstract void createCustomCaptureSession(android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, int, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
+ method @Deprecated public abstract void createCustomCaptureSession(android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, int, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException;
field public static final int SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED = 1; // 0x1
field public static final int SESSION_OPERATION_MODE_NORMAL = 0; // 0x0
field public static final int SESSION_OPERATION_MODE_VENDOR_START = 32768; // 0x8000
@@ -1479,7 +1503,9 @@
public class CaptivePortal implements android.os.Parcelable {
method public void logEvent(int, @NonNull String);
+ method public void reevaluateNetwork();
method public void useNetwork();
+ field public static final int APP_REQUEST_REEVALUATION_REQUIRED = 100; // 0x64
field public static final int APP_RETURN_DISMISSED = 0; // 0x0
field public static final int APP_RETURN_UNWANTED = 1; // 0x1
field public static final int APP_RETURN_WANTED_AS_IS = 2; // 0x2
@@ -2183,6 +2209,14 @@
field public static final String ACTION_USER_RESTRICTIONS_CHANGED = "android.os.action.USER_RESTRICTIONS_CHANGED";
}
+ public final class VibrationAttributes implements android.os.Parcelable {
+ method @Deprecated @NonNull public android.media.AudioAttributes getAudioAttributes();
+ }
+
+ public static final class VibrationAttributes.Builder {
+ ctor public VibrationAttributes.Builder(@NonNull android.media.AudioAttributes, @Nullable android.os.VibrationEffect);
+ }
+
public abstract class VibrationEffect implements android.os.Parcelable {
method public static android.os.VibrationEffect get(int);
method public static android.os.VibrationEffect get(int, boolean);
@@ -2535,6 +2569,7 @@
field public static final String ACTION_ENTERPRISE_PRIVACY_SETTINGS = "android.settings.ENTERPRISE_PRIVACY_SETTINGS";
field public static final String ACTION_MANAGE_APP_OVERLAY_PERMISSION = "android.settings.MANAGE_APP_OVERLAY_PERMISSION";
field public static final String ACTION_REQUEST_ENABLE_CONTENT_CAPTURE = "android.settings.REQUEST_ENABLE_CONTENT_CAPTURE";
+ field public static final String ACTION_TETHER_PROVISIONING_UI = "android.settings.TETHER_PROVISIONING_UI";
field public static final int RESET_MODE_PACKAGE_DEFAULTS = 1; // 0x1
}
@@ -2553,6 +2588,7 @@
field public static final String LOW_POWER_MODE_STICKY = "low_power_sticky";
field public static final String NOTIFICATION_BUBBLES = "notification_bubbles";
field public static final String OVERLAY_DISPLAY_DEVICES = "overlay_display_devices";
+ field public static final String TETHER_OFFLOAD_DISABLED = "tether_offload_disabled";
field public static final String USE_OPEN_WIFI_PACKAGE = "use_open_wifi_package";
}
@@ -2608,7 +2644,7 @@
field public static final String SERIAL_NUMBER = "serial_number";
field public static final String SERVICE_CATEGORY = "service_category";
field public static final String SLOT_INDEX = "slot_index";
- field public static final String SUB_ID = "sub_id";
+ field public static final String SUBSCRIPTION_ID = "sub_id";
}
public static final class Telephony.Sms.Intents {
@@ -2712,6 +2748,11 @@
method @Nullable public android.util.SparseArray<android.service.autofill.InternalOnClickAction> getActions();
}
+ public static final class Dataset.Builder {
+ ctor public Dataset.Builder(@NonNull android.service.autofill.InlinePresentation);
+ method @NonNull public android.service.autofill.Dataset.Builder setInlinePresentation(@NonNull android.view.autofill.AutofillId, @Nullable android.view.autofill.AutofillValue, @Nullable java.util.regex.Pattern, @NonNull android.service.autofill.InlinePresentation);
+ }
+
public final class DateTransformation extends android.service.autofill.InternalTransformation implements android.os.Parcelable android.service.autofill.Transformation {
method public void apply(@NonNull android.service.autofill.ValueFinder, @NonNull android.widget.RemoteViews, int) throws java.lang.Exception;
}
@@ -2910,7 +2951,10 @@
method @Nullable public android.service.notification.Adjustment onNotificationEnqueued(@NonNull android.service.notification.StatusBarNotification, @NonNull android.app.NotificationChannel);
method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean);
method public abstract void onNotificationSnoozedUntilContext(@NonNull android.service.notification.StatusBarNotification, @NonNull String);
+ method public void onNotificationVisibilityChanged(@NonNull String, boolean);
method public void onNotificationsSeen(@NonNull java.util.List<java.lang.String>);
+ method public void onPanelHidden();
+ method public void onPanelRevealed(int);
method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int);
method public final void unsnoozeNotification(@NonNull String);
field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService";
@@ -3101,8 +3145,18 @@
field public static final int TRANSPORT_TYPE_WWAN = 1; // 0x1
}
+ public final class BarringInfo implements android.os.Parcelable {
+ ctor public BarringInfo();
+ ctor public BarringInfo(@Nullable android.telephony.CellIdentity, @NonNull android.util.SparseArray<android.telephony.BarringInfo.BarringServiceInfo>);
+ }
+
+ public static final class BarringInfo.BarringServiceInfo implements android.os.Parcelable {
+ ctor public BarringInfo.BarringServiceInfo(int, boolean, int, int);
+ }
+
public final class CallQuality implements android.os.Parcelable {
ctor public CallQuality(int, int, int, int, int, int, int, int, int, int, int);
+ ctor public CallQuality(int, int, int, int, int, int, int, int, int, int, int, boolean, boolean, boolean);
method public int describeContents();
method public int getAverageRelativeJitter();
method public int getAverageRoundTripTime();
@@ -3115,6 +3169,9 @@
method public int getNumRtpPacketsTransmitted();
method public int getNumRtpPacketsTransmittedLost();
method public int getUplinkCallQualityLevel();
+ method public boolean isIncomingSilenceDetected();
+ method public boolean isOutgoingSilenceDetected();
+ method public boolean isRtpInactivityDetected();
method public void writeToParcel(android.os.Parcel, int);
field public static final int CALL_QUALITY_BAD = 4; // 0x4
field public static final int CALL_QUALITY_EXCELLENT = 0; // 0x0
@@ -3176,7 +3233,9 @@
method public void writeToParcel(android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.telephony.NetworkRegistrationInfo> CREATOR;
field public static final int DOMAIN_CS = 1; // 0x1
+ field public static final int DOMAIN_CS_PS = 3; // 0x3
field public static final int DOMAIN_PS = 2; // 0x2
+ field public static final int DOMAIN_UNKNOWN = 0; // 0x0
field public static final int REGISTRATION_STATE_DENIED = 3; // 0x3
field public static final int REGISTRATION_STATE_HOME = 1; // 0x1
field public static final int REGISTRATION_STATE_NOT_REGISTERED_OR_SEARCHING = 0; // 0x0
@@ -3225,6 +3284,7 @@
public class ServiceState implements android.os.Parcelable {
method public void addNetworkRegistrationInfo(android.telephony.NetworkRegistrationInfo);
+ method public int getDataNetworkType();
method public void setCdmaSystemAndNetworkId(int, int);
method public void setCellBandwidths(int[]);
method public void setChannelNumber(int);
@@ -3376,6 +3436,7 @@
field public static final int DIALSTRING_USSD = 2; // 0x2
field public static final String EXTRA_ADDITIONAL_CALL_INFO = "AdditionalCallInfo";
field public static final String EXTRA_ADDITIONAL_SIP_INVITE_FIELDS = "android.telephony.ims.extra.ADDITIONAL_SIP_INVITE_FIELDS";
+ field public static final String EXTRA_CALL_DISCONNECT_CAUSE = "android.telephony.ims.extra.CALL_DISCONNECT_CAUSE";
field public static final String EXTRA_CALL_RAT_TYPE = "CallRadioTech";
field public static final String EXTRA_CHILD_NUMBER = "ChildNum";
field public static final String EXTRA_CNA = "cna";
@@ -3495,6 +3556,7 @@
public class ImsManager {
method @NonNull public android.telephony.ims.ImsMmTelManager getImsMmTelManager(int);
method @NonNull public android.telephony.ims.ImsRcsManager getImsRcsManager(int);
+ field public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION = "com.android.internal.intent.action.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION";
}
public class ImsMmTelManager implements android.telephony.ims.RegistrationManager {
@@ -3785,13 +3847,30 @@
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") @WorkerThread public int getProvisioningIntValue(int);
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") @WorkerThread public boolean getProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int);
method @Nullable @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") @WorkerThread public String getProvisioningStringValue(int);
+ method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") @WorkerThread public boolean getRcsProvisioningStatusForCapability(int);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void notifyRcsAutoConfigurationReceived(@NonNull byte[], boolean);
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void registerProvisioningChangedCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.ProvisioningManager.Callback) throws android.telephony.ims.ImsException;
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningIntValue(int, int);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setProvisioningStatusForCapability(@android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability int, int, boolean);
method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public int setProvisioningStringValue(int, @NonNull String);
+ method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) @WorkerThread public void setRcsProvisioningStatusForCapability(int, boolean);
method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void unregisterProvisioningChangedCallback(@NonNull android.telephony.ims.ProvisioningManager.Callback);
+ field public static final int KEY_EAB_PROVISIONING_STATUS = 25; // 0x19
+ field public static final int KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC = 19; // 0x13
+ field public static final int KEY_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC = 18; // 0x12
+ field public static final int KEY_RCS_CAPABILITIES_POLL_INTERVAL_SEC = 20; // 0x14
+ field public static final int KEY_RCS_CAPABILITY_DISCOVERY_ENABLED = 17; // 0x11
+ field public static final int KEY_RCS_CAPABILITY_POLL_LIST_SUB_EXP_SEC = 23; // 0x17
+ field public static final int KEY_RCS_MAX_NUM_ENTRIES_IN_RCL = 22; // 0x16
+ field public static final int KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS = 21; // 0x15
+ field public static final int KEY_RCS_PUBLISH_TIMER_EXTENDED_SEC = 16; // 0x10
+ field public static final int KEY_RCS_PUBLISH_TIMER_SEC = 15; // 0xf
+ field public static final int KEY_T1_TIMER_VALUE_MS = 7; // 0x7
field public static final int KEY_VOICE_OVER_WIFI_MODE_OVERRIDE = 27; // 0x1b
field public static final int KEY_VOICE_OVER_WIFI_ROAMING_ENABLED_OVERRIDE = 26; // 0x1a
+ field public static final int KEY_VOLTE_PROVISIONING_STATUS = 10; // 0xa
+ field public static final int KEY_VT_PROVISIONING_STATUS = 11; // 0xb
+ field public static final int PROVISIONING_RESULT_UNKNOWN = -1; // 0xffffffff
field public static final int PROVISIONING_VALUE_DISABLED = 0; // 0x0
field public static final int PROVISIONING_VALUE_ENABLED = 1; // 0x1
field public static final String STRING_QUERY_RESULT_ERROR_GENERIC = "STRING_QUERY_RESULT_ERROR_GENERIC";
@@ -3993,6 +4072,7 @@
method public String getConfigString(int);
method public final void notifyProvisionedValueChanged(int, int);
method public final void notifyProvisionedValueChanged(int, String);
+ method public void notifyRcsAutoConfigurationReceived(@NonNull byte[], boolean);
method public int setConfig(int, int);
method public int setConfig(int, String);
field public static final int CONFIG_RESULT_FAILED = 1; // 0x1
@@ -4222,6 +4302,7 @@
field public static final String FFLAG_OVERRIDE_PREFIX = "sys.fflag.override.";
field public static final String FFLAG_PREFIX = "sys.fflag.";
field public static final String HEARING_AID_SETTINGS = "settings_bluetooth_hearing_aid";
+ field public static final String NOTIF_CONVO_BYPASS_SHORTCUT_REQ = "settings_notif_convo_bypass_shortcut_req";
field public static final String PERSIST_PREFIX = "persist.sys.fflag.override.";
field public static final String SCREENRECORD_LONG_PRESS = "settings_screenrecord_long_press";
field public static final String SEAMLESS_TRANSFER = "settings_seamless_transfer";
@@ -4321,6 +4402,7 @@
}
public final class Display {
+ method @NonNull public android.graphics.ColorSpace[] getSupportedWideColorGamut();
method public boolean hasAccess(int);
}
@@ -4438,6 +4520,8 @@
}
public class AccessibilityNodeInfo implements android.os.Parcelable {
+ method public void addChild(@NonNull android.os.IBinder);
+ method public void setLeashedParent(@Nullable android.os.IBinder, int);
method public static void setNumInstancesInUseCounter(java.util.concurrent.atomic.AtomicInteger);
method public void writeToParcelNoRecycle(android.os.Parcel, int);
}
@@ -4596,6 +4680,18 @@
package android.view.inputmethod {
+ public final class InlineSuggestion implements android.os.Parcelable {
+ method @NonNull public static android.view.inputmethod.InlineSuggestion newInlineSuggestion(@NonNull android.view.inputmethod.InlineSuggestionInfo);
+ }
+
+ public final class InlineSuggestionInfo implements android.os.Parcelable {
+ method @NonNull public static android.view.inputmethod.InlineSuggestionInfo newInlineSuggestionInfo(@NonNull android.view.inline.InlinePresentationSpec, @NonNull String, @Nullable String[]);
+ }
+
+ public final class InlineSuggestionsResponse implements android.os.Parcelable {
+ method @NonNull public static android.view.inputmethod.InlineSuggestionsResponse newInlineSuggestionsResponse(@NonNull java.util.List<android.view.inputmethod.InlineSuggestion>);
+ }
+
public final class InputMethodManager {
method public int getDisplayId();
method public boolean isInputMethodPickerShown();
diff --git a/api/test-lint-baseline.txt b/api/test-lint-baseline.txt
index 6d1f291..603f7a2 100644
--- a/api/test-lint-baseline.txt
+++ b/api/test-lint-baseline.txt
@@ -400,7 +400,7 @@
GetterSetterNames: android.location.GnssClock#setTimeUncertaintyNanos(double):
GetterSetterNames: android.location.GnssMeasurement#setBasebandCn0DbHz(double):
-
+
GetterSetterNames: android.location.GnssMeasurement#setCarrierFrequencyHz(float):
GetterSetterNames: android.location.GnssMeasurement#setCodeType(String):
@@ -466,7 +466,7 @@
KotlinOperator: android.os.WorkSource#get(int):
KotlinOperator: android.util.SparseArrayMap#get(int, String):
- Method can be invoked with an indexing operator from Kotlin: `get` (this is usually desirable; just make sure it makes sense for this type of object)
+
ListenerInterface: android.media.audiopolicy.AudioPolicy.AudioPolicyFocusListener:
@@ -2059,6 +2059,18 @@
MissingNullability: android.view.KeyEvent#actionToString(int):
+MissingNullability: android.view.SurfaceControlViewHost#SurfaceControlViewHost(android.content.Context, android.view.Display, android.view.SurfaceControl) parameter #0:
+
+MissingNullability: android.view.SurfaceControlViewHost#SurfaceControlViewHost(android.content.Context, android.view.Display, android.view.SurfaceControl) parameter #1:
+
+MissingNullability: android.view.SurfaceControlViewHost#SurfaceControlViewHost(android.content.Context, android.view.Display, android.view.SurfaceControl) parameter #2:
+
+MissingNullability: android.view.SurfaceControlViewHost#addView(android.view.View, android.view.WindowManager.LayoutParams) parameter #0:
+
+MissingNullability: android.view.SurfaceControlViewHost#addView(android.view.View, android.view.WindowManager.LayoutParams) parameter #1:
+
+MissingNullability: android.view.SurfaceControlViewHost#relayout(android.view.WindowManager.LayoutParams) parameter #0:
+
MissingNullability: android.view.View#getTooltipView():
MissingNullability: android.view.View#isDefaultFocusHighlightNeeded(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable) parameter #0:
@@ -2079,18 +2091,6 @@
MissingNullability: android.view.WindowManager.LayoutParams#accessibilityTitle:
-MissingNullability: android.view.SurfaceControlViewHost#SurfaceControlViewHost(android.content.Context, android.view.Display, android.view.SurfaceControl) parameter #0:
-
-MissingNullability: android.view.SurfaceControlViewHost#SurfaceControlViewHost(android.content.Context, android.view.Display, android.view.SurfaceControl) parameter #1:
-
-MissingNullability: android.view.SurfaceControlViewHost#SurfaceControlViewHost(android.content.Context, android.view.Display, android.view.SurfaceControl) parameter #2:
-
-MissingNullability: android.view.SurfaceControlViewHost#addView(android.view.View, android.view.WindowManager.LayoutParams) parameter #0:
-
-MissingNullability: android.view.SurfaceControlViewHost#addView(android.view.View, android.view.WindowManager.LayoutParams) parameter #1:
-
-MissingNullability: android.view.SurfaceControlViewHost#relayout(android.view.WindowManager.LayoutParams) parameter #0:
-
MissingNullability: android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener#onAccessibilityServicesStateChanged(android.view.accessibility.AccessibilityManager) parameter #0:
MissingNullability: android.view.accessibility.AccessibilityNodeInfo#setNumInstancesInUseCounter(java.util.concurrent.atomic.AtomicInteger) parameter #0:
@@ -2426,11 +2426,13 @@
ProtectedMember: android.view.ViewGroup#resetResolvedDrawables():
-PublicTypedef: android.os.HwParcel.Status: Don't expose @IntDef: @Status must be hidden.
-PublicTypedef: android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability: Don't expose @IntDef: @MmTelCapability must be hidden.
-
-PublicTypedef: android.telephony.ims.feature.MmTelFeature.ProcessCallResult: Don't expose @IntDef: @ProcessCallResult must be hidden.
+PublicTypedef: android.os.HwParcel.Status:
+
+PublicTypedef: android.telephony.ims.feature.MmTelFeature.MmTelCapabilities.MmTelCapability:
+
+PublicTypedef: android.telephony.ims.feature.MmTelFeature.ProcessCallResult:
+
RawAidl: android.telephony.mbms.vendor.MbmsDownloadServiceBase:
@@ -2513,6 +2515,8 @@
SamShouldBeLast: android.database.sqlite.SQLiteDirectCursorDriver#query(android.database.sqlite.SQLiteDatabase.CursorFactory, String[]):
+SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(android.location.LocationRequest, android.location.LocationListener, android.os.Looper):
+ SAM-compatible parameters (such as parameter 2, "listener", in android.location.LocationManager.requestLocationUpdates) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
SamShouldBeLast: android.location.LocationManager#requestLocationUpdates(android.location.LocationRequest, java.util.concurrent.Executor, android.location.LocationListener):
SamShouldBeLast: android.os.BugreportManager#startBugreport(android.os.ParcelFileDescriptor, android.os.ParcelFileDescriptor, android.os.BugreportParams, java.util.concurrent.Executor, android.os.BugreportManager.BugreportCallback):
@@ -2593,6 +2597,8 @@
+UserHandle: android.app.ActivityManager#switchUser(android.os.UserHandle):
+ When a method overload is needed to target a specific UserHandle, callers should be directed to use Context.createPackageContextAsUser() and re-obtain the relevant Manager, and no new API should be added
UserHandle: android.app.admin.DevicePolicyManager#getOwnerInstalledCaCerts(android.os.UserHandle):
UserHandle: android.app.role.RoleManager#addOnRoleHoldersChangedListenerAsUser(java.util.concurrent.Executor, android.app.role.OnRoleHoldersChangedListener, android.os.UserHandle):
@@ -2607,8 +2613,12 @@
UserHandle: android.app.role.RoleManager#removeRoleHolderAsUser(String, String, int, android.os.UserHandle, java.util.concurrent.Executor, java.util.function.Consumer<java.lang.Boolean>):
-UserHandle: android.companion.CompanionDeviceManager#isDeviceAssociated(String, android.net.MacAddress, android.os.UserHandle):
+UserHandle: android.app.usage.StorageStatsManager#queryCratesForPackage(java.util.UUID, String, android.os.UserHandle):
When a method overload is needed to target a specific UserHandle, callers should be directed to use Context.createPackageContextAsUser() and re-obtain the relevant Manager, and no new API should be added
+UserHandle: android.app.usage.StorageStatsManager#queryCratesForUser(java.util.UUID, android.os.UserHandle):
+ When a method overload is needed to target a specific UserHandle, callers should be directed to use Context.createPackageContextAsUser() and re-obtain the relevant Manager, and no new API should be added
+UserHandle: android.companion.CompanionDeviceManager#isDeviceAssociated(String, android.net.MacAddress, android.os.UserHandle):
+
UserHandle: android.content.pm.PackageManager#getInstallReason(String, android.os.UserHandle):
UserHandle: android.content.pm.PackageManager#getPermissionFlags(String, String, android.os.UserHandle):
diff --git a/cmds/bootanimation/BootAnimation.cpp b/cmds/bootanimation/BootAnimation.cpp
index 459520a..8fac31a 100644
--- a/cmds/bootanimation/BootAnimation.cpp
+++ b/cmds/bootanimation/BootAnimation.cpp
@@ -1113,7 +1113,7 @@
SurfaceComposerClient::Transaction t;
t.setPosition(mFlingerSurfaceControl, 0, -mTargetInset)
.setCrop(mFlingerSurfaceControl, Rect(0, mTargetInset, mWidth, mHeight));
- t.setDisplayProjection(mDisplayToken, 0 /* orientation */, layerStackRect, displayRect);
+ t.setDisplayProjection(mDisplayToken, ui::ROTATION_0, layerStackRect, displayRect);
t.apply();
mTargetInset = mCurrentInset = 0;
diff --git a/cmds/idmap2/libidmap2/ResourceMapping.cpp b/cmds/idmap2/libidmap2/ResourceMapping.cpp
index 229628c..4074789 100644
--- a/cmds/idmap2/libidmap2/ResourceMapping.cpp
+++ b/cmds/idmap2/libidmap2/ResourceMapping.cpp
@@ -33,6 +33,8 @@
namespace {
+#define REWRITE_PACKAGE(resid, package_id) \
+ (((resid)&0x00ffffffU) | (((uint32_t)(package_id)) << 24U))
#define EXTRACT_PACKAGE(resid) ((0xff000000 & (resid)) >> 24)
std::string ConcatPolicies(const std::vector<std::string>& policies) {
@@ -154,6 +156,7 @@
return Error("root element is not <overlay> tag");
}
+ const uint8_t target_package_id = target_package->GetPackageId();
const uint8_t overlay_package_id = overlay_package->GetPackageId();
auto overlay_it_end = root_it.end();
for (auto overlay_it = root_it.begin(); overlay_it != overlay_it_end; ++overlay_it) {
@@ -187,6 +190,9 @@
continue;
}
+ // Retrieve the compile-time resource id of the target resource.
+ target_id = REWRITE_PACKAGE(target_id, target_package_id);
+
if (overlay_resource->dataType == Res_value::TYPE_STRING) {
overlay_resource->data += string_pool_offset;
}
@@ -214,6 +220,7 @@
const AssetManager2* target_am, const AssetManager2* overlay_am,
const LoadedPackage* target_package, const LoadedPackage* overlay_package) {
ResourceMapping resource_mapping;
+ const uint8_t target_package_id = target_package->GetPackageId();
const auto end = overlay_package->end();
for (auto iter = overlay_package->begin(); iter != end; ++iter) {
const ResourceId overlay_resid = *iter;
@@ -225,11 +232,14 @@
// Find the resource with the same type and entry name within the target package.
const std::string full_name =
base::StringPrintf("%s:%s", target_package->GetPackageName().c_str(), name->c_str());
- const ResourceId target_resource = target_am->GetResourceId(full_name);
+ ResourceId target_resource = target_am->GetResourceId(full_name);
if (target_resource == 0U) {
continue;
}
+ // Retrieve the compile-time resource id of the target resource.
+ target_resource = REWRITE_PACKAGE(target_resource, target_package_id);
+
resource_mapping.AddMapping(target_resource, Res_value::TYPE_REFERENCE, overlay_resid,
/* rewrite_overlay_reference */ false);
}
diff --git a/cmds/incidentd/src/IncidentService.cpp b/cmds/incidentd/src/IncidentService.cpp
index cfd77c2..62312d1 100644
--- a/cmds/incidentd/src/IncidentService.cpp
+++ b/cmds/incidentd/src/IncidentService.cpp
@@ -123,14 +123,17 @@
// ================================================================================
ReportHandler::ReportHandler(const sp<WorkDirectory>& workDirectory,
- const sp<Broadcaster>& broadcaster, const sp<Looper>& handlerLooper,
- const sp<Throttler>& throttler)
+ const sp<Broadcaster>& broadcaster,
+ const sp<Looper>& handlerLooper,
+ const sp<Throttler>& throttler,
+ const vector<BringYourOwnSection*>& registeredSections)
:mLock(),
mWorkDirectory(workDirectory),
mBroadcaster(broadcaster),
mHandlerLooper(handlerLooper),
mBacklogDelay(DEFAULT_DELAY_NS),
mThrottler(throttler),
+ mRegisteredSections(registeredSections),
mBatch(new ReportBatch()) {
}
@@ -185,7 +188,7 @@
return;
}
- sp<Reporter> reporter = new Reporter(mWorkDirectory, batch);
+ sp<Reporter> reporter = new Reporter(mWorkDirectory, batch, mRegisteredSections);
// Take the report, which might take a while. More requests might queue
// up while we're doing this, and we'll handle them in their next batch.
@@ -237,7 +240,7 @@
mWorkDirectory = new WorkDirectory();
mBroadcaster = new Broadcaster(mWorkDirectory);
mHandler = new ReportHandler(mWorkDirectory, mBroadcaster, handlerLooper,
- mThrottler);
+ mThrottler, mRegisteredSections);
mBroadcaster->setHandler(mHandler);
}
@@ -327,6 +330,11 @@
incidentArgs.addSection(id);
}
}
+ for (const Section* section : mRegisteredSections) {
+ if (!section_requires_specific_mention(section->id)) {
+ incidentArgs.addSection(section->id);
+ }
+ }
// The ReportRequest takes ownership of the fd, so we need to dup it.
int fd = dup(stream.get());
@@ -339,6 +347,45 @@
return Status::ok();
}
+Status IncidentService::registerSection(const int id, const String16& name16,
+ const sp<IIncidentDumpCallback>& callback) {
+ const char* name = String8(name16).c_str();
+ ALOGI("Register section %d: %s", id, name);
+ if (callback == nullptr) {
+ return Status::fromExceptionCode(Status::EX_NULL_POINTER);
+ }
+ const uid_t callingUid = IPCThreadState::self()->getCallingUid();
+ for (int i = 0; i < mRegisteredSections.size(); i++) {
+ if (mRegisteredSections.at(i)->id == id) {
+ if (mRegisteredSections.at(i)->uid != callingUid) {
+ ALOGW("Error registering section %d: calling uid does not match", id);
+ return Status::fromExceptionCode(Status::EX_SECURITY);
+ }
+ mRegisteredSections.at(i) = new BringYourOwnSection(id, name, callingUid, callback);
+ return Status::ok();
+ }
+ }
+ mRegisteredSections.push_back(new BringYourOwnSection(id, name, callingUid, callback));
+ return Status::ok();
+}
+
+Status IncidentService::unregisterSection(const int id) {
+ ALOGI("Unregister section %d", id);
+ uid_t callingUid = IPCThreadState::self()->getCallingUid();
+ for (auto it = mRegisteredSections.begin(); it != mRegisteredSections.end(); it++) {
+ if ((*it)->id == id) {
+ if ((*it)->uid != callingUid) {
+ ALOGW("Error unregistering section %d: calling uid does not match", id);
+ return Status::fromExceptionCode(Status::EX_SECURITY);
+ }
+ mRegisteredSections.erase(it);
+ return Status::ok();
+ }
+ }
+ ALOGW("Section %d not found", id);
+ return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE);
+}
+
Status IncidentService::systemRunning() {
if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
return Status::fromExceptionCode(Status::EX_SECURITY,
diff --git a/cmds/incidentd/src/IncidentService.h b/cmds/incidentd/src/IncidentService.h
index b2c7f23..49fc566 100644
--- a/cmds/incidentd/src/IncidentService.h
+++ b/cmds/incidentd/src/IncidentService.h
@@ -40,12 +40,16 @@
using namespace android::binder;
using namespace android::os;
+class BringYourOwnSection;
+
// ================================================================================
class ReportHandler : public MessageHandler {
public:
ReportHandler(const sp<WorkDirectory>& workDirectory,
- const sp<Broadcaster>& broadcaster, const sp<Looper>& handlerLooper,
- const sp<Throttler>& throttler);
+ const sp<Broadcaster>& broadcaster,
+ const sp<Looper>& handlerLooper,
+ const sp<Throttler>& throttler,
+ const vector<BringYourOwnSection*>& registeredSections);
virtual ~ReportHandler();
virtual void handleMessage(const Message& message);
@@ -79,6 +83,8 @@
nsecs_t mBacklogDelay;
sp<Throttler> mThrottler;
+ const vector<BringYourOwnSection*>& mRegisteredSections;
+
sp<ReportBatch> mBatch;
/**
@@ -126,6 +132,11 @@
virtual Status reportIncidentToDumpstate(unique_fd stream,
const sp<IIncidentReportStatusListener>& listener);
+ virtual Status registerSection(int id, const String16& name,
+ const sp<IIncidentDumpCallback>& callback);
+
+ virtual Status unregisterSection(int id);
+
virtual Status systemRunning();
virtual Status getIncidentReportList(const String16& pkg, const String16& cls,
@@ -149,6 +160,7 @@
sp<Broadcaster> mBroadcaster;
sp<ReportHandler> mHandler;
sp<Throttler> mThrottler;
+ vector<BringYourOwnSection*> mRegisteredSections;
/**
* Commands print out help.
diff --git a/cmds/incidentd/src/Reporter.cpp b/cmds/incidentd/src/Reporter.cpp
index 02b6bbe..aa40f85 100644
--- a/cmds/incidentd/src/Reporter.cpp
+++ b/cmds/incidentd/src/Reporter.cpp
@@ -364,7 +364,6 @@
mSectionBufferSuccess = false;
mHadError = false;
mSectionErrors.clear();
-
}
void ReportWriter::setSectionStats(const FdBuffer& buffer) {
@@ -470,10 +469,13 @@
// ================================================================================
-Reporter::Reporter(const sp<WorkDirectory>& workDirectory, const sp<ReportBatch>& batch)
+Reporter::Reporter(const sp<WorkDirectory>& workDirectory,
+ const sp<ReportBatch>& batch,
+ const vector<BringYourOwnSection*>& registeredSections)
:mWorkDirectory(workDirectory),
mWriter(batch),
- mBatch(batch) {
+ mBatch(batch),
+ mRegisteredSections(registeredSections) {
}
Reporter::~Reporter() {
@@ -580,50 +582,15 @@
// For each of the report fields, see if we need it, and if so, execute the command
// and report to those that care that we're doing it.
for (const Section** section = SECTION_LIST; *section; section++) {
- const int sectionId = (*section)->id;
-
- // If nobody wants this section, skip it.
- if (!mBatch->containsSection(sectionId)) {
- continue;
- }
-
- ALOGD("Start incident report section %d '%s'", sectionId, (*section)->name.string());
- IncidentMetadata::SectionStats* sectionMetadata = metadata.add_sections();
-
- // Notify listener of starting
- mBatch->forEachListener(sectionId, [sectionId](const auto& listener) {
- listener->onReportSectionStatus(
- sectionId, IIncidentReportStatusListener::STATUS_STARTING);
- });
-
- // Go get the data and write it into the file descriptors.
- mWriter.startSection(sectionId);
- err = (*section)->Execute(&mWriter);
- mWriter.endSection(sectionMetadata);
-
- // Sections returning errors are fatal. Most errors should not be fatal.
- if (err != NO_ERROR) {
- mWriter.error((*section), err, "Section failed. Stopping report.");
+ if (execute_section(*section, &metadata, reportByteSize) != NO_ERROR) {
goto DONE;
}
+ }
- // The returned max data size is used for throttling too many incident reports.
- (*reportByteSize) += sectionMetadata->report_size_bytes();
-
- // For any requests that failed during this section, remove them now. We do this
- // before calling back about section finished, so listeners do not erroniously get the
- // impression that the section succeeded. But we do it here instead of inside
- // writeSection so that the callback is done from a known context and not from the
- // bowels of a section, where changing the batch could cause odd errors.
- cancel_and_remove_failed_requests();
-
- // Notify listener of finishing
- mBatch->forEachListener(sectionId, [sectionId](const auto& listener) {
- listener->onReportSectionStatus(
- sectionId, IIncidentReportStatusListener::STATUS_FINISHED);
- });
-
- ALOGD("Finish incident report section %d '%s'", sectionId, (*section)->name.string());
+ for (const Section* section : mRegisteredSections) {
+ if (execute_section(section, &metadata, reportByteSize) != NO_ERROR) {
+ goto DONE;
+ }
}
DONE:
@@ -681,6 +648,55 @@
ALOGI("Done taking incident report err=%s", strerror(-err));
}
+status_t Reporter::execute_section(const Section* section, IncidentMetadata* metadata,
+ size_t* reportByteSize) {
+ const int sectionId = section->id;
+
+ // If nobody wants this section, skip it.
+ if (!mBatch->containsSection(sectionId)) {
+ return NO_ERROR;
+ }
+
+ ALOGD("Start incident report section %d '%s'", sectionId, section->name.string());
+ IncidentMetadata::SectionStats* sectionMetadata = metadata->add_sections();
+
+ // Notify listener of starting
+ mBatch->forEachListener(sectionId, [sectionId](const auto& listener) {
+ listener->onReportSectionStatus(
+ sectionId, IIncidentReportStatusListener::STATUS_STARTING);
+ });
+
+ // Go get the data and write it into the file descriptors.
+ mWriter.startSection(sectionId);
+ status_t err = section->Execute(&mWriter);
+ mWriter.endSection(sectionMetadata);
+
+ // Sections returning errors are fatal. Most errors should not be fatal.
+ if (err != NO_ERROR) {
+ mWriter.error(section, err, "Section failed. Stopping report.");
+ return err;
+ }
+
+ // The returned max data size is used for throttling too many incident reports.
+ (*reportByteSize) += sectionMetadata->report_size_bytes();
+
+ // For any requests that failed during this section, remove them now. We do this
+ // before calling back about section finished, so listeners do not erroniously get the
+ // impression that the section succeeded. But we do it here instead of inside
+ // writeSection so that the callback is done from a known context and not from the
+ // bowels of a section, where changing the batch could cause odd errors.
+ cancel_and_remove_failed_requests();
+
+ // Notify listener of finishing
+ mBatch->forEachListener(sectionId, [sectionId](const auto& listener) {
+ listener->onReportSectionStatus(
+ sectionId, IIncidentReportStatusListener::STATUS_FINISHED);
+ });
+
+ ALOGD("Finish incident report section %d '%s'", sectionId, section->name.string());
+ return NO_ERROR;
+}
+
void Reporter::cancel_and_remove_failed_requests() {
// Handle a failure in the persisted file
if (mPersistedFile != nullptr) {
diff --git a/cmds/incidentd/src/Reporter.h b/cmds/incidentd/src/Reporter.h
index fb3961a..cbc8b13 100644
--- a/cmds/incidentd/src/Reporter.h
+++ b/cmds/incidentd/src/Reporter.h
@@ -21,6 +21,7 @@
#include "frameworks/base/core/proto/android/os/metadata.pb.h"
#include <android/content/ComponentName.h>
#include <android/os/IIncidentReportStatusListener.h>
+#include <android/os/IIncidentDumpCallback.h>
#include <android/os/IncidentReportArgs.h>
#include <android/util/protobuf.h>
@@ -39,6 +40,7 @@
using namespace android::content;
using namespace android::os;
+class BringYourOwnSection;
class Section;
// ================================================================================
@@ -122,7 +124,7 @@
void forEachStreamingRequest(const function<void (const sp<ReportRequest>&)>& func);
/**
- * Call func(request) for each file descriptor that has
+ * Call func(request) for each file descriptor.
*/
void forEachFd(int sectionId, const function<void (const sp<ReportRequest>&)>& func);
@@ -251,7 +253,9 @@
// ================================================================================
class Reporter : public virtual RefBase {
public:
- Reporter(const sp<WorkDirectory>& workDirectory, const sp<ReportBatch>& batch);
+ Reporter(const sp<WorkDirectory>& workDirectory,
+ const sp<ReportBatch>& batch,
+ const vector<BringYourOwnSection*>& registeredSections);
virtual ~Reporter();
@@ -263,6 +267,10 @@
ReportWriter mWriter;
sp<ReportBatch> mBatch;
sp<ReportFile> mPersistedFile;
+ const vector<BringYourOwnSection*>& mRegisteredSections;
+
+ status_t execute_section(const Section* section, IncidentMetadata* metadata,
+ size_t* reportByteSize);
void cancel_and_remove_failed_requests();
};
diff --git a/cmds/incidentd/src/Section.cpp b/cmds/incidentd/src/Section.cpp
index c9277a5..2229e1c 100644
--- a/cmds/incidentd/src/Section.cpp
+++ b/cmds/incidentd/src/Section.cpp
@@ -267,7 +267,7 @@
signal(SIGPIPE, sigpipe_handler);
WorkerThreadData* data = (WorkerThreadData*)cookie;
- status_t err = data->section->BlockingCall(data->pipe.writeFd().get());
+ status_t err = data->section->BlockingCall(data->pipe.writeFd());
{
unique_lock<mutex> lock(data->lock);
@@ -458,7 +458,7 @@
DumpsysSection::~DumpsysSection() {}
-status_t DumpsysSection::BlockingCall(int pipeWriteFd) const {
+status_t DumpsysSection::BlockingCall(unique_fd& pipeWriteFd) const {
// checkService won't wait for the service to show up like getService will.
sp<IBinder> service = defaultServiceManager()->checkService(mService);
@@ -467,7 +467,7 @@
return NAME_NOT_FOUND;
}
- service->dump(pipeWriteFd, mArgs);
+ service->dump(pipeWriteFd.get(), mArgs);
return NO_ERROR;
}
@@ -526,7 +526,7 @@
return src[0] | (src[1] << 8) | (src[2] << 16) | (src[3] << 24);
}
-status_t LogSection::BlockingCall(int pipeWriteFd) const {
+status_t LogSection::BlockingCall(unique_fd& pipeWriteFd) const {
// Open log buffer and getting logs since last retrieved time if any.
unique_ptr<logger_list, void (*)(logger_list*)> loggers(
gLastLogsRetrieved.find(mLogID) == gLastLogsRetrieved.end()
@@ -643,7 +643,7 @@
}
}
gLastLogsRetrieved[mLogID] = lastTimestamp;
- if (!proto.flush(pipeWriteFd) && errno == EPIPE) {
+ if (!proto.flush(pipeWriteFd.get()) && errno == EPIPE) {
ALOGE("[%s] wrote to a broken pipe\n", this->name.string());
return EPIPE;
}
@@ -660,7 +660,7 @@
TombstoneSection::~TombstoneSection() {}
-status_t TombstoneSection::BlockingCall(int pipeWriteFd) const {
+status_t TombstoneSection::BlockingCall(unique_fd& pipeWriteFd) const {
std::unique_ptr<DIR, decltype(&closedir)> proc(opendir("/proc"), closedir);
if (proc.get() == nullptr) {
ALOGE("opendir /proc failed: %s\n", strerror(errno));
@@ -768,7 +768,7 @@
dumpPipe.readFd().reset();
}
- if (!proto.flush(pipeWriteFd) && errno == EPIPE) {
+ if (!proto.flush(pipeWriteFd.get()) && errno == EPIPE) {
ALOGE("[%s] wrote to a broken pipe\n", this->name.string());
if (err != NO_ERROR) {
return EPIPE;
@@ -778,6 +778,22 @@
return err;
}
+// ================================================================================
+BringYourOwnSection::BringYourOwnSection(int id, const char* customName, const uid_t callingUid,
+ const sp<IIncidentDumpCallback>& callback)
+ : WorkerThreadSection(id, REMOTE_CALL_TIMEOUT_MS), uid(callingUid), mCallback(callback) {
+ name = "registered ";
+ name += customName;
+}
+
+BringYourOwnSection::~BringYourOwnSection() {}
+
+status_t BringYourOwnSection::BlockingCall(unique_fd& pipeWriteFd) const {
+ android::os::ParcelFileDescriptor pfd(std::move(pipeWriteFd));
+ mCallback->onDumpSection(pfd);
+ return NO_ERROR;
+}
+
} // namespace incidentd
} // namespace os
} // namespace android
diff --git a/cmds/incidentd/src/Section.h b/cmds/incidentd/src/Section.h
index fcf12f7..0bb9da9 100644
--- a/cmds/incidentd/src/Section.h
+++ b/cmds/incidentd/src/Section.h
@@ -23,6 +23,8 @@
#include <stdarg.h>
#include <map>
+#include <android/os/IIncidentDumpCallback.h>
+
#include <utils/String16.h>
#include <utils/String8.h>
#include <utils/Vector.h>
@@ -89,7 +91,7 @@
virtual status_t Execute(ReportWriter* writer) const;
- virtual status_t BlockingCall(int pipeWriteFd) const = 0;
+ virtual status_t BlockingCall(unique_fd& pipeWriteFd) const = 0;
};
/**
@@ -117,7 +119,7 @@
DumpsysSection(int id, const char* service, ...);
virtual ~DumpsysSection();
- virtual status_t BlockingCall(int pipeWriteFd) const;
+ virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
private:
String16 mService;
@@ -132,7 +134,7 @@
SystemPropertyDumpsysSection(int id, const char* service, ...);
virtual ~SystemPropertyDumpsysSection();
- virtual status_t BlockingCall(int pipeWriteFd) const;
+ virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
private:
String16 mService;
@@ -153,7 +155,7 @@
LogSection(int id, const char* logID, ...);
virtual ~LogSection();
- virtual status_t BlockingCall(int pipeWriteFd) const;
+ virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
private:
log_id_t mLogID;
@@ -169,12 +171,29 @@
TombstoneSection(int id, const char* type, int64_t timeoutMs = 120000 /* 2 minutes */);
virtual ~TombstoneSection();
- virtual status_t BlockingCall(int pipeWriteFd) const;
+ virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
private:
std::string mType;
};
+/**
+ * Section that gets data from a registered dump callback.
+ */
+class BringYourOwnSection : public WorkerThreadSection {
+public:
+ const uid_t uid;
+
+ BringYourOwnSection(int id, const char* customName, const uid_t callingUid,
+ const sp<IIncidentDumpCallback>& callback);
+ virtual ~BringYourOwnSection();
+
+ virtual status_t BlockingCall(unique_fd& pipeWriteFd) const;
+
+private:
+ const sp<IIncidentDumpCallback>& mCallback;
+};
+
/**
* These sections will not be generated when doing an 'all' report, either
diff --git a/cmds/sm/src/com/android/commands/sm/Sm.java b/cmds/sm/src/com/android/commands/sm/Sm.java
index 6033655..c2ee6dc 100644
--- a/cmds/sm/src/com/android/commands/sm/Sm.java
+++ b/cmds/sm/src/com/android/commands/sm/Sm.java
@@ -103,6 +103,10 @@
runSetVirtualDisk();
} else if ("set-isolated-storage".equals(op)) {
runIsolatedStorage();
+ } else if ("start-checkpoint".equals(op)) {
+ runStartCheckpoint();
+ } else if ("supports-checkpoint".equals(op)) {
+ runSupportsCheckpoint();
} else {
throw new IllegalArgumentException();
}
@@ -313,6 +317,27 @@
}
}
+ private void runStartCheckpoint() throws RemoteException {
+ final String numRetriesString = nextArg();
+ if (numRetriesString == null) {
+ throw new IllegalArgumentException("Expected <num-retries>");
+ }
+ int numRetries;
+ try {
+ numRetries = Integer.parseInt(numRetriesString);
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("<num-retries> must be a positive integer");
+ }
+ if (numRetries <= 0) {
+ throw new IllegalArgumentException("<num-retries> must be a positive integer");
+ }
+ mSm.startCheckpoint(numRetries);
+ }
+
+ private void runSupportsCheckpoint() throws RemoteException {
+ System.out.println(mSm.supportsCheckpoint());
+ }
+
private String nextArg() {
if (mNextArg >= mArgs.length) {
return null;
@@ -344,6 +369,10 @@
System.err.println("");
System.err.println(" sm set-isolated-storage [on|off|default]");
System.err.println("");
+ System.err.println(" sm start-checkpoint <num-retries>");
+ System.err.println("");
+ System.err.println(" sm supports-checkpoint");
+ System.err.println("");
return 1;
}
}
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index 7b96ce9..1c6867c3 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -78,7 +78,6 @@
"src/external/StatsPuller.cpp",
"src/external/StatsPullerManager.cpp",
"src/external/SubsystemSleepStatePuller.cpp",
- "src/external/SurfaceflingerStatsPuller.cpp",
"src/external/TrainInfoPuller.cpp",
"src/FieldValue.cpp",
"src/guardrail/StatsdStats.cpp",
@@ -126,30 +125,28 @@
],
static_libs: [
- "libhealthhalutils",
- "libplatformprotos",
- ],
-
- shared_libs: [
"android.frameworks.stats@1.0",
- "android.hardware.health@2.0",
"android.hardware.power.stats@1.0",
"android.hardware.power@1.0",
"android.hardware.power@1.1",
"libbase",
- "libbinder",
"libcutils",
+ "libhealthhalutils",
+ "liblog",
+ "libplatformprotos",
+ "libprotoutil",
+ "libstatslog",
+ "libstatssocket",
+ "libsysutils",
+ ],
+ shared_libs: [
+ "android.hardware.health@2.0",
+ "libbinder",
"libgraphicsenv",
"libhidlbase",
"libincident",
- "liblog",
- "libprotoutil",
"libservices",
- "libstatslog",
"libstatsmetadata",
- "libstatssocket",
- "libsysutils",
- "libtimestats_proto",
"libutils",
],
}
@@ -298,7 +295,6 @@
"tests/external/puller_util_test.cpp",
"tests/external/StatsCallbackPuller_test.cpp",
"tests/external/StatsPuller_test.cpp",
- "tests/external/SurfaceflingerStatsPuller_test.cpp",
"tests/FieldValue_test.cpp",
"tests/guardrail/StatsdStats_test.cpp",
"tests/indexed_priority_queue_test.cpp",
diff --git a/cmds/statsd/OWNERS b/cmds/statsd/OWNERS
index 04464ce..a61babf 100644
--- a/cmds/statsd/OWNERS
+++ b/cmds/statsd/OWNERS
@@ -1,7 +1,8 @@
-jianjin@google.com
+jeffreyhuang@google.com
joeo@google.com
jtnguyen@google.com
muhammadq@google.com
+ruchirr@google.com
singhtejinder@google.com
tsaichristine@google.com
yaochen@google.com
diff --git a/cmds/statsd/src/FieldValue.h b/cmds/statsd/src/FieldValue.h
index 6fc1e23..967fd32 100644
--- a/cmds/statsd/src/FieldValue.h
+++ b/cmds/statsd/src/FieldValue.h
@@ -261,6 +261,11 @@
return Matcher(Field(tag, getSimpleField(field)), 0xff7f0000);
}
+inline Matcher getFirstUidMatcher(int32_t atomId) {
+ int32_t pos[] = {1, 1, 1};
+ return Matcher(Field(atomId, pos, 2), 0xff7f7f7f);
+}
+
/**
* A wrapper for a union type to contain multiple types of values.
*
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index bb3a094..ada2f2d 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -170,33 +170,32 @@
mUidMap, mPullerManager, mAnomalyAlarmMonitor, mPeriodicAlarmMonitor,
getElapsedRealtimeNs(),
[this](const ConfigKey& key) {
- sp<IStatsCompanionService> sc = getStatsCompanionService();
- auto receiver = mConfigManager->GetConfigReceiver(key);
- if (sc == nullptr) {
- VLOG("Could not find StatsCompanionService");
+ sp<IPendingIntentRef> receiver = mConfigManager->GetConfigReceiver(key);
+ if (receiver == nullptr) {
+ VLOG("Could not find a broadcast receiver for %s",
+ key.ToString().c_str());
return false;
- } else if (receiver == nullptr) {
- VLOG("Statscompanion could not find a broadcast receiver for %s",
- key.ToString().c_str());
- return false;
- } else {
- sc->sendDataBroadcast(receiver, mProcessor->getLastReportTimeNs(key));
+ } else if (receiver->sendDataBroadcast(
+ mProcessor->getLastReportTimeNs(key)).isOk()) {
return true;
+ } else {
+ VLOG("Failed to send a broadcast for receiver %s",
+ key.ToString().c_str());
+ return false;
}
},
[this](const int& uid, const vector<int64_t>& activeConfigs) {
- auto receiver = mConfigManager->GetActiveConfigsChangedReceiver(uid);
- sp<IStatsCompanionService> sc = getStatsCompanionService();
- if (sc == nullptr) {
- VLOG("Could not access statsCompanion");
- return false;
- } else if (receiver == nullptr) {
+ sp<IPendingIntentRef> receiver =
+ mConfigManager->GetActiveConfigsChangedReceiver(uid);
+ if (receiver == nullptr) {
VLOG("Could not find receiver for uid %d", uid);
return false;
- } else {
- sc->sendActiveConfigsChangedBroadcast(receiver, activeConfigs);
+ } else if (receiver->sendActiveConfigsChangedBroadcast(activeConfigs).isOk()) {
VLOG("StatsService::active configs broadcast succeeded for uid %d" , uid);
return true;
+ } else {
+ VLOG("StatsService::active configs broadcast failed for uid %d" , uid);
+ return false;
}
});
@@ -574,18 +573,19 @@
return UNKNOWN_ERROR;
}
ConfigKey key(uid, StrToInt64(name));
- auto receiver = mConfigManager->GetConfigReceiver(key);
- sp<IStatsCompanionService> sc = getStatsCompanionService();
- if (sc == nullptr) {
- VLOG("Could not access statsCompanion");
- } else if (receiver == nullptr) {
- VLOG("Could not find receiver for %s, %s", args[1].c_str(), args[2].c_str())
- } else {
- sc->sendDataBroadcast(receiver, mProcessor->getLastReportTimeNs(key));
+ sp<IPendingIntentRef> receiver = mConfigManager->GetConfigReceiver(key);
+ if (receiver == nullptr) {
+ VLOG("Could not find receiver for %s, %s", args[1].c_str(), args[2].c_str());
+ return UNKNOWN_ERROR;
+ } else if (receiver->sendDataBroadcast(
+ mProcessor->getLastReportTimeNs(key)).isOk()) {
VLOG("StatsService::trigger broadcast succeeded to %s, %s", args[1].c_str(),
args[2].c_str());
+ } else {
+ VLOG("StatsService::trigger broadcast failed to %s, %s", args[1].c_str(),
+ args[2].c_str());
+ return UNKNOWN_ERROR;
}
-
return NO_ERROR;
}
@@ -629,15 +629,15 @@
}
}
}
- auto receiver = mConfigManager->GetActiveConfigsChangedReceiver(uid);
- sp<IStatsCompanionService> sc = getStatsCompanionService();
- if (sc == nullptr) {
- VLOG("Could not access statsCompanion");
- } else if (receiver == nullptr) {
+ sp<IPendingIntentRef> receiver = mConfigManager->GetActiveConfigsChangedReceiver(uid);
+ if (receiver == nullptr) {
VLOG("Could not find receiver for uid %d", uid);
- } else {
- sc->sendActiveConfigsChangedBroadcast(receiver, configIds);
+ return UNKNOWN_ERROR;
+ } else if (receiver->sendActiveConfigsChangedBroadcast(configIds).isOk()) {
VLOG("StatsService::trigger active configs changed broadcast succeeded for uid %d" , uid);
+ } else {
+ VLOG("StatsService::trigger active configs changed broadcast failed for uid %d", uid);
+ return UNKNOWN_ERROR;
}
return NO_ERROR;
}
@@ -1111,7 +1111,6 @@
mPullerManager->SetStatsCompanionService(statsCompanion);
mAnomalyAlarmMonitor->setStatsCompanionService(statsCompanion);
mPeriodicAlarmMonitor->setStatsCompanionService(statsCompanion);
- SubscriberReporter::getInstance().setStatsCompanionService(statsCompanion);
return Status::ok();
}
@@ -1136,12 +1135,11 @@
}
}
-Status StatsService::getData(int64_t key, const String16& packageName, vector<uint8_t>* output) {
- ENFORCE_DUMP_AND_USAGE_STATS(packageName);
+Status StatsService::getData(int64_t key, const int32_t callingUid, vector<uint8_t>* output) {
+ ENFORCE_UID(AID_SYSTEM);
- IPCThreadState* ipc = IPCThreadState::self();
- VLOG("StatsService::getData with Pid %i, Uid %i", ipc->getCallingPid(), ipc->getCallingUid());
- ConfigKey configKey(ipc->getCallingUid(), key);
+ VLOG("StatsService::getData with Uid %i", callingUid);
+ ConfigKey configKey(callingUid, key);
// The dump latency does not matter here since we do not include the current bucket, we do not
// need to pull any new data anyhow.
mProcessor->onDumpReport(configKey, getElapsedRealtimeNs(), false /* include_current_bucket*/,
@@ -1149,22 +1147,18 @@
return Status::ok();
}
-Status StatsService::getMetadata(const String16& packageName, vector<uint8_t>* output) {
- ENFORCE_DUMP_AND_USAGE_STATS(packageName);
+Status StatsService::getMetadata(vector<uint8_t>* output) {
+ ENFORCE_UID(AID_SYSTEM);
- IPCThreadState* ipc = IPCThreadState::self();
- VLOG("StatsService::getMetadata with Pid %i, Uid %i", ipc->getCallingPid(),
- ipc->getCallingUid());
StatsdStats::getInstance().dumpStats(output, false); // Don't reset the counters.
return Status::ok();
}
Status StatsService::addConfiguration(int64_t key, const vector <uint8_t>& config,
- const String16& packageName) {
- ENFORCE_DUMP_AND_USAGE_STATS(packageName);
+ const int32_t callingUid) {
+ ENFORCE_UID(AID_SYSTEM);
- IPCThreadState* ipc = IPCThreadState::self();
- if (addConfigurationChecked(ipc->getCallingUid(), key, config)) {
+ if (addConfigurationChecked(callingUid, key, config)) {
return Status::ok();
} else {
ALOGE("Could not parse malformatted StatsdConfig");
@@ -1185,23 +1179,21 @@
return true;
}
-Status StatsService::removeDataFetchOperation(int64_t key, const String16& packageName) {
- ENFORCE_DUMP_AND_USAGE_STATS(packageName);
-
- IPCThreadState* ipc = IPCThreadState::self();
- ConfigKey configKey(ipc->getCallingUid(), key);
+Status StatsService::removeDataFetchOperation(int64_t key,
+ const int32_t callingUid) {
+ ENFORCE_UID(AID_SYSTEM);
+ ConfigKey configKey(callingUid, key);
mConfigManager->RemoveConfigReceiver(configKey);
return Status::ok();
}
Status StatsService::setDataFetchOperation(int64_t key,
- const sp<android::IBinder>& intentSender,
- const String16& packageName) {
- ENFORCE_DUMP_AND_USAGE_STATS(packageName);
+ const sp<IPendingIntentRef>& pir,
+ const int32_t callingUid) {
+ ENFORCE_UID(AID_SYSTEM);
- IPCThreadState* ipc = IPCThreadState::self();
- ConfigKey configKey(ipc->getCallingUid(), key);
- mConfigManager->SetConfigReceiver(configKey, intentSender);
+ ConfigKey configKey(callingUid, key);
+ mConfigManager->SetConfigReceiver(configKey, pir);
if (StorageManager::hasConfigMetricsReport(configKey)) {
VLOG("StatsService::setDataFetchOperation marking configKey %s to dump reports on disk",
configKey.ToString().c_str());
@@ -1210,62 +1202,55 @@
return Status::ok();
}
-Status StatsService::setActiveConfigsChangedOperation(const sp<android::IBinder>& intentSender,
- const String16& packageName,
+Status StatsService::setActiveConfigsChangedOperation(const sp<IPendingIntentRef>& pir,
+ const int32_t callingUid,
vector<int64_t>* output) {
- ENFORCE_DUMP_AND_USAGE_STATS(packageName);
+ ENFORCE_UID(AID_SYSTEM);
- IPCThreadState* ipc = IPCThreadState::self();
- int uid = ipc->getCallingUid();
- mConfigManager->SetActiveConfigsChangedReceiver(uid, intentSender);
+ mConfigManager->SetActiveConfigsChangedReceiver(callingUid, pir);
if (output != nullptr) {
- mProcessor->GetActiveConfigs(uid, *output);
+ mProcessor->GetActiveConfigs(callingUid, *output);
} else {
ALOGW("StatsService::setActiveConfigsChanged output was nullptr");
}
return Status::ok();
}
-Status StatsService::removeActiveConfigsChangedOperation(const String16& packageName) {
- ENFORCE_DUMP_AND_USAGE_STATS(packageName);
+Status StatsService::removeActiveConfigsChangedOperation(const int32_t callingUid) {
+ ENFORCE_UID(AID_SYSTEM);
- IPCThreadState* ipc = IPCThreadState::self();
- mConfigManager->RemoveActiveConfigsChangedReceiver(ipc->getCallingUid());
+ mConfigManager->RemoveActiveConfigsChangedReceiver(callingUid);
return Status::ok();
}
-Status StatsService::removeConfiguration(int64_t key, const String16& packageName) {
- ENFORCE_DUMP_AND_USAGE_STATS(packageName);
+Status StatsService::removeConfiguration(int64_t key, const int32_t callingUid) {
+ ENFORCE_UID(AID_SYSTEM);
- IPCThreadState* ipc = IPCThreadState::self();
- ConfigKey configKey(ipc->getCallingUid(), key);
+ ConfigKey configKey(callingUid, key);
mConfigManager->RemoveConfig(configKey);
- SubscriberReporter::getInstance().removeConfig(configKey);
return Status::ok();
}
Status StatsService::setBroadcastSubscriber(int64_t configId,
int64_t subscriberId,
- const sp<android::IBinder>& intentSender,
- const String16& packageName) {
- ENFORCE_DUMP_AND_USAGE_STATS(packageName);
+ const sp<IPendingIntentRef>& pir,
+ const int32_t callingUid) {
+ ENFORCE_UID(AID_SYSTEM);
VLOG("StatsService::setBroadcastSubscriber called.");
- IPCThreadState* ipc = IPCThreadState::self();
- ConfigKey configKey(ipc->getCallingUid(), configId);
+ ConfigKey configKey(callingUid, configId);
SubscriberReporter::getInstance()
- .setBroadcastSubscriber(configKey, subscriberId, intentSender);
+ .setBroadcastSubscriber(configKey, subscriberId, pir);
return Status::ok();
}
Status StatsService::unsetBroadcastSubscriber(int64_t configId,
int64_t subscriberId,
- const String16& packageName) {
- ENFORCE_DUMP_AND_USAGE_STATS(packageName);
+ const int32_t callingUid) {
+ ENFORCE_UID(AID_SYSTEM);
VLOG("StatsService::unsetBroadcastSubscriber called.");
- IPCThreadState* ipc = IPCThreadState::self();
- ConfigKey configKey(ipc->getCallingUid(), configId);
+ ConfigKey configKey(callingUid, configId);
SubscriberReporter::getInstance()
.unsetBroadcastSubscriber(configKey, subscriberId);
return Status::ok();
@@ -1327,6 +1312,13 @@
return Status::ok();
}
+Status StatsService::unregisterNativePullAtomCallback(int32_t atomTag) {
+ VLOG("StatsService::unregisterNativePullAtomCallback called.");
+ int32_t uid = IPCThreadState::self()->getCallingUid();
+ mPullerManager->UnregisterPullAtomCallback(uid, atomTag);
+ return Status::ok();
+}
+
Status StatsService::sendBinaryPushStateChangedAtom(const android::String16& trainNameIn,
const int64_t trainVersionCodeIn,
const int options,
@@ -1485,17 +1477,7 @@
Status StatsService::getRegisteredExperimentIds(std::vector<int64_t>* experimentIdsOut) {
- uid_t uid = IPCThreadState::self()->getCallingUid();
-
- // Caller must be granted these permissions
- if (!checkCallingPermission(String16(kPermissionDump))) {
- return exception(binder::Status::EX_SECURITY,
- StringPrintf("UID %d lacks permission %s", uid, kPermissionDump));
- }
- if (!checkCallingPermission(String16(kPermissionUsage))) {
- return exception(binder::Status::EX_SECURITY,
- StringPrintf("UID %d lacks permission %s", uid, kPermissionUsage));
- }
+ ENFORCE_UID(AID_SYSTEM);
// TODO: add verifier permission
// Read the latest train info
@@ -1631,7 +1613,6 @@
}
mAnomalyAlarmMonitor->setStatsCompanionService(nullptr);
mPeriodicAlarmMonitor->setStatsCompanionService(nullptr);
- SubscriberReporter::getInstance().setStatsCompanionService(nullptr);
mPullerManager->SetStatsCompanionService(nullptr);
}
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index de55ca9..7990e5e 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -30,6 +30,7 @@
#include <android/frameworks/stats/1.0/IStats.h>
#include <android/frameworks/stats/1.0/types.h>
#include <android/os/BnStatsd.h>
+#include <android/os/IPendingIntentRef.h>
#include <android/os/IStatsCompanionService.h>
#include <android/os/IStatsd.h>
#include <binder/IResultReceiver.h>
@@ -98,15 +99,14 @@
* Binder call for clients to request data for this configuration key.
*/
virtual Status getData(int64_t key,
- const String16& packageName,
+ const int32_t callingUid,
vector<uint8_t>* output) override;
/**
* Binder call for clients to get metadata across all configs in statsd.
*/
- virtual Status getMetadata(const String16& packageName,
- vector<uint8_t>* output) override;
+ virtual Status getMetadata(vector<uint8_t>* output) override;
/**
@@ -115,53 +115,52 @@
*/
virtual Status addConfiguration(int64_t key,
const vector<uint8_t>& config,
- const String16& packageName) override;
+ const int32_t callingUid) override;
/**
* Binder call to let clients register the data fetch operation for a configuration.
*/
virtual Status setDataFetchOperation(int64_t key,
- const sp<android::IBinder>& intentSender,
- const String16& packageName) override;
+ const sp<IPendingIntentRef>& pir,
+ const int32_t callingUid) override;
/**
* Binder call to remove the data fetch operation for the specified config key.
*/
virtual Status removeDataFetchOperation(int64_t key,
- const String16& packageName) override;
+ const int32_t callingUid) override;
/**
* Binder call to let clients register the active configs changed operation.
*/
- virtual Status setActiveConfigsChangedOperation(const sp<android::IBinder>& intentSender,
- const String16& packageName,
+ virtual Status setActiveConfigsChangedOperation(const sp<IPendingIntentRef>& pir,
+ const int32_t callingUid,
vector<int64_t>* output) override;
/**
* Binder call to remove the active configs changed operation for the specified package..
*/
- virtual Status removeActiveConfigsChangedOperation(const String16& packageName) override;
+ virtual Status removeActiveConfigsChangedOperation(const int32_t callingUid) override;
/**
* Binder call to allow clients to remove the specified configuration.
*/
virtual Status removeConfiguration(int64_t key,
- const String16& packageName) override;
+ const int32_t callingUid) override;
/**
- * Binder call to associate the given config's subscriberId with the given intentSender.
- * intentSender must be convertible into an IntentSender (in Java) using IntentSender(IBinder).
+ * Binder call to associate the given config's subscriberId with the given pendingIntentRef.
*/
virtual Status setBroadcastSubscriber(int64_t configId,
int64_t subscriberId,
- const sp<android::IBinder>& intentSender,
- const String16& packageName) override;
+ const sp<IPendingIntentRef>& pir,
+ const int32_t callingUid) override;
/**
- * Binder call to unassociate the given config's subscriberId with any intentSender.
+ * Binder call to unassociate the given config's subscriberId with any pendingIntentRef.
*/
virtual Status unsetBroadcastSubscriber(int64_t configId,
int64_t subscriberId,
- const String16& packageName) override;
+ const int32_t callingUid) override;
/** Inform statsCompanion that statsd is ready. */
virtual void sayHiToStatsCompanion();
@@ -204,6 +203,11 @@
virtual Status unregisterPullAtomCallback(int32_t uid, int32_t atomTag) override;
/**
+ * Binder call to unregister any existing callback for the given atom and calling uid.
+ */
+ virtual Status unregisterNativePullAtomCallback(int32_t atomTag) override;
+
+ /**
* Binder call to log BinaryPushStateChanged atom.
*/
virtual Status sendBinaryPushStateChangedAtom(
diff --git a/cmds/statsd/src/atom_field_options.proto b/cmds/statsd/src/atom_field_options.proto
index 6d2bd04..946c550 100644
--- a/cmds/statsd/src/atom_field_options.proto
+++ b/cmds/statsd/src/atom_field_options.proto
@@ -30,6 +30,8 @@
PRIMARY = 1;
// The field that represents the state. It's an exclusive state.
EXCLUSIVE = 2;
+
+ PRIMARY_FIELD_FIRST_UID = 3;
}
// Used to annotate an atom that reprsents a state change. A state change atom must have exactly ONE
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 19b9709..4372e22 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -53,6 +53,7 @@
import "frameworks/base/core/proto/android/stats/mediaprovider/mediaprovider_enums.proto";
import "frameworks/base/core/proto/android/stats/storage/storage_enums.proto";
import "frameworks/base/core/proto/android/stats/style/style_enums.proto";
+import "frameworks/base/core/proto/android/stats/sysui/notification_enums.proto";
import "frameworks/base/core/proto/android/telecomm/enums.proto";
import "frameworks/base/core/proto/android/telephony/enums.proto";
import "frameworks/base/core/proto/android/view/enums.proto";
@@ -126,10 +127,10 @@
AppStartOccurred app_start_occurred = 48;
AppStartCanceled app_start_canceled = 49;
AppStartFullyDrawn app_start_fully_drawn = 50;
- LmkKillOccurred lmk_kill_occurred = 51;
+ LmkKillOccurred lmk_kill_occurred = 51 [(module) = "lmkd"];
PictureInPictureStateChanged picture_in_picture_state_changed = 52;
WifiMulticastLockStateChanged wifi_multicast_lock_state_changed = 53 [(module) = "wifi"];
- LmkStateChanged lmk_state_changed = 54;
+ LmkStateChanged lmk_state_changed = 54 [(module) = "lmkd"];
AppStartMemoryStateCaptured app_start_memory_state_captured = 55;
ShutdownSequenceReported shutdown_sequence_reported = 56;
BootSequenceReported boot_sequence_reported = 57;
@@ -324,8 +325,6 @@
228 [(allow_from_any_uid) = true];
PerfettoUploaded perfetto_uploaded = 229 [(module) = "perfetto"];
VmsClientConnectionStateChanged vms_client_connection_state_changed = 230;
- GpsLocationStatusReported gps_location_status_reported = 231;
- GpsTimeToFirstFixReported gps_time_to_first_fix_reported = 232;
MediaProviderScanEvent media_provider_scan_event = 233 [(module) = "mediaprovider"];
MediaProviderDeletionEvent media_provider_deletion_event = 234 [(module) = "mediaprovider"];
MediaProviderPermissionEvent media_provider_permission_event =
@@ -333,10 +332,19 @@
MediaProviderSchemaChange media_provider_schema_change = 236 [(module) = "mediaprovider"];
MediaProviderIdleMaintenance media_provider_idle_maintenance =
237 [(module) = "mediaprovider"];
+ RebootEscrowRecoveryReported reboot_escrow_recovery_reported = 238;
+ BootTimeEventDuration boot_time_event_duration_reported = 239;
+ BootTimeEventElapsedTime boot_time_event_elapsed_time_reported = 240;
+ BootTimeEventUtcTime boot_time_event_utc_time_reported = 241;
+ BootTimeEventErrorCode boot_time_event_error_code_reported = 242;
+ UserspaceRebootReported userspace_reboot_reported = 243;
+ NotificationReported notification_reported = 244;
+ NotificationPanelReported notification_panel_reported = 245;
+ NotificationChannelModified notification_panel_modified = 246;
}
// Pulled events will start at field 10000.
- // Next: 10068
+ // Next: 10071
oneof pulled {
WifiBytesTransfer wifi_bytes_transfer = 10000;
WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001;
@@ -393,7 +401,7 @@
ExternalStorageInfo external_storage_info = 10053;
GpuStatsGlobalInfo gpu_stats_global_info = 10054;
GpuStatsAppInfo gpu_stats_app_info = 10055;
- SystemIonHeapSize system_ion_heap_size = 10056;
+ SystemIonHeapSize system_ion_heap_size = 10056 [deprecated = true];
AppsOnExternalStorageInfo apps_on_external_storage_info = 10057;
FaceSettings face_settings = 10058;
CoolingDevice cooling_device = 10059;
@@ -405,6 +413,9 @@
VmsClientStats vms_client_stats = 10065;
NotificationRemoteViews notification_remote_views = 10066;
DangerousPermissionStateSampled dangerous_permission_state_sampled = 10067;
+ GraphicsStats graphics_stats = 10068;
+ RuntimeAppOpsAccess runtime_app_ops_access = 10069;
+ IonHeapSize ion_heap_size = 10070;
}
// DO NOT USE field numbers above 100,000 in AOSP.
@@ -729,27 +740,6 @@
optional android.server.location.GpsSignalQualityEnum level = 1;
}
-/**
- * Gps location status report
- *
- * Logged from:
- * /frameworks/base/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java
- */
-message GpsLocationStatusReported {
- // Boolean stating if location was acquired
- optional bool location_success = 1;
-}
-
-/**
- * Gps log time to first fix report
- *
- * Logged from:
- * /frameworks/base/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java
- */
-message GpsTimeToFirstFixReported {
- // int32 reporting the time to first fix in milliseconds
- optional int32 time_to_first_fix_millis = 1;
-}
/**
* Logs when a sync manager sync state changes.
@@ -906,14 +896,16 @@
* TODO
*/
message WakelockStateChanged {
- repeated AttributionNode attribution_node = 1;
+ repeated AttributionNode attribution_node = 1
+ [(state_field_option).option = PRIMARY_FIELD_FIRST_UID];
// The type (level) of the wakelock; e.g. a partial wakelock or a full wakelock.
// From frameworks/base/core/proto/android/os/enums.proto.
- optional android.os.WakeLockLevelEnum type = 2;
+ optional android.os.WakeLockLevelEnum type = 2 [(state_field_option).option = PRIMARY];
+ ;
// The wakelock tag (Called tag in the Java API, sometimes name elsewhere).
- optional string tag = 3;
+ optional string tag = 3 [(state_field_option).option = PRIMARY];
enum State {
RELEASE = 0;
@@ -921,7 +913,7 @@
CHANGE_RELEASE = 2;
CHANGE_ACQUIRE = 3;
}
- optional State state = 4;
+ optional State state = 4 [(state_field_option).option = EXCLUSIVE];
}
/**
@@ -3299,6 +3291,10 @@
* this button" or "this dialog was displayed".
* Keep the UI event stream clean: don't use for system or background events.
* Log using the UiEventLogger wrapper - don't write with the StatsLog API directly.
+ *
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/
+ * frameworks/base/packages/SystemUI/src/com/android/systemui/
*/
message UiEventReported {
// The event_id.
@@ -3310,6 +3306,122 @@
}
/**
+ * Reports a notification was created or updated.
+ *
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/notification/
+ */
+message NotificationReported {
+ // The event_id (as for UiEventReported).
+ optional int32 event_id = 1;
+ // The notifying app's uid and package.
+ optional int32 uid = 2 [(is_uid) = true];
+ optional string package_name = 3;
+ // A small system-assigned identifier for the notification.
+ // Locally probably-unique, but expect collisions across users and/or days.
+ optional int32 instance_id = 4;
+ // The app-assigned notification ID and tag
+ optional int32 notification_id = 5;
+ optional string notification_tag = 6;
+ optional string channel_id = 7; // App-assigned channel ID
+
+ // Grouping information
+ optional string group_id = 8; // Group the notification currently belongs to
+ optional int32 group_instance_id = 9; // Instance_id of the group-summary notification
+ optional bool is_group_summary = 10; // Tags the group-summary notification
+
+ // Attributes
+ optional string category = 11; // App-assigned notification category (API-defined strings)
+ optional int32 style = 12; // App-assigned notification style
+ optional int32 num_people = 13; // Number of Person records attached to the notification
+
+ // Ordering, importance and interruptiveness
+
+ optional int32 position = 14; // Position in NotificationManager's list
+
+ optional android.stats.sysui.NotificationImportance importance = 15;
+ optional int32 alerting = 16; // Bitfield, 1=buzz 2=beep 4=blink
+
+ enum NotificationImportanceExplanation {
+ IMPORTANCE_EXPLANATION_UNKNOWN = 0;
+ IMPORTANCE_EXPLANATION_APP = 1; // App-specified channel importance.
+ IMPORTANCE_EXPLANATION_USER = 2; // User-specified channel importance.
+ IMPORTANCE_EXPLANATION_ASST = 3; // Notification Assistant override.
+ IMPORTANCE_EXPLANATION_SYSTEM = 4; // System override.
+ // Like _APP, but based on pre-channels priority signal.
+ IMPORTANCE_EXPLANATION_APP_PRE_CHANNELS = 5;
+ }
+
+ optional NotificationImportanceExplanation importance_source = 17;
+ optional android.stats.sysui.NotificationImportance importance_initial = 18;
+ optional NotificationImportanceExplanation importance_initial_source = 19;
+ optional android.stats.sysui.NotificationImportance importance_asst = 20;
+ optional int32 assistant_hash = 21;
+ optional float assistant_ranking_score = 22;
+}
+
+message Notification {
+ // The notifying app's uid and package.
+ optional int32 uid = 1 [(is_uid) = true];
+ optional string package_name = 2;
+ // A small system-assigned identifier for the notification.
+ optional int32 instance_id = 3;
+
+ // Grouping information.
+ optional int32 group_instance_id = 4;
+ optional bool is_group_summary = 5;
+
+ // The section of the shade that the notification is in.
+ // See NotificationSectionsManager.PriorityBucket.
+ enum NotificationSection {
+ SECTION_UNKNOWN = 0;
+ SECTION_PEOPLE = 1;
+ SECTION_ALERTING = 2;
+ SECTION_SILENT = 3;
+ }
+ optional NotificationSection section = 6;
+}
+
+message NotificationList {
+ repeated Notification notifications = 1; // An ordered sequence of notifications.
+}
+
+/**
+ * Reports a notification panel was displayed, e.g. from the lockscreen or status bar.
+ *
+ * Logged from:
+ * frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/notification/
+ */
+message NotificationPanelReported {
+ // The event_id (as for UiEventReported).
+ optional int32 event_id = 1;
+ optional int32 num_notifications = 2;
+ // The notifications in the panel, in the order that they appear there.
+ optional NotificationList notifications = 3 [(log_mode) = MODE_BYTES];
+}
+
+/**
+ * Reports a notification channel, or channel group, was created, updated, or deleted.
+ *
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/notification/
+ */
+message NotificationChannelModified {
+ // The event_id (as for UiEventReported).
+ optional int32 event_id = 1;
+ // The notifying app's uid and package.
+ optional int32 uid = 2 [(is_uid) = true];
+ optional string package_name = 3;
+ // App-assigned notification channel ID or channel-group ID
+ optional string channel_id = 4;
+ // Previous importance setting, if applicable
+ optional android.stats.sysui.NotificationImportance old_importance = 5;
+ // New importance setting
+ optional android.stats.sysui.NotificationImportance importance = 6;
+}
+
+
+/**
* Logs when a biometric acquire event occurs.
*
* Logged from:
@@ -3497,12 +3609,14 @@
INSTALL_FAILURE_DOWNLOAD = 23;
INSTALL_FAILURE_STATE_MISMATCH = 24;
INSTALL_FAILURE_COMMIT = 25;
+ REBOOT_TRIGGERED = 26;
}
optional State state = 6;
// Possible experiment ids for monitoring this push.
optional TrainExperimentIds experiment_ids = 7 [(log_mode) = MODE_BYTES];
// user id
optional int32 user_id = 8;
+ optional int32 reason = 9;
}
/* Test atom, is not logged anywhere */
@@ -3923,6 +4037,207 @@
optional float normalized_expired_media = 5;
}
+/**
+ * Represents boot time event with duration in ms.
+ *
+ * Logged from: bootstat and various system server components. Check each enums for details.
+ */
+message BootTimeEventDuration {
+ enum DurationEvent {
+ UNKNOWN = 0;
+ // Bootloader time excluding BOOTLOADER_UI_WAIT + boot complete time. Logged from bootstat.
+ ABSOLUTE_BOOT_TIME = 1;
+ // Bootloader's 1st stage execution time.
+ // Logged from bootstat.
+ BOOTLOADER_FIRST_STAGE_EXEC = 2;
+ // Bootloader's 1st stage loading time.
+ // Logged from bootstat.
+ BOOTLOADER_FIRST_STAGE_LOAD = 3;
+ // Bootloader's kernel loading time.
+ // Logged from bootstat.
+ BOOTLOADER_KERNEL_LOAD = 4;
+ // Bootloader's 2nd stage execution time.
+ // Logged from bootstat.
+ BOOTLOADER_SECOND_STAGE_EXEC = 5;
+ // Bootloader's 2nd stage loading time.
+ // Logged from bootstat.
+ BOOTLOADER_SECOND_STAGE_LOAD = 6;
+ // Duration for Bootloader to show unlocked device's warning UI. This should not happen
+ // for locked device.
+ // Logged from bootstat.
+ BOOTLOADER_UI_WAIT = 7;
+ // Total time spend in bootloader. This is the sum of all BOOTLOADER_* listed above.
+ // Logged from bootstat.
+ BOOTLOADER_TOTAL = 8;
+ // Shutdown duration inside init for the reboot before the current boot up.
+ // Logged from f/b/services/.../BootReceiver.java.
+ SHUTDOWN_DURATION = 9;
+ // Total time for mounting of disk devices during bootup.
+ // Logged from f/b/services/.../BootReceiver.java.
+ MOUNT_DEFAULT_DURATION = 10;
+ // Total time for early stage mounting of disk devices during bootup.
+ // Logged from f/b/services/.../BootReceiver.java.
+ MOUNT_EARLY_DURATION = 11;
+ // Total time for late stage mounting of disk devices during bootup.
+ // Logged from f/b/services/.../BootReceiver.java.
+ MOUNT_LATE_DURATION = 12;
+ // Average time to scan non-system app after OTA
+ // Logged from f/b/services/.../PackageManagerService.java
+ OTA_PACKAGE_MANAGER_INIT_TIME = 13;
+ // Time to initialize Package manager after OTA
+ // Logged from f/b/services/.../PackageManagerService.java
+ OTA_PACKAGE_MANAGER_DATA_APP_AVG_SCAN_TIME = 14;
+ // Time to scan all system app from Package manager after OTA
+ // Logged from f/b/services/.../PackageManagerService.java
+ OTA_PACKAGE_MANAGER_SYSTEM_APP_AVG_SCAN_TIME = 15;
+ // Init's total time for cold boot stage.
+ // Logged from bootstat.
+ COLDBOOT_WAIT = 16;
+ // Init's total time for initializing selinux.
+ // Logged from bootstat.
+ SELINUX_INIT = 17;
+ // Time since last factory reset.
+ // Logged from bootstat.
+ FACTORY_RESET_TIME_SINCE_RESET = 18;
+ }
+
+ // Type of the event.
+ optional DurationEvent event = 1;
+ // Duration of the event in ms.
+ optional int64 duration_millis = 2;
+}
+
+/**
+ * Represents the start of specific boot time event during bootup in ms. This is usually a time
+ * since boot-up.
+ *
+ * Logged from: bootstat and various system server components. Check each enums for details.
+ */
+message BootTimeEventElapsedTime {
+ enum ElapsedTimeEvent {
+ UNKNOWN = 0;
+ // Time when init starts 1st stage. Logged from bootstat.
+ ANDROID_INIT_STAGE_1 = 1;
+ // Time when sys.boot_completed prop is set.
+ // Logged from bootstat.
+ BOOT_COMPLETE = 2;
+ // BOOT_COMPLETE for encrypted device.
+ BOOT_COMPLETE_ENCRYPTION = 3;
+ // BOOT_COMPLETE for device with no encryption.
+ BOOT_COMPLETE_NO_ENCRYPTION = 4;
+ // Adjusted BOOT_COMPLETE for encrypted device extracting decryption time.
+ BOOT_COMPLETE_POST_DESCRYPT = 5;
+ // BOOT_COMPLETE after factory reset.
+ FACTORY_RESET_BOOT_COMPLETE = 6;
+ // BOOT_COMPLETE_NO_ENCRYPTION after factory reset.
+ FACTORY_RESET_BOOT_COMPLETE_NO_ENCRYPTION = 7;
+ // BOOT_COMPLETE_POST_DESCRYPT after factory reset.
+ FACTORY_RESET_BOOT_COMPLETE_POST_DESCRYPT = 8;
+ // BOOT_COMPLETE after OTA.
+ OTA_BOOT_COMPLETE = 9;
+ // BOOT_COMPLETE_NO_ENCRYPTION after OTA.
+ OTA_BOOT_COMPLETE_NO_ENCRYPTION = 10;
+ // BOOT_COMPLETE_POST_DESCRYPT after OTA.
+ OTA_BOOT_COMPLETE_POST_DESCRYPT = 11;
+ // Time when the system starts sending LOCKED_BOOT_COMPLETED broadcast.
+ // Logged from f/b/services/.../UserController.java
+ FRAMEWORK_LOCKED_BOOT_COMPLETED = 12;
+ // Time when the system starts sending BOOT_COMPLETED broadcast.
+ // Logged from f/b/services/.../UserController.java
+ FRAMEWORK_BOOT_COMPLETED = 13;
+ // Time when the package manager starts init.
+ // Logged from f/b/services/.../SystemServer.java
+ PACKAGE_MANAGER_INIT_START = 14;
+ // Time when package manager is ready
+ // Logged from f/b/services/.../SystemServer.java
+ PACKAGE_MANAGER_INIT_READY = 15;
+ // Represents the time when user has entered unlock credential for system with user pin.
+ // Logged from bootstat.
+ POST_DECRYPT = 16;
+ // Represents the start of zygote's init.
+ // Logged from zygote itself.
+ ZYGOTE_INIT_START = 17;
+ // Represents the start of secondary zygote's init.
+ // TODO: add logging to zygote
+ SECONDARY_ZYGOTE_INIT_START = 18;
+ // Represents the start of system server's init.
+ // Logged from f/b/services/.../SystemServer.java
+ SYSTEM_SERVER_INIT_START = 19;
+ // Represents the completion of system server's init.
+ // Logged from f/b/services/.../SystemServer.java
+ SYSTEM_SERVER_READY = 20;
+ // Represents the start of launcher during boot-up.
+ // TODO: add logging
+ LAUNCHER_START = 21;
+ // Represents the completion of launcher's initial rendering. User can use other apps from
+ // launcher from this point.
+ // TODO: add logging
+ LAUNCHER_SHOWN = 22;
+ }
+
+ // Type of the event.
+ optional ElapsedTimeEvent event = 1;
+ // Time since bootup for the event.
+ // It should be acquired from SystemClock elapsedRealtime() call or equivalent.
+ optional int64 time_millis = 2;
+}
+
+/**
+ * Boot time events with UTC time.
+ *
+ * Logged from: bootstat and various system server components. Check each enums for details.
+ */
+message BootTimeEventUtcTime {
+ enum UtcTimeEvent {
+ UNKNOWN = 0;
+ // Time of the bootstat's marking of 1st boot after the last factory reset.
+ // Logged from bootstat.
+ FACTORY_RESET_RESET_TIME = 1;
+ // The time when bootstat records FACTORY_RESET_* events. This is close to
+ // BOOT_COMPLETE time for the current bootup.
+ // Logged from bootstat.
+ FACTORY_RESET_CURRENT_TIME = 2;
+ // DUplicate of FACTORY_RESET_RESET_TIME added for debugging purpose.
+ // Logged from bootstat.
+ FACTORY_RESET_RECORD_VALUE = 3;
+ }
+
+ // Type of the event.
+ optional UtcTimeEvent event = 1;
+ // UTC time for the event.
+ optional int64 utc_time_secs = 2;
+}
+
+/**
+ * Boot time events representing specific error code during bootup.
+ * Meaning of error code can be different per each event type.
+ *
+ * Logged from: bootstat and various system server components. Check each enums for details.
+ */
+message BootTimeEventErrorCode {
+ enum ErrorCodeEvent {
+ UNKNOWN = 0;
+ // Linux error code for time() call to get the current UTC time.
+ // Logged from bootstat.
+ FACTORY_RESET_CURRENT_TIME_FAILURE = 1;
+ // Represents UmountStat before the reboot for the current boot up. Error codes defined
+ // as UMOUNT_STAT_* from init/reboot.cpp.
+ // Logged from f/b/services/.../BootReceiver.java.
+ SHUTDOWN_UMOUNT_STAT = 2;
+ // Reprepsents fie system mounting error code of /data partition for the current boot.
+ // Error codes defined as combination of FsStatFlags from system/core/fs_mgr/fs_mgr.cpp.
+ // Logged from f/b/services/.../BootReceiver.java.
+ FS_MGR_FS_STAT_DATA_PARTITION = 3;
+ }
+
+ // Type of the event.
+ optional ErrorCodeEvent event = 1;
+ // error code defined per each event type.
+ // For example, this can have a value of FsStatFlags.FS_STAT_FULL_MOUNT_FAILED for the event of
+ // FS_MGR_FS_STAT.
+ optional int32 error_code = 2;
+}
+
//////////////////////////////////////////////////////////////////////
// Pulled atoms below this line //
//////////////////////////////////////////////////////////////////////
@@ -4723,36 +5038,69 @@
}
message AggStats {
- optional int64 min = 1;
+ // These are all in byte resolution.
+ optional int64 min = 1 [deprecated = true];
+ optional int64 average = 2 [deprecated = true];
+ optional int64 max = 3 [deprecated = true];
- optional int64 average = 2;
-
- optional int64 max = 3;
+ // These are all in kilobyte resolution. Can fit in int32, so smaller on the wire than the above
+ // int64 fields.
+ optional int32 mean_kb = 4;
+ optional int32 max_kb = 5;
}
+// A reduced subset of process states; reducing the number of possible states allows more
+// aggressive device-side aggregation of statistics and hence reduces metric upload size.
+enum ProcessStateAggregated {
+ PROCESS_STATE_UNKNOWN = 0;
+ // Persistent system process.
+ PROCESS_STATE_PERSISTENT = 1;
+ // Top activity; actually any visible activity.
+ PROCESS_STATE_TOP = 2;
+ // Process binding to top or a foreground service.
+ PROCESS_STATE_BOUND_TOP_OR_FGS = 3;
+ // Processing running a foreground service.
+ PROCESS_STATE_FGS = 4;
+ // Important foreground process (ime, wallpaper, etc).
+ PROCESS_STATE_IMPORTANT_FOREGROUND = 5;
+ // Important background process.
+ PROCESS_STATE_BACKGROUND = 6;
+ // Process running a receiver.
+ PROCESS_STATE_RECEIVER = 7;
+ // All kinds of cached processes.
+ PROCESS_STATE_CACHED = 8;
+}
+
+// Next tag: 13
message ProcessStatsStateProto {
optional android.service.procstats.ScreenState screen_state = 1;
- optional android.service.procstats.MemoryState memory_state = 2;
+ optional android.service.procstats.MemoryState memory_state = 2 [deprecated = true];
// this enum list is from frameworks/base/core/java/com/android/internal/app/procstats/ProcessStats.java
// and not frameworks/base/core/java/android/app/ActivityManager.java
- optional android.service.procstats.ProcessState process_state = 3;
+ optional android.service.procstats.ProcessState process_state = 3 [deprecated = true];
+
+ optional ProcessStateAggregated process_state_aggregated = 10;
// Millisecond uptime duration spent in this state
- optional int64 duration_millis = 4;
+ optional int64 duration_millis = 4 [deprecated = true];
+ // Same as above, but with minute resolution so it fits into an int32.
+ optional int32 duration_minutes = 11;
// Millisecond elapsed realtime duration spent in this state
- optional int64 realtime_duration_millis = 9;
+ optional int64 realtime_duration_millis = 9 [deprecated = true];
+ // Same as above, but with minute resolution so it fits into an int32.
+ optional int32 realtime_duration_minutes = 12;
// # of samples taken
optional int32 sample_size = 5;
// PSS is memory reserved for this process
- optional AggStats pss = 6;
+ optional AggStats pss = 6 [deprecated = true];
// USS is memory shared between processes, divided evenly for accounting
- optional AggStats uss = 7;
+ optional AggStats uss = 7 [deprecated = true];
// RSS is memory resident for this process
optional AggStats rss = 8;
@@ -4777,7 +5125,7 @@
// PSS stats during cached kill
optional AggStats cached_pss = 3;
}
- optional Kill kill = 3;
+ optional Kill kill = 3 [deprecated = true];
// Time and memory spent in various states.
repeated ProcessStatsStateProto states = 5;
@@ -6560,6 +6908,7 @@
INSTALL_FAILURE_DOWNLOAD = 23;
INSTALL_FAILURE_STATE_MISMATCH = 24;
INSTALL_FAILURE_COMMIT = 25;
+ REBOOT_TRIGGERED = 26;
}
optional Status status = 4;
}
@@ -6702,11 +7051,27 @@
* Pulled from StatsCompanionService.
*/
message SystemIonHeapSize {
+ // Deprecated due to limited support of ion stats in debugfs.
+ // Use `IonHeapSize` instead.
+ option deprecated = true;
+
// Size of the system ion heap in bytes.
+ // Read from debugfs.
optional int64 size_in_bytes = 1;
}
/*
+ * Logs the total size of the ion heap.
+ *
+ * Pulled from StatsCompanionService.
+ */
+message IonHeapSize {
+ // Total size of all ion heaps in kilobytes.
+ // Read from: /sys/kernel/ion/total_heaps_kb.
+ optional int32 total_size_kb = 1;
+}
+
+/*
* Logs the per-process size of the system ion heap.
*
* Pulled from StatsCompanionService.
@@ -6844,7 +7209,7 @@
// Uid of the package requesting the op
optional int32 uid = 1 [(is_uid) = true];
- // Nmae of the package performing the op
+ // Name of the package performing the op
optional string package_name = 2;
// operation id; maps to the OP_* constants in AppOpsManager.java
@@ -7338,6 +7703,17 @@
}
/**
+ * Reported when the RebootEscrow HAL has attempted to recover the escrowed
+ * key to indicate whether it was successful or not.
+ *
+ * Logged from:
+ * frameworks/base/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
+ */
+message RebootEscrowRecoveryReported {
+ optional bool successful = 1;
+}
+
+/**
* Global display pipeline metrics reported by SurfaceFlinger.
* Pulled from:
* frameworks/native/services/surfaceflinger/TimeStats/TimeStats.cpp
@@ -7553,3 +7929,139 @@
optional int32 permission_flags = 4;
}
+/**
+ * HWUI renders pipeline type: GL (0) or Vulkan (1).
+ */
+enum PipelineType {
+ GL = 0;
+ VULKAN = 1;
+}
+
+/**
+ * HWUI stats for a given app.
+ */
+message GraphicsStats {
+ // The package name of the app
+ optional string package_name = 1;
+
+ // The version code of the app
+ optional int64 version_code = 2;
+
+ // The start & end timestamps in UTC as
+ // milliseconds since January 1, 1970
+ // Compatible with java.util.Date#setTime()
+ optional int64 stats_start = 3;
+
+ optional int64 stats_end = 4;
+
+ // HWUI renders pipeline type: GL or Vulkan.
+ optional PipelineType pipeline = 5;
+
+ // Distinct frame count.
+ optional int32 total_frames = 6;
+
+ // Number of "missed vsync" events.
+ optional int32 missed_vsync_count = 7;
+
+ // Number of frames in triple-buffering scenario (high input latency)
+ optional int32 high_input_latency_count = 8;
+
+ // Number of "slow UI thread" events.
+ optional int32 slow_ui_thread_count = 9;
+
+ // Number of "slow bitmap upload" events.
+ optional int32 slow_bitmap_upload_count = 10;
+
+ // Number of "slow draw" events.
+ optional int32 slow_draw_count = 11;
+
+ // Number of frames that missed their deadline (aka, visibly janked)
+ optional int32 missed_deadline_count = 12;
+
+ // The frame time histogram for the package
+ optional FrameTimingHistogram cpu_histogram = 13
+ [(android.os.statsd.log_mode) = MODE_BYTES];
+
+ // The gpu frame time histogram for the package
+ optional FrameTimingHistogram gpu_histogram = 14
+ [(android.os.statsd.log_mode) = MODE_BYTES];
+
+ // UI mainline module version.
+ optional int64 version_ui_module = 15;
+
+ // If true, these are HWUI stats for up to a 24h period for a given app from today.
+ // If false, these are HWUI stats for a 24h period for a given app from the last complete
+ // day (yesterday). Stats from yesterday stay constant, while stats from today may change as
+ // more apps are running / rendering.
+ optional bool is_today = 16;
+}
+
+/**
+ * Message related to dangerous (runtime) app ops access
+ */
+message RuntimeAppOpsAccess {
+ // Uid of the package accessing app op
+ optional int32 uid = 1 [(is_uid) = true];
+
+ // Name of the package accessing app op
+ optional string package_name = 2;
+
+ // operation id; maps to the OP_* constants in AppOpsManager.java
+ optional int32 op_id = 3;
+
+ // feature id; provided by developer when accessing related API, limited at 50 chars by API.
+ // Features must be provided through manifest using <feature> tag available in R and above.
+ optional string feature_id = 4;
+
+ // message related to app op access, limited to 600 chars by API
+ optional string message = 5;
+
+ enum SamplingStrategy {
+ DEFAULT = 0;
+ UNIFORM = 1;
+ RARELY_USED = 2;
+ }
+
+ // sampling strategy used to collect this message
+ optional SamplingStrategy sampling_strategy = 6;
+}
+
+/*
+ * Logs userspace reboot outcome and duration.
+ *
+ * Logged from:
+ * frameworks/base/core/java/com/android/server/BootReceiver.java
+ */
+message UserspaceRebootReported {
+ // Possible outcomes of userspace reboot.
+ enum Outcome {
+ // Default value in case platform failed to determine the outcome.
+ OUTCOME_UNKNOWN = 0;
+ // Userspace reboot succeeded (i.e. boot completed without a fall back to hard reboot).
+ SUCCESS = 1;
+ // Userspace reboot shutdown sequence was aborted.
+ FAILED_SHUTDOWN_SEQUENCE_ABORTED = 2;
+ // Remounting userdata into checkpointing mode failed.
+ FAILED_USERDATA_REMOUNT = 3;
+ // Device didn't finish booting before timeout and userspace reboot watchdog issued a hard
+ // reboot.
+ FAILED_USERSPACE_REBOOT_WATCHDOG_TRIGGERED = 4;
+ }
+ // Outcome of userspace reboot. Always set.
+ optional Outcome outcome = 1;
+ // Duration of userspace reboot in case it has a successful outcome.
+ // Duration is measured as time between userspace reboot was initiated and until boot completed
+ // (e.g. sys.boot_completed=1).
+ optional int64 duration_millis = 2;
+ // State of primary user's (user0) credential encryption storage.
+ enum UserEncryptionState {
+ // Default value.
+ USER_ENCRYPTION_STATE_UNKNOWN = 0;
+ // Credential encrypted storage is unlocked.
+ UNLOCKED = 1;
+ // Credential encrypted storage is locked.
+ LOCKED = 2;
+ }
+ // State of primary user's encryption storage at the moment boot completed. Always set.
+ optional UserEncryptionState user_encryption_state = 3;
+}
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index fc949b4..972adf7 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -46,6 +46,41 @@
using android::base::StringPrintf;
using std::unique_ptr;
+class ConfigReceiverDeathRecipient : public android::IBinder::DeathRecipient {
+ public:
+ ConfigReceiverDeathRecipient(sp<ConfigManager> configManager, const ConfigKey& configKey):
+ mConfigManager(configManager),
+ mConfigKey(configKey) {}
+ ~ConfigReceiverDeathRecipient() override = default;
+ private:
+ sp<ConfigManager> mConfigManager;
+ ConfigKey mConfigKey;
+
+ void binderDied(const android::wp<android::IBinder>& who) override {
+ if (IInterface::asBinder(mConfigManager->GetConfigReceiver(mConfigKey)) == who.promote()) {
+ mConfigManager->RemoveConfigReceiver(mConfigKey);
+ }
+ }
+};
+
+class ActiveConfigChangedReceiverDeathRecipient : public android::IBinder::DeathRecipient {
+ public:
+ ActiveConfigChangedReceiverDeathRecipient(sp<ConfigManager> configManager, const int uid):
+ mConfigManager(configManager),
+ mUid(uid) {}
+ ~ActiveConfigChangedReceiverDeathRecipient() override = default;
+ private:
+ sp<ConfigManager> mConfigManager;
+ int mUid;
+
+ void binderDied(const android::wp<android::IBinder>& who) override {
+ if (IInterface::asBinder(mConfigManager->GetActiveConfigsChangedReceiver(mUid))
+ == who.promote()) {
+ mConfigManager->RemoveActiveConfigsChangedReceiver(mUid);
+ }
+ }
+};
+
ConfigManager::ConfigManager() {
}
@@ -118,9 +153,11 @@
}
}
-void ConfigManager::SetConfigReceiver(const ConfigKey& key, const sp<IBinder>& intentSender) {
+void ConfigManager::SetConfigReceiver(const ConfigKey& key,
+ const sp<IPendingIntentRef>& pir) {
lock_guard<mutex> lock(mMutex);
- mConfigReceivers[key] = intentSender;
+ mConfigReceivers[key] = pir;
+ IInterface::asBinder(pir)->linkToDeath(new ConfigReceiverDeathRecipient(this, key));
}
void ConfigManager::RemoveConfigReceiver(const ConfigKey& key) {
@@ -129,9 +166,11 @@
}
void ConfigManager::SetActiveConfigsChangedReceiver(const int uid,
- const sp<IBinder>& intentSender) {
+ const sp<IPendingIntentRef>& pir) {
lock_guard<mutex> lock(mMutex);
- mActiveConfigsChangedReceivers[uid] = intentSender;
+ mActiveConfigsChangedReceivers[uid] = pir;
+ IInterface::asBinder(pir)->linkToDeath(
+ new ActiveConfigChangedReceiverDeathRecipient(this, uid));
}
void ConfigManager::RemoveActiveConfigsChangedReceiver(const int uid) {
@@ -150,25 +189,11 @@
// Remove from map
uidIt->second.erase(key);
- // No more configs for this uid, lets remove the active configs callback.
- if (uidIt->second.empty()) {
- auto itActiveConfigsChangedReceiver = mActiveConfigsChangedReceivers.find(uid);
- if (itActiveConfigsChangedReceiver != mActiveConfigsChangedReceivers.end()) {
- mActiveConfigsChangedReceivers.erase(itActiveConfigsChangedReceiver);
- }
- }
-
for (const sp<ConfigListener>& listener : mListeners) {
broadcastList.push_back(listener);
}
}
- auto itReceiver = mConfigReceivers.find(key);
- if (itReceiver != mConfigReceivers.end()) {
- // Remove from map
- mConfigReceivers.erase(itReceiver);
- }
-
// Remove from disk. There can still be a lingering file on disk so we check
// whether or not the config was on memory.
remove_saved_configs(key);
@@ -199,12 +224,6 @@
// Remove from map
remove_saved_configs(*it);
removed.push_back(*it);
- mConfigReceivers.erase(*it);
- }
-
- auto itActiveConfigsChangedReceiver = mActiveConfigsChangedReceivers.find(uid);
- if (itActiveConfigsChangedReceiver != mActiveConfigsChangedReceivers.end()) {
- mActiveConfigsChangedReceivers.erase(itActiveConfigsChangedReceiver);
}
mConfigs.erase(uidIt);
@@ -238,8 +257,6 @@
uidIt = mConfigs.erase(uidIt);
}
- mConfigReceivers.clear();
- mActiveConfigsChangedReceivers.clear();
for (const sp<ConfigListener>& listener : mListeners) {
broadcastList.push_back(listener);
}
@@ -266,7 +283,7 @@
return ret;
}
-const sp<android::IBinder> ConfigManager::GetConfigReceiver(const ConfigKey& key) const {
+const sp<IPendingIntentRef> ConfigManager::GetConfigReceiver(const ConfigKey& key) const {
lock_guard<mutex> lock(mMutex);
auto it = mConfigReceivers.find(key);
@@ -277,7 +294,7 @@
}
}
-const sp<android::IBinder> ConfigManager::GetActiveConfigsChangedReceiver(const int uid) const {
+const sp<IPendingIntentRef> ConfigManager::GetActiveConfigsChangedReceiver(const int uid) const {
lock_guard<mutex> lock(mMutex);
auto it = mActiveConfigsChangedReceivers.find(uid);
diff --git a/cmds/statsd/src/config/ConfigManager.h b/cmds/statsd/src/config/ConfigManager.h
index c064a51..88e864a 100644
--- a/cmds/statsd/src/config/ConfigManager.h
+++ b/cmds/statsd/src/config/ConfigManager.h
@@ -16,10 +16,10 @@
#pragma once
-#include "binder/IBinder.h"
#include "config/ConfigKey.h"
#include "config/ConfigListener.h"
+#include <android/os/IPendingIntentRef.h>
#include <map>
#include <mutex>
#include <set>
@@ -64,12 +64,12 @@
/**
* Sets the broadcast receiver for a configuration key.
*/
- void SetConfigReceiver(const ConfigKey& key, const sp<IBinder>& intentSender);
+ void SetConfigReceiver(const ConfigKey& key, const sp<IPendingIntentRef>& pir);
/**
* Returns the package name and class name representing the broadcast receiver for this config.
*/
- const sp<android::IBinder> GetConfigReceiver(const ConfigKey& key) const;
+ const sp<IPendingIntentRef> GetConfigReceiver(const ConfigKey& key) const;
/**
* Returns all config keys registered.
@@ -85,13 +85,13 @@
* Sets the broadcast receiver that is notified whenever the list of active configs
* changes for this uid.
*/
- void SetActiveConfigsChangedReceiver(const int uid, const sp<IBinder>& intentSender);
+ void SetActiveConfigsChangedReceiver(const int uid, const sp<IPendingIntentRef>& pir);
/**
* Returns the broadcast receiver for active configs changed for this uid.
*/
- const sp<IBinder> GetActiveConfigsChangedReceiver(const int uid) const;
+ const sp<IPendingIntentRef> GetActiveConfigsChangedReceiver(const int uid) const;
/**
* Erase any active configs changed broadcast receiver associated with this uid.
@@ -141,16 +141,15 @@
std::map<int, std::set<ConfigKey>> mConfigs;
/**
- * Each config key can be subscribed by up to one receiver, specified as IBinder from
- * PendingIntent.
+ * Each config key can be subscribed by up to one receiver, specified as IPendingIntentRef.
*/
- std::map<ConfigKey, sp<android::IBinder>> mConfigReceivers;
+ std::map<ConfigKey, sp<IPendingIntentRef>> mConfigReceivers;
/**
* Each uid can be subscribed by up to one receiver to notify that the list of active configs
- * for this uid has changed. The receiver is specified as IBinder from PendingIntent.
+ * for this uid has changed. The receiver is specified as IPendingIntentRef.
*/
- std::map<int, sp<android::IBinder>> mActiveConfigsChangedReceivers;
+ std::map<int, sp<IPendingIntentRef>> mActiveConfigsChangedReceivers;
/**
* The ConfigListeners that will be told about changes.
diff --git a/cmds/statsd/src/external/StatsCallbackPuller.cpp b/cmds/statsd/src/external/StatsCallbackPuller.cpp
index 0e6b677..e5a83a2 100644
--- a/cmds/statsd/src/external/StatsCallbackPuller.cpp
+++ b/cmds/statsd/src/external/StatsCallbackPuller.cpp
@@ -42,7 +42,7 @@
}
bool StatsCallbackPuller::PullInternal(vector<shared_ptr<LogEvent>>* data) {
- VLOG("StatsCallbackPuller called for tag %d", mTagId)
+ VLOG("StatsCallbackPuller called for tag %d", mTagId);
if(mCallback == nullptr) {
ALOGW("No callback registered");
return false;
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index 50896f8..591d727 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -41,7 +41,6 @@
#include "StatsCallbackPullerDeprecated.h"
#include "StatsCompanionServicePuller.h"
#include "SubsystemSleepStatePuller.h"
-#include "SurfaceflingerStatsPuller.h"
#include "TrainInfoPuller.h"
#include "statslog.h"
@@ -60,161 +59,124 @@
const int64_t NO_ALARM_UPDATE = INT64_MAX;
std::map<PullerKey, PullAtomInfo> StatsPullerManager::kAllPullAtomInfo = {
- // wifi_bytes_transfer
- {{.atomTag = android::util::WIFI_BYTES_TRANSFER},
- {.additiveFields = {2, 3, 4, 5},
- .puller = new StatsCompanionServicePuller(android::util::WIFI_BYTES_TRANSFER)}},
- // wifi_bytes_transfer_by_fg_bg
- {{.atomTag = android::util::WIFI_BYTES_TRANSFER_BY_FG_BG},
- {.additiveFields = {3, 4, 5, 6},
- .puller = new StatsCompanionServicePuller(android::util::WIFI_BYTES_TRANSFER_BY_FG_BG)}},
- // mobile_bytes_transfer
- {{.atomTag = android::util::MOBILE_BYTES_TRANSFER},
- {.additiveFields = {2, 3, 4, 5},
- .puller = new StatsCompanionServicePuller(android::util::MOBILE_BYTES_TRANSFER)}},
- // mobile_bytes_transfer_by_fg_bg
- {{.atomTag = android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG},
- {.additiveFields = {3, 4, 5, 6},
- .puller =
- new StatsCompanionServicePuller(android::util::MOBILE_BYTES_TRANSFER_BY_FG_BG)}},
- // bluetooth_bytes_transfer
- {{.atomTag = android::util::BLUETOOTH_BYTES_TRANSFER},
- {.additiveFields = {2, 3},
- .puller = new StatsCompanionServicePuller(android::util::BLUETOOTH_BYTES_TRANSFER)}},
- // kernel_wakelock
- {{.atomTag = android::util::KERNEL_WAKELOCK},
- {.puller = new StatsCompanionServicePuller(android::util::KERNEL_WAKELOCK)}},
+
// subsystem_sleep_state
{{.atomTag = android::util::SUBSYSTEM_SLEEP_STATE},
{.puller = new SubsystemSleepStatePuller()}},
+
// on_device_power_measurement
{{.atomTag = android::util::ON_DEVICE_POWER_MEASUREMENT},
{.puller = new PowerStatsPuller()}},
- // cpu_time_per_freq
- {{.atomTag = android::util::CPU_TIME_PER_FREQ},
- {.additiveFields = {3},
- .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_FREQ)}},
- // cpu_time_per_uid
- {{.atomTag = android::util::CPU_TIME_PER_UID},
- {.additiveFields = {2, 3},
- .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_UID)}},
- // cpu_time_per_uid_freq
- // the throttling is 3sec, handled in
- // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
- {{.atomTag = android::util::CPU_TIME_PER_UID_FREQ},
- {.additiveFields = {4},
- .puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_UID_FREQ)}},
- // cpu_active_time
- // the throttling is 3sec, handled in
- // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
- {{.atomTag = android::util::CPU_ACTIVE_TIME},
- {.additiveFields = {2},
- .puller = new StatsCompanionServicePuller(android::util::CPU_ACTIVE_TIME)}},
- // cpu_cluster_time
- // the throttling is 3sec, handled in
- // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
- {{.atomTag = android::util::CPU_CLUSTER_TIME},
- {.additiveFields = {3},
- .puller = new StatsCompanionServicePuller(android::util::CPU_CLUSTER_TIME)}},
- // wifi_activity_energy_info
- {{.atomTag = android::util::WIFI_ACTIVITY_INFO},
- {.puller = new StatsCompanionServicePuller(android::util::WIFI_ACTIVITY_INFO)}},
- // modem_activity_info
- {{.atomTag = android::util::MODEM_ACTIVITY_INFO},
- {.puller = new StatsCompanionServicePuller(android::util::MODEM_ACTIVITY_INFO)}},
- // bluetooth_activity_info
- {{.atomTag = android::util::BLUETOOTH_ACTIVITY_INFO},
- {.puller = new StatsCompanionServicePuller(android::util::BLUETOOTH_ACTIVITY_INFO)}},
+
// system_elapsed_realtime
{{.atomTag = android::util::SYSTEM_ELAPSED_REALTIME},
{.coolDownNs = NS_PER_SEC,
.puller = new StatsCompanionServicePuller(android::util::SYSTEM_ELAPSED_REALTIME),
.pullTimeoutNs = NS_PER_SEC / 2,
}},
- // system_uptime
- {{.atomTag = android::util::SYSTEM_UPTIME},
- {.puller = new StatsCompanionServicePuller(android::util::SYSTEM_UPTIME)}},
+
// remaining_battery_capacity
{{.atomTag = android::util::REMAINING_BATTERY_CAPACITY},
{.puller = new ResourceHealthManagerPuller(android::util::REMAINING_BATTERY_CAPACITY)}},
+
// full_battery_capacity
{{.atomTag = android::util::FULL_BATTERY_CAPACITY},
{.puller = new ResourceHealthManagerPuller(android::util::FULL_BATTERY_CAPACITY)}},
+
// battery_voltage
{{.atomTag = android::util::BATTERY_VOLTAGE},
{.puller = new ResourceHealthManagerPuller(android::util::BATTERY_VOLTAGE)}},
+
// battery_level
{{.atomTag = android::util::BATTERY_LEVEL},
{.puller = new ResourceHealthManagerPuller(android::util::BATTERY_LEVEL)}},
+
// battery_cycle_count
{{.atomTag = android::util::BATTERY_CYCLE_COUNT},
{.puller = new ResourceHealthManagerPuller(android::util::BATTERY_CYCLE_COUNT)}},
+
// process_memory_state
{{.atomTag = android::util::PROCESS_MEMORY_STATE},
{.additiveFields = {4, 5, 6, 7, 8},
.puller = new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_STATE)}},
+
// process_memory_high_water_mark
{{.atomTag = android::util::PROCESS_MEMORY_HIGH_WATER_MARK},
{.puller =
new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_HIGH_WATER_MARK)}},
+
// process_memory_snapshot
{{.atomTag = android::util::PROCESS_MEMORY_SNAPSHOT},
{.puller = new StatsCompanionServicePuller(android::util::PROCESS_MEMORY_SNAPSHOT)}},
+
// system_ion_heap_size
{{.atomTag = android::util::SYSTEM_ION_HEAP_SIZE},
{.puller = new StatsCompanionServicePuller(android::util::SYSTEM_ION_HEAP_SIZE)}},
+
// process_system_ion_heap_size
{{.atomTag = android::util::PROCESS_SYSTEM_ION_HEAP_SIZE},
{.puller = new StatsCompanionServicePuller(android::util::PROCESS_SYSTEM_ION_HEAP_SIZE)}},
+
// temperature
{{.atomTag = android::util::TEMPERATURE},
{.puller = new StatsCompanionServicePuller(android::util::TEMPERATURE)}},
+
// cooling_device
{{.atomTag = android::util::COOLING_DEVICE},
{.puller = new StatsCompanionServicePuller(android::util::COOLING_DEVICE)}},
+
// binder_calls
{{.atomTag = android::util::BINDER_CALLS},
{.additiveFields = {4, 5, 6, 8, 12},
.puller = new StatsCompanionServicePuller(android::util::BINDER_CALLS)}},
+
// binder_calls_exceptions
{{.atomTag = android::util::BINDER_CALLS_EXCEPTIONS},
{.puller = new StatsCompanionServicePuller(android::util::BINDER_CALLS_EXCEPTIONS)}},
+
// looper_stats
{{.atomTag = android::util::LOOPER_STATS},
{.additiveFields = {5, 6, 7, 8, 9},
.puller = new StatsCompanionServicePuller(android::util::LOOPER_STATS)}},
+
// Disk Stats
{{.atomTag = android::util::DISK_STATS},
{.puller = new StatsCompanionServicePuller(android::util::DISK_STATS)}},
+
// Directory usage
{{.atomTag = android::util::DIRECTORY_USAGE},
{.puller = new StatsCompanionServicePuller(android::util::DIRECTORY_USAGE)}},
+
// Size of app's code, data, and cache
{{.atomTag = android::util::APP_SIZE},
{.puller = new StatsCompanionServicePuller(android::util::APP_SIZE)}},
+
// Size of specific categories of files. Eg. Music.
{{.atomTag = android::util::CATEGORY_SIZE},
{.puller = new StatsCompanionServicePuller(android::util::CATEGORY_SIZE)}},
+
// Number of fingerprints enrolled for each user.
{{.atomTag = android::util::NUM_FINGERPRINTS_ENROLLED},
{.puller = new StatsCompanionServicePuller(android::util::NUM_FINGERPRINTS_ENROLLED)}},
+
// Number of faces enrolled for each user.
{{.atomTag = android::util::NUM_FACES_ENROLLED},
{.puller = new StatsCompanionServicePuller(android::util::NUM_FACES_ENROLLED)}},
+
// ProcStats.
{{.atomTag = android::util::PROC_STATS},
{.puller = new StatsCompanionServicePuller(android::util::PROC_STATS)}},
+
// ProcStatsPkgProc.
{{.atomTag = android::util::PROC_STATS_PKG_PROC},
{.puller = new StatsCompanionServicePuller(android::util::PROC_STATS_PKG_PROC)}},
+
// Disk I/O stats per uid.
{{.atomTag = android::util::DISK_IO},
{.additiveFields = {2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
.coolDownNs = 3 * NS_PER_SEC,
.puller = new StatsCompanionServicePuller(android::util::DISK_IO)}},
- // PowerProfile constants for power model calculations.
- {{.atomTag = android::util::POWER_PROFILE},
- {.puller = new StatsCompanionServicePuller(android::util::POWER_PROFILE)}},
+
// Process cpu stats. Min cool-down is 5 sec, inline with what AcitivityManagerService uses.
{{.atomTag = android::util::PROCESS_CPU_TIME},
{.coolDownNs = 5 * NS_PER_SEC /* min cool-down in seconds*/,
@@ -222,68 +184,65 @@
{{.atomTag = android::util::CPU_TIME_PER_THREAD_FREQ},
{.additiveFields = {7, 9, 11, 13, 15, 17, 19, 21},
.puller = new StatsCompanionServicePuller(android::util::CPU_TIME_PER_THREAD_FREQ)}},
- // DeviceCalculatedPowerUse.
- {{.atomTag = android::util::DEVICE_CALCULATED_POWER_USE},
- {.puller = new StatsCompanionServicePuller(android::util::DEVICE_CALCULATED_POWER_USE)}},
- // DeviceCalculatedPowerBlameUid.
- {{.atomTag = android::util::DEVICE_CALCULATED_POWER_BLAME_UID},
- {.puller = new StatsCompanionServicePuller(
- android::util::DEVICE_CALCULATED_POWER_BLAME_UID)}},
- // DeviceCalculatedPowerBlameOther.
- {{.atomTag = android::util::DEVICE_CALCULATED_POWER_BLAME_OTHER},
- {.puller = new StatsCompanionServicePuller(
- android::util::DEVICE_CALCULATED_POWER_BLAME_OTHER)}},
+
// DebugElapsedClock.
{{.atomTag = android::util::DEBUG_ELAPSED_CLOCK},
{.additiveFields = {1, 2, 3, 4},
.puller = new StatsCompanionServicePuller(android::util::DEBUG_ELAPSED_CLOCK)}},
+
// DebugFailingElapsedClock.
{{.atomTag = android::util::DEBUG_FAILING_ELAPSED_CLOCK},
{.additiveFields = {1, 2, 3, 4},
.puller = new StatsCompanionServicePuller(android::util::DEBUG_FAILING_ELAPSED_CLOCK)}},
- // BuildInformation.
- {{.atomTag = android::util::BUILD_INFORMATION},
- {.puller = new StatsCompanionServicePuller(android::util::BUILD_INFORMATION)}},
+
// RoleHolder.
{{.atomTag = android::util::ROLE_HOLDER},
{.puller = new StatsCompanionServicePuller(android::util::ROLE_HOLDER)}},
+
// PermissionState.
{{.atomTag = android::util::DANGEROUS_PERMISSION_STATE},
{.puller = new StatsCompanionServicePuller(android::util::DANGEROUS_PERMISSION_STATE)}},
+
// TrainInfo.
{{.atomTag = android::util::TRAIN_INFO}, {.puller = new TrainInfoPuller()}},
+
// TimeZoneDataInfo.
{{.atomTag = android::util::TIME_ZONE_DATA_INFO},
{.puller = new StatsCompanionServicePuller(android::util::TIME_ZONE_DATA_INFO)}},
+
// ExternalStorageInfo
{{.atomTag = android::util::EXTERNAL_STORAGE_INFO},
{.puller = new StatsCompanionServicePuller(android::util::EXTERNAL_STORAGE_INFO)}},
+
// GpuStatsGlobalInfo
{{.atomTag = android::util::GPU_STATS_GLOBAL_INFO},
{.puller = new GpuStatsPuller(android::util::GPU_STATS_GLOBAL_INFO)}},
+
// GpuStatsAppInfo
{{.atomTag = android::util::GPU_STATS_APP_INFO},
{.puller = new GpuStatsPuller(android::util::GPU_STATS_APP_INFO)}},
+
// AppsOnExternalStorageInfo
{{.atomTag = android::util::APPS_ON_EXTERNAL_STORAGE_INFO},
{.puller = new StatsCompanionServicePuller(android::util::APPS_ON_EXTERNAL_STORAGE_INFO)}},
+
// Face Settings
{{.atomTag = android::util::FACE_SETTINGS},
{.puller = new StatsCompanionServicePuller(android::util::FACE_SETTINGS)}},
+
// App ops
{{.atomTag = android::util::APP_OPS},
{.puller = new StatsCompanionServicePuller(android::util::APP_OPS)}},
- // SurfaceflingerStatsGlobalInfo
- {{.atomTag = android::util::SURFACEFLINGER_STATS_GLOBAL_INFO},
- {.puller =
- new SurfaceflingerStatsPuller(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO)}},
+
// VmsClientStats
{{.atomTag = android::util::VMS_CLIENT_STATS},
{.additiveFields = {5, 6, 7, 8, 9, 10},
.puller = new CarStatsPuller(android::util::VMS_CLIENT_STATS)}},
+
// NotiifcationRemoteViews.
{{.atomTag = android::util::NOTIFICATION_REMOTE_VIEWS},
{.puller = new StatsCompanionServicePuller(android::util::NOTIFICATION_REMOTE_VIEWS)}},
+
// PermissionStateSampled.
{{.atomTag = android::util::DANGEROUS_PERMISSION_STATE_SAMPLED},
{.puller =
diff --git a/cmds/statsd/src/external/SurfaceflingerStatsPuller.cpp b/cmds/statsd/src/external/SurfaceflingerStatsPuller.cpp
deleted file mode 100644
index 23b2236..0000000
--- a/cmds/statsd/src/external/SurfaceflingerStatsPuller.cpp
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * 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.
- */
-
-#include "SurfaceflingerStatsPuller.h"
-
-#include <cutils/compiler.h>
-
-#include <numeric>
-
-#include "logd/LogEvent.h"
-#include "stats_log_util.h"
-#include "statslog.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-SurfaceflingerStatsPuller::SurfaceflingerStatsPuller(const int tagId) : StatsPuller(tagId) {
-}
-
-bool SurfaceflingerStatsPuller::PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) {
- switch (mTagId) {
- case android::util::SURFACEFLINGER_STATS_GLOBAL_INFO:
- return pullGlobalInfo(data);
- default:
- break;
- }
-
- return false;
-}
-
-static int64_t getTotalTime(
- const google::protobuf::RepeatedPtrField<surfaceflinger::SFTimeStatsHistogramBucketProto>&
- buckets) {
- int64_t total = 0;
- for (const auto& bucket : buckets) {
- if (bucket.time_millis() == 1000) {
- continue;
- }
-
- total += bucket.time_millis() * bucket.frame_count();
- }
-
- return total;
-}
-
-bool SurfaceflingerStatsPuller::pullGlobalInfo(std::vector<std::shared_ptr<LogEvent>>* data) {
- std::string protoBytes;
- if (CC_UNLIKELY(mStatsProvider)) {
- protoBytes = mStatsProvider();
- } else {
- std::unique_ptr<FILE, decltype(&pclose)> pipe(popen("dumpsys SurfaceFlinger --timestats -dump --proto", "r"), pclose);
- if (!pipe.get()) {
- return false;
- }
- char buf[1024];
- size_t bytesRead = 0;
- do {
- bytesRead = fread(buf, 1, sizeof(buf), pipe.get());
- protoBytes.append(buf, bytesRead);
- } while (bytesRead > 0);
- }
- surfaceflinger::SFTimeStatsGlobalProto proto;
- proto.ParseFromString(protoBytes);
-
- int64_t totalTime = getTotalTime(proto.present_to_present());
-
- data->clear();
- data->reserve(1);
- std::shared_ptr<LogEvent> event =
- make_shared<LogEvent>(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO, getWallClockNs(),
- getElapsedRealtimeNs());
- if (!event->write(proto.total_frames())) return false;
- if (!event->write(proto.missed_frames())) return false;
- if (!event->write(proto.client_composition_frames())) return false;
- if (!event->write(proto.display_on_time())) return false;
- if (!event->write(totalTime)) return false;
- event->init();
- data->emplace_back(event);
-
- return true;
-}
-
-} // namespace statsd
-} // namespace os
-} // namespace android
diff --git a/cmds/statsd/src/external/SurfaceflingerStatsPuller.h b/cmds/statsd/src/external/SurfaceflingerStatsPuller.h
deleted file mode 100644
index ed7153e..0000000
--- a/cmds/statsd/src/external/SurfaceflingerStatsPuller.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * 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.
- */
-
-#pragma once
-
-#include <timestatsproto/TimeStatsProtoHeader.h>
-
-#include "StatsPuller.h"
-
-namespace android {
-namespace os {
-namespace statsd {
-
-/**
- * Pull metrics from Surfaceflinger
- */
-class SurfaceflingerStatsPuller : public StatsPuller {
-public:
- explicit SurfaceflingerStatsPuller(const int tagId);
-
- // StatsPuller interface
- bool PullInternal(std::vector<std::shared_ptr<LogEvent>>* data) override;
-
-protected:
- // Test-only, for injecting fake data
- using StatsProvider = std::function<std::string()>;
- StatsProvider mStatsProvider;
-
-private:
- bool pullGlobalInfo(std::vector<std::shared_ptr<LogEvent>>* data);
-};
-
-} // namespace statsd
-} // namespace os
-} // namespace android
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index 35c6d37..e85b975 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -207,10 +207,7 @@
&linkedConditionDimensionKey);
if (trueConditionDimensions.find(linkedConditionDimensionKey) !=
trueConditionDimensions.end()) {
- for (auto& condIt : whatIt.second) {
- condIt.second->onConditionChanged(
- currentUnSlicedPartCondition, eventTime);
- }
+ whatIt.second->onConditionChanged(currentUnSlicedPartCondition, eventTime);
}
}
} else {
@@ -222,15 +219,11 @@
&linkedConditionDimensionKey);
if (dimensionsChangedToTrue->find(linkedConditionDimensionKey) !=
dimensionsChangedToTrue->end()) {
- for (auto& condIt : whatIt.second) {
- condIt.second->onConditionChanged(true, eventTime);
- }
+ whatIt.second->onConditionChanged(true, eventTime);
}
if (dimensionsChangedToFalse->find(linkedConditionDimensionKey) !=
dimensionsChangedToFalse->end()) {
- for (auto& condIt : whatIt.second) {
- condIt.second->onConditionChanged(false, eventTime);
- }
+ whatIt.second->onConditionChanged(false, eventTime);
}
}
}
@@ -247,9 +240,7 @@
// Now for each of the on-going event, check if the condition has changed for them.
for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
- for (auto& pair : whatIt.second) {
- pair.second->onSlicedConditionMayChange(overallCondition, eventTimeNs);
- }
+ whatIt.second->onSlicedConditionMayChange(overallCondition, eventTimeNs);
}
}
@@ -283,18 +274,14 @@
}
for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
- for (auto& pair : whatIt.second) {
- pair.second->onConditionChanged(mIsActive, eventTimeNs);
- }
+ whatIt.second->onConditionChanged(mIsActive, eventTimeNs);
}
} else if (mIsActive) {
flushIfNeededLocked(eventTimeNs);
onSlicedConditionMayChangeInternalLocked(mIsActive, eventTimeNs);
} else { // mConditionSliced == true && !mIsActive
for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
- for (auto& pair : whatIt.second) {
- pair.second->onConditionChanged(mIsActive, eventTimeNs);
- }
+ whatIt.second->onConditionChanged(mIsActive, eventTimeNs);
}
}
}
@@ -310,9 +297,7 @@
flushIfNeededLocked(eventTime);
for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
- for (auto& pair : whatIt.second) {
- pair.second->onConditionChanged(conditionMet, eventTime);
- }
+ whatIt.second->onConditionChanged(conditionMet, eventTime);
}
}
@@ -425,19 +410,11 @@
const int64_t& nextBucketStartTimeNs) {
for (auto whatIt = mCurrentSlicedDurationTrackerMap.begin();
whatIt != mCurrentSlicedDurationTrackerMap.end();) {
- for (auto it = whatIt->second.begin(); it != whatIt->second.end();) {
- if (it->second->flushCurrentBucket(eventTimeNs, &mPastBuckets)) {
- VLOG("erase bucket for key %s %s", whatIt->first.toString().c_str(),
- it->first.toString().c_str());
- it = whatIt->second.erase(it);
- } else {
- ++it;
- }
- }
- if (whatIt->second.empty()) {
+ if (whatIt->second->flushCurrentBucket(eventTimeNs, &mPastBuckets)) {
+ VLOG("erase bucket for key %s", whatIt->first.toString().c_str());
whatIt = mCurrentSlicedDurationTrackerMap.erase(whatIt);
} else {
- whatIt++;
+ ++whatIt;
}
}
StatsdStats::getInstance().noteBucketCount(mMetricId);
@@ -453,35 +430,15 @@
(unsigned long)mCurrentSlicedDurationTrackerMap.size());
if (verbose) {
for (const auto& whatIt : mCurrentSlicedDurationTrackerMap) {
- for (const auto& slice : whatIt.second) {
- fprintf(out, "\t(what)%s\t(states)%s\n", whatIt.first.toString().c_str(),
- slice.first.toString().c_str());
- slice.second->dumpStates(out, verbose);
- }
+ fprintf(out, "\t(what)%s\n", whatIt.first.toString().c_str());
+ whatIt.second->dumpStates(out, verbose);
}
}
}
bool DurationMetricProducer::hitGuardRailLocked(const MetricDimensionKey& newKey) {
auto whatIt = mCurrentSlicedDurationTrackerMap.find(newKey.getDimensionKeyInWhat());
- if (whatIt != mCurrentSlicedDurationTrackerMap.end()) {
- auto stateIt = whatIt->second.find(newKey.getStateValuesKey());
- if (stateIt != whatIt->second.end()) {
- return false;
- }
- if (whatIt->second.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
- size_t newTupleCount = whatIt->second.size() + 1;
- StatsdStats::getInstance().noteMetricDimensionInConditionSize(
- mConfigKey, mMetricId, newTupleCount);
- // 2. Don't add more tuples, we are above the allowed threshold. Drop the data.
- if (newTupleCount > StatsdStats::kDimensionKeySizeHardLimit) {
- ALOGE("DurationMetric %lld dropping data for state values key %s",
- (long long)mMetricId, newKey.getStateValuesKey().toString().c_str());
- StatsdStats::getInstance().noteHardDimensionLimitReached(mMetricId);
- return true;
- }
- }
- } else {
+ if (whatIt == mCurrentSlicedDurationTrackerMap.end()) {
// 1. Report the tuple count if the tuple count > soft limit
if (mCurrentSlicedDurationTrackerMap.size() > StatsdStats::kDimensionKeySizeSoftLimit - 1) {
size_t newTupleCount = mCurrentSlicedDurationTrackerMap.size() + 1;
@@ -503,24 +460,16 @@
const ConditionKey& conditionKeys,
bool condition, const LogEvent& event) {
const auto& whatKey = eventKey.getDimensionKeyInWhat();
- const auto& stateKey = eventKey.getStateValuesKey();
auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatKey);
if (whatIt == mCurrentSlicedDurationTrackerMap.end()) {
if (hitGuardRailLocked(eventKey)) {
return;
}
- mCurrentSlicedDurationTrackerMap[whatKey][stateKey] = createDurationTracker(eventKey);
- } else {
- if (whatIt->second.find(stateKey) == whatIt->second.end()) {
- if (hitGuardRailLocked(eventKey)) {
- return;
- }
- mCurrentSlicedDurationTrackerMap[whatKey][stateKey] = createDurationTracker(eventKey);
- }
+ mCurrentSlicedDurationTrackerMap[whatKey] = createDurationTracker(eventKey);
}
- auto it = mCurrentSlicedDurationTrackerMap.find(whatKey)->second.find(stateKey);
+ auto it = mCurrentSlicedDurationTrackerMap.find(whatKey);
if (mUseWhatDimensionAsInternalDimension) {
it->second->noteStart(whatKey, condition,
event.GetElapsedTimestampNs(), conditionKeys);
@@ -560,18 +509,14 @@
// Handles Stopall events.
if (matcherIndex == mStopAllIndex) {
for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
- for (auto& pair : whatIt.second) {
- pair.second->noteStopAll(event.GetElapsedTimestampNs());
- }
+ whatIt.second->noteStopAll(event.GetElapsedTimestampNs());
}
return;
}
- HashableDimensionKey dimensionInWhat;
+ HashableDimensionKey dimensionInWhat = DEFAULT_DIMENSION_KEY;
if (!mDimensionsInWhat.empty()) {
filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhat);
- } else {
- dimensionInWhat = DEFAULT_DIMENSION_KEY;
}
// Handles Stop events.
@@ -579,9 +524,7 @@
if (mUseWhatDimensionAsInternalDimension) {
auto whatIt = mCurrentSlicedDurationTrackerMap.find(dimensionInWhat);
if (whatIt != mCurrentSlicedDurationTrackerMap.end()) {
- for (const auto& stateIt : whatIt->second) {
- stateIt.second->noteStop(dimensionInWhat, event.GetElapsedTimestampNs(), false);
- }
+ whatIt->second->noteStop(dimensionInWhat, event.GetElapsedTimestampNs(), false);
}
return;
}
@@ -593,10 +536,7 @@
auto whatIt = mCurrentSlicedDurationTrackerMap.find(dimensionInWhat);
if (whatIt != mCurrentSlicedDurationTrackerMap.end()) {
- for (const auto& stateIt : whatIt->second) {
- stateIt.second->noteStop(internalDimensionKey, event.GetElapsedTimestampNs(),
- false);
- }
+ whatIt->second->noteStop(internalDimensionKey, event.GetElapsedTimestampNs(), false);
}
return;
}
@@ -619,8 +559,8 @@
condition = condition && mIsActive;
- handleStartEvent(MetricDimensionKey(dimensionInWhat, DEFAULT_DIMENSION_KEY),
- conditionKey, condition, event);
+ handleStartEvent(MetricDimensionKey(dimensionInWhat, DEFAULT_DIMENSION_KEY), conditionKey,
+ condition, event);
}
size_t DurationMetricProducer::byteSizeLocked() const {
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index 45908fb..06da0f6 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -132,8 +132,7 @@
std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>> mPastBuckets;
// The duration trackers in the current bucket.
- std::unordered_map<HashableDimensionKey,
- std::unordered_map<HashableDimensionKey, std::unique_ptr<DurationTracker>>>
+ std::unordered_map<HashableDimensionKey, std::unique_ptr<DurationTracker>>
mCurrentSlicedDurationTrackerMap;
// Helper function to create a duration tracker given the metric aggregation type.
diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
index 6b5c299..afe93d4 100644
--- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
@@ -82,8 +82,6 @@
virtual ~DurationTracker(){};
- virtual unique_ptr<DurationTracker> clone(const int64_t eventTime) = 0;
-
virtual void noteStart(const HashableDimensionKey& key, bool condition,
const int64_t eventTime, const ConditionKey& conditionKey) = 0;
virtual void noteStop(const HashableDimensionKey& key, const int64_t eventTime,
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
index df66cb0..2be5855 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
@@ -37,24 +37,6 @@
conditionSliced, fullLink, anomalyTrackers) {
}
-unique_ptr<DurationTracker> MaxDurationTracker::clone(const int64_t eventTime) {
- auto clonedTracker = make_unique<MaxDurationTracker>(*this);
- for (auto it = clonedTracker->mInfos.begin(); it != clonedTracker->mInfos.end();) {
- if (it->second.state != kStopped) {
- it->second.lastStartTime = eventTime;
- it->second.lastDuration = 0;
- it++;
- } else {
- it = clonedTracker->mInfos.erase(it);
- }
- }
- if (clonedTracker->mInfos.empty()) {
- return nullptr;
- } else {
- return clonedTracker;
- }
-}
-
bool MaxDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) {
// ===========GuardRail==============
if (mInfos.find(newKey) != mInfos.end()) {
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
index d0371da..efb8dc7 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
@@ -38,8 +38,6 @@
MaxDurationTracker(const MaxDurationTracker& tracker) = default;
- unique_ptr<DurationTracker> clone(const int64_t eventTime) override;
-
void noteStart(const HashableDimensionKey& key, bool condition, const int64_t eventTime,
const ConditionKey& conditionKey) override;
void noteStop(const HashableDimensionKey& key, const int64_t eventTime,
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
index b0fd975..57f3965 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
@@ -38,13 +38,6 @@
mLastStartTime = 0;
}
-unique_ptr<DurationTracker> OringDurationTracker::clone(const int64_t eventTime) {
- auto clonedTracker = make_unique<OringDurationTracker>(*this);
- clonedTracker->mLastStartTime = eventTime;
- clonedTracker->mDuration = 0;
- return clonedTracker;
-}
-
bool OringDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) {
// ===========GuardRail==============
// 1. Report the tuple count if the tuple count > soft limit
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
index 43c48d5..c3aad66 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
@@ -37,8 +37,6 @@
OringDurationTracker(const OringDurationTracker& tracker) = default;
- unique_ptr<DurationTracker> clone(const int64_t eventTime) override;
-
void noteStart(const HashableDimensionKey& key, bool condition, const int64_t eventTime,
const ConditionKey& conditionKey) override;
void noteStop(const HashableDimensionKey& key, const int64_t eventTime,
diff --git a/cmds/statsd/src/state/StateTracker.cpp b/cmds/statsd/src/state/StateTracker.cpp
index ef59c92..3ad21e0 100644
--- a/cmds/statsd/src/state/StateTracker.cpp
+++ b/cmds/statsd/src/state/StateTracker.cpp
@@ -28,10 +28,14 @@
StateTracker::StateTracker(const int32_t atomId, const util::StateAtomFieldOptions& stateAtomInfo)
: mAtomId(atomId), mStateField(getSimpleMatcher(atomId, stateAtomInfo.exclusiveField)) {
// create matcher for each primary field
- // TODO(tsaichristine): b/142108433 handle when primary field is first uid in chain
- for (const auto& primary : stateAtomInfo.primaryFields) {
- Matcher matcher = getSimpleMatcher(atomId, primary);
- mPrimaryFields.push_back(matcher);
+ for (const auto& primaryField : stateAtomInfo.primaryFields) {
+ if (primaryField == util::FIRST_UID_IN_CHAIN) {
+ Matcher matcher = getFirstUidMatcher(atomId);
+ mPrimaryFields.push_back(matcher);
+ } else {
+ Matcher matcher = getSimpleMatcher(atomId, primaryField);
+ mPrimaryFields.push_back(matcher);
+ }
}
// TODO(tsaichristine): b/142108433 set default state, reset state, and nesting
diff --git a/cmds/statsd/src/state/StateTracker.h b/cmds/statsd/src/state/StateTracker.h
index 7453370..70f1627 100644
--- a/cmds/statsd/src/state/StateTracker.h
+++ b/cmds/statsd/src/state/StateTracker.h
@@ -72,7 +72,7 @@
int32_t mDefaultState = kStateUnknown;
- int32_t mResetState;
+ int32_t mResetState = kStateUnknown;
// Maps primary key to state value info
std::unordered_map<HashableDimensionKey, StateValueInfo> mStateMap;
diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.cpp b/cmds/statsd/src/subscriber/SubscriberReporter.cpp
index 25d2257..a37cad1 100644
--- a/cmds/statsd/src/subscriber/SubscriberReporter.cpp
+++ b/cmds/statsd/src/subscriber/SubscriberReporter.cpp
@@ -19,7 +19,6 @@
#include "SubscriberReporter.h"
-using android::IBinder;
using std::lock_guard;
using std::unordered_map;
@@ -29,12 +28,32 @@
using std::vector;
+class BroadcastSubscriberDeathRecipient : public android::IBinder::DeathRecipient {
+ public:
+ BroadcastSubscriberDeathRecipient(const ConfigKey& configKey, int64_t subscriberId):
+ mConfigKey(configKey),
+ mSubscriberId(subscriberId) {}
+ ~BroadcastSubscriberDeathRecipient() override = default;
+ private:
+ ConfigKey mConfigKey;
+ int64_t mSubscriberId;
+
+ void binderDied(const android::wp<android::IBinder>& who) override {
+ if (IInterface::asBinder(SubscriberReporter::getInstance().getBroadcastSubscriber(
+ mConfigKey, mSubscriberId)) == who.promote()) {
+ SubscriberReporter::getInstance().unsetBroadcastSubscriber(mConfigKey, mSubscriberId);
+ }
+ }
+};
+
void SubscriberReporter::setBroadcastSubscriber(const ConfigKey& configKey,
int64_t subscriberId,
- const sp<IBinder>& intentSender) {
+ const sp<IPendingIntentRef>& pir) {
VLOG("SubscriberReporter::setBroadcastSubscriber called.");
lock_guard<std::mutex> lock(mLock);
- mIntentMap[configKey][subscriberId] = intentSender;
+ mIntentMap[configKey][subscriberId] = pir;
+ IInterface::asBinder(pir)->linkToDeath(
+ new BroadcastSubscriberDeathRecipient(configKey, subscriberId));
}
void SubscriberReporter::unsetBroadcastSubscriber(const ConfigKey& configKey,
@@ -50,12 +69,6 @@
}
}
-void SubscriberReporter::removeConfig(const ConfigKey& configKey) {
- VLOG("SubscriberReporter::removeConfig called.");
- lock_guard<std::mutex> lock(mLock);
- mIntentMap.erase(configKey);
-}
-
void SubscriberReporter::alertBroadcastSubscriber(const ConfigKey& configKey,
const Subscription& subscription,
const MetricDimensionKey& dimKey) const {
@@ -97,18 +110,13 @@
sendBroadcastLocked(it2->second, configKey, subscription, cookies, dimKey);
}
-void SubscriberReporter::sendBroadcastLocked(const sp<IBinder>& intentSender,
+void SubscriberReporter::sendBroadcastLocked(const sp<IPendingIntentRef>& pir,
const ConfigKey& configKey,
const Subscription& subscription,
const vector<String16>& cookies,
const MetricDimensionKey& dimKey) const {
VLOG("SubscriberReporter::sendBroadcastLocked called.");
- if (mStatsCompanionService == nullptr) {
- ALOGW("Failed to send subscriber broadcast: could not access StatsCompanionService.");
- return;
- }
- mStatsCompanionService->sendSubscriberBroadcast(
- intentSender,
+ pir->sendSubscriberBroadcast(
configKey.GetUid(),
configKey.GetId(),
subscription.id(),
@@ -117,6 +125,20 @@
getStatsDimensionsValue(dimKey.getDimensionKeyInWhat()));
}
+sp<IPendingIntentRef> SubscriberReporter::getBroadcastSubscriber(const ConfigKey& configKey,
+ int64_t subscriberId) {
+ lock_guard<std::mutex> lock(mLock);
+ auto subscriberMapIt = mIntentMap.find(configKey);
+ if (subscriberMapIt == mIntentMap.end()) {
+ return nullptr;
+ }
+ auto pirMapIt = subscriberMapIt->second.find(subscriberId);
+ if (pirMapIt == subscriberMapIt->second.end()) {
+ return nullptr;
+ }
+ return pirMapIt->second;
+}
+
void getStatsDimensionsValueHelper(const vector<FieldValue>& dims, size_t* index, int depth,
int prefix, vector<StatsDimensionsValue>* output) {
size_t count = dims.size();
diff --git a/cmds/statsd/src/subscriber/SubscriberReporter.h b/cmds/statsd/src/subscriber/SubscriberReporter.h
index 2a7f771..087a1b8 100644
--- a/cmds/statsd/src/subscriber/SubscriberReporter.h
+++ b/cmds/statsd/src/subscriber/SubscriberReporter.h
@@ -16,6 +16,7 @@
#pragma once
+#include <android/os/IPendingIntentRef.h>
#include <android/os/IStatsCompanionService.h>
#include <utils/RefBase.h>
@@ -47,32 +48,17 @@
void operator=(SubscriberReporter const&) = delete;
/**
- * Tells SubscriberReporter what IStatsCompanionService to use.
- * May be nullptr, but SubscriberReporter will not send broadcasts for any calls
- * to alertBroadcastSubscriber that occur while nullptr.
- */
- void setStatsCompanionService(sp<IStatsCompanionService> statsCompanionService) {
- std::lock_guard<std::mutex> lock(mLock);
- sp<IStatsCompanionService> tmpForLock = mStatsCompanionService;
- mStatsCompanionService = statsCompanionService;
- }
-
- /**
* Stores the given intentSender, associating it with the given (configKey, subscriberId) pair.
- * intentSender must be convertible into an IntentSender (in Java) using IntentSender(IBinder).
*/
void setBroadcastSubscriber(const ConfigKey& configKey,
int64_t subscriberId,
- const sp<android::IBinder>& intentSender);
+ const sp<IPendingIntentRef>& pir);
/**
* Erases any intentSender information from the given (configKey, subscriberId) pair.
*/
void unsetBroadcastSubscriber(const ConfigKey& configKey, int64_t subscriberId);
- /** Remove all information stored by SubscriberReporter about the given config. */
- void removeConfig(const ConfigKey& configKey);
-
/**
* Sends a broadcast via the intentSender previously stored for the
* given (configKey, subscriberId) pair by setBroadcastSubscriber.
@@ -82,6 +68,8 @@
const Subscription& subscription,
const MetricDimensionKey& dimKey) const;
+ sp<IPendingIntentRef> getBroadcastSubscriber(const ConfigKey& configKey, int64_t subscriberId);
+
static StatsDimensionsValue getStatsDimensionsValue(const HashableDimensionKey& dim);
private:
@@ -92,15 +80,15 @@
/** Binder interface for communicating with StatsCompanionService. */
sp<IStatsCompanionService> mStatsCompanionService = nullptr;
- /** Maps <ConfigKey, SubscriberId> -> IBinder (which represents an IIntentSender). */
+ /** Maps <ConfigKey, SubscriberId> -> IPendingIntentRef (which represents a PendingIntent). */
std::unordered_map<ConfigKey,
- std::unordered_map<int64_t, sp<android::IBinder>>> mIntentMap;
+ std::unordered_map<int64_t, sp<IPendingIntentRef>>> mIntentMap;
/**
* Sends a broadcast via the given intentSender (using mStatsCompanionService), along
* with the information in the other parameters.
*/
- void sendBroadcastLocked(const sp<android::IBinder>& intentSender,
+ void sendBroadcastLocked(const sp<IPendingIntentRef>& pir,
const ConfigKey& configKey,
const Subscription& subscription,
const std::vector<String16>& cookies,
diff --git a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
index 0bc3ebb..16b51d9 100644
--- a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
@@ -28,16 +28,16 @@
#ifdef __ANDROID__
-const string kAndroid = "android";
const string kApp1 = "app1.sharing.1";
const int kConfigKey = 789130123; // Randomly chosen to avoid collisions with existing configs.
+const int kCallingUid = 0; // Randomly chosen
void SendConfig(StatsService& service, const StatsdConfig& config) {
string str;
config.SerializeToString(&str);
std::vector<uint8_t> configAsVec(str.begin(), str.end());
bool success;
- service.addConfiguration(kConfigKey, configAsVec, String16(kAndroid.c_str()));
+ service.addConfiguration(kConfigKey, configAsVec, kCallingUid);
}
ConfigMetricsReport GetReports(sp<StatsLogProcessor> processor, int64_t timestamp,
@@ -50,7 +50,7 @@
ConfigMetricsReportList reports;
reports.ParseFromArray(output.data(), output.size());
EXPECT_EQ(1, reports.reports_size());
- return reports.reports(0);
+ return reports.reports(kCallingUid);
}
StatsdConfig MakeConfig() {
diff --git a/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp b/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp
deleted file mode 100644
index 5b7a30d..0000000
--- a/cmds/statsd/tests/external/SurfaceflingerStatsPuller_test.cpp
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * 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.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "SurfaceflingerStatsPuller_test"
-
-#include "src/external/SurfaceflingerStatsPuller.h"
-#include "statslog.h"
-
-#include <gtest/gtest.h>
-#include <log/log.h>
-
-#ifdef __ANDROID__
-
-namespace android {
-namespace os {
-namespace statsd {
-
-class TestableSurfaceflingerStatsPuller : public SurfaceflingerStatsPuller {
-public:
- TestableSurfaceflingerStatsPuller(const int tagId) : SurfaceflingerStatsPuller(tagId){};
-
- void injectStats(const StatsProvider& statsProvider) {
- mStatsProvider = statsProvider;
- }
-};
-
-class SurfaceflingerStatsPullerTest : public ::testing::Test {
-public:
- SurfaceflingerStatsPullerTest() {
- const ::testing::TestInfo* const test_info =
- ::testing::UnitTest::GetInstance()->current_test_info();
- ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
- }
-
- ~SurfaceflingerStatsPullerTest() {
- const ::testing::TestInfo* const test_info =
- ::testing::UnitTest::GetInstance()->current_test_info();
- ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
- }
-};
-
-TEST_F(SurfaceflingerStatsPullerTest, pullGlobalStats) {
- surfaceflinger::SFTimeStatsGlobalProto proto;
- proto.set_total_frames(1);
- proto.set_missed_frames(2);
- proto.set_client_composition_frames(2);
- proto.set_display_on_time(4);
-
- auto bucketOne = proto.add_present_to_present();
- bucketOne->set_time_millis(2);
- bucketOne->set_frame_count(4);
- auto bucketTwo = proto.add_present_to_present();
- bucketTwo->set_time_millis(4);
- bucketTwo->set_frame_count(1);
- auto bucketThree = proto.add_present_to_present();
- bucketThree->set_time_millis(1000);
- bucketThree->set_frame_count(1);
- static constexpr int64_t expectedAnimationMillis = 12;
- TestableSurfaceflingerStatsPuller puller(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO);
-
- puller.injectStats([&] {
- return proto.SerializeAsString();
- });
- puller.ForceClearCache();
- vector<std::shared_ptr<LogEvent>> outData;
- puller.Pull(&outData);
-
- ASSERT_EQ(1, outData.size());
- EXPECT_EQ(android::util::SURFACEFLINGER_STATS_GLOBAL_INFO, outData[0]->GetTagId());
- EXPECT_EQ(proto.total_frames(), outData[0]->getValues()[0].mValue.long_value);
- EXPECT_EQ(proto.missed_frames(), outData[0]->getValues()[1].mValue.long_value);
- EXPECT_EQ(proto.client_composition_frames(), outData[0]->getValues()[2].mValue.long_value);
- EXPECT_EQ(proto.display_on_time(), outData[0]->getValues()[3].mValue.long_value);
- EXPECT_EQ(expectedAnimationMillis, outData[0]->getValues()[4].mValue.long_value);
-}
-
-} // namespace statsd
-} // namespace os
-} // namespace android
-#else
-GTEST_LOG_(INFO) << "This test does nothing.\n";
-#endif
diff --git a/cmds/statsd/tests/state/StateTracker_test.cpp b/cmds/statsd/tests/state/StateTracker_test.cpp
index 26a3733..84aaa54 100644
--- a/cmds/statsd/tests/state/StateTracker_test.cpp
+++ b/cmds/statsd/tests/state/StateTracker_test.cpp
@@ -76,6 +76,23 @@
return event;
}
+// State with first uid in attribution chain as primary field - WakelockStateChanged
+std::shared_ptr<LogEvent> buildPartialWakelockEvent(int uid, const std::string& tag, bool acquire) {
+ std::vector<AttributionNodeInternal> chain;
+ chain.push_back(AttributionNodeInternal());
+ AttributionNodeInternal& attr = chain.back();
+ attr.set_uid(uid);
+
+ std::shared_ptr<LogEvent> event =
+ std::make_shared<LogEvent>(android::util::WAKELOCK_STATE_CHANGED, 1000 /* timestamp */);
+ event->write(chain);
+ event->write((int32_t)1); // PARTIAL_WAKE_LOCK
+ event->write(tag);
+ event->write(acquire ? 1 : 0);
+ event->init();
+ return event;
+}
+
// State with multiple primary fields - OverlayStateChanged
std::shared_ptr<LogEvent> buildOverlayEvent(int uid, const std::string& packageName, int state) {
std::shared_ptr<LogEvent> event =
@@ -134,6 +151,39 @@
key->addValue(FieldValue(field1, value1));
key->addValue(FieldValue(field2, value2));
}
+
+void getPartialWakelockKey(int uid, const std::string& tag, HashableDimensionKey* key) {
+ int pos1[] = {1, 1, 1};
+ int pos3[] = {2, 0, 0};
+ int pos4[] = {3, 0, 0};
+
+ Field field1(10 /* atom id */, pos1, 2 /* depth */);
+
+ Field field3(10 /* atom id */, pos3, 0 /* depth */);
+ Field field4(10 /* atom id */, pos4, 0 /* depth */);
+
+ Value value1((int32_t)uid);
+ Value value3((int32_t)1 /*partial*/);
+ Value value4(tag);
+
+ key->addValue(FieldValue(field1, value1));
+ key->addValue(FieldValue(field3, value3));
+ key->addValue(FieldValue(field4, value4));
+}
+
+void getPartialWakelockKey(int uid, HashableDimensionKey* key) {
+ int pos1[] = {1, 1, 1};
+ int pos3[] = {2, 0, 0};
+
+ Field field1(10 /* atom id */, pos1, 2 /* depth */);
+ Field field3(10 /* atom id */, pos3, 0 /* depth */);
+
+ Value value1((int32_t)uid);
+ Value value3((int32_t)1 /*partial*/);
+
+ key->addValue(FieldValue(field1, value1));
+ key->addValue(FieldValue(field3, value3));
+}
// END: get primary key functions
TEST(StateListenerTest, TestStateListenerWeakPointer) {
@@ -247,7 +297,8 @@
// check StateTracker was updated by querying for state
HashableDimensionKey queryKey = DEFAULT_DIMENSION_KEY;
- EXPECT_EQ(2, getStateInt(mgr, android::util::SCREEN_STATE_CHANGED, queryKey));
+ EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON,
+ getStateInt(mgr, android::util::SCREEN_STATE_CHANGED, queryKey));
}
/**
@@ -272,7 +323,46 @@
// check StateTracker was updated by querying for state
HashableDimensionKey queryKey;
getUidProcessKey(1000 /* uid */, &queryKey);
- EXPECT_EQ(1002, getStateInt(mgr, android::util::UID_PROCESS_STATE_CHANGED, queryKey));
+ EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_TOP,
+ getStateInt(mgr, android::util::UID_PROCESS_STATE_CHANGED, queryKey));
+}
+
+TEST(StateTrackerTest, TestStateChangePrimaryFieldAttrChain) {
+ sp<TestStateListener> listener1 = new TestStateListener();
+ StateManager mgr;
+ mgr.registerListener(android::util::WAKELOCK_STATE_CHANGED, listener1);
+
+ // Log event.
+ std::shared_ptr<LogEvent> event =
+ buildPartialWakelockEvent(1001 /* uid */, "tag1", false /* acquire */);
+ mgr.onLogEvent(*event);
+
+ EXPECT_EQ(1, mgr.getStateTrackersCount());
+ EXPECT_EQ(1, mgr.getListenersCount(android::util::WAKELOCK_STATE_CHANGED));
+
+ // Check listener was updated.
+ EXPECT_EQ(1, listener1->updates.size());
+ EXPECT_EQ(3, listener1->updates[0].mKey.getValues().size());
+ EXPECT_EQ(1001, listener1->updates[0].mKey.getValues()[0].mValue.int_value);
+ EXPECT_EQ(1, listener1->updates[0].mKey.getValues()[1].mValue.int_value);
+ EXPECT_EQ("tag1", listener1->updates[0].mKey.getValues()[2].mValue.str_value);
+ EXPECT_EQ(WakelockStateChanged::RELEASE, listener1->updates[0].mState);
+
+ // Check StateTracker was updated by querying for state.
+ HashableDimensionKey queryKey;
+ getPartialWakelockKey(1001 /* uid */, "tag1", &queryKey);
+ EXPECT_EQ(WakelockStateChanged::RELEASE,
+ getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey));
+
+ // No state stored for this query key.
+ HashableDimensionKey queryKey2;
+ getPartialWakelockKey(1002 /* uid */, "tag1", &queryKey2);
+ EXPECT_EQ(-1, getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey2));
+
+ // Partial query fails.
+ HashableDimensionKey queryKey3;
+ getPartialWakelockKey(1001 /* uid */, &queryKey3);
+ EXPECT_EQ(-1, getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey3));
}
/**
@@ -297,7 +387,8 @@
// check StateTracker was updated by querying for state
HashableDimensionKey queryKey;
getOverlayKey(1000 /* uid */, "package1", &queryKey);
- EXPECT_EQ(1, getStateInt(mgr, android::util::OVERLAY_STATE_CHANGED, queryKey));
+ EXPECT_EQ(OverlayStateChanged::ENTERED,
+ getStateInt(mgr, android::util::OVERLAY_STATE_CHANGED, queryKey));
}
/**
@@ -326,10 +417,12 @@
sp<TestStateListener> listener1 = new TestStateListener();
sp<TestStateListener> listener2 = new TestStateListener();
sp<TestStateListener> listener3 = new TestStateListener();
+ sp<TestStateListener> listener4 = new TestStateListener();
StateManager mgr;
mgr.registerListener(android::util::SCREEN_STATE_CHANGED, listener1);
mgr.registerListener(android::util::UID_PROCESS_STATE_CHANGED, listener2);
mgr.registerListener(android::util::OVERLAY_STATE_CHANGED, listener3);
+ mgr.registerListener(android::util::WAKELOCK_STATE_CHANGED, listener4);
std::shared_ptr<LogEvent> event1 = buildUidProcessEvent(
1000,
@@ -346,8 +439,12 @@
android::app::ProcessStateEnum::PROCESS_STATE_TOP); // state value: 1002
std::shared_ptr<LogEvent> event5 =
buildScreenEvent(android::view::DisplayStateEnum::DISPLAY_STATE_ON);
- std::shared_ptr<LogEvent> event6 = buildOverlayEvent(1000, "package1", 1);
- std::shared_ptr<LogEvent> event7 = buildOverlayEvent(1000, "package2", 2);
+ std::shared_ptr<LogEvent> event6 =
+ buildOverlayEvent(1000, "package1", OverlayStateChanged::ENTERED);
+ std::shared_ptr<LogEvent> event7 =
+ buildOverlayEvent(1000, "package2", OverlayStateChanged::EXITED);
+ std::shared_ptr<LogEvent> event8 = buildPartialWakelockEvent(1005, "tag1", true);
+ std::shared_ptr<LogEvent> event9 = buildPartialWakelockEvent(1005, "tag2", false);
mgr.onLogEvent(*event1);
mgr.onLogEvent(*event2);
@@ -356,11 +453,14 @@
mgr.onLogEvent(*event5);
mgr.onLogEvent(*event6);
mgr.onLogEvent(*event7);
+ mgr.onLogEvent(*event8);
+ mgr.onLogEvent(*event9);
// Query for UidProcessState of uid 1001
HashableDimensionKey queryKey1;
getUidProcessKey(1001, &queryKey1);
- EXPECT_EQ(1003, getStateInt(mgr, android::util::UID_PROCESS_STATE_CHANGED, queryKey1));
+ EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE,
+ getStateInt(mgr, android::util::UID_PROCESS_STATE_CHANGED, queryKey1));
// Query for UidProcessState of uid 1004 - not in state map
HashableDimensionKey queryKey2;
@@ -370,15 +470,30 @@
// Query for UidProcessState of uid 1001 - after change in state
mgr.onLogEvent(*event4);
- EXPECT_EQ(1002, getStateInt(mgr, android::util::UID_PROCESS_STATE_CHANGED, queryKey1));
+ EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_TOP,
+ getStateInt(mgr, android::util::UID_PROCESS_STATE_CHANGED, queryKey1));
// Query for ScreenState
- EXPECT_EQ(2, getStateInt(mgr, android::util::SCREEN_STATE_CHANGED, DEFAULT_DIMENSION_KEY));
+ EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON,
+ getStateInt(mgr, android::util::SCREEN_STATE_CHANGED, DEFAULT_DIMENSION_KEY));
// Query for OverlayState of uid 1000, package name "package2"
HashableDimensionKey queryKey3;
getOverlayKey(1000, "package2", &queryKey3);
- EXPECT_EQ(2, getStateInt(mgr, android::util::OVERLAY_STATE_CHANGED, queryKey3));
+ EXPECT_EQ(OverlayStateChanged::EXITED,
+ getStateInt(mgr, android::util::OVERLAY_STATE_CHANGED, queryKey3));
+
+ // Query for WakelockState of uid 1005, tag 2
+ HashableDimensionKey queryKey4;
+ getPartialWakelockKey(1005, "tag2", &queryKey4);
+ EXPECT_EQ(WakelockStateChanged::RELEASE,
+ getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey4));
+
+ // Query for WakelockState of uid 1005, tag 1
+ HashableDimensionKey queryKey5;
+ getPartialWakelockKey(1005, "tag1", &queryKey5);
+ EXPECT_EQ(WakelockStateChanged::ACQUIRE,
+ getStateInt(mgr, android::util::WAKELOCK_STATE_CHANGED, queryKey5));
}
} // namespace statsd
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index 7b651df..e0aecce 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -617,19 +617,6 @@
void ValidateAttributionUidDimension(const DimensionsValue& value, int atomId, int uid) {
EXPECT_EQ(value.field(), atomId);
- // Attribution field.
- EXPECT_EQ(value.value_tuple().dimensions_value(0).field(), 1);
- // Uid only.
- EXPECT_EQ(value.value_tuple().dimensions_value(0)
- .value_tuple().dimensions_value_size(), 1);
- EXPECT_EQ(value.value_tuple().dimensions_value(0)
- .value_tuple().dimensions_value(0).field(), 1);
- EXPECT_EQ(value.value_tuple().dimensions_value(0)
- .value_tuple().dimensions_value(0).value_int(), uid);
-}
-
-void ValidateUidDimension(const DimensionsValue& value, int atomId, int uid) {
- EXPECT_EQ(value.field(), atomId);
EXPECT_EQ(value.value_tuple().dimensions_value_size(), 1);
// Attribution field.
EXPECT_EQ(value.value_tuple().dimensions_value(0).field(), 1);
diff --git a/config/boot-image-profile.txt b/config/boot-image-profile.txt
index 4d0acb3..4aaf727 100644
--- a/config/boot-image-profile.txt
+++ b/config/boot-image-profile.txt
@@ -22519,8 +22519,6 @@
HSPLcom/android/internal/telephony/PhoneFactory;->makeDefaultPhones(Landroid/content/Context;)V
HSPLcom/android/internal/telephony/PhoneInternalInterface$DataActivityState;-><init>(Ljava/lang/String;I)V
HSPLcom/android/internal/telephony/PhoneInternalInterface$DataActivityState;->values()[Lcom/android/internal/telephony/PhoneInternalInterface$DataActivityState;
-HSPLcom/android/internal/telephony/PhoneStateIntentReceiver;-><init>(Landroid/content/Context;Landroid/os/Handler;)V
-HSPLcom/android/internal/telephony/PhoneStateIntentReceiver;->notifyServiceState(I)V
HSPLcom/android/internal/telephony/PhoneSubInfoController;->callPhoneMethodWithPermissionCheck(ILjava/lang/String;Ljava/lang/String;Lcom/android/internal/telephony/PhoneSubInfoController$CallPhoneMethodHelper;Lcom/android/internal/telephony/PhoneSubInfoController$PermissionCheckHelper;)Ljava/lang/Object;
HSPLcom/android/internal/telephony/PhoneSubInfoController;->getCarrierInfoForImsiEncryption(IILjava/lang/String;)Landroid/telephony/ImsiEncryptionInfo;
HSPLcom/android/internal/telephony/PhoneSubInfoController;->getGroupIdLevel1ForSubscriber(ILjava/lang/String;)Ljava/lang/String;
@@ -37729,7 +37727,6 @@
Lcom/android/internal/telephony/PhoneInternalInterface$DataActivityState;
Lcom/android/internal/telephony/PhoneInternalInterface;
Lcom/android/internal/telephony/PhoneNotifier;
-Lcom/android/internal/telephony/PhoneStateIntentReceiver;
Lcom/android/internal/telephony/PhoneSubInfoController$CallPhoneMethodHelper;
Lcom/android/internal/telephony/PhoneSubInfoController$PermissionCheckHelper;
Lcom/android/internal/telephony/PhoneSubInfoController;
diff --git a/config/preloaded-classes b/config/preloaded-classes
index 97f009c..e53c74b 100644
--- a/config/preloaded-classes
+++ b/config/preloaded-classes
@@ -4841,7 +4841,6 @@
com.android.internal.telephony.PhoneFactory
com.android.internal.telephony.PhoneInternalInterface
com.android.internal.telephony.PhoneNotifier
-com.android.internal.telephony.PhoneStateIntentReceiver
com.android.internal.telephony.PhoneSubInfoController$CallPhoneMethodHelper
com.android.internal.telephony.PhoneSubInfoController$PermissionCheckHelper
com.android.internal.telephony.PhoneSubInfoController
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index c0fee6e..0bd8ce6 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -17,6 +17,7 @@
package android.accessibilityservice;
import android.accessibilityservice.GestureDescription.MotionEventGenerator;
+import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -26,12 +27,15 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.ParceledListSlice;
+import android.graphics.Bitmap;
import android.graphics.Region;
+import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
+import android.os.RemoteCallback;
import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
@@ -48,10 +52,13 @@
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
+import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* Accessibility services should only be used to assist users with disabilities in using
@@ -483,6 +490,9 @@
private FingerprintGestureController mFingerprintGestureController;
+ /** @hide */
+ public static final String KEY_ACCESSIBILITY_SCREENSHOT = "screenshot";
+
/**
* Callback for {@link android.view.accessibility.AccessibilityEvent}s.
*
@@ -1547,6 +1557,34 @@
void onShowModeChanged(@NonNull SoftKeyboardController controller,
@SoftKeyboardShowMode int showMode);
}
+
+ /**
+ * Switches the current IME for the user for whom the service is enabled. The change will
+ * persist until the current IME is explicitly changed again, and may persist beyond the
+ * life cycle of the requesting service.
+ *
+ * @param imeId The ID of the input method to make current. This IME must be installed and
+ * enabled.
+ * @return {@code true} if the current input method was successfully switched to the input
+ * method by {@code imeId},
+ * {@code false} if the input method specified is not installed, not enabled, or
+ * otherwise not available to become the current IME
+ *
+ * @see android.view.inputmethod.InputMethodInfo#getId()
+ */
+ public boolean switchToInputMethod(@NonNull String imeId) {
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance().getConnection(
+ mService.mConnectionId);
+ if (connection != null) {
+ try {
+ return connection.switchToInputMethod(imeId);
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ }
+ return false;
+ }
}
/**
@@ -1733,6 +1771,51 @@
}
/**
+ * Takes a screenshot of the specified display and returns it by {@link Bitmap.Config#HARDWARE}
+ * format.
+ * <p>
+ * <strong>Note:</strong> In order to take screenshot your service has
+ * to declare the capability to take screenshot by setting the
+ * {@link android.R.styleable#AccessibilityService_canTakeScreenshot}
+ * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}.
+ * Besides, This API is only supported for default display now
+ * {@link Display#DEFAULT_DISPLAY}.
+ * </p>
+ *
+ * @param displayId The logic display id, must be {@link Display#DEFAULT_DISPLAY} for
+ * default display.
+ * @param executor Executor on which to run the callback.
+ * @param callback The callback invoked when the taking screenshot is done.
+ *
+ * @return {@code true} if the taking screenshot accepted, {@code false} if not.
+ */
+ public boolean takeScreenshot(int displayId, @NonNull @CallbackExecutor Executor executor,
+ @NonNull Consumer<Bitmap> callback) {
+ Preconditions.checkNotNull(executor, "executor cannot be null");
+ Preconditions.checkNotNull(callback, "callback cannot be null");
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance().getConnection(
+ mConnectionId);
+ if (connection == null) {
+ return false;
+ }
+ try {
+ connection.takeScreenshotWithCallback(displayId, new RemoteCallback((result) -> {
+ final Bitmap screenshot = result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT);
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ executor.execute(() -> callback.accept(screenshot));
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }));
+ } catch (RemoteException re) {
+ throw new RuntimeException(re);
+ }
+ return true;
+ }
+
+ /**
* Implement to return the implementation of the internal accessibility
* service interface.
*/
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 5e2c1fa..12f2c3b 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -86,6 +86,7 @@
* @attr ref android.R.styleable#AccessibilityService_settingsActivity
* @attr ref android.R.styleable#AccessibilityService_nonInteractiveUiTimeout
* @attr ref android.R.styleable#AccessibilityService_interactiveUiTimeout
+ * @attr ref android.R.styleable#AccessibilityService_canTakeScreenshot
* @see AccessibilityService
* @see android.view.accessibility.AccessibilityEvent
* @see android.view.accessibility.AccessibilityManager
@@ -136,6 +137,12 @@
*/
public static final int CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES = 0x00000040;
+ /**
+ * Capability: This accessibility service can take screenshot.
+ * @see android.R.styleable#AccessibilityService_canTakeScreenshot
+ */
+ public static final int CAPABILITY_CAN_TAKE_SCREENSHOT = 0x00000080;
+
private static SparseArray<CapabilityInfo> sAvailableCapabilityInfos;
/**
@@ -625,6 +632,10 @@
.AccessibilityService_canRequestFingerprintGestures, false)) {
mCapabilities |= CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES;
}
+ if (asAttributes.getBoolean(com.android.internal.R.styleable
+ .AccessibilityService_canTakeScreenshot, false)) {
+ mCapabilities |= CAPABILITY_CAN_TAKE_SCREENSHOT;
+ }
TypedValue peekedValue = asAttributes.peekValue(
com.android.internal.R.styleable.AccessibilityService_description);
if (peekedValue != null) {
@@ -794,6 +805,7 @@
* @see #CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS
* @see #CAPABILITY_CAN_CONTROL_MAGNIFICATION
* @see #CAPABILITY_CAN_PERFORM_GESTURES
+ * @see #CAPABILITY_CAN_TAKE_SCREENSHOT
*/
public int getCapabilities() {
return mCapabilities;
@@ -810,6 +822,7 @@
* @see #CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS
* @see #CAPABILITY_CAN_CONTROL_MAGNIFICATION
* @see #CAPABILITY_CAN_PERFORM_GESTURES
+ * @see #CAPABILITY_CAN_TAKE_SCREENSHOT
*
* @hide
*/
@@ -1253,6 +1266,8 @@
return "CAPABILITY_CAN_PERFORM_GESTURES";
case CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES:
return "CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES";
+ case CAPABILITY_CAN_TAKE_SCREENSHOT:
+ return "CAPABILITY_CAN_TAKE_SCREENSHOT";
default:
return "UNKNOWN";
}
@@ -1314,6 +1329,10 @@
new CapabilityInfo(CAPABILITY_CAN_PERFORM_GESTURES,
R.string.capability_title_canPerformGestures,
R.string.capability_desc_canPerformGestures));
+ sAvailableCapabilityInfos.put(CAPABILITY_CAN_TAKE_SCREENSHOT,
+ new CapabilityInfo(CAPABILITY_CAN_TAKE_SCREENSHOT,
+ R.string.capability_title_canTakeScreenshot,
+ R.string.capability_desc_canTakeScreenshot));
if ((context == null) || fingerprintAvailable(context)) {
sAvailableCapabilityInfos.put(CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES,
new CapabilityInfo(CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES,
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 4841781..4ea5c62 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -18,8 +18,10 @@
import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.pm.ParceledListSlice;
+import android.graphics.Bitmap;
import android.graphics.Region;
import android.os.Bundle;
+import android.os.RemoteCallback;
import android.view.MagnificationSpec;
import android.view.MotionEvent;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -91,6 +93,8 @@
void setSoftKeyboardCallbackEnabled(boolean enabled);
+ boolean switchToInputMethod(String imeId);
+
boolean isAccessibilityButtonAvailable();
void sendGesture(int sequence, in ParceledListSlice gestureSteps);
@@ -100,4 +104,10 @@
boolean isFingerprintGestureDetectionAvailable();
IBinder getOverlayWindowToken(int displayid);
+
+ int getWindowIdForLeashToken(IBinder token);
+
+ Bitmap takeScreenshot(int displayId);
+
+ void takeScreenshotWithCallback(int displayId, in RemoteCallback callback);
}
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 8fe2f12..f2702a8 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -25,6 +25,7 @@
import android.annotation.Size;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.UserHandleAware;
import android.app.Activity;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
@@ -40,6 +41,7 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Parcelable;
+import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.text.TextUtils;
@@ -528,12 +530,9 @@
* authenticator known to the AccountManager service. Empty (never
* null) if no authenticators are known.
*/
+ @UserHandleAware
public AuthenticatorDescription[] getAuthenticatorTypes() {
- try {
- return mService.getAuthenticatorTypes(UserHandle.getCallingUserId());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return getAuthenticatorTypesAsUser(mContext.getUserId());
}
/**
@@ -584,13 +583,10 @@
* @return An array of {@link Account}, one for each account. Empty (never null) if no accounts
* have been added.
*/
+ @UserHandleAware
@NonNull
public Account[] getAccounts() {
- try {
- return mService.getAccounts(null, mContext.getOpPackageName());
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
+ return getAccountsAsUser(mContext.getUserId());
}
/**
@@ -708,6 +704,7 @@
* @return An array of {@link Account}, one per matching account. Empty (never null) if no
* accounts of the specified type have been added.
*/
+ @UserHandleAware
@NonNull
public Account[] getAccountsByType(String type) {
return getAccountsByTypeAsUser(type, mContext.getUser());
@@ -1183,23 +1180,11 @@
* {@link #removeAccount(Account, Activity, AccountManagerCallback, Handler)}
* instead
*/
+ @UserHandleAware
@Deprecated
public AccountManagerFuture<Boolean> removeAccount(final Account account,
AccountManagerCallback<Boolean> callback, Handler handler) {
- if (account == null) throw new IllegalArgumentException("account is null");
- return new Future2Task<Boolean>(handler, callback) {
- @Override
- public void doWork() throws RemoteException {
- mService.removeAccount(mResponse, account, false);
- }
- @Override
- public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
- if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) {
- throw new AuthenticatorException("no result in response");
- }
- return bundle.getBoolean(KEY_BOOLEAN_RESULT);
- }
- }.start();
+ return removeAccountAsUser(account, callback, handler, mContext.getUser());
}
/**
@@ -1243,15 +1228,10 @@
* adding accounts (of this type) has been disabled by policy
* </ul>
*/
+ @UserHandleAware
public AccountManagerFuture<Bundle> removeAccount(final Account account,
final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
- if (account == null) throw new IllegalArgumentException("account is null");
- return new AmsTask(activity, handler, callback) {
- @Override
- public void doWork() throws RemoteException {
- mService.removeAccount(mResponse, account, activity != null);
- }
- }.start();
+ return removeAccountAsUser(account, activity, callback, handler, mContext.getUser());
}
/**
@@ -1841,24 +1821,30 @@
* creating a new account, usually because of network trouble
* </ul>
*/
+ @UserHandleAware
public AccountManagerFuture<Bundle> addAccount(final String accountType,
final String authTokenType, final String[] requiredFeatures,
final Bundle addAccountOptions,
final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
- if (accountType == null) throw new IllegalArgumentException("accountType is null");
- final Bundle optionsIn = new Bundle();
- if (addAccountOptions != null) {
- optionsIn.putAll(addAccountOptions);
- }
- optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName());
-
- return new AmsTask(activity, handler, callback) {
- @Override
- public void doWork() throws RemoteException {
- mService.addAccount(mResponse, accountType, authTokenType,
- requiredFeatures, activity != null, optionsIn);
+ if (Process.myUserHandle().equals(mContext.getUser())) {
+ if (accountType == null) throw new IllegalArgumentException("accountType is null");
+ final Bundle optionsIn = new Bundle();
+ if (addAccountOptions != null) {
+ optionsIn.putAll(addAccountOptions);
}
- }.start();
+ optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName());
+
+ return new AmsTask(activity, handler, callback) {
+ @Override
+ public void doWork() throws RemoteException {
+ mService.addAccount(mResponse, accountType, authTokenType,
+ requiredFeatures, activity != null, optionsIn);
+ }
+ }.start();
+ } else {
+ return addAccountAsUser(accountType, authTokenType, requiredFeatures, addAccountOptions,
+ activity, callback, handler, mContext.getUser());
+ }
}
/**
@@ -2002,6 +1988,7 @@
* verifying the password, usually because of network trouble
* </ul>
*/
+ @UserHandleAware
public AccountManagerFuture<Bundle> confirmCredentials(final Account account,
final Bundle options,
final Activity activity,
@@ -3209,6 +3196,7 @@
* </ul>
* @see #startAddAccountSession and #startUpdateCredentialsSession
*/
+ @UserHandleAware
public AccountManagerFuture<Bundle> finishSession(
final Bundle sessionBundle,
final Activity activity,
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index 0127138..ce68e08 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -34,7 +34,6 @@
String getPassword(in Account account);
String getUserData(in Account account, String key);
AuthenticatorDescription[] getAuthenticatorTypes(int userId);
- Account[] getAccounts(String accountType, String opPackageName);
Account[] getAccountsForPackage(String packageName, int uid, String opPackageName);
Account[] getAccountsByTypeForPackage(String type, String packageName, String opPackageName);
Account[] getAccountsAsUser(String accountType, int userId, String opPackageName);
@@ -45,8 +44,6 @@
void getAccountsByFeatures(in IAccountManagerResponse response, String accountType,
in String[] features, String opPackageName);
boolean addAccountExplicitly(in Account account, String password, in Bundle extras);
- void removeAccount(in IAccountManagerResponse response, in Account account,
- boolean expectActivityLaunch);
void removeAccountAsUser(in IAccountManagerResponse response, in Account account,
boolean expectActivityLaunch, int userId);
boolean removeAccountExplicitly(in Account account);
diff --git a/core/java/android/annotation/SystemService.java b/core/java/android/annotation/SystemService.java
index 0c5d15e..c05c1ba 100644
--- a/core/java/android/annotation/SystemService.java
+++ b/core/java/android/annotation/SystemService.java
@@ -19,14 +19,12 @@
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.SOURCE;
-import android.content.Context;
-
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
/**
* Description of a system service available through
- * {@link Context#getSystemService(Class)}. This is used to auto-generate
+ * {@link android.content.Context#getSystemService(Class)}. This is used to auto-generate
* documentation explaining how to obtain a reference to the service.
*
* @hide
@@ -36,9 +34,9 @@
public @interface SystemService {
/**
* The string name of the system service that can be passed to
- * {@link Context#getSystemService(String)}.
+ * {@link android.content.Context#getSystemService(String)}.
*
- * @see Context#getSystemServiceName(Class)
+ * @see android.content.Context#getSystemServiceName(Class)
*/
String value();
}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 070a4f8..d952be5 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -2539,7 +2539,8 @@
mCalled = true;
if (mAutoFillResetNeeded) {
- getAutofillManager().onInvisibleForAutofill();
+ // If stopped without changing the configurations, the response should expire.
+ getAutofillManager().onInvisibleForAutofill(!mChangingConfigurations);
} else if (mIntent != null
&& mIntent.hasExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN)
&& mIntent.hasExtra(AutofillManager.EXTRA_RESTORE_CROSS_ACTIVITY)) {
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 3f9f7fb..2010cc9 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -3107,7 +3107,7 @@
public static final int IMPORTANCE_CANT_SAVE_STATE_PRE_26 = 170;
/**
- * Constant for {@link #importance}: This process is contains services
+ * Constant for {@link #importance}: This process contains services
* that should remain running. These are background services apps have
* started, not something the user is aware of, so they may be killed by
* the system relatively freely (though it is generally desired that they
@@ -4064,6 +4064,7 @@
* @hide
*/
@SystemApi
+ @TestApi
@RequiresPermission(android.Manifest.permission.MANAGE_USERS)
public boolean switchUser(@NonNull UserHandle user) {
if (user == null) {
@@ -4073,6 +4074,29 @@
}
/**
+ * Updates mcc mnc configuration and applies changes to the entire system.
+ *
+ * @param mcc mcc configuration to update.
+ * @param mnc mnc configuration to update.
+ * @throws RemoteException; IllegalArgumentException if mcc or mnc is null;
+ * @return Returns {@code true} if the configuration was updated successfully;
+ * {@code false} otherwise.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION)
+ public boolean updateMccMncConfiguration(@NonNull String mcc, @NonNull String mnc) {
+ if (mcc == null || mnc == null) {
+ throw new IllegalArgumentException("mcc or mnc cannot be null.");
+ }
+ try {
+ return getService().updateMccMncConfiguration(mcc, mnc);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Logs out current current foreground user by switching to the system user and stopping the
* user being switched from.
* @hide
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index de7cc9d..4f3e8ec 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -45,8 +45,19 @@
// Access modes for handleIncomingUser.
public static final int ALLOW_NON_FULL = 0;
+ /**
+ * Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_USERS}
+ * if in the same profile group.
+ * Otherwise, {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required.
+ */
public static final int ALLOW_NON_FULL_IN_PROFILE = 1;
public static final int ALLOW_FULL_ONLY = 2;
+ /**
+ * Allows access to a caller with {@link android.Manifest.permission#INTERACT_ACROSS_PROFILES}
+ * or {@link android.Manifest.permission#INTERACT_ACROSS_USERS} if in the same profile group.
+ * Otherwise, {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required.
+ */
+ public static final int ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE = 3;
/**
* Verify that calling app has access to the given provider.
@@ -278,6 +289,9 @@
public abstract boolean isBackgroundActivityStartsEnabled();
public abstract void reportCurKeyguardUsageEvent(boolean keyguardShowing);
+ /** @see com.android.server.am.ActivityManagerService#monitor */
+ public abstract void monitor();
+
/** Input dispatch timeout to a window, start the ANR process. */
public abstract long inputDispatchingTimedOut(int pid, boolean aboveSystem, String reason);
public abstract boolean inputDispatchingTimedOut(Object proc, String activityShortComponentName,
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index a4f6f57..b82a675 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -3349,8 +3349,8 @@
}
@Override
- public void handleStartActivity(ActivityClientRecord r,
- PendingTransactionActions pendingActions) {
+ public void handleStartActivity(IBinder token, PendingTransactionActions pendingActions) {
+ final ActivityClientRecord r = mActivities.get(token);
final Activity activity = r.activity;
if (r.activity == null) {
// TODO(lifecycler): What do we do in this case?
@@ -3364,6 +3364,8 @@
return;
}
+ unscheduleGcIdler();
+
// Start
activity.performStart("handleStartActivity");
r.setState(ON_START);
@@ -3400,6 +3402,9 @@
+ " did not call through to super.onPostCreate()");
}
}
+
+ updateVisibility(r, true /* show */);
+ mSomeActivitiesChanged = true;
}
/**
@@ -4660,8 +4665,8 @@
@UnsupportedAppUsage
final void performStopActivity(IBinder token, boolean saveState, String reason) {
ActivityClientRecord r = mActivities.get(token);
- performStopActivityInner(r, null /* stopInfo */, false /* keepShown */, saveState,
- false /* finalStateRequest */, reason);
+ performStopActivityInner(r, null /* stopInfo */, saveState, false /* finalStateRequest */,
+ reason);
}
private static final class ProviderRefCount {
@@ -4687,25 +4692,19 @@
}
/**
- * Core implementation of stopping an activity. Note this is a little
- * tricky because the server's meaning of stop is slightly different
- * than our client -- for the server, stop means to save state and give
- * it the result when it is done, but the window may still be visible.
- * For the client, we want to call onStop()/onStart() to indicate when
- * the activity's UI visibility changes.
+ * Core implementation of stopping an activity.
* @param r Target activity client record.
* @param info Action that will report activity stop to server.
- * @param keepShown Flag indicating whether the activity is still shown.
* @param saveState Flag indicating whether the activity state should be saved.
* @param finalStateRequest Flag indicating if this call is handling final lifecycle state
* request for a transaction.
* @param reason Reason for performing this operation.
*/
- private void performStopActivityInner(ActivityClientRecord r, StopInfo info, boolean keepShown,
+ private void performStopActivityInner(ActivityClientRecord r, StopInfo info,
boolean saveState, boolean finalStateRequest, String reason) {
if (localLOGV) Slog.v(TAG, "Performing stop of " + r);
if (r != null) {
- if (!keepShown && r.stopped) {
+ if (r.stopped) {
if (r.activity.mFinished) {
// If we are finishing, we won't call onResume() in certain
// cases. So here we likewise don't want to call onStop()
@@ -4740,9 +4739,7 @@
}
}
- if (!keepShown) {
- callActivityOnStop(r, saveState, reason);
- }
+ callActivityOnStop(r, saveState, reason);
}
}
@@ -4810,20 +4807,19 @@
}
@Override
- public void handleStopActivity(IBinder token, boolean show, int configChanges,
+ public void handleStopActivity(IBinder token, int configChanges,
PendingTransactionActions pendingActions, boolean finalStateRequest, String reason) {
final ActivityClientRecord r = mActivities.get(token);
r.activity.mConfigChangeFlags |= configChanges;
final StopInfo stopInfo = new StopInfo();
- performStopActivityInner(r, stopInfo, show, true /* saveState */, finalStateRequest,
+ performStopActivityInner(r, stopInfo, true /* saveState */, finalStateRequest,
reason);
if (localLOGV) Slog.v(
- TAG, "Finishing stop of " + r + ": show=" + show
- + " win=" + r.window);
+ TAG, "Finishing stop of " + r + ": win=" + r.window);
- updateVisibility(r, show);
+ updateVisibility(r, false);
// Make sure any pending writes are now committed.
if (!r.isPreHoneycomb()) {
@@ -4859,34 +4855,6 @@
}
}
- @Override
- public void handleWindowVisibility(IBinder token, boolean show) {
- ActivityClientRecord r = mActivities.get(token);
-
- if (r == null) {
- Log.w(TAG, "handleWindowVisibility: no activity for token " + token);
- return;
- }
-
- if (!show && !r.stopped) {
- performStopActivityInner(r, null /* stopInfo */, show, false /* saveState */,
- false /* finalStateRequest */, "handleWindowVisibility");
- } else if (show && r.getLifecycleState() == ON_STOP) {
- // If we are getting ready to gc after going to the background, well
- // we are back active so skip it.
- unscheduleGcIdler();
-
- r.activity.performRestart(true /* start */, "handleWindowVisibility");
- r.setState(ON_START);
- }
- if (r.activity.mDecor != null) {
- if (false) Slog.v(
- TAG, "Handle window " + r + " visibility: " + show);
- updateVisibility(r, show);
- }
- mSomeActivitiesChanged = true;
- }
-
// TODO: This method should be changed to use {@link #performStopActivityInner} to perform to
// stop operation on the activity to reduce code duplication and the chance of fixing a bug in
// one place and missing the other.
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index c942a46..a11f41f 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -726,7 +726,17 @@
/** @hide Capture the device's display contents and/or audio */
@UnsupportedAppUsage
public static final int OP_PROJECT_MEDIA = 46;
- /** @hide Activate a VPN connection without user intervention. */
+ /**
+ * Start (without additional user intervention) a VPN connection, as used by {@link
+ * android.net.VpnService} along with as Platform VPN connections, as used by {@link
+ * android.net.VpnManager}
+ *
+ * <p>This appop is granted to apps that have already been given user consent to start
+ * VpnService based VPN connections. As this is a superset of OP_ACTIVATE_PLATFORM_VPN, this
+ * appop also allows the starting of Platform VPNs.
+ *
+ * @hide
+ */
@UnsupportedAppUsage
public static final int OP_ACTIVATE_VPN = 47;
/** @hide Access the WallpaperManagerAPI to write wallpapers. */
@@ -850,10 +860,23 @@
public static final int OP_QUERY_ALL_PACKAGES = 91;
/** @hide Access all external storage */
public static final int OP_MANAGE_EXTERNAL_STORAGE = 92;
+ /** @hide Communicate cross-profile within the same profile group. */
+ public static final int OP_INTERACT_ACROSS_PROFILES = 93;
+ /**
+ * Start (without additional user intervention) a Platform VPN connection, as used by {@link
+ * android.net.VpnManager}
+ *
+ * <p>This appop is granted to apps that have already been given user consent to start Platform
+ * VPN connections. This appop is insufficient to start VpnService based VPNs; OP_ACTIVATE_VPN
+ * is needed for that.
+ *
+ * @hide
+ */
+ public static final int OP_ACTIVATE_PLATFORM_VPN = 94;
/** @hide */
@UnsupportedAppUsage
- public static final int _NUM_OP = 93;
+ public static final int _NUM_OP = 95;
/** Access to coarse location information. */
public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1144,6 +1167,12 @@
public static final String OPSTR_MANAGE_EXTERNAL_STORAGE =
"android:manage_external_storage";
+ /** @hide Communicate cross-profile within the same profile group. */
+ @SystemApi
+ public static final String OPSTR_INTERACT_ACROSS_PROFILES = "android:interact_across_profiles";
+ /** @hide Start Platform VPN without user intervention */
+ public static final String OPSTR_ACTIVATE_PLATFORM_VPN = "android:activate_platform_vpn";
+
/** {@link #sAppOpsToNote} not initialized yet for this op */
private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
@@ -1221,6 +1250,7 @@
OP_MANAGE_IPSEC_TUNNELS,
OP_INSTANT_APP_START_FOREGROUND,
OP_MANAGE_EXTERNAL_STORAGE,
+ OP_INTERACT_ACROSS_PROFILES
};
/**
@@ -1325,6 +1355,8 @@
OP_ACCESS_MEDIA_LOCATION, // ACCESS_MEDIA_LOCATION
OP_QUERY_ALL_PACKAGES, // QUERY_ALL_PACKAGES
OP_MANAGE_EXTERNAL_STORAGE, // MANAGE_EXTERNAL_STORAGE
+ OP_INTERACT_ACROSS_PROFILES, //INTERACT_ACROSS_PROFILES
+ OP_ACTIVATE_PLATFORM_VPN, // ACTIVATE_PLATFORM_VPN
};
/**
@@ -1424,6 +1456,8 @@
OPSTR_ACCESS_MEDIA_LOCATION,
OPSTR_QUERY_ALL_PACKAGES,
OPSTR_MANAGE_EXTERNAL_STORAGE,
+ OPSTR_INTERACT_ACROSS_PROFILES,
+ OPSTR_ACTIVATE_PLATFORM_VPN,
};
/**
@@ -1523,7 +1557,9 @@
"READ_DEVICE_IDENTIFIERS",
"ACCESS_MEDIA_LOCATION",
"QUERY_ALL_PACKAGES",
- "MANAGE_EXTERNAL_STORAGE"
+ "MANAGE_EXTERNAL_STORAGE",
+ "INTERACT_ACROSS_PROFILES",
+ "ACTIVATE_PLATFORM_VPN",
};
/**
@@ -1625,6 +1661,8 @@
Manifest.permission.ACCESS_MEDIA_LOCATION,
null, // no permission for OP_QUERY_ALL_PACKAGES
Manifest.permission.MANAGE_EXTERNAL_STORAGE,
+ android.Manifest.permission.INTERACT_ACROSS_PROFILES,
+ null, // no permission for OP_ACTIVATE_PLATFORM_VPN
};
/**
@@ -1726,6 +1764,8 @@
null, // ACCESS_MEDIA_LOCATION
null, // QUERY_ALL_PACKAGES
null, // MANAGE_EXTERNAL_STORAGE
+ null, // INTERACT_ACROSS_PROFILES
+ null, // ACTIVATE_PLATFORM_VPN
};
/**
@@ -1826,6 +1866,8 @@
false, // ACCESS_MEDIA_LOCATION
false, // QUERY_ALL_PACKAGES
false, // MANAGE_EXTERNAL_STORAGE
+ false, // INTERACT_ACROSS_PROFILES
+ false, // ACTIVATE_PLATFORM_VPN
};
/**
@@ -1925,6 +1967,8 @@
AppOpsManager.MODE_ALLOWED, // ALLOW_MEDIA_LOCATION
AppOpsManager.MODE_DEFAULT, // QUERY_ALL_PACKAGES
AppOpsManager.MODE_DEFAULT, // MANAGE_EXTERNAL_STORAGE
+ AppOpsManager.MODE_DEFAULT, // INTERACT_ACROSS_PROFILES
+ AppOpsManager.MODE_IGNORED, // ACTIVATE_PLATFORM_VPN
};
/**
@@ -2028,6 +2072,8 @@
false, // ACCESS_MEDIA_LOCATION
false, // QUERY_ALL_PACKAGES
false, // MANAGE_EXTERNAL_STORAGE
+ false, // INTERACT_ACROSS_PROFILES
+ false, // ACTIVATE_PLATFORM_VPN
};
/**
@@ -3902,10 +3948,53 @@
void visitHistoricalOps(@NonNull HistoricalOps ops);
void visitHistoricalUidOps(@NonNull HistoricalUidOps ops);
void visitHistoricalPackageOps(@NonNull HistoricalPackageOps ops);
+ void visitHistoricalFeatureOps(@NonNull HistoricalFeatureOps ops);
void visitHistoricalOp(@NonNull HistoricalOp ops);
}
/**
+ * Specifies what parameters to filter historical appop requests for
+ *
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "FILTER_BY_" }, value = {
+ FILTER_BY_UID,
+ FILTER_BY_PACKAGE_NAME,
+ FILTER_BY_FEATURE_ID,
+ FILTER_BY_OP_NAMES
+ })
+ public @interface HistoricalOpsRequestFilter {}
+
+ /**
+ * Filter historical appop request by uid.
+ *
+ * @hide
+ */
+ public static final int FILTER_BY_UID = 1<<0;
+
+ /**
+ * Filter historical appop request by package name.
+ *
+ * @hide
+ */
+ public static final int FILTER_BY_PACKAGE_NAME = 1<<1;
+
+ /**
+ * Filter historical appop request by feature id.
+ *
+ * @hide
+ */
+ public static final int FILTER_BY_FEATURE_ID = 1<<2;
+
+ /**
+ * Filter historical appop request by op names.
+ *
+ * @hide
+ */
+ public static final int FILTER_BY_OP_NAMES = 1<<3;
+
+ /**
* Request for getting historical app op usage. The request acts
* as a filtering criteria when querying historical op usage.
*
@@ -3917,17 +4006,22 @@
public static final class HistoricalOpsRequest {
private final int mUid;
private final @Nullable String mPackageName;
+ private final @Nullable String mFeatureId;
private final @Nullable List<String> mOpNames;
+ private final @HistoricalOpsRequestFilter int mFilter;
private final long mBeginTimeMillis;
private final long mEndTimeMillis;
private final @OpFlags int mFlags;
private HistoricalOpsRequest(int uid, @Nullable String packageName,
- @Nullable List<String> opNames, long beginTimeMillis, long endTimeMillis,
- @OpFlags int flags) {
+ @Nullable String featureId, @Nullable List<String> opNames,
+ @HistoricalOpsRequestFilter int filter, long beginTimeMillis,
+ long endTimeMillis, @OpFlags int flags) {
mUid = uid;
mPackageName = packageName;
+ mFeatureId = featureId;
mOpNames = opNames;
+ mFilter = filter;
mBeginTimeMillis = beginTimeMillis;
mEndTimeMillis = endTimeMillis;
mFlags = flags;
@@ -3943,7 +4037,9 @@
public static final class Builder {
private int mUid = Process.INVALID_UID;
private @Nullable String mPackageName;
+ private @Nullable String mFeatureId;
private @Nullable List<String> mOpNames;
+ private @HistoricalOpsRequestFilter int mFilter;
private final long mBeginTimeMillis;
private final long mEndTimeMillis;
private @OpFlags int mFlags = OP_FLAGS_ALL;
@@ -3976,6 +4072,13 @@
Preconditions.checkArgument(uid == Process.INVALID_UID || uid >= 0,
"uid must be " + Process.INVALID_UID + " or non negative");
mUid = uid;
+
+ if (uid == Process.INVALID_UID) {
+ mFilter &= ~FILTER_BY_UID;
+ } else {
+ mFilter |= FILTER_BY_UID;
+ }
+
return this;
}
@@ -3987,6 +4090,26 @@
*/
public @NonNull Builder setPackageName(@Nullable String packageName) {
mPackageName = packageName;
+
+ if (packageName == null) {
+ mFilter &= ~FILTER_BY_PACKAGE_NAME;
+ } else {
+ mFilter |= FILTER_BY_PACKAGE_NAME;
+ }
+
+ return this;
+ }
+
+ /**
+ * Sets the feature id to query for.
+ *
+ * @param featureId The id of the feature.
+ * @return This builder.
+ */
+ public @NonNull Builder setFeatureId(@Nullable String featureId) {
+ mFeatureId = featureId;
+ mFilter |= FILTER_BY_FEATURE_ID;
+
return this;
}
@@ -4005,6 +4128,13 @@
}
}
mOpNames = opNames;
+
+ if (mOpNames == null) {
+ mFilter &= ~FILTER_BY_OP_NAMES;
+ } else {
+ mFilter |= FILTER_BY_OP_NAMES;
+ }
+
return this;
}
@@ -4029,8 +4159,8 @@
* @return a new {@link HistoricalOpsRequest}.
*/
public @NonNull HistoricalOpsRequest build() {
- return new HistoricalOpsRequest(mUid, mPackageName, mOpNames,
- mBeginTimeMillis, mEndTimeMillis, mFlags);
+ return new HistoricalOpsRequest(mUid, mPackageName, mFeatureId, mOpNames,
+ mFilter, mBeginTimeMillis, mEndTimeMillis, mFlags);
}
}
}
@@ -4187,15 +4317,18 @@
/**
* AppPermissionUsage the ops to leave only the data we filter for.
*
- * @param uid Uid to filter for or {@link android.os.Process#INCIDENTD_UID} for all.
- * @param packageName Package to filter for or null for all.
- * @param opNames Ops to filter for or null for all.
+ * @param uid Uid to filter for.
+ * @param packageName Package to filter for.
+ * @param featureId Package to filter for.
+ * @param opNames Ops to filter for.
+ * @param filter Which parameters to filter on.
* @param beginTimeMillis The begin time to filter for or {@link Long#MIN_VALUE} for all.
* @param endTimeMillis The end time to filter for or {@link Long#MAX_VALUE} for all.
*
* @hide
*/
- public void filter(int uid, @Nullable String packageName, @Nullable String[] opNames,
+ public void filter(int uid, @Nullable String packageName, @Nullable String featureId,
+ @Nullable String[] opNames, @HistoricalOpsRequestFilter int filter,
long beginTimeMillis, long endTimeMillis) {
final long durationMillis = getDurationMillis();
mBeginTimeMillis = Math.max(mBeginTimeMillis, beginTimeMillis);
@@ -4205,10 +4338,10 @@
final int uidCount = getUidCount();
for (int i = uidCount - 1; i >= 0; i--) {
final HistoricalUidOps uidOp = mHistoricalUidOps.valueAt(i);
- if (uid != Process.INVALID_UID && uid != uidOp.getUid()) {
+ if ((filter & FILTER_BY_UID) != 0 && uid != uidOp.getUid()) {
mHistoricalUidOps.removeAt(i);
} else {
- uidOp.filter(packageName, opNames, scaleFactor);
+ uidOp.filter(packageName, featureId, opNames, filter, scaleFactor);
}
}
}
@@ -4236,25 +4369,28 @@
/** @hide */
@TestApi
public void increaseAccessCount(int opCode, int uid, @NonNull String packageName,
- @UidState int uidState, @OpFlags int flags, long increment) {
+ @Nullable String featureId, @UidState int uidState, @OpFlags int flags,
+ long increment) {
getOrCreateHistoricalUidOps(uid).increaseAccessCount(opCode,
- packageName, uidState, flags, increment);
+ packageName, featureId, uidState, flags, increment);
}
/** @hide */
@TestApi
public void increaseRejectCount(int opCode, int uid, @NonNull String packageName,
- @UidState int uidState, @OpFlags int flags, long increment) {
+ @Nullable String featureId, @UidState int uidState, @OpFlags int flags,
+ long increment) {
getOrCreateHistoricalUidOps(uid).increaseRejectCount(opCode,
- packageName, uidState, flags, increment);
+ packageName, featureId, uidState, flags, increment);
}
/** @hide */
@TestApi
public void increaseAccessDuration(int opCode, int uid, @NonNull String packageName,
- @UidState int uidState, @OpFlags int flags, long increment) {
+ @Nullable String featureId, @UidState int uidState, @OpFlags int flags,
+ long increment) {
getOrCreateHistoricalUidOps(uid).increaseAccessDuration(opCode,
- packageName, uidState, flags, increment);
+ packageName, featureId, uidState, flags, increment);
}
/** @hide */
@@ -4534,15 +4670,17 @@
}
}
- private void filter(@Nullable String packageName, @Nullable String[] opNames,
+ private void filter(@Nullable String packageName, @Nullable String featureId,
+ @Nullable String[] opNames, @HistoricalOpsRequestFilter int filter,
double fractionToRemove) {
final int packageCount = getPackageCount();
for (int i = packageCount - 1; i >= 0; i--) {
final HistoricalPackageOps packageOps = getPackageOpsAt(i);
- if (packageName != null && !packageName.equals(packageOps.getPackageName())) {
+ if ((filter & FILTER_BY_PACKAGE_NAME) != 0 && !packageName.equals(
+ packageOps.getPackageName())) {
mHistoricalPackageOps.removeAt(i);
} else {
- packageOps.filter(opNames, fractionToRemove);
+ packageOps.filter(featureId, opNames, filter, fractionToRemove);
}
}
}
@@ -4559,21 +4697,24 @@
}
private void increaseAccessCount(int opCode, @NonNull String packageName,
- @UidState int uidState, @OpFlags int flags, long increment) {
+ @Nullable String featureId, @UidState int uidState, @OpFlags int flags,
+ long increment) {
getOrCreateHistoricalPackageOps(packageName).increaseAccessCount(
- opCode, uidState, flags, increment);
+ opCode, featureId, uidState, flags, increment);
}
private void increaseRejectCount(int opCode, @NonNull String packageName,
- @UidState int uidState, @OpFlags int flags, long increment) {
+ @Nullable String featureId, @UidState int uidState, @OpFlags int flags,
+ long increment) {
getOrCreateHistoricalPackageOps(packageName).increaseRejectCount(
- opCode, uidState, flags, increment);
+ opCode, featureId, uidState, flags, increment);
}
private void increaseAccessDuration(int opCode, @NonNull String packageName,
- @UidState int uidState, @OpFlags int flags, long increment) {
+ @Nullable String featureId, @UidState int uidState, @OpFlags int flags,
+ long increment) {
getOrCreateHistoricalPackageOps(packageName).increaseAccessDuration(
- opCode, uidState, flags, increment);
+ opCode, featureId, uidState, flags, increment);
}
/**
@@ -4718,7 +4859,7 @@
@SystemApi
public static final class HistoricalPackageOps implements Parcelable {
private final @NonNull String mPackageName;
- private @Nullable ArrayMap<String, HistoricalOp> mHistoricalOps;
+ private @Nullable ArrayMap<String, HistoricalFeatureOps> mHistoricalFeatureOps;
/** @hide */
public HistoricalPackageOps(@NonNull String packageName) {
@@ -4727,6 +4868,339 @@
private HistoricalPackageOps(@NonNull HistoricalPackageOps other) {
mPackageName = other.mPackageName;
+ final int opCount = other.getFeatureCount();
+ for (int i = 0; i < opCount; i++) {
+ final HistoricalFeatureOps origOps = other.getFeatureOpsAt(i);
+ final HistoricalFeatureOps cloneOps = new HistoricalFeatureOps(origOps);
+ if (mHistoricalFeatureOps == null) {
+ mHistoricalFeatureOps = new ArrayMap<>(opCount);
+ }
+ mHistoricalFeatureOps.put(cloneOps.getFeatureId(), cloneOps);
+ }
+ }
+
+ private HistoricalPackageOps(@NonNull Parcel parcel) {
+ mPackageName = parcel.readString();
+ mHistoricalFeatureOps = parcel.createTypedArrayMap(HistoricalFeatureOps.CREATOR);
+ }
+
+ private @Nullable HistoricalPackageOps splice(double fractionToRemove) {
+ HistoricalPackageOps splice = null;
+ final int featureCount = getFeatureCount();
+ for (int i = 0; i < featureCount; i++) {
+ final HistoricalFeatureOps origOps = getFeatureOpsAt(i);
+ final HistoricalFeatureOps spliceOps = origOps.splice(fractionToRemove);
+ if (spliceOps != null) {
+ if (splice == null) {
+ splice = new HistoricalPackageOps(mPackageName);
+ }
+ if (splice.mHistoricalFeatureOps == null) {
+ splice.mHistoricalFeatureOps = new ArrayMap<>();
+ }
+ splice.mHistoricalFeatureOps.put(spliceOps.getFeatureId(), spliceOps);
+ }
+ }
+ return splice;
+ }
+
+ private void merge(@NonNull HistoricalPackageOps other) {
+ final int featureCount = other.getFeatureCount();
+ for (int i = 0; i < featureCount; i++) {
+ final HistoricalFeatureOps otherFeatureOps = other.getFeatureOpsAt(i);
+ final HistoricalFeatureOps thisFeatureOps = getFeatureOps(
+ otherFeatureOps.getFeatureId());
+ if (thisFeatureOps != null) {
+ thisFeatureOps.merge(otherFeatureOps);
+ } else {
+ if (mHistoricalFeatureOps == null) {
+ mHistoricalFeatureOps = new ArrayMap<>();
+ }
+ mHistoricalFeatureOps.put(otherFeatureOps.getFeatureId(), otherFeatureOps);
+ }
+ }
+ }
+
+ private void filter(@Nullable String featureId, @Nullable String[] opNames,
+ @HistoricalOpsRequestFilter int filter, double fractionToRemove) {
+ final int featureCount = getFeatureCount();
+ for (int i = featureCount - 1; i >= 0; i--) {
+ final HistoricalFeatureOps featureOps = getFeatureOpsAt(i);
+ if ((filter & FILTER_BY_FEATURE_ID) != 0 && !Objects.equals(featureId,
+ featureOps.getFeatureId())) {
+ mHistoricalFeatureOps.removeAt(i);
+ } else {
+ featureOps.filter(opNames, filter, fractionToRemove);
+ }
+ }
+ }
+
+ private void accept(@NonNull HistoricalOpsVisitor visitor) {
+ visitor.visitHistoricalPackageOps(this);
+ final int featureCount = getFeatureCount();
+ for (int i = 0; i < featureCount; i++) {
+ getFeatureOpsAt(i).accept(visitor);
+ }
+ }
+
+ private boolean isEmpty() {
+ final int featureCount = getFeatureCount();
+ for (int i = featureCount - 1; i >= 0; i--) {
+ final HistoricalFeatureOps featureOps = mHistoricalFeatureOps.valueAt(i);
+ if (!featureOps.isEmpty()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void increaseAccessCount(int opCode, @Nullable String featureId,
+ @UidState int uidState, @OpFlags int flags, long increment) {
+ getOrCreateHistoricalFeatureOps(featureId).increaseAccessCount(
+ opCode, uidState, flags, increment);
+ }
+
+ private void increaseRejectCount(int opCode, @Nullable String featureId,
+ @UidState int uidState, @OpFlags int flags, long increment) {
+ getOrCreateHistoricalFeatureOps(featureId).increaseRejectCount(
+ opCode, uidState, flags, increment);
+ }
+
+ private void increaseAccessDuration(int opCode, @Nullable String featureId,
+ @UidState int uidState, @OpFlags int flags, long increment) {
+ getOrCreateHistoricalFeatureOps(featureId).increaseAccessDuration(
+ opCode, uidState, flags, increment);
+ }
+
+ /**
+ * Gets the package name which the data represents.
+ *
+ * @return The package name which the data represents.
+ */
+ public @NonNull String getPackageName() {
+ return mPackageName;
+ }
+
+ private @NonNull HistoricalFeatureOps getOrCreateHistoricalFeatureOps(
+ @Nullable String featureId) {
+ if (mHistoricalFeatureOps == null) {
+ mHistoricalFeatureOps = new ArrayMap<>();
+ }
+ HistoricalFeatureOps historicalFeatureOp = mHistoricalFeatureOps.get(featureId);
+ if (historicalFeatureOp == null) {
+ historicalFeatureOp = new HistoricalFeatureOps(featureId);
+ mHistoricalFeatureOps.put(featureId, historicalFeatureOp);
+ }
+ return historicalFeatureOp;
+ }
+
+ /**
+ * Gets number historical app ops.
+ *
+ * @return The number historical app ops.
+ * @see #getOpAt(int)
+ */
+ public @IntRange(from = 0) int getOpCount() {
+ int numOps = 0;
+ int numFeatures = getFeatureCount();
+
+ for (int code = 0; code < _NUM_OP; code++) {
+ String opName = opToPublicName(code);
+
+ for (int featureNum = 0; featureNum < numFeatures; featureNum++) {
+ if (getFeatureOpsAt(featureNum).getOp(opName) != null) {
+ numOps++;
+ break;
+ }
+ }
+ }
+
+ return numOps;
+ }
+
+ /**
+ * Gets the historical op at a given index.
+ *
+ * <p>This combines the counts from all features.
+ *
+ * @param index The index to lookup.
+ * @return The op at the given index.
+ * @see #getOpCount()
+ */
+ public @NonNull HistoricalOp getOpAt(@IntRange(from = 0) int index) {
+ int numOpsFound = 0;
+ int numFeatures = getFeatureCount();
+
+ for (int code = 0; code < _NUM_OP; code++) {
+ String opName = opToPublicName(code);
+
+ for (int featureNum = 0; featureNum < numFeatures; featureNum++) {
+ if (getFeatureOpsAt(featureNum).getOp(opName) != null) {
+ if (numOpsFound == index) {
+ return getOp(opName);
+ } else {
+ numOpsFound++;
+ break;
+ }
+ }
+ }
+ }
+
+ throw new IndexOutOfBoundsException();
+ }
+
+ /**
+ * Gets the historical entry for a given op name.
+ *
+ * <p>This combines the counts from all features.
+ *
+ * @param opName The op name.
+ * @return The historical entry for that op name.
+ */
+ public @Nullable HistoricalOp getOp(@NonNull String opName) {
+ if (mHistoricalFeatureOps == null) {
+ return null;
+ }
+
+ HistoricalOp combinedOp = null;
+ int numFeatures = getFeatureCount();
+ for (int i = 0; i < numFeatures; i++) {
+ HistoricalOp featureOp = getFeatureOpsAt(i).getOp(opName);
+ if (featureOp != null) {
+ if (combinedOp == null) {
+ combinedOp = new HistoricalOp(featureOp);
+ } else {
+ combinedOp.merge(featureOp);
+ }
+ }
+ }
+
+ return combinedOp;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel parcel, int flags) {
+ parcel.writeString(mPackageName);
+ parcel.writeTypedArrayMap(mHistoricalFeatureOps, flags);
+ }
+
+ public static final @android.annotation.NonNull Creator<HistoricalPackageOps> CREATOR =
+ new Creator<HistoricalPackageOps>() {
+ @Override
+ public @NonNull HistoricalPackageOps createFromParcel(@NonNull Parcel parcel) {
+ return new HistoricalPackageOps(parcel);
+ }
+
+ @Override
+ public @NonNull HistoricalPackageOps[] newArray(int size) {
+ return new HistoricalPackageOps[size];
+ }
+ };
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final HistoricalPackageOps other = (HistoricalPackageOps) obj;
+ if (!mPackageName.equals(other.mPackageName)) {
+ return false;
+ }
+ if (mHistoricalFeatureOps == null) {
+ if (other.mHistoricalFeatureOps != null) {
+ return false;
+ }
+ } else if (!mHistoricalFeatureOps.equals(other.mHistoricalFeatureOps)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mPackageName != null ? mPackageName.hashCode() : 0;
+ result = 31 * result + (mHistoricalFeatureOps != null ? mHistoricalFeatureOps.hashCode()
+ : 0);
+ return result;
+ }
+
+ /**
+ * Gets number of feature with historical ops.
+ *
+ * @return The number of feature with historical ops.
+ *
+ * @see #getFeatureOpsAt(int)
+ */
+ public @IntRange(from = 0) int getFeatureCount() {
+ if (mHistoricalFeatureOps == null) {
+ return 0;
+ }
+ return mHistoricalFeatureOps.size();
+ }
+
+ /**
+ * Gets the historical feature ops at a given index.
+ *
+ * @param index The index.
+ *
+ * @return The historical feature ops at the given index.
+ *
+ * @see #getFeatureCount()
+ */
+ public @NonNull HistoricalFeatureOps getFeatureOpsAt(@IntRange(from = 0) int index) {
+ if (mHistoricalFeatureOps == null) {
+ throw new IndexOutOfBoundsException();
+ }
+ return mHistoricalFeatureOps.valueAt(index);
+ }
+
+ /**
+ * Gets the historical feature ops for a given feature.
+ *
+ * @param featureId The feature id.
+ *
+ * @return The historical ops for the feature.
+ */
+ public @Nullable HistoricalFeatureOps getFeatureOps(@NonNull String featureId) {
+ if (mHistoricalFeatureOps == null) {
+ return null;
+ }
+ return mHistoricalFeatureOps.get(featureId);
+ }
+ }
+
+ /**
+ * This class represents historical app op information about a feature in a package.
+ *
+ * @hide
+ */
+ @TestApi
+ @SystemApi
+ /* codegen verifier cannot deal with nested class parameters
+ @DataClass(genHiddenConstructor = true,
+ genEqualsHashCode = true, genHiddenCopyConstructor = true) */
+ @DataClass.Suppress("getHistoricalOps")
+ public static final class HistoricalFeatureOps implements Parcelable {
+ /** Id of the {@link Context#createFeatureContext feature} in the package */
+ private final @Nullable String mFeatureId;
+
+ /** Ops for this feature */
+ private @Nullable ArrayMap<String, HistoricalOp> mHistoricalOps;
+
+ /** @hide */
+ public HistoricalFeatureOps(@NonNull String featureId) {
+ mFeatureId = featureId;
+ }
+
+ private HistoricalFeatureOps(@NonNull HistoricalFeatureOps other) {
+ mFeatureId = other.mFeatureId;
final int opCount = other.getOpCount();
for (int i = 0; i < opCount; i++) {
final HistoricalOp origOp = other.getOpAt(i);
@@ -4738,20 +5212,15 @@
}
}
- private HistoricalPackageOps(@NonNull Parcel parcel) {
- mPackageName = parcel.readString();
- mHistoricalOps = parcel.createTypedArrayMap(HistoricalOp.CREATOR);
- }
-
- private @Nullable HistoricalPackageOps splice(double fractionToRemove) {
- HistoricalPackageOps splice = null;
+ private @Nullable HistoricalFeatureOps splice(double fractionToRemove) {
+ HistoricalFeatureOps splice = null;
final int opCount = getOpCount();
for (int i = 0; i < opCount; i++) {
final HistoricalOp origOps = getOpAt(i);
final HistoricalOp spliceOps = origOps.splice(fractionToRemove);
if (spliceOps != null) {
if (splice == null) {
- splice = new HistoricalPackageOps(mPackageName);
+ splice = new HistoricalFeatureOps(mFeatureId, null);
}
if (splice.mHistoricalOps == null) {
splice.mHistoricalOps = new ArrayMap<>();
@@ -4762,7 +5231,7 @@
return splice;
}
- private void merge(@NonNull HistoricalPackageOps other) {
+ private void merge(@NonNull HistoricalFeatureOps other) {
final int opCount = other.getOpCount();
for (int i = 0; i < opCount; i++) {
final HistoricalOp otherOp = other.getOpAt(i);
@@ -4778,11 +5247,13 @@
}
}
- private void filter(@Nullable String[] opNames, double scaleFactor) {
+ private void filter(@Nullable String[] opNames, @HistoricalOpsRequestFilter int filter,
+ double scaleFactor) {
final int opCount = getOpCount();
for (int i = opCount - 1; i >= 0; i--) {
final HistoricalOp op = mHistoricalOps.valueAt(i);
- if (opNames != null && !ArrayUtils.contains(opNames, op.getOpName())) {
+ if ((filter & FILTER_BY_OP_NAMES) != 0 && !ArrayUtils.contains(opNames,
+ op.getOpName())) {
mHistoricalOps.removeAt(i);
} else {
op.filter(scaleFactor);
@@ -4817,15 +5288,6 @@
}
/**
- * Gets the package name which the data represents.
- *
- * @return The package name which the data represents.
- */
- public @NonNull String getPackageName() {
- return mPackageName;
- }
-
- /**
* Gets number historical app ops.
*
* @return The number historical app ops.
@@ -4865,19 +5327,8 @@
return mHistoricalOps.get(opName);
}
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel parcel, int flags) {
- parcel.writeString(mPackageName);
- parcel.writeTypedArrayMap(mHistoricalOps, flags);
- }
-
private void accept(@NonNull HistoricalOpsVisitor visitor) {
- visitor.visitHistoricalPackageOps(this);
+ visitor.visitHistoricalFeatureOps(this);
final int opCount = getOpCount();
for (int i = 0; i < opCount; i++) {
getOpAt(i).accept(visitor);
@@ -4897,47 +5348,143 @@
return op;
}
- public static final @android.annotation.NonNull Creator<HistoricalPackageOps> CREATOR =
- new Creator<HistoricalPackageOps>() {
+
+
+ // Code below generated by codegen v1.0.14.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/app/AppOpsManager.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /**
+ * Creates a new HistoricalFeatureOps.
+ *
+ * @param featureId
+ * Id of the {@link Context#createFeatureContext feature} in the package
+ * @param historicalOps
+ * Ops for this feature
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public HistoricalFeatureOps(
+ @Nullable String featureId,
+ @Nullable ArrayMap<String,HistoricalOp> historicalOps) {
+ this.mFeatureId = featureId;
+ this.mHistoricalOps = historicalOps;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * Id of the {@link Context#createFeatureContext feature} in the package
+ */
+ @DataClass.Generated.Member
+ public @Nullable String getFeatureId() {
+ return mFeatureId;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(HistoricalFeatureOps other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ HistoricalFeatureOps that = (HistoricalFeatureOps) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && Objects.equals(mFeatureId, that.mFeatureId)
+ && Objects.equals(mHistoricalOps, that.mHistoricalOps);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + Objects.hashCode(mFeatureId);
+ _hash = 31 * _hash + Objects.hashCode(mHistoricalOps);
+ return _hash;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ byte flg = 0;
+ if (mFeatureId != null) flg |= 0x1;
+ if (mHistoricalOps != null) flg |= 0x2;
+ dest.writeByte(flg);
+ if (mFeatureId != null) dest.writeString(mFeatureId);
+ if (mHistoricalOps != null) dest.writeMap(mHistoricalOps);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ HistoricalFeatureOps(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ byte flg = in.readByte();
+ String featureId = (flg & 0x1) == 0 ? null : in.readString();
+ ArrayMap<String,HistoricalOp> historicalOps = null;
+ if ((flg & 0x2) != 0) {
+ historicalOps = new ArrayMap();
+ in.readMap(historicalOps, HistoricalOp.class.getClassLoader());
+ }
+
+ this.mFeatureId = featureId;
+ this.mHistoricalOps = historicalOps;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<HistoricalFeatureOps> CREATOR
+ = new Parcelable.Creator<HistoricalFeatureOps>() {
@Override
- public @NonNull HistoricalPackageOps createFromParcel(@NonNull Parcel parcel) {
- return new HistoricalPackageOps(parcel);
+ public HistoricalFeatureOps[] newArray(int size) {
+ return new HistoricalFeatureOps[size];
}
@Override
- public @NonNull HistoricalPackageOps[] newArray(int size) {
- return new HistoricalPackageOps[size];
+ public HistoricalFeatureOps createFromParcel(@NonNull Parcel in) {
+ return new HistoricalFeatureOps(in);
}
};
- @Override
- public boolean equals(@Nullable Object obj) {
- if (this == obj) {
- return true;
- }
- if (obj == null || getClass() != obj.getClass()) {
- return false;
- }
- final HistoricalPackageOps other = (HistoricalPackageOps) obj;
- if (!mPackageName.equals(other.mPackageName)) {
- return false;
- }
- if (mHistoricalOps == null) {
- if (other.mHistoricalOps != null) {
- return false;
- }
- } else if (!mHistoricalOps.equals(other.mHistoricalOps)) {
- return false;
- }
- return true;
- }
+ /*
+ @DataClass.Generated(
+ time = 1578113234821L,
+ codegenVersion = "1.0.14",
+ sourceFile = "frameworks/base/core/java/android/app/AppOpsManager.java",
+ inputSignatures = "private final @android.annotation.Nullable java.lang.String mFeatureId\nprivate @android.annotation.Nullable android.util.ArrayMap<java.lang.String,android.app.HistoricalOp> mHistoricalOps\nprivate @android.annotation.Nullable android.app.HistoricalFeatureOps splice(double)\nprivate void merge(android.app.HistoricalFeatureOps)\nprivate void filter(java.lang.String[],int,double)\nprivate boolean isEmpty()\nprivate void increaseAccessCount(int,int,int,long)\nprivate void increaseRejectCount(int,int,int,long)\nprivate void increaseAccessDuration(int,int,int,long)\npublic @android.annotation.IntRange(from=0L) int getOpCount()\npublic @android.annotation.NonNull android.app.HistoricalOp getOpAt(int)\npublic @android.annotation.Nullable android.app.HistoricalOp getOp(java.lang.String)\nprivate void accept(android.app.HistoricalOpsVisitor)\nprivate @android.annotation.NonNull android.app.HistoricalOp getOrCreateHistoricalOp(int)\nclass HistoricalFeatureOps extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true, genEqualsHashCode=true, genHiddenCopyConstructor=true)")
+ @Deprecated
+ private void __metadata() {}
+ */
- @Override
- public int hashCode() {
- int result = mPackageName != null ? mPackageName.hashCode() : 0;
- result = 31 * result + (mHistoricalOps != null ? mHistoricalOps.hashCode() : 0);
- return result;
- }
+ //@formatter:on
+ // End of generated code
+
}
/**
@@ -5273,13 +5820,13 @@
if (mOp != other.mOp) {
return false;
}
- if (!Objects.equals(mAccessCount, other.mAccessCount)) {
+ if (!equalsLongSparseLongArray(mAccessCount, other.mAccessCount)) {
return false;
}
- if (!Objects.equals(mRejectCount, other.mRejectCount)) {
+ if (!equalsLongSparseLongArray(mRejectCount, other.mRejectCount)) {
return false;
}
- return Objects.equals(mAccessDuration, other.mAccessDuration);
+ return equalsLongSparseLongArray(mAccessDuration, other.mAccessDuration);
}
@Override
@@ -5606,9 +6153,9 @@
Objects.requireNonNull(executor, "executor cannot be null");
Objects.requireNonNull(callback, "callback cannot be null");
try {
- mService.getHistoricalOps(request.mUid, request.mPackageName, request.mOpNames,
- request.mBeginTimeMillis, request.mEndTimeMillis, request.mFlags,
- new RemoteCallback((result) -> {
+ mService.getHistoricalOps(request.mUid, request.mPackageName, request.mFeatureId,
+ request.mOpNames, request.mFilter, request.mBeginTimeMillis,
+ request.mEndTimeMillis, request.mFlags, new RemoteCallback((result) -> {
final HistoricalOps ops = result.getParcelable(KEY_HISTORICAL_OPS);
final long identity = Binder.clearCallingIdentity();
try {
@@ -5646,8 +6193,8 @@
Objects.requireNonNull(callback, "callback cannot be null");
try {
mService.getHistoricalOpsFromDiskRaw(request.mUid, request.mPackageName,
- request.mOpNames, request.mBeginTimeMillis, request.mEndTimeMillis,
- request.mFlags, new RemoteCallback((result) -> {
+ request.mFeatureId, request.mOpNames, request.mFilter, request.mBeginTimeMillis,
+ request.mEndTimeMillis, request.mFlags, new RemoteCallback((result) -> {
final HistoricalOps ops = result.getParcelable(KEY_HISTORICAL_OPS);
final long identity = Binder.clearCallingIdentity();
try {
@@ -7488,6 +8035,30 @@
return lastEvent;
}
+ private static boolean equalsLongSparseLongArray(@Nullable LongSparseLongArray a,
+ @Nullable LongSparseLongArray b) {
+ if (a == b) {
+ return true;
+ }
+
+ if (a == null || b == null) {
+ return false;
+ }
+
+ if (a.size() != b.size()) {
+ return false;
+ }
+
+ int numEntries = a.size();
+ for (int i = 0; i < numEntries; i++) {
+ if (a.keyAt(i) != b.keyAt(i) || a.valueAt(i) != b.valueAt(i)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
private static void writeLongSparseLongArrayToParcel(
@Nullable LongSparseLongArray array, @NonNull Parcel parcel) {
if (array != null) {
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 1c6a561..d39a8c4 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -133,6 +133,10 @@
// Default flags to use with PackageManager when no flags are given.
private final static int sDefaultFlags = PackageManager.GET_SHARED_LIBRARY_FILES;
+ // Name of the resource which provides background permission button string
+ public static final String APP_PERMISSION_BUTTON_ALLOW_ALWAYS =
+ "app_permission_button_allow_always";
+
private final Object mLock = new Object();
@GuardedBy("mLock")
@@ -807,6 +811,26 @@
}
@Override
+ public CharSequence getBackgroundPermissionButtonLabel() {
+ try {
+
+ String permissionController = getPermissionControllerPackageName();
+ Context context =
+ mContext.createPackageContext(permissionController, 0);
+
+ int textId = context.getResources().getIdentifier(APP_PERMISSION_BUTTON_ALLOW_ALWAYS,
+ "string", "com.android.permissioncontroller");
+// permissionController); STOPSHIP b/147434671
+ if (textId != 0) {
+ return context.getText(textId);
+ }
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "Permission controller not found.", e);
+ }
+ return "";
+ }
+
+ @Override
public int checkSignatures(String pkg1, String pkg2) {
try {
return mPM.checkSignatures(pkg1, pkg2);
diff --git a/core/java/android/app/AsyncNotedAppOp.java b/core/java/android/app/AsyncNotedAppOp.java
index c3ef3ad..3febf71 100644
--- a/core/java/android/app/AsyncNotedAppOp.java
+++ b/core/java/android/app/AsyncNotedAppOp.java
@@ -256,10 +256,10 @@
};
@DataClass.Generated(
- time = 1576864422226L,
+ time = 1578516519372L,
codegenVersion = "1.0.14",
sourceFile = "frameworks/base/core/java/android/app/AsyncNotedAppOp.java",
- inputSignatures = "private final @android.annotation.IntRange(from=0L, to=92L) int mOpCode\nprivate final @android.annotation.IntRange(from=0L) int mNotingUid\nprivate final @android.annotation.Nullable java.lang.String mFeatureId\nprivate final @android.annotation.NonNull java.lang.String mMessage\nprivate final @android.annotation.IntRange(from=0L) long mTime\npublic @android.annotation.NonNull java.lang.String getOp()\nclass AsyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genHiddenConstructor=true)")
+ inputSignatures = "private final @android.annotation.IntRange(from=0L, to=94L) int mOpCode\nprivate final @android.annotation.IntRange(from=0L) int mNotingUid\nprivate final @android.annotation.Nullable java.lang.String mFeatureId\nprivate final @android.annotation.NonNull java.lang.String mMessage\nprivate final @android.annotation.IntRange(from=0L) long mTime\npublic @android.annotation.NonNull java.lang.String getOp()\nclass AsyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genHiddenConstructor=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java
index f9a689a..d2235f1 100644
--- a/core/java/android/app/ClientTransactionHandler.java
+++ b/core/java/android/app/ClientTransactionHandler.java
@@ -119,7 +119,6 @@
/**
* Stop the activity.
* @param token Target activity token.
- * @param show Flag indicating whether activity is still shown.
* @param configChanges Activity configuration changes.
* @param pendingActions Pending actions to be used on this or later stages of activity
* transaction.
@@ -127,7 +126,7 @@
* request for a transaction.
* @param reason Reason for performing this operation.
*/
- public abstract void handleStopActivity(IBinder token, boolean show, int configChanges,
+ public abstract void handleStopActivity(IBinder token, int configChanges,
PendingTransactionActions pendingActions, boolean finalStateRequest, String reason);
/** Report that activity was stopped to server. */
@@ -161,15 +160,12 @@
/** Request that an activity enter picture-in-picture. */
public abstract void handlePictureInPictureRequested(IBinder token);
- /** Update window visibility. */
- public abstract void handleWindowVisibility(IBinder token, boolean show);
-
/** Perform activity launch. */
public abstract Activity handleLaunchActivity(ActivityThread.ActivityClientRecord r,
PendingTransactionActions pendingActions, Intent customIntent);
/** Perform activity start. */
- public abstract void handleStartActivity(ActivityThread.ActivityClientRecord r,
+ public abstract void handleStartActivity(IBinder token,
PendingTransactionActions pendingActions);
/** Get package info. */
diff --git a/core/java/android/app/DisabledWallpaperManager.java b/core/java/android/app/DisabledWallpaperManager.java
index 5185941..1f323c3 100644
--- a/core/java/android/app/DisabledWallpaperManager.java
+++ b/core/java/android/app/DisabledWallpaperManager.java
@@ -41,8 +41,7 @@
// Don't need to worry about synchronization
private static DisabledWallpaperManager sInstance;
- // TODO(b/138939803): STOPSHIP changed to false and/or remove it
- private static final boolean DEBUG = true;
+ private static final boolean DEBUG = false;
@NonNull
static DisabledWallpaperManager getInstance() {
@@ -53,7 +52,6 @@
}
private DisabledWallpaperManager() {
- super(null, null, null);
}
@Override
@@ -66,10 +64,6 @@
return false;
}
- // TODO(b/138939803): STOPSHIP methods below should not be necessary,
- // callers should check if isWallpaperSupported(), consider removing them to keep this class
- // simpler
-
private static <T> T unsupported() {
if (DEBUG) Log.w(TAG, "unsupported method called; returning null", new Exception());
return null;
@@ -343,4 +337,9 @@
public boolean isWallpaperBackupEligible(int which) {
return unsupportedBoolean();
}
+
+ @Override
+ public boolean wallpaperSupportsWcg(int which) {
+ return unsupportedBoolean();
+ }
}
diff --git a/core/java/android/app/DownloadManager.java b/core/java/android/app/DownloadManager.java
index 49c389a..1278ff6 100644
--- a/core/java/android/app/DownloadManager.java
+++ b/core/java/android/app/DownloadManager.java
@@ -16,7 +16,9 @@
package android.app;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
@@ -40,11 +42,13 @@
import android.os.FileUtils;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
+import android.provider.BaseColumns;
import android.provider.Downloads;
import android.provider.MediaStore;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.text.TextUtils;
+import android.util.LongSparseArray;
import android.util.Pair;
import java.io.File;
@@ -1069,6 +1073,37 @@
}
/**
+ * Notify {@link DownloadManager} that the given {@link MediaStore} items
+ * were just deleted so that {@link DownloadManager} internal data
+ * structures can be cleaned up.
+ *
+ * @param idToMime map from {@link BaseColumns#_ID} to
+ * {@link ContentResolver#getType(Uri)}.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.WRITE_MEDIA_STORAGE)
+ public void onMediaStoreDownloadsDeleted(@NonNull LongSparseArray<String> idToMime) {
+ try (ContentProviderClient client = mResolver
+ .acquireUnstableContentProviderClient(mBaseUri)) {
+ final Bundle callExtras = new Bundle();
+ final long[] ids = new long[idToMime.size()];
+ final String[] mimeTypes = new String[idToMime.size()];
+ for (int i = idToMime.size() - 1; i >= 0; --i) {
+ ids[i] = idToMime.keyAt(i);
+ mimeTypes[i] = idToMime.valueAt(i);
+ }
+ callExtras.putLongArray(android.provider.Downloads.EXTRA_IDS, ids);
+ callExtras.putStringArray(android.provider.Downloads.EXTRA_MIME_TYPES,
+ mimeTypes);
+ client.call(android.provider.Downloads.CALL_MEDIASTORE_DOWNLOADS_DELETED,
+ null, callExtras);
+ } catch (RemoteException e) {
+ // Should not happen
+ }
+ }
+
+ /**
* Enqueue a new download. The download will start automatically once the download manager is
* ready to execute it and connectivity is available.
*
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index e8494c4..580382e 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -188,6 +188,16 @@
*/
@UnsupportedAppUsage
boolean updateConfiguration(in Configuration values);
+ /**
+ * Updates mcc mnc configuration and applies changes to the entire system.
+ *
+ * @param mcc mcc configuration to update.
+ * @param mnc mnc configuration to update.
+ * @throws RemoteException; IllegalArgumentException if mcc or mnc is null.
+ * @return Returns {@code true} if the configuration was updated;
+ * {@code false} otherwise.
+ */
+ boolean updateMccMncConfiguration(in String mcc, in String mnc);
boolean stopServiceToken(in ComponentName className, in IBinder token, int startId);
@UnsupportedAppUsage
void setProcessLimit(int max);
@@ -345,6 +355,12 @@
in Bundle options, int userId);
@UnsupportedAppUsage
int stopUser(int userid, boolean force, in IStopUserCallback callback);
+ /**
+ * Check {@link com.android.server.am.ActivityManagerService#stopUserWithDelayedLocking(int, boolean, IStopUserCallback)}
+ * for details.
+ */
+ int stopUserWithDelayedLocking(int userid, boolean force, in IStopUserCallback callback);
+
@UnsupportedAppUsage
void registerUserSwitchObserver(in IUserSwitchObserver observer, in String name);
void unregisterUserSwitchObserver(in IUserSwitchObserver observer);
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 700b3c1..e5c046c 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -68,6 +68,7 @@
import android.os.WorkSource;
import android.service.voice.IVoiceInteractionSession;
import android.view.IRecentsAnimationRunner;
+import android.view.ITaskOrganizer;
import android.view.RemoteAnimationDefinition;
import android.view.RemoteAnimationAdapter;
import android.view.WindowContainerTransaction;
@@ -121,6 +122,9 @@
in Intent intent, in String resolvedType, in IBinder resultTo, in String resultWho,
int requestCode, int flags, in ProfilerInfo profilerInfo, in Bundle options,
IBinder permissionToken, boolean ignoreTargetSecurity, int userId);
+
+ void registerTaskOrganizer(in ITaskOrganizer organizer, int windowingMode);
+
boolean isActivityStartAllowedOnDisplay(int displayId, in Intent intent, in String resolvedType,
int userId);
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 86f52af..7af7a4a 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -94,8 +94,11 @@
void updateNotificationChannelGroupForPackage(String pkg, int uid, in NotificationChannelGroup group);
void updateNotificationChannelForPackage(String pkg, int uid, in NotificationChannel channel);
NotificationChannel getNotificationChannel(String callingPkg, int userId, String pkg, String channelId);
+ NotificationChannel getConversationNotificationChannel(String callingPkg, int userId, String pkg, String channelId, boolean returnParentIfNoConversationChannel, String conversationId);
+ void createConversationNotificationChannelForPackage(String pkg, int uid, in NotificationChannel parentChannel, String conversationId);
NotificationChannel getNotificationChannelForPackage(String pkg, int uid, String channelId, boolean includeDeleted);
void deleteNotificationChannel(String pkg, String channelId);
+ void deleteConversationNotificationChannels(String pkg, int uid, String conversationId);
ParceledListSlice getNotificationChannels(String callingPkg, String targetPkg, int userId);
ParceledListSlice getNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted);
int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted);
diff --git a/core/java/android/app/IUiAutomationConnection.aidl b/core/java/android/app/IUiAutomationConnection.aidl
index 8c3180b..80ba464 100644
--- a/core/java/android/app/IUiAutomationConnection.aidl
+++ b/core/java/android/app/IUiAutomationConnection.aidl
@@ -39,7 +39,6 @@
boolean injectInputEvent(in InputEvent event, boolean sync);
void syncInputTransactions();
boolean setRotation(int rotation);
- Bitmap takeScreenshot(in Rect crop, int rotation);
boolean clearWindowContentFrameStats(int windowId);
WindowContentFrameStats getWindowContentFrameStats(int windowId);
void clearWindowAnimationFrameStats();
diff --git a/core/java/android/app/InstantAppResolverService.java b/core/java/android/app/InstantAppResolverService.java
index a413c60..0cd030e 100644
--- a/core/java/android/app/InstantAppResolverService.java
+++ b/core/java/android/app/InstantAppResolverService.java
@@ -35,6 +35,7 @@
import android.util.Log;
import android.util.Slog;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
import java.util.Arrays;
@@ -297,7 +298,10 @@
public static final class InstantAppResolutionCallback {
private final IRemoteCallback mCallback;
private final int mSequence;
- InstantAppResolutionCallback(int sequence, IRemoteCallback callback) {
+
+ /** @hide **/
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public InstantAppResolutionCallback(int sequence, IRemoteCallback callback) {
mCallback = callback;
mSequence = sequence;
}
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 775b1d1..0c5e67c 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -251,7 +251,7 @@
}
private AppComponentFactory createAppFactory(ApplicationInfo appInfo, ClassLoader cl) {
- if (appInfo.appComponentFactory != null && cl != null) {
+ if (mIncludeCode && appInfo.appComponentFactory != null && cl != null) {
try {
return (AppComponentFactory)
cl.loadClass(appInfo.appComponentFactory).newInstance();
@@ -464,6 +464,9 @@
|| appDir.equals(instrumentedAppDir)) {
outZipPaths.clear();
outZipPaths.add(instrumentationAppDir);
+ if (!instrumentationAppDir.equals(instrumentedAppDir)) {
+ outZipPaths.add(instrumentedAppDir);
+ }
// Only add splits if the app did not request isolated split loading.
if (!aInfo.requestsIsolatedSplitLoading()) {
@@ -472,7 +475,6 @@
}
if (!instrumentationAppDir.equals(instrumentedAppDir)) {
- outZipPaths.add(instrumentedAppDir);
if (instrumentedSplitAppDirs != null) {
Collections.addAll(outZipPaths, instrumentedSplitAppDirs);
}
diff --git a/core/java/android/app/LocalActivityManager.java b/core/java/android/app/LocalActivityManager.java
index 4033aea..7cdf85e 100644
--- a/core/java/android/app/LocalActivityManager.java
+++ b/core/java/android/app/LocalActivityManager.java
@@ -177,7 +177,7 @@
pendingActions = null;
}
- mActivityThread.handleStartActivity(clientRecord, pendingActions);
+ mActivityThread.handleStartActivity(r, pendingActions);
r.curState = STARTED;
if (desiredState == RESUMED) {
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 3eee1ae..78f9cc8 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -23,6 +23,7 @@
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.ShortcutInfo;
import android.media.AudioAttributes;
import android.net.Uri;
import android.os.Parcel;
@@ -30,6 +31,7 @@
import android.provider.Settings;
import android.service.notification.NotificationListenerService;
import android.text.TextUtils;
+import android.util.Log;
import android.util.proto.ProtoOutputStream;
import com.android.internal.util.Preconditions;
@@ -57,6 +59,22 @@
public static final String DEFAULT_CHANNEL_ID = "miscellaneous";
/**
+ * The formatter used by the system to create an id for notification
+ * channels when it automatically creates conversation channels on behalf of an app. The format
+ * string takes two arguments, in this order: the
+ * {@link #getId()} of the original notification channel, and the
+ * {@link ShortcutInfo#getId() id} of the conversation.
+ */
+ public static final String CONVERSATION_CHANNEL_ID_FORMAT = "%1$s : %2$s";
+
+ /**
+ * TODO: STOPSHIP remove
+ * Conversation id to use for apps that aren't providing them yet.
+ * @hide
+ */
+ public static final String PLACEHOLDER_CONVERSATION_ID = ":placeholder_id";
+
+ /**
* The maximum length for text fields in a NotificationChannel. Fields will be truncated at this
* limit.
*/
@@ -85,6 +103,9 @@
private static final String ATT_BLOCKABLE_SYSTEM = "blockable_system";
private static final String ATT_ALLOW_BUBBLE = "can_bubble";
private static final String ATT_ORIG_IMP = "orig_imp";
+ private static final String ATT_PARENT_CHANNEL = "parent";
+ private static final String ATT_CONVERSATION_ID = "conv_id";
+ private static final String ATT_DEMOTE = "dem";
private static final String DELIMITER = ",";
/**
@@ -147,7 +168,7 @@
private static final boolean DEFAULT_ALLOW_BUBBLE = true;
@UnsupportedAppUsage
- private final String mId;
+ private String mId;
private String mName;
private String mDesc;
private int mImportance = DEFAULT_IMPORTANCE;
@@ -172,6 +193,9 @@
private boolean mAllowBubbles = DEFAULT_ALLOW_BUBBLE;
private boolean mImportanceLockedByOEM;
private boolean mImportanceLockedDefaultApp;
+ private String mParentId = null;
+ private String mConversationId = null;
+ private boolean mDemoted = false;
/**
* Creates a notification channel.
@@ -236,6 +260,9 @@
mAllowBubbles = in.readBoolean();
mImportanceLockedByOEM = in.readBoolean();
mOriginalImportance = in.readInt();
+ mParentId = in.readString();
+ mConversationId = in.readString();
+ mDemoted = in.readBoolean();
}
@Override
@@ -291,6 +318,9 @@
dest.writeBoolean(mAllowBubbles);
dest.writeBoolean(mImportanceLockedByOEM);
dest.writeInt(mOriginalImportance);
+ dest.writeString(mParentId);
+ dest.writeString(mConversationId);
+ dest.writeBoolean(mDemoted);
}
/**
@@ -360,6 +390,13 @@
return input;
}
+ /**
+ * @hide
+ */
+ public void setId(String id) {
+ mId = id;
+ }
+
// Modifiable by apps on channel creation.
/**
@@ -502,6 +539,23 @@
}
/**
+ * Sets this channel as being person-centric. Different settings and functionality may be
+ * exposed for people-centric channels.
+ *
+ * @param parentChannelId The {@link #getId()} id} of the generic channel that notifications of
+ * this type would be posted to in absence of a specific conversation id.
+ * For example, if this channel represents 'Messages from Person A', the
+ * parent channel would be 'Messages.'
+ * @param conversationId The {@link ShortcutInfo#getId()} of the shortcut representing this
+ * channel's conversation.
+ */
+ public void setConversationId(@Nullable String parentChannelId,
+ @Nullable String conversationId) {
+ mParentId = parentChannelId;
+ mConversationId = conversationId;
+ }
+
+ /**
* Returns the id of this channel.
*/
public String getId() {
@@ -622,6 +676,22 @@
}
/**
+ * Returns the {@link #getId() id} of the parent notification channel to this channel, if it's
+ * a conversation related channel. See {@link #setConversationId(String, String)}.
+ */
+ public @Nullable String getParentChannelId() {
+ return mParentId;
+ }
+
+ /**
+ * Returns the {@link ShortcutInfo#getId() id} of the conversation backing this channel, if it's
+ * associated with a conversation. See {@link #setConversationId(String, String)}.
+ */
+ public @Nullable String getConversationId() {
+ return mConversationId;
+ }
+
+ /**
* @hide
*/
@SystemApi
@@ -701,6 +771,20 @@
}
/**
+ * @hide
+ */
+ public void setDemoted(boolean demoted) {
+ mDemoted = demoted;
+ }
+
+ /**
+ * @hide
+ */
+ public boolean isDemoted() {
+ return mDemoted;
+ }
+
+ /**
* Returns whether the user has chosen the importance of this channel, either to affirm the
* initial selection from the app, or changed it to be higher or lower.
* @see #getImportance()
@@ -761,6 +845,9 @@
setBlockableSystem(safeBool(parser, ATT_BLOCKABLE_SYSTEM, false));
setAllowBubbles(safeBool(parser, ATT_ALLOW_BUBBLE, DEFAULT_ALLOW_BUBBLE));
setOriginalImportance(safeInt(parser, ATT_ORIG_IMP, DEFAULT_IMPORTANCE));
+ setConversationId(parser.getAttributeValue(null, ATT_PARENT_CHANNEL),
+ parser.getAttributeValue(null, ATT_CONVERSATION_ID));
+ setDemoted(safeBool(parser, ATT_DEMOTE, false));
}
@Nullable
@@ -885,6 +972,15 @@
if (getOriginalImportance() != DEFAULT_IMPORTANCE) {
out.attribute(null, ATT_ORIG_IMP, Integer.toString(getOriginalImportance()));
}
+ if (getParentChannelId() != null) {
+ out.attribute(null, ATT_PARENT_CHANNEL, getParentChannelId());
+ }
+ if (getConversationId() != null) {
+ out.attribute(null, ATT_CONVERSATION_ID, getConversationId());
+ }
+ if (isDemoted()) {
+ out.attribute(null, ATT_DEMOTE, Boolean.toString(isDemoted()));
+ }
// mImportanceLockedDefaultApp and mImportanceLockedByOEM have a different source of
// truth and so aren't written to this xml file
@@ -1042,7 +1138,10 @@
&& Objects.equals(getAudioAttributes(), that.getAudioAttributes())
&& mImportanceLockedByOEM == that.mImportanceLockedByOEM
&& mImportanceLockedDefaultApp == that.mImportanceLockedDefaultApp
- && mOriginalImportance == that.mOriginalImportance;
+ && mOriginalImportance == that.mOriginalImportance
+ && Objects.equals(getParentChannelId(), that.getParentChannelId())
+ && Objects.equals(getConversationId(), that.getConversationId())
+ && isDemoted() == that.isDemoted();
}
@Override
@@ -1052,7 +1151,8 @@
getUserLockedFields(),
isFgServiceShown(), mVibrationEnabled, mShowBadge, isDeleted(), getGroup(),
getAudioAttributes(), isBlockableSystem(), mAllowBubbles,
- mImportanceLockedByOEM, mImportanceLockedDefaultApp, mOriginalImportance);
+ mImportanceLockedByOEM, mImportanceLockedDefaultApp, mOriginalImportance,
+ mParentId, mConversationId, mDemoted);
result = 31 * result + Arrays.hashCode(mVibration);
return result;
}
@@ -1063,26 +1163,7 @@
String output = "NotificationChannel{"
+ "mId='" + mId + '\''
+ ", mName=" + redactedName
- + ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "")
- + ", mImportance=" + mImportance
- + ", mBypassDnd=" + mBypassDnd
- + ", mLockscreenVisibility=" + mLockscreenVisibility
- + ", mSound=" + mSound
- + ", mLights=" + mLights
- + ", mLightColor=" + mLightColor
- + ", mVibration=" + Arrays.toString(mVibration)
- + ", mUserLockedFields=" + Integer.toHexString(mUserLockedFields)
- + ", mFgServiceShown=" + mFgServiceShown
- + ", mVibrationEnabled=" + mVibrationEnabled
- + ", mShowBadge=" + mShowBadge
- + ", mDeleted=" + mDeleted
- + ", mGroup='" + mGroup + '\''
- + ", mAudioAttributes=" + mAudioAttributes
- + ", mBlockableSystem=" + mBlockableSystem
- + ", mAllowBubbles=" + mAllowBubbles
- + ", mImportanceLockedByOEM=" + mImportanceLockedByOEM
- + ", mImportanceLockedDefaultApp=" + mImportanceLockedDefaultApp
- + ", mOriginalImp=" + mOriginalImportance
+ + getFieldsString()
+ '}';
pw.println(prefix + output);
}
@@ -1092,7 +1173,12 @@
return "NotificationChannel{"
+ "mId='" + mId + '\''
+ ", mName=" + mName
- + ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "")
+ + getFieldsString()
+ + '}';
+ }
+
+ private String getFieldsString() {
+ return ", mDescription=" + (!TextUtils.isEmpty(mDesc) ? "hasDescription " : "")
+ ", mImportance=" + mImportance
+ ", mBypassDnd=" + mBypassDnd
+ ", mLockscreenVisibility=" + mLockscreenVisibility
@@ -1112,7 +1198,9 @@
+ ", mImportanceLockedByOEM=" + mImportanceLockedByOEM
+ ", mImportanceLockedDefaultApp=" + mImportanceLockedDefaultApp
+ ", mOriginalImp=" + mOriginalImportance
- + '}';
+ + ", mParent=" + mParentId
+ + ", mConversationId=" + mConversationId
+ + ", mDemoted=" + mDemoted;
}
/** @hide */
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index fdbb8bb..1a8e15c 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -843,6 +843,26 @@
}
/**
+ * Returns the notification channel settings for a given channel and conversation id.
+ *
+ * <p>The channel must belong to your package, or to a package you are an approved notification
+ * delegate for (see {@link #canNotifyAsPackage(String)}), or it will not be returned. To query
+ * a channel as a notification delegate, call this method from a context created for that
+ * package (see {@link Context#createPackageContext(String, int)}).</p>
+ */
+ public @Nullable NotificationChannel getNotificationChannel(@NonNull String channelId,
+ @NonNull String conversationId) {
+ INotificationManager service = getService();
+ try {
+ return service.getConversationNotificationChannel(mContext.getOpPackageName(),
+ mContext.getUserId(), mContext.getPackageName(), channelId, true,
+ conversationId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns all notification channels belonging to the calling package.
*
* <p>Approved notification delegates (see {@link #canNotifyAsPackage(String)}) can query
diff --git a/core/java/android/app/PropertyInvalidatedCache.java b/core/java/android/app/PropertyInvalidatedCache.java
index 844e72e..736efb6 100644
--- a/core/java/android/app/PropertyInvalidatedCache.java
+++ b/core/java/android/app/PropertyInvalidatedCache.java
@@ -15,6 +15,7 @@
*/
package android.app;
+
import android.annotation.NonNull;
import android.os.SystemProperties;
import android.util.Log;
@@ -23,6 +24,7 @@
import java.util.LinkedHashMap;
import java.util.Map;
+import java.util.Objects;
import java.util.Random;
import java.util.concurrent.atomic.AtomicLong;
@@ -164,6 +166,7 @@
private static final String TAG = "PropertyInvalidatedCache";
private static final boolean DEBUG = false;
private static final boolean ENABLE = true;
+ private static final boolean VERIFY = false;
private final Object mLock = new Object();
@@ -228,6 +231,18 @@
protected abstract Result recompute(Query query);
/**
+ * Determines if a pair of responses are considered equal. Used to determine whether
+ * a cache is inadvertently returning stale results when VERIFY is set to true.
+ */
+ protected boolean debugCompareQueryResults(Result cachedResult, Result fetchedResult) {
+ // If a service crashes and returns a null result, the cached value remains valid.
+ if (fetchedResult != null) {
+ return Objects.equals(cachedResult, fetchedResult);
+ }
+ return true;
+ }
+
+ /**
* Make result up-to-date on a cache hit. Called unlocked;
* may block.
*
@@ -334,12 +349,12 @@
mCache.put(query, refreshedResult);
}
}
- return refreshedResult;
+ return maybeCheckConsistency(query, refreshedResult);
}
if (DEBUG) {
Log.d(TAG, "cache hit for " + query);
}
- return cachedResult;
+ return maybeCheckConsistency(query, cachedResult);
}
// Cache miss: make the value from scratch.
if (DEBUG) {
@@ -353,7 +368,7 @@
mCache.put(query, result);
}
}
- return result;
+ return maybeCheckConsistency(query, result);
}
}
@@ -425,4 +440,15 @@
}
SystemProperties.set(name, newValueString);
}
+
+ private Result maybeCheckConsistency(Query query, Result proposedResult) {
+ if (VERIFY) {
+ Result resultToCompare = recompute(query);
+ boolean nonceChanged = (getCurrentNonce() != mLastSeenNonce);
+ if (!nonceChanged && !debugCompareQueryResults(proposedResult, resultToCompare)) {
+ throw new AssertionError("cache returned out of date response for " + query);
+ }
+ }
+ return proposedResult;
+ }
}
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index aa11598..d23754e 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -482,19 +482,6 @@
}
}
- if (key.mOverlayDirs != null) {
- for (final String idmapPath : key.mOverlayDirs) {
- try {
- builder.addApkAssets(loadApkAssets(idmapPath, false /*sharedLib*/,
- true /*overlay*/));
- } catch (IOException e) {
- Log.w(TAG, "failed to add overlay path " + idmapPath);
-
- // continue.
- }
- }
- }
-
if (key.mLibDirs != null) {
for (final String libDir : key.mLibDirs) {
if (libDir.endsWith(".apk")) {
@@ -513,6 +500,19 @@
}
}
+ if (key.mOverlayDirs != null) {
+ for (final String idmapPath : key.mOverlayDirs) {
+ try {
+ builder.addApkAssets(loadApkAssets(idmapPath, false /*sharedLib*/,
+ true /*overlay*/));
+ } catch (IOException e) {
+ Log.w(TAG, "failed to add overlay path " + idmapPath);
+
+ // continue.
+ }
+ }
+ }
+
return builder.build();
}
diff --git a/core/java/android/app/StatsManager.java b/core/java/android/app/StatsManager.java
index b9893aa..dde6dda 100644
--- a/core/java/android/app/StatsManager.java
+++ b/core/java/android/app/StatsManager.java
@@ -26,7 +26,6 @@
import android.os.IBinder;
import android.os.IPullAtomCallback;
import android.os.IPullAtomResultReceiver;
-import android.os.IStatsCompanionService;
import android.os.IStatsManagerService;
import android.os.IStatsPullerCallback;
import android.os.IStatsd;
@@ -61,9 +60,6 @@
private IStatsd mService;
@GuardedBy("sLock")
- private IStatsCompanionService mStatsCompanion;
-
- @GuardedBy("sLock")
private IStatsManagerService mStatsManagerService;
/**
@@ -157,11 +153,11 @@
public void addConfig(long configKey, byte[] config) throws StatsUnavailableException {
synchronized (sLock) {
try {
- IStatsd service = getIStatsdLocked();
+ IStatsManagerService service = getIStatsManagerServiceLocked();
// can throw IllegalArgumentException
service.addConfiguration(configKey, config, mContext.getOpPackageName());
} catch (RemoteException e) {
- Slog.e(TAG, "Failed to connect to statsd when adding configuration");
+ Slog.e(TAG, "Failed to connect to statsmanager when adding configuration");
throw new StatsUnavailableException("could not connect", e);
} catch (SecurityException e) {
throw new StatsUnavailableException(e.getMessage(), e);
@@ -194,10 +190,10 @@
public void removeConfig(long configKey) throws StatsUnavailableException {
synchronized (sLock) {
try {
- IStatsd service = getIStatsdLocked();
+ IStatsManagerService service = getIStatsManagerServiceLocked();
service.removeConfiguration(configKey, mContext.getOpPackageName());
} catch (RemoteException e) {
- Slog.e(TAG, "Failed to connect to statsd when removing configuration");
+ Slog.e(TAG, "Failed to connect to statsmanager when removing configuration");
throw new StatsUnavailableException("could not connect", e);
} catch (SecurityException e) {
throw new StatsUnavailableException(e.getMessage(), e);
@@ -255,18 +251,17 @@
throws StatsUnavailableException {
synchronized (sLock) {
try {
- IStatsd service = getIStatsdLocked();
+ IStatsManagerService service = getIStatsManagerServiceLocked();
if (pendingIntent != null) {
- // Extracts IIntentSender from the PendingIntent and turns it into an IBinder.
- IBinder intentSender = pendingIntent.getTarget().asBinder();
- service.setBroadcastSubscriber(configKey, subscriberId, intentSender,
+ service.setBroadcastSubscriber(configKey, subscriberId, pendingIntent,
mContext.getOpPackageName());
} else {
service.unsetBroadcastSubscriber(configKey, subscriberId,
mContext.getOpPackageName());
}
} catch (RemoteException e) {
- Slog.e(TAG, "Failed to connect to statsd when adding broadcast subscriber", e);
+ Slog.e(TAG, "Failed to connect to statsmanager when adding broadcast subscriber",
+ e);
throw new StatsUnavailableException("could not connect", e);
} catch (SecurityException e) {
throw new StatsUnavailableException(e.getMessage(), e);
@@ -309,18 +304,16 @@
throws StatsUnavailableException {
synchronized (sLock) {
try {
- IStatsd service = getIStatsdLocked();
+ IStatsManagerService service = getIStatsManagerServiceLocked();
if (pendingIntent == null) {
service.removeDataFetchOperation(configKey, mContext.getOpPackageName());
} else {
- // Extracts IIntentSender from the PendingIntent and turns it into an IBinder.
- IBinder intentSender = pendingIntent.getTarget().asBinder();
- service.setDataFetchOperation(configKey, intentSender,
+ service.setDataFetchOperation(configKey, pendingIntent,
mContext.getOpPackageName());
}
} catch (RemoteException e) {
- Slog.e(TAG, "Failed to connect to statsd when registering data listener.");
+ Slog.e(TAG, "Failed to connect to statsmanager when registering data listener.");
throw new StatsUnavailableException("could not connect", e);
} catch (SecurityException e) {
throw new StatsUnavailableException(e.getMessage(), e);
@@ -347,20 +340,18 @@
throws StatsUnavailableException {
synchronized (sLock) {
try {
- IStatsd service = getIStatsdLocked();
+ IStatsManagerService service = getIStatsManagerServiceLocked();
if (pendingIntent == null) {
service.removeActiveConfigsChangedOperation(mContext.getOpPackageName());
return new long[0];
} else {
- // Extracts IIntentSender from the PendingIntent and turns it into an IBinder.
- IBinder intentSender = pendingIntent.getTarget().asBinder();
- return service.setActiveConfigsChangedOperation(intentSender,
+ return service.setActiveConfigsChangedOperation(pendingIntent,
mContext.getOpPackageName());
}
} catch (RemoteException e) {
- Slog.e(TAG,
- "Failed to connect to statsd when registering active configs listener.");
+ Slog.e(TAG, "Failed to connect to statsmanager "
+ + "when registering active configs listener.");
throw new StatsUnavailableException("could not connect", e);
} catch (SecurityException e) {
throw new StatsUnavailableException(e.getMessage(), e);
@@ -395,10 +386,10 @@
public byte[] getReports(long configKey) throws StatsUnavailableException {
synchronized (sLock) {
try {
- IStatsd service = getIStatsdLocked();
+ IStatsManagerService service = getIStatsManagerServiceLocked();
return service.getData(configKey, mContext.getOpPackageName());
} catch (RemoteException e) {
- Slog.e(TAG, "Failed to connect to statsd when getting data");
+ Slog.e(TAG, "Failed to connect to statsmanager when getting data");
throw new StatsUnavailableException("could not connect", e);
} catch (SecurityException e) {
throw new StatsUnavailableException(e.getMessage(), e);
@@ -432,10 +423,10 @@
public byte[] getStatsMetadata() throws StatsUnavailableException {
synchronized (sLock) {
try {
- IStatsd service = getIStatsdLocked();
+ IStatsManagerService service = getIStatsManagerServiceLocked();
return service.getMetadata(mContext.getOpPackageName());
} catch (RemoteException e) {
- Slog.e(TAG, "Failed to connect to statsd when getting metadata");
+ Slog.e(TAG, "Failed to connect to statsmanager when getting metadata");
throw new StatsUnavailableException("could not connect", e);
} catch (SecurityException e) {
throw new StatsUnavailableException(e.getMessage(), e);
@@ -467,21 +458,19 @@
throws StatsUnavailableException {
synchronized (sLock) {
try {
- IStatsd service = getIStatsdLocked();
+ IStatsManagerService service = getIStatsManagerServiceLocked();
if (service == null) {
- if (DEBUG) {
- Slog.d(TAG, "Failed to find statsd when getting experiment IDs");
- }
- return new long[0];
+ throw new StatsUnavailableException("Failed to find statsmanager when "
+ + "getting experiment IDs");
}
return service.getRegisteredExperimentIds();
} catch (RemoteException e) {
if (DEBUG) {
Slog.d(TAG,
- "Failed to connect to StatsCompanionService when getting "
+ "Failed to connect to StatsManagerService when getting "
+ "registered experiment IDs");
}
- return new long[0];
+ throw new StatsUnavailableException("could not connect", e);
}
}
}
@@ -545,7 +534,7 @@
}
synchronized (sLock) {
try {
- IStatsCompanionService service = getIStatsCompanionServiceLocked();
+ IStatsManagerService service = getIStatsManagerServiceLocked();
PullAtomCallbackInternal rec =
new PullAtomCallbackInternal(atomTag, callback, executor);
service.registerPullAtomCallback(atomTag, coolDownNs, timeoutNs, additiveFields,
@@ -567,7 +556,7 @@
public void unregisterPullAtomCallback(int atomTag) {
synchronized (sLock) {
try {
- IStatsCompanionService service = getIStatsCompanionServiceLocked();
+ IStatsManagerService service = getIStatsManagerServiceLocked();
service.unregisterPullAtomCallback(atomTag);
} catch (RemoteException e) {
throw new RuntimeException("Unable to unregister pull atom callback");
@@ -753,16 +742,6 @@
}
@GuardedBy("sLock")
- private IStatsCompanionService getIStatsCompanionServiceLocked() {
- if (mStatsCompanion != null) {
- return mStatsCompanion;
- }
- mStatsCompanion = IStatsCompanionService.Stub.asInterface(
- ServiceManager.getService("statscompanion"));
- return mStatsCompanion;
- }
-
- @GuardedBy("sLock")
private IStatsManagerService getIStatsManagerServiceLocked() {
if (mStatsManagerService != null) {
return mStatsManagerService;
diff --git a/core/java/android/app/StatusBarManager.java b/core/java/android/app/StatusBarManager.java
index a1765c8..42563b5 100644
--- a/core/java/android/app/StatusBarManager.java
+++ b/core/java/android/app/StatusBarManager.java
@@ -153,6 +153,11 @@
*/
public static final int DEFAULT_SETUP_DISABLE2_FLAGS = DISABLE2_ROTATE_SUGGESTIONS;
+ /**
+ * disable flags to be applied when the device is sim-locked.
+ */
+ private static final int DEFAULT_SIM_LOCKED_DISABLED_FLAGS = DISABLE_EXPAND;
+
/** @hide */
public static final int NAVIGATION_HINT_BACK_ALT = 1 << 0;
/** @hide */
@@ -262,6 +267,7 @@
* @hide
*/
@UnsupportedAppUsage
+ @TestApi
public void expandNotificationsPanel() {
try {
final IStatusBarService svc = getService();
@@ -279,6 +285,7 @@
* @hide
*/
@UnsupportedAppUsage
+ @TestApi
public void collapsePanels() {
try {
final IStatusBarService svc = getService();
@@ -385,6 +392,30 @@
}
/**
+ * Enable or disable expansion of the status bar. When the device is SIM-locked, the status
+ * bar should not be expandable.
+ *
+ * @param disabled If {@code true}, the status bar will be set to non-expandable. If
+ * {@code false}, re-enables expansion of the status bar.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ @RequiresPermission(android.Manifest.permission.STATUS_BAR)
+ public void setDisabledForSimNetworkLock(boolean disabled) {
+ try {
+ final int userId = Binder.getCallingUserHandle().getIdentifier();
+ final IStatusBarService svc = getService();
+ if (svc != null) {
+ svc.disableForUser(disabled ? DEFAULT_SIM_LOCKED_DISABLED_FLAGS : DISABLE_NONE,
+ mToken, mContext.getPackageName(), userId);
+ }
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Get this app's currently requested disabled components
*
* @return a new DisableInfo
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index ca3d0d7..7b21f12 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -54,6 +54,7 @@
import android.content.integrity.IAppIntegrityManager;
import android.content.om.IOverlayManager;
import android.content.om.OverlayManager;
+import android.content.pm.ApplicationInfo;
import android.content.pm.CrossProfileApps;
import android.content.pm.DataLoaderManager;
import android.content.pm.ICrossProfileApps;
@@ -170,6 +171,7 @@
import android.service.persistentdata.PersistentDataBlockManager;
import android.service.vr.IVrManager;
import android.telecom.TelecomManager;
+import android.telephony.MmsManager;
import android.telephony.TelephonyFrameworkInitializer;
import android.telephony.TelephonyRegistryManager;
import android.util.ArrayMap;
@@ -345,6 +347,14 @@
}
});
+ registerService(Context.NETWORK_STACK_SERVICE, IBinder.class,
+ new StaticServiceFetcher<IBinder>() {
+ @Override
+ public IBinder createService() {
+ return ServiceManager.getService(Context.NETWORK_STACK_SERVICE);
+ }
+ });
+
registerService(Context.TETHERING_SERVICE, TetheringManager.class,
new CachedServiceFetcher<TetheringManager>() {
@Override
@@ -631,6 +641,13 @@
return new TelecomManager(ctx.getOuterContext());
}});
+ registerService(Context.MMS_SERVICE, MmsManager.class,
+ new CachedServiceFetcher<MmsManager>() {
+ @Override
+ public MmsManager createService(ContextImpl ctx) {
+ return new MmsManager(ctx.getOuterContext());
+ }});
+
registerService(Context.UI_MODE_SERVICE, UiModeManager.class,
new CachedServiceFetcher<UiModeManager>() {
@Override
@@ -677,20 +694,20 @@
throws ServiceNotFoundException {
final IBinder b = ServiceManager.getService(Context.WALLPAPER_SERVICE);
if (b == null) {
- // There are 2 reason service can be null:
- // 1.Device doesn't support it - that's fine
- // 2.App is running on instant mode - should fail
- final boolean enabled = Resources.getSystem()
- .getBoolean(com.android.internal.R.bool.config_enableWallpaperService);
- if (!enabled) {
- // Life moves on...
- return DisabledWallpaperManager.getInstance();
- }
- if (ctx.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.P) {
+ ApplicationInfo appInfo = ctx.getApplicationInfo();
+ if (appInfo.targetSdkVersion >= Build.VERSION_CODES.P
+ && appInfo.isInstantApp()) {
// Instant app
throw new ServiceNotFoundException(Context.WALLPAPER_SERVICE);
}
+ final boolean enabled = Resources.getSystem()
+ .getBoolean(com.android.internal.R.bool.config_enableWallpaperService);
+ if (!enabled) {
+ // Device doesn't support wallpaper, return a limited manager
+ return DisabledWallpaperManager.getInstance();
+ }
// Bad state - WallpaperManager methods will throw exception
+ Log.e(TAG, "No wallpaper service");
}
IWallpaperManager service = IWallpaperManager.Stub.asInterface(b);
return new WallpaperManager(service, ctx.getOuterContext(),
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index c585b5f..7b45b72 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -48,6 +48,10 @@
}
],
"file_patterns": ["INotificationManager\\.aidl"]
+ },
+ {
+ "name": "FrameworksInstantAppResolverTests",
+ "file_patterns": ["(/|^)InstantAppResolve[^/]*"]
}
],
"postsubmit": [
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 18a3e6e..2579bd1 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -27,10 +27,7 @@
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Bitmap;
-import android.graphics.Point;
-import android.graphics.Rect;
import android.graphics.Region;
-import android.hardware.display.DisplayManagerGlobal;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
@@ -831,39 +828,20 @@
}
/**
- * Takes a screenshot.
+ * Takes a screenshot of the default display and returns it by {@link Bitmap.Config#HARDWARE}
+ * format.
*
* @return The screenshot bitmap on success, null otherwise.
*/
public Bitmap takeScreenshot() {
+ final int connectionId;
synchronized (mLock) {
throwIfNotConnectedLocked();
+ connectionId = mConnectionId;
}
- Display display = DisplayManagerGlobal.getInstance()
- .getRealDisplay(Display.DEFAULT_DISPLAY);
- Point displaySize = new Point();
- display.getRealSize(displaySize);
-
- int rotation = display.getRotation();
-
- // Take the screenshot
- Bitmap screenShot = null;
- try {
- // Calling out without a lock held.
- screenShot = mUiAutomationConnection.takeScreenshot(
- new Rect(0, 0, displaySize.x, displaySize.y), rotation);
- if (screenShot == null) {
- return null;
- }
- } catch (RemoteException re) {
- Log.e(LOG_TAG, "Error while taking screnshot!", re);
- return null;
- }
-
- // Optimization
- screenShot.setHasAlpha(false);
-
- return screenShot;
+ // Calling out without a lock held.
+ return AccessibilityInteractionClient.getInstance()
+ .takeScreenshot(connectionId, Display.DEFAULT_DISPLAY);
}
/**
diff --git a/core/java/android/app/UiAutomationConnection.java b/core/java/android/app/UiAutomationConnection.java
index 82e9881..4fb9743 100644
--- a/core/java/android/app/UiAutomationConnection.java
+++ b/core/java/android/app/UiAutomationConnection.java
@@ -21,8 +21,6 @@
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Rect;
import android.hardware.input.InputManager;
import android.os.Binder;
import android.os.IBinder;
@@ -180,23 +178,6 @@
}
@Override
- public Bitmap takeScreenshot(Rect crop, int rotation) {
- synchronized (mLock) {
- throwIfCalledByNotTrustedUidLocked();
- throwIfShutdownLocked();
- throwIfNotConnectedLocked();
- }
- final long identity = Binder.clearCallingIdentity();
- try {
- int width = crop.width();
- int height = crop.height();
- return SurfaceControl.screenshot(crop, width, height, rotation);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- @Override
public boolean clearWindowContentFrameStats(int windowId) throws RemoteException {
synchronized (mLock) {
throwIfCalledByNotTrustedUidLocked();
@@ -449,7 +430,8 @@
info.setCapabilities(AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
| AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
| AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY
- | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS);
+ | AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS
+ | AccessibilityServiceInfo.CAPABILITY_CAN_TAKE_SCREENSHOT);
try {
// Calling out with a lock held is fine since if the system
// process is gone the client calling in will be killed.
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 2507991..9ad3d38 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -543,6 +543,13 @@
mCmProxy = new ColorManagementProxy(context);
}
+ // no-op constructor called just by DisabledWallpaperManager
+ /*package*/ WallpaperManager() {
+ mContext = null;
+ mCmProxy = null;
+ mWcgEnabled = false;
+ }
+
/**
* Retrieve a WallpaperManager associated with the given Context.
*/
@@ -568,8 +575,10 @@
*
* @see Configuration#isScreenWideColorGamut()
* @return True if wcg should be enabled for this device.
+ * @hide
*/
- private boolean shouldEnableWideColorGamut() {
+ @TestApi
+ public boolean shouldEnableWideColorGamut() {
return mWcgEnabled;
}
@@ -877,6 +886,7 @@
* @see #FLAG_SYSTEM
* @hide
*/
+ @TestApi
@RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE)
public boolean wallpaperSupportsWcg(int which) {
if (!shouldEnableWideColorGamut()) {
@@ -893,6 +903,8 @@
*
* @hide
*/
+ @TestApi
+ @Nullable
@UnsupportedAppUsage
public Bitmap getBitmap() {
return getBitmap(false);
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index c9060c4..be8e1d6 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -398,6 +398,42 @@
"android.app.action.PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE";
/**
+ * Activity action: Starts the provisioning flow which sets up a financed device.
+ *
+ * <p>During financed device provisioning, a device admin app is downloaded and set as the owner
+ * of the device. A device owner has full control over the device. The device owner can not be
+ * modified by the user.
+ *
+ * <p>A typical use case would be a device that is bought from the reseller through financing
+ * program.
+ *
+ * <p>An intent with this action can be sent only on an unprovisioned device.
+ *
+ * <p>Unlike {@link #ACTION_PROVISION_MANAGED_DEVICE}, the provisioning message can only be sent
+ * by a privileged app with the permission
+ * {@link android.Manifest.permission#DISPATCH_PROVISIONING_MESSAGE}.
+ *
+ * <p>The provisioning intent contains the following properties:
+ * <ul>
+ * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME}</li>
+ * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_LOCATION}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_DOWNLOAD_COOKIE_HEADER}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_CHECKSUM}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_LABEL}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_DEVICE_ADMIN_PACKAGE_ICON_URI}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_SUPPORT_URL}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_ORGANIZATION_NAME}, optional</li>
+ * <li>{@link #EXTRA_PROVISIONING_ADMIN_EXTRAS_BUNDLE}, optional</li>
+ * </ul>
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ @SystemApi
+ public static final String ACTION_PROVISION_FINANCED_DEVICE =
+ "android.app.action.PROVISION_FINANCED_DEVICE";
+
+ /**
* Activity action: Starts the provisioning flow which sets up a managed device.
* Must be started with {@link android.app.Activity#startActivityForResult(Intent, int)}.
*
@@ -864,6 +900,7 @@
* The name is displayed only during provisioning.
*
* <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
+ * or {@link #ACTION_PROVISION_FINANCED_DEVICE}
*
* @hide
*/
@@ -876,6 +913,7 @@
* during provisioning. If the url is not HTTPS, an error will be shown.
*
* <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
+ * or {@link #ACTION_PROVISION_FINANCED_DEVICE}
*
* @hide
*/
@@ -888,6 +926,7 @@
* as the app label of the package.
*
* <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
+ * or {@link #ACTION_PROVISION_FINANCED_DEVICE}
*
* @hide
*/
@@ -912,6 +951,7 @@
* {@link android.content.ClipData} of the intent too.
*
* <p>Use in an intent with action {@link #ACTION_PROVISION_MANAGED_DEVICE_FROM_TRUSTED_SOURCE}
+ * or {@link #ACTION_PROVISION_FINANCED_DEVICE}
*
* @hide
*/
@@ -1371,6 +1411,16 @@
= "android.app.action.DEVICE_OWNER_CHANGED";
/**
+ * Broadcast action: sent when the factory reset protection (FRP) policy is changed.
+ *
+ * @see #setFactoryResetProtectionPolicy
+ * @hide
+ */
+ @SystemApi
+ public static final String ACTION_RESET_PROTECTION_POLICY_CHANGED =
+ "android.app.action.RESET_PROTECTION_POLICY_CHANGED";
+
+ /**
* The ComponentName of the administrator component.
*
* @see #ACTION_ADD_DEVICE_ADMIN
@@ -1942,16 +1992,6 @@
public static final int CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER = 14;
/**
- * Result code for {@link #checkProvisioningPreCondition}.
- *
- * <p>Returned for {@link #ACTION_PROVISION_MANAGED_PROFILE} when adding a managed profile is
- * disallowed by {@link UserManager#DISALLOW_ADD_MANAGED_PROFILE}.
- *
- * @hide
- */
- public static final int CODE_ADD_MANAGED_PROFILE_DISALLOWED = 15;
-
- /**
* Result codes for {@link #checkProvisioningPreCondition} indicating all the provisioning pre
* conditions.
*
@@ -1963,7 +2003,7 @@
CODE_USER_SETUP_COMPLETED, CODE_NOT_SYSTEM_USER, CODE_HAS_PAIRED,
CODE_MANAGED_USERS_NOT_SUPPORTED, CODE_SYSTEM_USER, CODE_CANNOT_ADD_MANAGED_PROFILE,
CODE_NOT_SYSTEM_USER_SPLIT, CODE_DEVICE_ADMIN_NOT_SUPPORTED,
- CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER, CODE_ADD_MANAGED_PROFILE_DISALLOWED
+ CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER
})
public @interface ProvisioningPreCondition {}
@@ -4289,6 +4329,60 @@
}
/**
+ * Callable by device owner or profile owner of an organization-owned device, to set a
+ * factory reset protection (FRP) policy. When a new policy is set, the system
+ * notifies the FRP management agent of a policy change by broadcasting
+ * {@code ACTION_RESET_PROTECTION_POLICY_CHANGED}.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with.
+ * @param policy the new FRP policy, or {@code null} to clear the current policy.
+ * @throws SecurityException if {@code admin} is not a device owner or a profile owner of
+ * an organization-owned device.
+ * @throws UnsupportedOperationException if factory reset protection is not
+ * supported on the device.
+ */
+ public void setFactoryResetProtectionPolicy(@NonNull ComponentName admin,
+ @Nullable FactoryResetProtectionPolicy policy) {
+ throwIfParentInstance("setFactoryResetProtectionPolicy");
+ if (mService != null) {
+ try {
+ mService.setFactoryResetProtectionPolicy(admin, policy);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Callable by device owner or profile owner of an organization-owned device, to retrieve
+ * the current factory reset protection (FRP) policy set previously by
+ * {@link #setFactoryResetProtectionPolicy}.
+ * <p>
+ * This method can also be called by the FRP management agent on device, in which case,
+ * it can pass {@code null} as the ComponentName.
+ *
+ * @param admin Which {@link DeviceAdminReceiver} this request is associated with or
+ * {@code null} if called by the FRP management agent on device.
+ * @return The current FRP policy object or {@code null} if no policy is set.
+ * @throws SecurityException if {@code admin} is not a device owner, a profile owner of
+ * an organization-owned device or the FRP management agent.
+ * @throws UnsupportedOperationException if factory reset protection is not
+ * supported on the device.
+ */
+ public @Nullable FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(
+ @Nullable ComponentName admin) {
+ throwIfParentInstance("getFactoryResetProtectionPolicy");
+ if (mService != null) {
+ try {
+ return mService.getFactoryResetProtectionPolicy(admin);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return null;
+ }
+
+ /**
* Called by an application that is administering the device to set the
* global proxy and exclusion list.
* <p>
@@ -6756,6 +6850,31 @@
}
/**
+ * Apps can use this method to find out if the device was provisioned as
+ * organization-owend device with a managed profile.
+ *
+ * This, together with checking whether the device has a device owner (by calling
+ * {@link #isDeviceOwnerApp}), could be used to learn whether the device is owned by an
+ * organization or an individual:
+ * If this method returns true OR {@link #isDeviceOwnerApp} returns true (for any package),
+ * then the device is owned by an organization. Otherwise, it's owned by an individual.
+ *
+ * @return {@code true} if the device was provisioned as organization-owned device,
+ * {@code false} otherwise.
+ */
+ public boolean isOrganizationOwnedDeviceWithManagedProfile() {
+ throwIfParentInstance("isOrganizationOwnedDeviceWithManagedProfile");
+ if (mService != null) {
+ try {
+ return mService.isOrganizationOwnedDeviceWithManagedProfile();
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+ return false;
+ }
+
+ /**
* Returns whether the specified package can read the device identifiers.
*
* @param packageName The package name of the app to check for device identifier access.
@@ -7296,7 +7415,9 @@
* @param userHandle The user for whom to check the caller-id permission
* @hide
*/
- public boolean getBluetoothContactSharingDisabled(UserHandle userHandle) {
+ @SystemApi
+ @RequiresPermission(permission.INTERACT_ACROSS_USERS)
+ public boolean getBluetoothContactSharingDisabled(@NonNull UserHandle userHandle) {
if (mService != null) {
try {
return mService.getBluetoothContactSharingDisabledForUser(userHandle
@@ -11207,6 +11328,40 @@
}
/**
+ * Returns the combined set of the following:
+ * <ul>
+ * <li>The package names that the admin has previously set as allowed to request user consent
+ * for cross-profile communication, via {@link
+ * #setCrossProfilePackages(ComponentName, Set)}.</li>
+ * <li>The default package names set by the OEM that are allowed to request user consent for
+ * cross-profile communication without being explicitly enabled by the admin, via
+ * {@link com.android.internal.R.array#cross_profile_apps}</li>
+ * </ul>
+ *
+ * @return the combined set of whitelisted package names set via
+ * {@link #setCrossProfilePackages(ComponentName, Set)} and
+ * {@link com.android.internal.R.array#cross_profile_apps}
+ *
+ * @hide
+ */
+ @RequiresPermission(anyOf = {
+ permission.INTERACT_ACROSS_USERS_FULL,
+ permission.INTERACT_ACROSS_USERS,
+ permission.INTERACT_ACROSS_PROFILES
+ })
+ public @NonNull Set<String> getAllCrossProfilePackages() {
+ throwIfParentInstance("getDefaultCrossProfilePackages");
+ if (mService != null) {
+ try {
+ return new ArraySet<>(mService.getAllCrossProfilePackages());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ return Collections.emptySet();
+ }
+
+ /**
* Returns whether the device is being used as a managed kiosk. These requirements are as
* follows:
* <ul>
@@ -11303,4 +11458,39 @@
}
return false;
}
+
+ /**
+ * Called by Device owner to set packages as protected. User will not be able to clear app
+ * data or force-stop protected packages.
+ *
+ * @param admin which {@link DeviceAdminReceiver} this request is associated with
+ * @param packages The package names to protect.
+ * @throws SecurityException if {@code admin} is not a device owner.
+ */
+ public void setProtectedPackages(@NonNull ComponentName admin, @NonNull List<String> packages) {
+ if (mService != null) {
+ try {
+ mService.setProtectedPackages(admin, packages);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+ }
+
+ /**
+ * Returns the list of packages protected by the device owner.
+ *
+ * @param admin which {@link DeviceAdminReceiver} this request is associated with
+ * @throws SecurityException if {@code admin} is not a device owner.
+ */
+ public @NonNull List<String> getProtectedPackages(@NonNull ComponentName admin) {
+ if (mService != null) {
+ try {
+ return mService.getProtectedPackages(admin);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+ return Collections.emptyList();
+ }
}
diff --git a/core/java/android/app/admin/DevicePolicyManagerInternal.java b/core/java/android/app/admin/DevicePolicyManagerInternal.java
index f299d45..e6c89d9 100644
--- a/core/java/android/app/admin/DevicePolicyManagerInternal.java
+++ b/core/java/android/app/admin/DevicePolicyManagerInternal.java
@@ -17,9 +17,12 @@
package android.app.admin;
import android.annotation.UserIdInt;
+import android.content.ComponentName;
import android.content.Intent;
+import android.os.UserHandle;
import java.util.List;
+import java.util.Set;
/**
* Device policy manager local system service interface.
@@ -165,4 +168,23 @@
* Do not call it directly. Use {@link DevicePolicyCache#getInstance()} instead.
*/
protected abstract DeviceStateCache getDeviceStateCache();
+
+ /**
+ * Returns the combined set of the following:
+ * <ul>
+ * <li>The package names that the admin has previously set as allowed to request user consent
+ * for cross-profile communication, via {@link
+ * DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)}.</li>
+ * <li>The default package names that are allowed to request user consent for cross-profile
+ * communication without being explicitly enabled by the admin , via {@link
+ * DevicePolicyManager#setDefaultCrossProfilePackages(ComponentName, UserHandle, Set)}.</li>
+ * </ul>
+ *
+ * @return the combined set of whitelisted package names set via
+ * {@link DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)} and
+ * {@link DevicePolicyManager#setDefaultCrossProfilePackages(ComponentName, UserHandle, Set)}
+ *
+ * @hide
+ */
+ public abstract List<String> getAllCrossProfilePackages();
}
diff --git a/media/java/android/media/RouteSessionInfo.aidl b/core/java/android/app/admin/FactoryResetProtectionPolicy.aidl
similarity index 81%
copy from media/java/android/media/RouteSessionInfo.aidl
copy to core/java/android/app/admin/FactoryResetProtectionPolicy.aidl
index fb5d836..72e639a 100644
--- a/media/java/android/media/RouteSessionInfo.aidl
+++ b/core/java/android/app/admin/FactoryResetProtectionPolicy.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 The Android Open Source Project
+ * 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.
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.media;
+package android.app.admin;
-parcelable RouteSessionInfo;
+parcelable FactoryResetProtectionPolicy;
diff --git a/core/java/android/app/admin/FactoryResetProtectionPolicy.java b/core/java/android/app/admin/FactoryResetProtectionPolicy.java
new file mode 100644
index 0000000..ed74779
--- /dev/null
+++ b/core/java/android/app/admin/FactoryResetProtectionPolicy.java
@@ -0,0 +1,237 @@
+/*
+ * 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.admin;
+
+import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
+import static org.xmlpull.v1.XmlPullParser.END_TAG;
+import static org.xmlpull.v1.XmlPullParser.TEXT;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The factory reset protection policy determines which accounts can unlock a device that
+ * has gone through untrusted factory reset.
+ * <p>
+ * Only a device owner or profile owner of an organization-owned device can set a factory
+ * reset protection policy for the device by calling the {@code DevicePolicyManager} method
+ * {@link DevicePolicyManager#setFactoryResetProtectionPolicy(ComponentName,
+ * FactoryResetProtectionPolicy)}}.
+ *
+ * @see DevicePolicyManager#setFactoryResetProtectionPolicy
+ * @see DevicePolicyManager#getFactoryResetProtectionPolicy
+ */
+public final class FactoryResetProtectionPolicy implements Parcelable {
+
+ private static final String LOG_TAG = "FactoryResetProtectionPolicy";
+
+ private static final String KEY_FACTORY_RESET_PROTECTION_ACCOUNT =
+ "factory_reset_protection_account";
+ private static final String KEY_FACTORY_RESET_PROTECTION_DISABLED =
+ "factory_reset_protection_disabled";
+ private static final String ATTR_VALUE = "value";
+
+ private final List<String> mFactoryResetProtectionAccounts;
+ private final boolean mFactoryResetProtectionDisabled;
+
+ private FactoryResetProtectionPolicy(List<String> factoryResetProtectionAccounts,
+ boolean factoryResetProtectionDisabled) {
+ mFactoryResetProtectionAccounts = factoryResetProtectionAccounts;
+ mFactoryResetProtectionDisabled = factoryResetProtectionDisabled;
+ }
+
+ /**
+ * Get the list of accounts that can provision a device which has been factory reset.
+ */
+ public @NonNull List<String> getFactoryResetProtectionAccounts() {
+ return mFactoryResetProtectionAccounts;
+ }
+
+ /**
+ * Return whether factory reset protection for the device is disabled or not.
+ */
+ public boolean isFactoryResetProtectionDisabled() {
+ return mFactoryResetProtectionDisabled;
+ }
+
+ /**
+ * Builder class for {@link FactoryResetProtectionPolicy} objects.
+ */
+ public static class Builder {
+ private List<String> mFactoryResetProtectionAccounts;
+ private boolean mFactoryResetProtectionDisabled;
+
+ /**
+ * Initialize a new Builder to construct a {@link FactoryResetProtectionPolicy}.
+ */
+ public Builder() {
+ };
+
+ /**
+ * Sets which accounts can unlock a device that has been factory reset.
+ * <p>
+ * Once set, the consumer unlock flow will be disabled and only accounts in this list
+ * can unlock factory reset protection after untrusted factory reset.
+ * <p>
+ * It's up to the FRP management agent to interpret the {@code String} as account it
+ * supports. Please consult their relevant documentation for details.
+ *
+ * @param factoryResetProtectionAccounts list of accounts.
+ * @return the same Builder instance.
+ */
+ @NonNull
+ public Builder setFactoryResetProtectionAccounts(
+ @NonNull List<String> factoryResetProtectionAccounts) {
+ mFactoryResetProtectionAccounts = new ArrayList<>(factoryResetProtectionAccounts);
+ return this;
+ }
+
+ /**
+ * Sets whether factory reset protection is disabled or not.
+ * <p>
+ * Once disabled, factory reset protection will not kick in all together when the device
+ * goes through untrusted factory reset. This applies to both the consumer unlock flow and
+ * the admin account overrides via {@link #setFactoryResetProtectionAccounts}
+ *
+ * @param factoryResetProtectionDisabled Whether the policy is disabled or not.
+ * @return the same Builder instance.
+ */
+ @NonNull
+ public Builder setFactoryResetProtectionDisabled(boolean factoryResetProtectionDisabled) {
+ mFactoryResetProtectionDisabled = factoryResetProtectionDisabled;
+ return this;
+ }
+
+ /**
+ * Combines all of the attributes that have been set on this {@code Builder}
+ *
+ * @return a new {@link FactoryResetProtectionPolicy} object.
+ */
+ @NonNull
+ public FactoryResetProtectionPolicy build() {
+ return new FactoryResetProtectionPolicy(mFactoryResetProtectionAccounts,
+ mFactoryResetProtectionDisabled);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "FactoryResetProtectionPolicy{"
+ + "mFactoryResetProtectionAccounts=" + mFactoryResetProtectionAccounts
+ + ", mFactoryResetProtectionDisabled=" + mFactoryResetProtectionDisabled
+ + '}';
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, @Nullable int flags) {
+ int accountsCount = mFactoryResetProtectionAccounts.size();
+ dest.writeInt(accountsCount);
+ for (String account: mFactoryResetProtectionAccounts) {
+ dest.writeString(account);
+ }
+ dest.writeBoolean(mFactoryResetProtectionDisabled);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final @NonNull Creator<FactoryResetProtectionPolicy> CREATOR =
+ new Creator<FactoryResetProtectionPolicy>() {
+
+ @Override
+ public FactoryResetProtectionPolicy createFromParcel(Parcel in) {
+ List<String> factoryResetProtectionAccounts = new ArrayList<>();
+ int accountsCount = in.readInt();
+ for (int i = 0; i < accountsCount; i++) {
+ factoryResetProtectionAccounts.add(in.readString());
+ }
+ boolean factoryResetProtectionDisabled = in.readBoolean();
+
+ return new FactoryResetProtectionPolicy(factoryResetProtectionAccounts,
+ factoryResetProtectionDisabled);
+ }
+
+ @Override
+ public FactoryResetProtectionPolicy[] newArray(int size) {
+ return new FactoryResetProtectionPolicy[size];
+ }
+ };
+
+ /**
+ * Restore a previously saved FactoryResetProtectionPolicy from XML.
+ * <p>
+ * No validation is required on the reconstructed policy since the XML was previously
+ * created by the system server from a validated policy.
+ * @hide
+ */
+ @Nullable
+ public static FactoryResetProtectionPolicy readFromXml(@NonNull XmlPullParser parser) {
+ try {
+ boolean factoryResetProtectionDisabled = Boolean.parseBoolean(
+ parser.getAttributeValue(null, KEY_FACTORY_RESET_PROTECTION_DISABLED));
+
+ List<String> factoryResetProtectionAccounts = new ArrayList<>();
+ int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != END_DOCUMENT
+ && (type != END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == END_TAG || type == TEXT) {
+ continue;
+ }
+ if (!parser.getName().equals(KEY_FACTORY_RESET_PROTECTION_ACCOUNT)) {
+ continue;
+ }
+ factoryResetProtectionAccounts.add(
+ parser.getAttributeValue(null, ATTR_VALUE));
+ }
+
+ return new FactoryResetProtectionPolicy(factoryResetProtectionAccounts,
+ factoryResetProtectionDisabled);
+ } catch (XmlPullParserException | IOException e) {
+ Log.w(LOG_TAG, "Reading from xml failed", e);
+ }
+ return null;
+ }
+
+ /**
+ * @hide
+ */
+ public void writeToXml(@NonNull XmlSerializer out) throws IOException {
+ out.attribute(null, KEY_FACTORY_RESET_PROTECTION_DISABLED,
+ Boolean.toString(mFactoryResetProtectionDisabled));
+ for (String account : mFactoryResetProtectionAccounts) {
+ out.startTag(null, KEY_FACTORY_RESET_PROTECTION_ACCOUNT);
+ out.attribute(null, ATTR_VALUE, account);
+ out.endTag(null, KEY_FACTORY_RESET_PROTECTION_ACCOUNT);
+ }
+ }
+
+}
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 9c82ff6..21c9eb5 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -24,6 +24,7 @@
import android.app.admin.SystemUpdateInfo;
import android.app.admin.SystemUpdatePolicy;
import android.app.admin.PasswordMetrics;
+import android.app.admin.FactoryResetProtectionPolicy;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
@@ -104,6 +105,9 @@
void wipeDataWithReason(int flags, String wipeReasonForUser, boolean parent);
+ void setFactoryResetProtectionPolicy(in ComponentName who, in FactoryResetProtectionPolicy policy);
+ FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(in ComponentName who);
+
ComponentName setGlobalProxy(in ComponentName admin, String proxySpec, String exclusionList);
ComponentName getGlobalProxyAdmin(int userHandle);
void setRecommendedGlobalProxy(in ComponentName admin, in ProxyInfo proxyInfo);
@@ -156,6 +160,7 @@
void setProfileName(in ComponentName who, String profileName);
void clearProfileOwner(in ComponentName who);
boolean hasUserSetupCompleted();
+ boolean isOrganizationOwnedDeviceWithManagedProfile();
boolean checkDeviceIdentifierAccess(in String packageName, int pid, int uid);
@@ -444,10 +449,16 @@
void setCrossProfilePackages(in ComponentName admin, in List<String> packageNames);
List<String> getCrossProfilePackages(in ComponentName admin);
+ List<String> getAllCrossProfilePackages();
+
boolean isManagedKiosk();
boolean isUnattendedManagedKiosk();
boolean startViewCalendarEventInManagedProfile(String packageName, long eventId, long start, long end, boolean allDay, int flags);
boolean setKeyGrantForApp(in ComponentName admin, String callerPackage, String alias, String packageName, boolean hasGrant);
+
+ void setProtectedPackages(in ComponentName admin, in List<String> packages);
+
+ List<String> getProtectedPackages(in ComponentName admin);
}
diff --git a/core/java/android/app/admin/SecurityLog.java b/core/java/android/app/admin/SecurityLog.java
index f0b87a8..91cf120 100644
--- a/core/java/android/app/admin/SecurityLog.java
+++ b/core/java/android/app/admin/SecurityLog.java
@@ -81,6 +81,7 @@
TAG_CRYPTO_SELF_TEST_COMPLETED,
TAG_KEY_INTEGRITY_VIOLATION,
TAG_CERT_VALIDATION_FAILURE,
+ TAG_CAMERA_POLICY_SET
})
public @interface SecurityLogTag {}
@@ -433,6 +434,19 @@
SecurityLogTags.SECURITY_CERT_VALIDATION_FAILURE;
/**
+ * Indicates that the admin has set policy to disable camera.
+ * The log entry contains the following information about the event, encapsulated in an
+ * {@link Object} array and accessible via {@link SecurityEvent#getData()}:
+ * <li> [0] admin package name ({@code String})
+ * <li> [1] admin user ID ({@code Integer})
+ * <li> [2] target user ID ({@code Integer})
+ * <li> [3] whether the camera is disabled or not ({@code Integer}, 1 if it's disabled,
+ * 0 if enabled)
+ */
+ public static final int TAG_CAMERA_POLICY_SET =
+ SecurityLogTags.SECURITY_CAMERA_POLICY_SET;
+
+ /**
* Event severity level indicating that the event corresponds to normal workflow.
*/
public static final int LEVEL_INFO = 1;
@@ -561,6 +575,7 @@
case TAG_MAX_PASSWORD_ATTEMPTS_SET:
case TAG_USER_RESTRICTION_ADDED:
case TAG_USER_RESTRICTION_REMOVED:
+ case TAG_CAMERA_POLICY_SET:
return LEVEL_INFO;
case TAG_CERT_AUTHORITY_REMOVED:
case TAG_CRYPTO_SELF_TEST_COMPLETED:
diff --git a/core/java/android/app/admin/SecurityLogTags.logtags b/core/java/android/app/admin/SecurityLogTags.logtags
index fe2519d..4e67fe2 100644
--- a/core/java/android/app/admin/SecurityLogTags.logtags
+++ b/core/java/android/app/admin/SecurityLogTags.logtags
@@ -38,3 +38,4 @@
210031 security_crypto_self_test_completed (success|1)
210032 security_key_integrity_violation (key_id|3),(uid|1)
210033 security_cert_validation_failure (reason|3)
+210034 security_camera_policy_set (package|3),(admin_user|1),(target_user|1),(disabled|1)
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
index c8f2ff3..567eb4a 100644
--- a/core/java/android/app/backup/BackupTransport.java
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -85,6 +85,15 @@
public static final int FLAG_NON_INCREMENTAL = 1 << 2;
/**
+ * For key value backup, indicates that the backup contains no new data since the last backup
+ * attempt completed without any errors. The transport should use this to record that
+ * a successful backup attempt has been completed but no backup data has been changed.
+ *
+ * @see #performBackup(PackageInfo, ParcelFileDescriptor, int)
+ */
+ public static final int FLAG_DATA_NOT_CHANGED = 1 << 3;
+
+ /**
* Used as a boolean extra in the binding intent of transports. We pass {@code true} to
* notify transports that the current connection is used for registering the transport.
*/
@@ -302,7 +311,8 @@
* BackupService.doBackup() method. This may be a pipe rather than a file on
* persistent media, so it may not be seekable.
* @param flags a combination of {@link BackupTransport#FLAG_USER_INITIATED}, {@link
- * BackupTransport#FLAG_NON_INCREMENTAL}, {@link BackupTransport#FLAG_INCREMENTAL}, or 0.
+ * BackupTransport#FLAG_NON_INCREMENTAL}, {@link BackupTransport#FLAG_INCREMENTAL},
+ * {@link BackupTransport#FLAG_DATA_NOT_CHANGED},or 0.
* @return one of {@link BackupTransport#TRANSPORT_OK} (OK so far),
* {@link BackupTransport#TRANSPORT_PACKAGE_REJECTED} (to suppress backup of this
* specific package, but allow others to proceed),
diff --git a/core/java/android/app/role/RoleManager.java b/core/java/android/app/role/RoleManager.java
index 61eeacc..ea66fd47 100644
--- a/core/java/android/app/role/RoleManager.java
+++ b/core/java/android/app/role/RoleManager.java
@@ -630,9 +630,12 @@
* {@link Manifest.permission#OBSERVE_ROLE_HOLDERS}, as required by
* {@link android.provider.Telephony.Sms#getDefaultSmsPackage(Context)}
*
+ * @param userId The user ID to get the default SMS package for.
+ * @return the package name of the default SMS app, or {@code null} if not configured.
* @hide
*/
@Nullable
+ @SystemApi
public String getDefaultSmsPackage(@UserIdInt int userId) {
try {
return mService.getDefaultSmsPackage(userId);
diff --git a/core/java/android/app/servertransaction/StartActivityItem.java b/core/java/android/app/servertransaction/StartActivityItem.java
new file mode 100644
index 0000000..4fbe02b
--- /dev/null
+++ b/core/java/android/app/servertransaction/StartActivityItem.java
@@ -0,0 +1,114 @@
+/*
+ * 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.servertransaction;
+
+import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
+
+import android.app.ClientTransactionHandler;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Trace;
+
+/**
+ * Request to move an activity to started and visible state.
+ * @hide
+ */
+public class StartActivityItem extends ActivityLifecycleItem {
+
+ private static final String TAG = "StartActivityItem";
+
+ @Override
+ public void execute(ClientTransactionHandler client, IBinder token,
+ PendingTransactionActions pendingActions) {
+ Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "startActivityItem");
+ client.handleStartActivity(token, pendingActions);
+ Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+ }
+
+ @Override
+ public int getTargetState() {
+ return ON_START;
+ }
+
+
+ // ObjectPoolItem implementation
+
+ private StartActivityItem() {}
+
+ /** Obtain an instance initialized with provided params. */
+ public static StartActivityItem obtain() {
+ StartActivityItem instance = ObjectPool.obtain(StartActivityItem.class);
+ if (instance == null) {
+ instance = new StartActivityItem();
+ }
+
+ return instance;
+ }
+
+ @Override
+ public void recycle() {
+ super.recycle();
+ ObjectPool.recycle(this);
+ }
+
+
+ // Parcelable implementation
+
+ /** Write to Parcel. */
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ // Empty
+ }
+
+ /** Read from Parcel. */
+ private StartActivityItem(Parcel in) {
+ // Empty
+ }
+
+ public static final @android.annotation.NonNull Creator<StartActivityItem> CREATOR =
+ new Creator<StartActivityItem>() {
+ public StartActivityItem createFromParcel(Parcel in) {
+ return new StartActivityItem(in);
+ }
+
+ public StartActivityItem[] newArray(int size) {
+ return new StartActivityItem[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return 17;
+ }
+
+ @Override
+ public String toString() {
+ return "StartActivityItem{}";
+ }
+}
+
diff --git a/core/java/android/app/servertransaction/StopActivityItem.java b/core/java/android/app/servertransaction/StopActivityItem.java
index 63efa6f..8668bd4 100644
--- a/core/java/android/app/servertransaction/StopActivityItem.java
+++ b/core/java/android/app/servertransaction/StopActivityItem.java
@@ -31,14 +31,13 @@
private static final String TAG = "StopActivityItem";
- private boolean mShowWindow;
private int mConfigChanges;
@Override
public void execute(ClientTransactionHandler client, IBinder token,
PendingTransactionActions pendingActions) {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
- client.handleStopActivity(token, mShowWindow, mConfigChanges, pendingActions,
+ client.handleStopActivity(token, mConfigChanges, pendingActions,
true /* finalStateRequest */, "STOP_ACTIVITY_ITEM");
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
@@ -59,13 +58,15 @@
private StopActivityItem() {}
- /** Obtain an instance initialized with provided params. */
- public static StopActivityItem obtain(boolean showWindow, int configChanges) {
+ /**
+ * Obtain an instance initialized with provided params.
+ * @param configChanges Configuration pieces that changed.
+ */
+ public static StopActivityItem obtain(int configChanges) {
StopActivityItem instance = ObjectPool.obtain(StopActivityItem.class);
if (instance == null) {
instance = new StopActivityItem();
}
- instance.mShowWindow = showWindow;
instance.mConfigChanges = configChanges;
return instance;
@@ -74,7 +75,6 @@
@Override
public void recycle() {
super.recycle();
- mShowWindow = false;
mConfigChanges = 0;
ObjectPool.recycle(this);
}
@@ -85,13 +85,11 @@
/** Write to Parcel. */
@Override
public void writeToParcel(Parcel dest, int flags) {
- dest.writeBoolean(mShowWindow);
dest.writeInt(mConfigChanges);
}
/** Read from Parcel. */
private StopActivityItem(Parcel in) {
- mShowWindow = in.readBoolean();
mConfigChanges = in.readInt();
}
@@ -115,20 +113,18 @@
return false;
}
final StopActivityItem other = (StopActivityItem) o;
- return mShowWindow == other.mShowWindow && mConfigChanges == other.mConfigChanges;
+ return mConfigChanges == other.mConfigChanges;
}
@Override
public int hashCode() {
int result = 17;
- result = 31 * result + (mShowWindow ? 1 : 0);
result = 31 * result + mConfigChanges;
return result;
}
@Override
public String toString() {
- return "StopActivityItem{showWindow=" + mShowWindow + ",configChanges=" + mConfigChanges
- + "}";
+ return "StopActivityItem{configChanges=" + mConfigChanges + "}";
}
}
diff --git a/core/java/android/app/servertransaction/TransactionExecutor.java b/core/java/android/app/servertransaction/TransactionExecutor.java
index 20e0da3..17fcda5 100644
--- a/core/java/android/app/servertransaction/TransactionExecutor.java
+++ b/core/java/android/app/servertransaction/TransactionExecutor.java
@@ -218,7 +218,7 @@
null /* customIntent */);
break;
case ON_START:
- mTransactionHandler.handleStartActivity(r, mPendingActions);
+ mTransactionHandler.handleStartActivity(r.token, mPendingActions);
break;
case ON_RESUME:
mTransactionHandler.handleResumeActivity(r.token, false /* finalStateRequest */,
@@ -230,8 +230,8 @@
"LIFECYCLER_PAUSE_ACTIVITY");
break;
case ON_STOP:
- mTransactionHandler.handleStopActivity(r.token, false /* show */,
- 0 /* configChanges */, mPendingActions, false /* finalStateRequest */,
+ mTransactionHandler.handleStopActivity(r.token, 0 /* configChanges */,
+ mPendingActions, false /* finalStateRequest */,
"LIFECYCLER_STOP_ACTIVITY");
break;
case ON_DESTROY:
diff --git a/core/java/android/app/servertransaction/TransactionExecutorHelper.java b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
index 0ea8c3c..6df92a7 100644
--- a/core/java/android/app/servertransaction/TransactionExecutorHelper.java
+++ b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
@@ -183,8 +183,7 @@
lifecycleItem = PauseActivityItem.obtain();
break;
case ON_STOP:
- lifecycleItem = StopActivityItem.obtain(r.isVisibleFromServer(),
- 0 /* configChanges */);
+ lifecycleItem = StopActivityItem.obtain(0 /* configChanges */);
break;
default:
lifecycleItem = ResumeActivityItem.obtain(false /* isForward */);
diff --git a/core/java/android/app/servertransaction/WindowVisibilityItem.java b/core/java/android/app/servertransaction/WindowVisibilityItem.java
deleted file mode 100644
index 115d1ec..0000000
--- a/core/java/android/app/servertransaction/WindowVisibilityItem.java
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright 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.app.servertransaction;
-
-import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
-
-import android.app.ClientTransactionHandler;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.os.Trace;
-
-/**
- * Window visibility change message.
- * @hide
- */
-public class WindowVisibilityItem extends ClientTransactionItem {
-
- private boolean mShowWindow;
-
- @Override
- public void execute(ClientTransactionHandler client, IBinder token,
- PendingTransactionActions pendingActions) {
- Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER,
- mShowWindow ? "activityShowWindow" : "activityHideWindow");
- client.handleWindowVisibility(token, mShowWindow);
- Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
- }
-
-
- // ObjectPoolItem implementation
-
- private WindowVisibilityItem() {}
-
- /** Obtain an instance initialized with provided params. */
- public static WindowVisibilityItem obtain(boolean showWindow) {
- WindowVisibilityItem instance = ObjectPool.obtain(WindowVisibilityItem.class);
- if (instance == null) {
- instance = new WindowVisibilityItem();
- }
- instance.mShowWindow = showWindow;
-
- return instance;
- }
-
- @Override
- public void recycle() {
- mShowWindow = false;
- ObjectPool.recycle(this);
- }
-
-
- // Parcelable implementation
-
- /** Write to Parcel. */
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeBoolean(mShowWindow);
- }
-
- /** Read from Parcel. */
- private WindowVisibilityItem(Parcel in) {
- mShowWindow = in.readBoolean();
- }
-
- public static final @android.annotation.NonNull Creator<WindowVisibilityItem> CREATOR =
- new Creator<WindowVisibilityItem>() {
- public WindowVisibilityItem createFromParcel(Parcel in) {
- return new WindowVisibilityItem(in);
- }
-
- public WindowVisibilityItem[] newArray(int size) {
- return new WindowVisibilityItem[size];
- }
- };
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- final WindowVisibilityItem other = (WindowVisibilityItem) o;
- return mShowWindow == other.mShowWindow;
- }
-
- @Override
- public int hashCode() {
- return 17 + 31 * (mShowWindow ? 1 : 0);
- }
-
- @Override
- public String toString() {
- return "WindowVisibilityItem{showWindow=" + mShowWindow + "}";
- }
-}
diff --git a/core/java/android/app/timedetector/ITimeDetectorService.aidl b/core/java/android/app/timedetector/ITimeDetectorService.aidl
index 9877fc7..de8f470 100644
--- a/core/java/android/app/timedetector/ITimeDetectorService.aidl
+++ b/core/java/android/app/timedetector/ITimeDetectorService.aidl
@@ -17,6 +17,7 @@
package android.app.timedetector;
import android.app.timedetector.ManualTimeSuggestion;
+import android.app.timedetector.NetworkTimeSuggestion;
import android.app.timedetector.PhoneTimeSuggestion;
/**
@@ -35,4 +36,5 @@
interface ITimeDetectorService {
void suggestPhoneTime(in PhoneTimeSuggestion timeSuggestion);
void suggestManualTime(in ManualTimeSuggestion timeSuggestion);
+ void suggestNetworkTime(in NetworkTimeSuggestion timeSuggestion);
}
diff --git a/core/java/android/app/timedetector/ManualTimeSuggestion.java b/core/java/android/app/timedetector/ManualTimeSuggestion.java
index 55f92be..50de7385 100644
--- a/core/java/android/app/timedetector/ManualTimeSuggestion.java
+++ b/core/java/android/app/timedetector/ManualTimeSuggestion.java
@@ -20,7 +20,7 @@
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
-import android.util.TimestampedValue;
+import android.os.TimestampedValue;
import java.util.ArrayList;
import java.util.Arrays;
diff --git a/media/java/android/media/RouteSessionInfo.aidl b/core/java/android/app/timedetector/NetworkTimeSuggestion.aidl
similarity index 81%
copy from media/java/android/media/RouteSessionInfo.aidl
copy to core/java/android/app/timedetector/NetworkTimeSuggestion.aidl
index fb5d836..731c907 100644
--- a/media/java/android/media/RouteSessionInfo.aidl
+++ b/core/java/android/app/timedetector/NetworkTimeSuggestion.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 The Android Open Source Project
+ * 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.
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.media;
+package android.app.timedetector;
-parcelable RouteSessionInfo;
+parcelable NetworkTimeSuggestion;
diff --git a/core/java/android/app/timedetector/NetworkTimeSuggestion.java b/core/java/android/app/timedetector/NetworkTimeSuggestion.java
new file mode 100644
index 0000000..17e9c5a
--- /dev/null
+++ b/core/java/android/app/timedetector/NetworkTimeSuggestion.java
@@ -0,0 +1,129 @@
+/*
+ * 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.timedetector;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.TimestampedValue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A time signal from a network time source like NTP. The value consists of the number of
+ * milliseconds elapsed since 1/1/1970 00:00:00 UTC and the time according to the elapsed realtime
+ * clock when that number was established. The elapsed realtime clock is considered accurate but
+ * volatile, so time signals must not be persisted across device resets.
+ *
+ * @hide
+ */
+public final class NetworkTimeSuggestion implements Parcelable {
+
+ public static final @NonNull Creator<NetworkTimeSuggestion> CREATOR =
+ new Creator<NetworkTimeSuggestion>() {
+ public NetworkTimeSuggestion createFromParcel(Parcel in) {
+ return NetworkTimeSuggestion.createFromParcel(in);
+ }
+
+ public NetworkTimeSuggestion[] newArray(int size) {
+ return new NetworkTimeSuggestion[size];
+ }
+ };
+
+ @NonNull
+ private final TimestampedValue<Long> mUtcTime;
+ @Nullable
+ private ArrayList<String> mDebugInfo;
+
+ public NetworkTimeSuggestion(@NonNull TimestampedValue<Long> utcTime) {
+ mUtcTime = Objects.requireNonNull(utcTime);
+ Objects.requireNonNull(utcTime.getValue());
+ }
+
+ private static NetworkTimeSuggestion createFromParcel(Parcel in) {
+ TimestampedValue<Long> utcTime = in.readParcelable(null /* classLoader */);
+ NetworkTimeSuggestion suggestion = new NetworkTimeSuggestion(utcTime);
+ @SuppressWarnings("unchecked")
+ ArrayList<String> debugInfo = (ArrayList<String>) in.readArrayList(null /* classLoader */);
+ suggestion.mDebugInfo = debugInfo;
+ return suggestion;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeParcelable(mUtcTime, 0);
+ dest.writeList(mDebugInfo);
+ }
+
+ @NonNull
+ public TimestampedValue<Long> getUtcTime() {
+ return mUtcTime;
+ }
+
+ @NonNull
+ public List<String> getDebugInfo() {
+ return mDebugInfo == null
+ ? Collections.emptyList() : Collections.unmodifiableList(mDebugInfo);
+ }
+
+ /**
+ * Associates information with the instance that can be useful for debugging / logging. The
+ * information is present in {@link #toString()} but is not considered for
+ * {@link #equals(Object)} and {@link #hashCode()}.
+ */
+ public void addDebugInfo(String... debugInfos) {
+ if (mDebugInfo == null) {
+ mDebugInfo = new ArrayList<>();
+ }
+ mDebugInfo.addAll(Arrays.asList(debugInfos));
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ NetworkTimeSuggestion that = (NetworkTimeSuggestion) o;
+ return Objects.equals(mUtcTime, that.mUtcTime);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mUtcTime);
+ }
+
+ @Override
+ public String toString() {
+ return "NetworkTimeSuggestion{"
+ + "mUtcTime=" + mUtcTime
+ + ", mDebugInfo=" + mDebugInfo
+ + '}';
+ }
+}
diff --git a/core/java/android/app/timedetector/PhoneTimeSuggestion.java b/core/java/android/app/timedetector/PhoneTimeSuggestion.java
index 4a89a12..bd649f8 100644
--- a/core/java/android/app/timedetector/PhoneTimeSuggestion.java
+++ b/core/java/android/app/timedetector/PhoneTimeSuggestion.java
@@ -18,9 +18,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
-import android.util.TimestampedValue;
+import android.os.TimestampedValue;
import java.util.ArrayList;
import java.util.Collections;
@@ -28,17 +29,23 @@
import java.util.Objects;
/**
- * A time signal from a telephony source. The value can be {@code null} to indicate that the
- * telephony source has entered an "un-opinionated" state and any previously sent suggestions are
- * being withdrawn. When not {@code null}, the value consists of the number of milliseconds elapsed
- * since 1/1/1970 00:00:00 UTC and the time according to the elapsed realtime clock when that number
- * was established. The elapsed realtime clock is considered accurate but volatile, so time signals
- * must not be persisted across device resets.
+ * A time suggestion from an identified telephony source. e.g. from NITZ information from a specific
+ * radio.
+ *
+ * <p>The time value can be {@code null} to indicate that the telephony source has entered an
+ * "un-opinionated" state and any previous suggestions from the source are being withdrawn. When not
+ * {@code null}, the value consists of the number of milliseconds elapsed since 1/1/1970 00:00:00
+ * UTC and the time according to the elapsed realtime clock when that number was established. The
+ * elapsed realtime clock is considered accurate but volatile, so time suggestions must not be
+ * persisted across device resets.
*
* @hide
*/
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public final class PhoneTimeSuggestion implements Parcelable {
+ /** @hide */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final @NonNull Parcelable.Creator<PhoneTimeSuggestion> CREATOR =
new Parcelable.Creator<PhoneTimeSuggestion>() {
public PhoneTimeSuggestion createFromParcel(Parcel in) {
@@ -85,15 +92,27 @@
dest.writeList(mDebugInfo);
}
+ /**
+ * Returns an identifier for the source of this suggestion. When a device has several "phones",
+ * i.e. sim slots or equivalent, it is used to identify which one.
+ */
public int getPhoneId() {
return mPhoneId;
}
+ /**
+ * Returns the suggestion. {@code null} means that the caller is no longer sure what time it
+ * is.
+ */
@Nullable
public TimestampedValue<Long> getUtcTime() {
return mUtcTime;
}
+ /**
+ * Returns debug metadata for the suggestion. The information is present in {@link #toString()}
+ * but is not considered for {@link #equals(Object)} and {@link #hashCode()}.
+ */
@NonNull
public List<String> getDebugInfo() {
return mDebugInfo == null
@@ -105,7 +124,7 @@
* information is present in {@link #toString()} but is not considered for
* {@link #equals(Object)} and {@link #hashCode()}.
*/
- public void addDebugInfo(String debugInfo) {
+ public void addDebugInfo(@NonNull String debugInfo) {
if (mDebugInfo == null) {
mDebugInfo = new ArrayList<>();
}
@@ -156,16 +175,19 @@
*
* @hide
*/
- public static class Builder {
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final class Builder {
private final int mPhoneId;
- private TimestampedValue<Long> mUtcTime;
- private List<String> mDebugInfo;
+ @Nullable private TimestampedValue<Long> mUtcTime;
+ @Nullable private List<String> mDebugInfo;
+ /** Creates a builder with the specified {@code phoneId}. */
public Builder(int phoneId) {
mPhoneId = phoneId;
}
/** Returns the builder for call chaining. */
+ @NonNull
public Builder setUtcTime(@Nullable TimestampedValue<Long> utcTime) {
if (utcTime != null) {
// utcTime can be null, but the value it holds cannot.
@@ -177,6 +199,7 @@
}
/** Returns the builder for call chaining. */
+ @NonNull
public Builder addDebugInfo(@NonNull String debugInfo) {
if (mDebugInfo == null) {
mDebugInfo = new ArrayList<>();
@@ -186,6 +209,7 @@
}
/** Returns the {@link PhoneTimeSuggestion}. */
+ @NonNull
public PhoneTimeSuggestion build() {
return new PhoneTimeSuggestion(this);
}
diff --git a/core/java/android/app/timedetector/TimeDetector.java b/core/java/android/app/timedetector/TimeDetector.java
index 48d5cd2..7c29f01 100644
--- a/core/java/android/app/timedetector/TimeDetector.java
+++ b/core/java/android/app/timedetector/TimeDetector.java
@@ -18,19 +18,23 @@
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.content.Context;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.os.SystemClock;
+import android.os.TimestampedValue;
import android.util.Log;
-import android.util.TimestampedValue;
/**
* The interface through which system components can send signals to the TimeDetectorService.
+ *
+ * <p>This class is marked non-final for mockito.
* @hide
*/
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@SystemService(Context.TIME_DETECTOR_SERVICE)
public class TimeDetector {
private static final String TAG = "timedetector.TimeDetector";
@@ -38,6 +42,7 @@
private final ITimeDetectorService mITimeDetectorService;
+ /** @hide */
public TimeDetector() throws ServiceNotFoundException {
mITimeDetectorService = ITimeDetectorService.Stub.asInterface(
ServiceManager.getServiceOrThrow(Context.TIME_DETECTOR_SERVICE));
@@ -48,7 +53,7 @@
* signal if better signals are available such as those that come from more reliable sources or
* were determined more recently.
*/
- @RequiresPermission(android.Manifest.permission.SET_TIME)
+ @RequiresPermission(android.Manifest.permission.SUGGEST_PHONE_TIME_AND_ZONE)
public void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion) {
if (DEBUG) {
Log.d(TAG, "suggestPhoneTime called: " + timeSuggestion);
@@ -62,8 +67,10 @@
/**
* Suggests the user's manually entered current time to the detector.
+ *
+ * @hide
*/
- @RequiresPermission(android.Manifest.permission.SET_TIME)
+ @RequiresPermission(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE)
public void suggestManualTime(@NonNull ManualTimeSuggestion timeSuggestion) {
if (DEBUG) {
Log.d(TAG, "suggestManualTime called: " + timeSuggestion);
@@ -77,6 +84,8 @@
/**
* A shared utility method to create a {@link ManualTimeSuggestion}.
+ *
+ * @hide
*/
public static ManualTimeSuggestion createManualTimeSuggestion(long when, String why) {
TimestampedValue<Long> utcTime =
@@ -85,4 +94,21 @@
manualTimeSuggestion.addDebugInfo(why);
return manualTimeSuggestion;
}
+
+ /**
+ * Suggests the time according to a network time source like NTP.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.SET_TIME)
+ public void suggestNetworkTime(NetworkTimeSuggestion timeSuggestion) {
+ if (DEBUG) {
+ Log.d(TAG, "suggestNetworkTime called: " + timeSuggestion);
+ }
+ try {
+ mITimeDetectorService.suggestNetworkTime(timeSuggestion);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java b/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java
index e8162488..d71ffcb 100644
--- a/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java
+++ b/core/java/android/app/timezonedetector/PhoneTimeZoneSuggestion.java
@@ -19,6 +19,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
@@ -30,12 +31,27 @@
import java.util.Objects;
/**
- * A suggested time zone from a Phone-based signal, e.g. from MCC and NITZ information.
+ * A time zone suggestion from an identified telephony source, e.g. from MCC and NITZ information
+ * associated with a specific radio.
+ *
+ * <p>The time zone ID can be {@code null} to indicate that the telephony source has entered an
+ * "un-opinionated" state and any previous suggestions from that source are being withdrawn.
+ * When not {@code null}, the value consists of a suggested time zone ID and metadata that can be
+ * used to judge quality / certainty of the suggestion.
+ *
+ * <p>{@code matchType} must be set to {@link #MATCH_TYPE_NA} when {@code zoneId} is {@code null},
+ * and one of the other {@code MATCH_TYPE_} values when it is not {@code null}.
+ *
+ * <p>{@code quality} must be set to {@link #QUALITY_NA} when {@code zoneId} is {@code null},
+ * and one of the other {@code QUALITY_} values when it is not {@code null}.
*
* @hide
*/
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public final class PhoneTimeZoneSuggestion implements Parcelable {
+ /** @hide */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@NonNull
public static final Creator<PhoneTimeZoneSuggestion> CREATOR =
new Creator<PhoneTimeZoneSuggestion>() {
@@ -58,6 +74,7 @@
return new Builder(phoneId).addDebugInfo(debugInfo).build();
}
+ /** @hide */
@IntDef({ MATCH_TYPE_NA, MATCH_TYPE_NETWORK_COUNTRY_ONLY, MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET,
MATCH_TYPE_EMULATOR_ZONE_ID, MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY })
@Retention(RetentionPolicy.SOURCE)
@@ -90,6 +107,7 @@
*/
public static final int MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY = 5;
+ /** @hide */
@IntDef({ QUALITY_NA, QUALITY_SINGLE_ZONE, QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET,
QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS })
@Retention(RetentionPolicy.SOURCE)
@@ -115,7 +133,7 @@
/**
* The ID of the phone this suggestion is associated with. For multiple-sim devices this
- * helps to establish origin so filtering / stickiness can be implemented.
+ * helps to establish source so filtering / stickiness can be implemented.
*/
private final int mPhoneId;
@@ -123,6 +141,7 @@
* The suggestion. {@code null} means there is no current suggestion and any previous suggestion
* should be forgotten.
*/
+ @Nullable
private final String mZoneId;
/**
@@ -139,9 +158,10 @@
private final int mQuality;
/**
- * Free-form debug information about how the signal was derived. Used for debug only,
+ * Free-form debug information about how the suggestion was derived. Used for debug only,
* intentionally not used in equals(), etc.
*/
+ @Nullable
private List<String> mDebugInfo;
private PhoneTimeZoneSuggestion(Builder builder) {
@@ -182,25 +202,47 @@
return 0;
}
+ /**
+ * Returns an identifier for the source of this suggestion. When a device has several "phones",
+ * i.e. sim slots or equivalent, it is used to identify which one.
+ */
public int getPhoneId() {
return mPhoneId;
}
+ /**
+ * Returns the suggested time zone Olson ID, e.g. "America/Los_Angeles". {@code null} means that
+ * the caller is no longer sure what the current time zone is. See
+ * {@link PhoneTimeZoneSuggestion} for the associated {@code matchType} / {@code quality} rules.
+ */
@Nullable
public String getZoneId() {
return mZoneId;
}
+ /**
+ * Returns information about how the suggestion was determined which could be used to rank
+ * suggestions when several are available from different sources. See
+ * {@link PhoneTimeZoneSuggestion} for the associated rules.
+ */
@MatchType
public int getMatchType() {
return mMatchType;
}
+ /**
+ * Returns information about the likelihood of the suggested zone being correct. See
+ * {@link PhoneTimeZoneSuggestion} for the associated rules.
+ */
@Quality
public int getQuality() {
return mQuality;
}
+ /**
+ * Returns debug metadata for the suggestion. The information is present in {@link #toString()}
+ * but is not considered for {@link #equals(Object)} and {@link #hashCode()}.
+ */
@NonNull
public List<String> getDebugInfo() {
return mDebugInfo == null
@@ -267,36 +309,43 @@
*
* @hide
*/
- public static class Builder {
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final class Builder {
private final int mPhoneId;
- private String mZoneId;
+ @Nullable private String mZoneId;
@MatchType private int mMatchType;
@Quality private int mQuality;
- private List<String> mDebugInfo;
+ @Nullable private List<String> mDebugInfo;
public Builder(int phoneId) {
mPhoneId = phoneId;
}
- /** Returns the builder for call chaining. */
- public Builder setZoneId(String zoneId) {
+ /**
+ * Returns the builder for call chaining.
+ */
+ @NonNull
+ public Builder setZoneId(@Nullable String zoneId) {
mZoneId = zoneId;
return this;
}
/** Returns the builder for call chaining. */
+ @NonNull
public Builder setMatchType(@MatchType int matchType) {
mMatchType = matchType;
return this;
}
/** Returns the builder for call chaining. */
+ @NonNull
public Builder setQuality(@Quality int quality) {
mQuality = quality;
return this;
}
/** Returns the builder for call chaining. */
+ @NonNull
public Builder addDebugInfo(@NonNull String debugInfo) {
if (mDebugInfo == null) {
mDebugInfo = new ArrayList<>();
@@ -333,6 +382,7 @@
}
/** Returns the {@link PhoneTimeZoneSuggestion}. */
+ @NonNull
public PhoneTimeZoneSuggestion build() {
validate();
return new PhoneTimeZoneSuggestion(this);
diff --git a/core/java/android/app/timezonedetector/TimeZoneDetector.java b/core/java/android/app/timezonedetector/TimeZoneDetector.java
index 387a36b..5b5f311 100644
--- a/core/java/android/app/timezonedetector/TimeZoneDetector.java
+++ b/core/java/android/app/timezonedetector/TimeZoneDetector.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.content.Context;
import android.os.RemoteException;
@@ -28,8 +29,10 @@
/**
* The interface through which system components can send signals to the TimeZoneDetectorService.
*
+ * <p>This class is non-final for mockito.
* @hide
*/
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@SystemService(Context.TIME_ZONE_DETECTOR_SERVICE)
public class TimeZoneDetector {
private static final String TAG = "timezonedetector.TimeZoneDetector";
@@ -37,6 +40,7 @@
private final ITimeZoneDetectorService mITimeZoneDetectorService;
+ /** @hide */
public TimeZoneDetector() throws ServiceNotFoundException {
mITimeZoneDetectorService = ITimeZoneDetectorService.Stub.asInterface(
ServiceManager.getServiceOrThrow(Context.TIME_ZONE_DETECTOR_SERVICE));
@@ -46,8 +50,11 @@
* Suggests the current time zone, determined using telephony signals, to the detector. The
* detector may ignore the signal based on system settings, whether better information is
* available, and so on.
+ *
+ * @hide
*/
- @RequiresPermission(android.Manifest.permission.SET_TIME_ZONE)
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ @RequiresPermission(android.Manifest.permission.SUGGEST_PHONE_TIME_AND_ZONE)
public void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion timeZoneSuggestion) {
if (DEBUG) {
Log.d(TAG, "suggestPhoneTimeZone called: " + timeZoneSuggestion);
@@ -62,8 +69,10 @@
/**
* Suggests the current time zone, determined for the user's manually information, to the
* detector. The detector may ignore the signal based on system settings.
+ *
+ * @hide
*/
- @RequiresPermission(android.Manifest.permission.SET_TIME_ZONE)
+ @RequiresPermission(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE)
public void suggestManualTimeZone(@NonNull ManualTimeZoneSuggestion timeZoneSuggestion) {
if (DEBUG) {
Log.d(TAG, "suggestManualTimeZone called: " + timeZoneSuggestion);
@@ -77,6 +86,8 @@
/**
* A shared utility method to create a {@link ManualTimeZoneSuggestion}.
+ *
+ * @hide
*/
public static ManualTimeZoneSuggestion createManualTimeZoneSuggestion(String tzId, String why) {
ManualTimeZoneSuggestion suggestion = new ManualTimeZoneSuggestion(tzId);
diff --git a/core/java/android/app/usage/NetworkStatsManager.java b/core/java/android/app/usage/NetworkStatsManager.java
index 4346d97..9c4a8f4 100644
--- a/core/java/android/app/usage/NetworkStatsManager.java
+++ b/core/java/android/app/usage/NetworkStatsManager.java
@@ -16,7 +16,10 @@
package android.app.usage;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.app.usage.NetworkStats.Bucket;
@@ -27,6 +30,9 @@
import android.net.INetworkStatsService;
import android.net.NetworkIdentity;
import android.net.NetworkTemplate;
+import android.net.netstats.provider.AbstractNetworkStatsProvider;
+import android.net.netstats.provider.NetworkStatsProviderCallback;
+import android.net.netstats.provider.NetworkStatsProviderWrapper;
import android.os.Binder;
import android.os.Handler;
import android.os.Looper;
@@ -519,6 +525,34 @@
private DataUsageRequest request;
}
+ /**
+ * Registers a custom provider of {@link android.net.NetworkStats} to combine the network
+ * statistics that cannot be seen by the kernel to system. To unregister, invoke
+ * {@link NetworkStatsProviderCallback#unregister()}.
+ *
+ * @param tag a human readable identifier of the custom network stats provider.
+ * @param provider a custom implementation of {@link AbstractNetworkStatsProvider} that needs to
+ * be registered to the system.
+ * @return a {@link NetworkStatsProviderCallback}, which can be used to report events to the
+ * system.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
+ @NonNull public NetworkStatsProviderCallback registerNetworkStatsProvider(
+ @NonNull String tag,
+ @NonNull AbstractNetworkStatsProvider provider) {
+ try {
+ final NetworkStatsProviderWrapper wrapper = new NetworkStatsProviderWrapper(provider);
+ return new NetworkStatsProviderCallback(
+ mService.registerNetworkStatsProvider(tag, wrapper));
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ // Unreachable code, but compiler doesn't know about it.
+ return null;
+ }
+
private static NetworkTemplate createTemplate(int networkType, String subscriberId) {
final NetworkTemplate template;
switch (networkType) {
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index d840c1c..6ab880d 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -451,21 +451,7 @@
/** @hide */
public Event(Event orig) {
- mPackage = orig.mPackage;
- mClass = orig.mClass;
- mInstanceId = orig.mInstanceId;
- mTaskRootPackage = orig.mTaskRootPackage;
- mTaskRootClass = orig.mTaskRootClass;
- mTimeStamp = orig.mTimeStamp;
- mEventType = orig.mEventType;
- mConfiguration = orig.mConfiguration;
- mShortcutId = orig.mShortcutId;
- mAction = orig.mAction;
- mContentType = orig.mContentType;
- mContentAnnotations = orig.mContentAnnotations;
- mFlags = orig.mFlags;
- mBucketAndReason = orig.mBucketAndReason;
- mNotificationChannelId = orig.mNotificationChannelId;
+ copyFrom(orig);
}
/**
@@ -622,6 +608,24 @@
// which instant apps can't use anyway, so there's no need to hide them.
return ret;
}
+
+ private void copyFrom(Event orig) {
+ mPackage = orig.mPackage;
+ mClass = orig.mClass;
+ mInstanceId = orig.mInstanceId;
+ mTaskRootPackage = orig.mTaskRootPackage;
+ mTaskRootClass = orig.mTaskRootClass;
+ mTimeStamp = orig.mTimeStamp;
+ mEventType = orig.mEventType;
+ mConfiguration = orig.mConfiguration;
+ mShortcutId = orig.mShortcutId;
+ mAction = orig.mAction;
+ mContentType = orig.mContentType;
+ mContentAnnotations = orig.mContentAnnotations;
+ mFlags = orig.mFlags;
+ mBucketAndReason = orig.mBucketAndReason;
+ mNotificationChannelId = orig.mNotificationChannelId;
+ }
}
// Only used when creating the resulting events. Not used for reading/unparceling.
@@ -725,10 +729,14 @@
return false;
}
- readEventFromParcel(mParcel, eventOut);
+ if (mParcel != null) {
+ readEventFromParcel(mParcel, eventOut);
+ } else {
+ eventOut.copyFrom(mEventsToWrite.get(mIndex));
+ }
mIndex++;
- if (mIndex >= mEventCount) {
+ if (mIndex >= mEventCount && mParcel != null) {
mParcel.recycle();
mParcel = null;
}
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index 176a181..a60e591 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -163,7 +163,6 @@
/**
* The app spent sufficient time in the old bucket without any substantial event so it reached
* the timeout threshold to have its bucket lowered.
- *
* @hide
*/
public static final int REASON_MAIN_TIMEOUT = 0x0200;
@@ -173,15 +172,25 @@
*/
public static final int REASON_MAIN_USAGE = 0x0300;
/**
- * Forced by a core UID.
+ * Forced by the user/developer, either explicitly or implicitly through some action. If user
+ * action was not involved and this is purely due to the system,
+ * {@link #REASON_MAIN_FORCED_BY_SYSTEM} should be used instead.
* @hide
*/
- public static final int REASON_MAIN_FORCED = 0x0400;
+ public static final int REASON_MAIN_FORCED_BY_USER = 0x0400;
/**
- * Set by a privileged system app.
+ * Set by a privileged system app. This may be overridden by
+ * {@link #REASON_MAIN_FORCED_BY_SYSTEM} or user action.
* @hide
*/
public static final int REASON_MAIN_PREDICTED = 0x0500;
+ /**
+ * Forced by the system, independent of user action. If user action is involved,
+ * {@link #REASON_MAIN_FORCED_BY_USER} should be used instead. When this is used, only
+ * {@link #REASON_MAIN_FORCED_BY_SYSTEM} or user action can change the bucket.
+ * @hide
+ */
+ public static final int REASON_MAIN_FORCED_BY_SYSTEM = 0x0600;
/** @hide */
public static final int REASON_SUB_MASK = 0x00FF;
@@ -1016,7 +1025,10 @@
case REASON_MAIN_DEFAULT:
sb.append("d");
break;
- case REASON_MAIN_FORCED:
+ case REASON_MAIN_FORCED_BY_SYSTEM:
+ sb.append("s");
+ break;
+ case REASON_MAIN_FORCED_BY_USER:
sb.append("f");
break;
case REASON_MAIN_PREDICTED:
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index b1b6f0d..cb1f055 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -2670,6 +2670,9 @@
} else if (profile == BluetoothProfile.PAN) {
BluetoothPan pan = new BluetoothPan(context, listener);
return true;
+ } else if (profile == BluetoothProfile.PBAP) {
+ BluetoothPbap pbap = new BluetoothPbap(context, listener);
+ return true;
} else if (profile == BluetoothProfile.HEALTH) {
Log.e(TAG, "getProfileProxy(): BluetoothHealth is deprecated");
return false;
@@ -2742,6 +2745,10 @@
BluetoothPan pan = (BluetoothPan) proxy;
pan.close();
break;
+ case BluetoothProfile.PBAP:
+ BluetoothPbap pbap = (BluetoothPbap) proxy;
+ pbap.close();
+ break;
case BluetoothProfile.GATT:
BluetoothGatt gatt = (BluetoothGatt) proxy;
gatt.close();
diff --git a/core/java/android/bluetooth/BluetoothHidDevice.java b/core/java/android/bluetooth/BluetoothHidDevice.java
index e9b0be2..a923be6 100644
--- a/core/java/android/bluetooth/BluetoothHidDevice.java
+++ b/core/java/android/bluetooth/BluetoothHidDevice.java
@@ -16,8 +16,12 @@
package android.bluetooth;
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
import android.content.Context;
import android.os.Binder;
import android.os.IBinder;
@@ -36,6 +40,7 @@
*/
public final class BluetoothHidDevice implements BluetoothProfile {
private static final String TAG = BluetoothHidDevice.class.getSimpleName();
+ private static final boolean DBG = false;
/**
* Intent used to broadcast the change in connection state of the Input Host profile.
@@ -682,4 +687,62 @@
return result;
}
+
+ /**
+ * Connects Hid Device if connectionPolicy is {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}
+ * and disconnects Hid device if connectionPolicy is
+ * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}.
+ *
+ * <p> The device should already be paired.
+ * Connection policy can be one of:
+ * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+ * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+ * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+ *
+ * @param device Paired bluetooth device
+ * @param connectionPolicy determines whether hid device should be connected or disconnected
+ * @return true if hid device is connected or disconnected, false otherwise
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
+ @ConnectionPolicy int connectionPolicy) {
+ log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
+ try {
+ final IBluetoothHidDevice service = getService();
+ if (service != null && isEnabled()
+ && isValidDevice(device)) {
+ if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+ && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ return false;
+ }
+ return service.setConnectionPolicy(device, connectionPolicy);
+ }
+ if (service == null) Log.w(TAG, "Proxy not attached to service");
+ return false;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return false;
+ }
+ }
+
+ private boolean isEnabled() {
+ if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
+ return false;
+ }
+
+ private boolean isValidDevice(BluetoothDevice device) {
+ if (device == null) return false;
+
+ if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
+ return false;
+ }
+
+ private static void log(String msg) {
+ if (DBG) {
+ Log.d(TAG, msg);
+ }
+ }
}
diff --git a/core/java/android/bluetooth/BluetoothHidHost.java b/core/java/android/bluetooth/BluetoothHidHost.java
index 8f5cdf0..c1233b8 100644
--- a/core/java/android/bluetooth/BluetoothHidHost.java
+++ b/core/java/android/bluetooth/BluetoothHidHost.java
@@ -17,9 +17,12 @@
package android.bluetooth;
import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.content.Context;
import android.os.Binder;
@@ -43,6 +46,7 @@
*
* @hide
*/
+@SystemApi
public final class BluetoothHidHost implements BluetoothProfile {
private static final String TAG = "BluetoothHidHost";
private static final boolean DBG = true;
@@ -66,6 +70,7 @@
* <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
* receive.
*/
+ @SuppressLint("ActionValue")
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_CONNECTION_STATE_CHANGED =
"android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED";
@@ -325,7 +330,7 @@
* {@inheritDoc}
*/
@Override
- public List<BluetoothDevice> getConnectedDevices() {
+ public @NonNull List<BluetoothDevice> getConnectedDevices() {
if (VDBG) log("getConnectedDevices()");
final IBluetoothHidHost service = getService();
if (service != null && isEnabled()) {
@@ -342,6 +347,8 @@
/**
* {@inheritDoc}
+ *
+ * @hide
*/
@Override
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
@@ -363,7 +370,7 @@
* {@inheritDoc}
*/
@Override
- public int getConnectionState(BluetoothDevice device) {
+ public int getConnectionState(@Nullable BluetoothDevice device) {
if (VDBG) log("getState(" + device + ")");
final IBluetoothHidHost service = getService();
if (service != null && isEnabled() && isValidDevice(device)) {
@@ -409,7 +416,7 @@
*/
@SystemApi
@RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
- public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
+ public boolean setConnectionPolicy(@Nullable BluetoothDevice device, int connectionPolicy) {
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
final IBluetoothHidHost service = getService();
if (service != null && isEnabled() && isValidDevice(device)) {
@@ -457,7 +464,7 @@
*/
@SystemApi
@RequiresPermission(Manifest.permission.BLUETOOTH)
- public int getConnectionPolicy(BluetoothDevice device) {
+ public int getConnectionPolicy(@Nullable BluetoothDevice device) {
if (VDBG) log("getConnectionPolicy(" + device + ")");
final IBluetoothHidHost service = getService();
if (service != null && isEnabled() && isValidDevice(device)) {
diff --git a/core/java/android/bluetooth/BluetoothMap.java b/core/java/android/bluetooth/BluetoothMap.java
index 917e7fa..4674706 100644
--- a/core/java/android/bluetooth/BluetoothMap.java
+++ b/core/java/android/bluetooth/BluetoothMap.java
@@ -17,7 +17,10 @@
package android.bluetooth;
import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
@@ -35,21 +38,35 @@
*
* @hide
*/
+@SystemApi
public final class BluetoothMap implements BluetoothProfile {
private static final String TAG = "BluetoothMap";
private static final boolean DBG = true;
private static final boolean VDBG = false;
+ /** @hide */
+ @SuppressLint("ActionValue")
+ @SystemApi
public static final String ACTION_CONNECTION_STATE_CHANGED =
"android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED";
- /** There was an error trying to obtain the state */
+ /**
+ * There was an error trying to obtain the state
+ *
+ * @hide
+ */
public static final int STATE_ERROR = -1;
+ /** @hide */
public static final int RESULT_FAILURE = 0;
+ /** @hide */
public static final int RESULT_SUCCESS = 1;
- /** Connection canceled before completion. */
+ /**
+ * Connection canceled before completion.
+ *
+ * @hide
+ */
public static final int RESULT_CANCELED = 2;
private BluetoothAdapter mAdapter;
@@ -71,6 +88,7 @@
mProfileConnector.connect(context, listener);
}
+ @SuppressLint("GenericException")
protected void finalize() throws Throwable {
try {
close();
@@ -84,6 +102,8 @@
* Other public functions of BluetoothMap will return default error
* results once close() has been called. Multiple invocations of close()
* are ok.
+ *
+ * @hide
*/
public synchronized void close() {
mProfileConnector.disconnect();
@@ -98,6 +118,8 @@
*
* @return One of the STATE_ return codes, or STATE_ERROR if this proxy object is currently not
* connected to the Map service.
+ *
+ * @hide
*/
public int getState() {
if (VDBG) log("getState()");
@@ -120,6 +142,8 @@
*
* @return The remote Bluetooth device, or null if not in connected or connecting state, or if
* this proxy object is not connected to the Map service.
+ *
+ * @hide
*/
public BluetoothDevice getClient() {
if (VDBG) log("getClient()");
@@ -141,6 +165,8 @@
* Returns true if the specified Bluetooth device is connected.
* Returns false if not connected, or if this proxy object is not
* currently connected to the Map service.
+ *
+ * @hide
*/
public boolean isConnected(BluetoothDevice device) {
if (VDBG) log("isConnected(" + device + ")");
@@ -161,6 +187,8 @@
/**
* Initiate connection. Initiation of outgoing connections is not
* supported for MAP server.
+ *
+ * @hide
*/
public boolean connect(BluetoothDevice device) {
if (DBG) log("connect(" + device + ")" + "not supported for MAPS");
@@ -172,6 +200,8 @@
*
* @param device Remote Bluetooth Device
* @return false on error, true otherwise
+ *
+ * @hide
*/
@UnsupportedAppUsage
public boolean disconnect(BluetoothDevice device) {
@@ -196,6 +226,8 @@
* devices. It tries to err on the side of false positives.
*
* @return True if this device might support Map.
+ *
+ * @hide
*/
public static boolean doesClassMatchSink(BluetoothClass btClass) {
// TODO optimize the rule
@@ -214,8 +246,11 @@
* Get the list of connected devices. Currently at most one.
*
* @return list of connected devices
+ *
+ * @hide
*/
- public List<BluetoothDevice> getConnectedDevices() {
+ @SystemApi
+ public @NonNull List<BluetoothDevice> getConnectedDevices() {
if (DBG) log("getConnectedDevices()");
final IBluetoothMap service = getService();
if (service != null && isEnabled()) {
@@ -234,6 +269,8 @@
* Get the list of devices matching specified states. Currently at most one.
*
* @return list of matching devices
+ *
+ * @hide
*/
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
if (DBG) log("getDevicesMatchingStates()");
@@ -254,6 +291,8 @@
* Get connection state of device
*
* @return device connection state
+ *
+ * @hide
*/
public int getConnectionState(BluetoothDevice device) {
if (DBG) log("getConnectionState(" + device + ")");
@@ -301,7 +340,7 @@
*/
@SystemApi
@RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
- public boolean setConnectionPolicy(BluetoothDevice device, int connectionPolicy) {
+ public boolean setConnectionPolicy(@Nullable BluetoothDevice device, int connectionPolicy) {
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
final IBluetoothMap service = getService();
if (service != null && isEnabled() && isValidDevice(device)) {
@@ -349,7 +388,7 @@
*/
@SystemApi
@RequiresPermission(Manifest.permission.BLUETOOTH)
- public int getConnectionPolicy(BluetoothDevice device) {
+ public int getConnectionPolicy(@Nullable BluetoothDevice device) {
if (VDBG) log("getConnectionPolicy(" + device + ")");
final IBluetoothMap service = getService();
if (service != null && isEnabled() && isValidDevice(device)) {
diff --git a/core/java/android/bluetooth/BluetoothPan.java b/core/java/android/bluetooth/BluetoothPan.java
index 42f27f2..024bb06 100644
--- a/core/java/android/bluetooth/BluetoothPan.java
+++ b/core/java/android/bluetooth/BluetoothPan.java
@@ -16,9 +16,11 @@
package android.bluetooth;
+import android.Manifest;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SuppressLint;
@@ -256,6 +258,41 @@
}
/**
+ * Set connection policy of the profile
+ *
+ * <p> The device should already be paired.
+ * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
+ * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
+ *
+ * @param device Paired bluetooth device
+ * @param connectionPolicy is the connection policy to set to for this profile
+ * @return true if connectionPolicy is set, false on error
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
+ @ConnectionPolicy int connectionPolicy) {
+ if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
+ try {
+ final IBluetoothPan service = getService();
+ if (service != null && isEnabled()
+ && isValidDevice(device)) {
+ if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+ && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ return false;
+ }
+ return service.setConnectionPolicy(device, connectionPolicy);
+ }
+ if (service == null) Log.w(TAG, "Proxy not attached to service");
+ return false;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return false;
+ }
+ }
+
+ /**
* {@inheritDoc}
*/
@Override
diff --git a/core/java/android/bluetooth/BluetoothPbap.java b/core/java/android/bluetooth/BluetoothPbap.java
index c579fdf..e07ca52 100644
--- a/core/java/android/bluetooth/BluetoothPbap.java
+++ b/core/java/android/bluetooth/BluetoothPbap.java
@@ -16,7 +16,10 @@
package android.bluetooth;
+import android.Manifest;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
@@ -271,6 +274,42 @@
}
/**
+ * Pbap does not store connection policy, so this function only disconnects pbap if
+ * connectionPolicy is {@link #CONNECTION_POLICY_FORBIDDEN}.
+ *
+ * <p> The device should already be paired.
+ * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
+ * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
+ *
+ * @param device Paired bluetooth device
+ * @param connectionPolicy determines whether to disconnect the device
+ * @return true if pbap is successfully disconnected, false otherwise
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+ public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
+ @ConnectionPolicy int connectionPolicy) {
+ if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
+ try {
+ final IBluetoothPbap service = mService;
+ if (service != null && isEnabled()
+ && isValidDevice(device)) {
+ if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+ && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+ return false;
+ }
+ return service.setConnectionPolicy(device, connectionPolicy);
+ }
+ if (service == null) Log.w(TAG, "Proxy not attached to service");
+ return false;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+ return false;
+ }
+ }
+
+ /**
* Disconnects the current Pbap client (PCE). Currently this call blocks,
* it may soon be made asynchronous. Returns false if this proxy object is
* not currently connected to the Pbap service.
diff --git a/core/java/android/content/ComponentName.java b/core/java/android/content/ComponentName.java
index 27960b0..1967a01 100644
--- a/core/java/android/content/ComponentName.java
+++ b/core/java/android/content/ComponentName.java
@@ -305,6 +305,12 @@
proto.end(token);
}
+ /**
+ * {@inheritDoc}
+ *
+ * <p>Two components are considered to be equal if the packages in which they reside have the
+ * same name, and if the classes that implement each component also have the same name.
+ */
@Override
public boolean equals(Object obj) {
try {
diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java
index 393d488..85826fd 100644
--- a/core/java/android/content/ContentProvider.java
+++ b/core/java/android/content/ContentProvider.java
@@ -27,6 +27,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.app.AppOpsManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.PackageManager;
@@ -942,7 +943,18 @@
return null;
}
- /** {@hide} */
+ /**
+ * Return the package name of the caller that initiated the request being
+ * processed on the current thread. The returned package will have
+ * <em>not</em> been verified to belong to the calling UID. Returns
+ * {@code null} if not currently processing a request.
+ * <p>
+ * This will always return {@code null} when processing
+ * {@link #getType(Uri)} or {@link #getStreamTypes(Uri, String)} requests.
+ *
+ * @see Binder#getCallingUid()
+ * @see Context#grantUriPermission(String, Uri, int)
+ */
public final @Nullable String getCallingPackageUnchecked() {
final Pair<String, String> pkg = mCallingPackage.get();
if (pkg != null) {
@@ -952,7 +964,14 @@
return null;
}
- /** {@hide} */
+ /**
+ * Called whenever the value of {@link #getCallingPackage()} changes, giving
+ * the provider an opportunity to invalidate any security related caching it
+ * may be performing.
+ * <p>
+ * This typically happens when a {@link ContentProvider} makes a nested call
+ * back into itself when already processing a call from a remote process.
+ */
public void onCallingPackageChanged() {
}
@@ -1390,8 +1409,11 @@
* @param uri The URI to query. This will be the full URI sent by the client.
* @param projection The list of columns to put into the cursor.
* If {@code null} provide a default set of columns.
- * @param queryArgs A Bundle containing all additional information necessary for the query.
- * Values in the Bundle may include SQL style arguments.
+ * @param queryArgs A Bundle containing additional information necessary for
+ * the operation. Arguments may include SQL style arguments, such
+ * as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that
+ * the documentation for each individual provider will indicate
+ * which arguments they support.
* @param cancellationSignal A signal to cancel the operation in progress,
* or {@code null}.
* @return a Cursor or {@code null}.
@@ -1525,8 +1547,24 @@
return false;
}
- /** {@hide} */
+ /**
+ * Perform a detailed internal check on a {@link Uri} to determine if a UID
+ * is able to access it with specific mode flags.
+ * <p>
+ * This method is typically used when the provider implements more dynamic
+ * access controls that cannot be expressed with {@code <path-permission>}
+ * style static rules.
+ *
+ * @param uri the {@link Uri} to perform an access check on.
+ * @param uid the UID to check the permission for.
+ * @param modeFlags the access flags to use for the access check, such as
+ * {@link Intent#FLAG_GRANT_READ_URI_PERMISSION}.
+ * @return {@link PackageManager#PERMISSION_GRANTED} if access is allowed,
+ * otherwise {@link PackageManager#PERMISSION_DENIED}.
+ * @hide
+ */
@Override
+ @SystemApi
public int checkUriPermission(@NonNull Uri uri, int uid, @Intent.AccessUriMode int modeFlags) {
return PackageManager.PERMISSION_DENIED;
}
@@ -1574,9 +1612,14 @@
*
* @param uri The content:// URI of the insertion request.
* @param values A set of column_name/value pairs to add to the database.
- * @param extras A Bundle containing all additional information necessary
- * for the insert.
+ * @param extras A Bundle containing additional information necessary for
+ * the operation. Arguments may include SQL style arguments, such
+ * as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that
+ * the documentation for each individual provider will indicate
+ * which arguments they support.
* @return The URI for the newly inserted item.
+ * @throws IllegalArgumentException if the provider doesn't support one of
+ * the requested Bundle arguments.
*/
@Override
public @Nullable Uri insert(@NonNull Uri uri, @Nullable ContentValues values,
@@ -1653,10 +1696,13 @@
*
* @param uri The full URI to query, including a row ID (if a specific
* record is requested).
- * @param extras A Bundle containing all additional information necessary
- * for the delete. Values in the Bundle may include SQL style
- * arguments.
- * @return The number of rows affected.
+ * @param extras A Bundle containing additional information necessary for
+ * the operation. Arguments may include SQL style arguments, such
+ * as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that
+ * the documentation for each individual provider will indicate
+ * which arguments they support.
+ * @throws IllegalArgumentException if the provider doesn't support one of
+ * the requested Bundle arguments.
* @throws SQLException
*/
@Override
@@ -1699,10 +1745,14 @@
* @param uri The URI to query. This can potentially have a record ID if
* this is an update request for a specific record.
* @param values A set of column_name/value pairs to update in the database.
- * @param extras A Bundle containing all additional information necessary
- * for the update. Values in the Bundle may include SQL style
- * arguments.
+ * @param extras A Bundle containing additional information necessary for
+ * the operation. Arguments may include SQL style arguments, such
+ * as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that
+ * the documentation for each individual provider will indicate
+ * which arguments they support.
* @return the number of rows affected.
+ * @throws IllegalArgumentException if the provider doesn't support one of
+ * the requested Bundle arguments.
*/
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values,
diff --git a/core/java/android/content/ContentProviderOperation.java b/core/java/android/content/ContentProviderOperation.java
index 93f4287..494d2ae 100644
--- a/core/java/android/content/ContentProviderOperation.java
+++ b/core/java/android/content/ContentProviderOperation.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
diff --git a/core/java/android/content/ContentProviderResult.java b/core/java/android/content/ContentProviderResult.java
index 11dda83..4fb1ddb 100644
--- a/core/java/android/content/ContentProviderResult.java
+++ b/core/java/android/content/ContentProviderResult.java
@@ -36,7 +36,7 @@
public final @Nullable Uri uri;
public final @Nullable Integer count;
public final @Nullable Bundle extras;
- public final @Nullable Exception exception;
+ public final @Nullable Throwable exception;
public ContentProviderResult(@NonNull Uri uri) {
this(Objects.requireNonNull(uri), null, null, null);
@@ -50,12 +50,12 @@
this(null, null, Objects.requireNonNull(extras), null);
}
- public ContentProviderResult(@NonNull Exception exception) {
+ public ContentProviderResult(@NonNull Throwable exception) {
this(null, null, null, exception);
}
/** {@hide} */
- public ContentProviderResult(Uri uri, Integer count, Bundle extras, Exception exception) {
+ public ContentProviderResult(Uri uri, Integer count, Bundle extras, Throwable exception) {
this.uri = uri;
this.count = count;
this.extras = extras;
@@ -79,7 +79,7 @@
extras = null;
}
if (source.readInt() != 0) {
- exception = (Exception) ParcelableException.readFromParcel(source);
+ exception = ParcelableException.readFromParcel(source);
} else {
exception = null;
}
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 1d3c650..6cd1cd3 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -984,7 +984,11 @@
* retrieve.
* @param projection A list of which columns to return. Passing null will
* return all columns, which is inefficient.
- * @param queryArgs A Bundle containing any arguments to the query.
+ * @param queryArgs A Bundle containing additional information necessary for
+ * the operation. Arguments may include SQL style arguments, such
+ * as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that
+ * the documentation for each individual provider will indicate
+ * which arguments they support.
* @param cancellationSignal A signal to cancel the operation in progress, or null if none.
* If the operation is canceled, then {@link OperationCanceledException} will be thrown
* when the query is executed.
@@ -1925,9 +1929,15 @@
* @param url The URL of the table to insert into.
* @param values The initial values for the newly inserted row. The key is the column name for
* the field. Passing an empty ContentValues will create an empty row.
- * @param extras A Bundle containing all additional information necessary for the insert.
+ * @param extras A Bundle containing additional information necessary for
+ * the operation. Arguments may include SQL style arguments, such
+ * as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that
+ * the documentation for each individual provider will indicate
+ * which arguments they support.
* @return the URL of the newly created row. May return <code>null</code> if the underlying
* content provider returns <code>null</code>, or if it crashes.
+ * @throws IllegalArgumentException if the provider doesn't support one of
+ * the requested Bundle arguments.
*/
@Override
public final @Nullable Uri insert(@RequiresPermission.Write @NonNull Uri url,
@@ -2061,9 +2071,14 @@
* If the content provider supports transactions, the deletion will be atomic.
*
* @param url The URL of the row to delete.
- * @param extras A Bundle containing all additional information necessary for the delete.
- * Values in the Bundle may include SQL style arguments.
+ * @param extras A Bundle containing additional information necessary for
+ * the operation. Arguments may include SQL style arguments, such
+ * as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that
+ * the documentation for each individual provider will indicate
+ * which arguments they support.
* @return The number of rows deleted.
+ * @throws IllegalArgumentException if the provider doesn't support one of
+ * the requested Bundle arguments.
*/
@Override
public final int delete(@RequiresPermission.Write @NonNull Uri url, @Nullable Bundle extras) {
@@ -2121,10 +2136,15 @@
* @param uri The URI to modify.
* @param values The new field values. The key is the column name for the field.
A null value will remove an existing field value.
- * @param extras A Bundle containing all additional information necessary for the update.
- * Values in the Bundle may include SQL style arguments.
+ * @param extras A Bundle containing additional information necessary for
+ * the operation. Arguments may include SQL style arguments, such
+ * as {@link ContentResolver#QUERY_ARG_SQL_LIMIT}, but note that
+ * the documentation for each individual provider will indicate
+ * which arguments they support.
* @return the number of rows updated.
* @throws NullPointerException if uri or values are null
+ * @throws IllegalArgumentException if the provider doesn't support one of
+ * the requested Bundle arguments.
*/
@Override
public final int update(@RequiresPermission.Write @NonNull Uri uri,
@@ -3851,15 +3871,47 @@
}
}
+ /**
+ * Decode a path generated by {@link #encodeToFile(Uri)} back into
+ * the original {@link Uri}.
+ * <p>
+ * This is used to offer a way to intercept filesystem calls in
+ * {@link ContentProvider} unaware code and redirect them to a
+ * {@link ContentProvider} when they attempt to use {@code _DATA} columns
+ * that are otherwise deprecated.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static @NonNull Uri decodeFromFile(@NonNull File file) {
+ return translateDeprecatedDataPath(file.getAbsolutePath());
+ }
+
+ /**
+ * Encode a {@link Uri} into an opaque filesystem path which can then be
+ * resurrected by {@link #decodeFromFile(File)}.
+ * <p>
+ * This is used to offer a way to intercept filesystem calls in
+ * {@link ContentProvider} unaware code and redirect them to a
+ * {@link ContentProvider} when they attempt to use {@code _DATA} columns
+ * that are otherwise deprecated.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static @NonNull File encodeToFile(@NonNull Uri uri) {
+ return new File(translateDeprecatedDataPath(uri));
+ }
+
/** {@hide} */
- public static Uri translateDeprecatedDataPath(String path) {
+ public static @NonNull Uri translateDeprecatedDataPath(@NonNull String path) {
final String ssp = "//" + path.substring(DEPRECATE_DATA_PREFIX.length());
return Uri.parse(new Uri.Builder().scheme(SCHEME_CONTENT)
.encodedOpaquePart(ssp).build().toString());
}
/** {@hide} */
- public static String translateDeprecatedDataPath(Uri uri) {
+ public static @NonNull String translateDeprecatedDataPath(@NonNull Uri uri) {
return DEPRECATE_DATA_PREFIX + uri.getEncodedSchemeSpecificPart().substring(2);
}
}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 4815d78..679de8a 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -1825,8 +1825,9 @@
* @throws ActivityNotFoundException
* @hide
*/
- @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
@SystemApi
+ @TestApi
public void startActivityAsUser(@RequiresPermission @NonNull Intent intent,
@NonNull UserHandle user) {
throw new RuntimeException("Not implemented. Must override in a subclass.");
@@ -1873,7 +1874,7 @@
* @throws ActivityNotFoundException
* @hide
*/
- @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
@UnsupportedAppUsage
public void startActivityAsUser(@RequiresPermission Intent intent, @Nullable Bundle options,
UserHandle userId) {
@@ -1979,7 +1980,7 @@
* @see #startActivities(Intent[])
* @see PackageManager#resolveActivity
*/
- @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
public int startActivitiesAsUser(Intent[] intents, Bundle options, UserHandle userHandle) {
throw new RuntimeException("Not implemented. Must override in a subclass.");
}
@@ -3240,15 +3241,40 @@
}
/**
- * Same as {@link #bindService(Intent, ServiceConnection, int)}, but with an explicit userHandle
- * argument for use by system server and other multi-user aware code.
- * @hide
+ * Binds to a service in the given {@code user} in the same manner as
+ * {@link #bindService(Intent, ServiceConnection, int)}.
+ *
+ * <p>If the given {@code user} is in the same profile group and the target package is the
+ * same as the caller, {@code android.Manifest.permission.INTERACT_ACROSS_PROFILES} is
+ * sufficient. Otherwise, requires {@code android.Manifest.permission.INTERACT_ACROSS_USERS}
+ * for interacting with other users.
+ *
+ * @param service Identifies the service to connect to. The Intent must
+ * specify an explicit component name.
+ * @param conn Receives information as the service is started and stopped.
+ * This must be a valid ServiceConnection object; it must not be null.
+ * @param flags Operation options for the binding. May be 0,
+ * {@link #BIND_AUTO_CREATE}, {@link #BIND_DEBUG_UNBIND},
+ * {@link #BIND_NOT_FOREGROUND}, {@link #BIND_ABOVE_CLIENT},
+ * {@link #BIND_ALLOW_OOM_MANAGEMENT}, {@link #BIND_WAIVE_PRIORITY}.
+ * {@link #BIND_IMPORTANT}, or
+ * {@link #BIND_ADJUST_WITH_ACTIVITY}.
+ * @return {@code true} if the system is in the process of bringing up a
+ * service that your client has permission to bind to; {@code false}
+ * if the system couldn't find the service. If this value is {@code true}, you
+ * should later call {@link #unbindService} to release the
+ * connection.
+ *
+ * @throws SecurityException if the client does not have the required permission to bind.
*/
- @SystemApi
@SuppressWarnings("unused")
- @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
- public boolean bindServiceAsUser(@RequiresPermission Intent service, ServiceConnection conn,
- int flags, UserHandle user) {
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.INTERACT_ACROSS_USERS,
+ android.Manifest.permission.INTERACT_ACROSS_PROFILES
+ })
+ public boolean bindServiceAsUser(
+ @NonNull @RequiresPermission Intent service, @NonNull ServiceConnection conn, int flags,
+ @NonNull UserHandle user) {
throw new RuntimeException("Not implemented. Must override in a subclass.");
}
@@ -3391,6 +3417,7 @@
TELEPHONY_SUBSCRIPTION_SERVICE,
CARRIER_CONFIG_SERVICE,
EUICC_SERVICE,
+ MMS_SERVICE,
TELECOM_SERVICE,
CLIPBOARD_SERVICE,
INPUT_METHOD_SERVICE,
@@ -3587,6 +3614,8 @@
* @see android.telephony.CarrierConfigManager
* @see #EUICC_SERVICE
* @see android.telephony.euicc.EuiccManager
+ * @see #MMS_SERVICE
+ * @see android.telephony.MmsManager
* @see #INPUT_METHOD_SERVICE
* @see android.view.inputmethod.InputMethodManager
* @see #UI_MODE_SERVICE
@@ -3925,10 +3954,12 @@
/**
* Use with {@link android.os.ServiceManager.getService()} to retrieve a
- * {@link NetworkStackClient} IBinder for communicating with the network stack
+ * {@link INetworkStackConnector} IBinder for communicating with the network stack
* @hide
* @see NetworkStackClient
*/
+ @SystemApi
+ @TestApi
public static final String NETWORK_STACK_SERVICE = "network_stack";
/**
@@ -3992,6 +4023,7 @@
*/
public static final String NETWORK_STATS_SERVICE = "netstats";
/** {@hide} */
+ @SystemApi
public static final String NETWORK_POLICY_SERVICE = "netpolicy";
/** {@hide} */
public static final String NETWORK_WATCHLIST_SERVICE = "network_watchlist";
@@ -4015,6 +4047,7 @@
* @see android.net.wifi.WifiCondManager
* @hide
*/
+ @SystemApi
public static final String WIFI_COND_SERVICE = "wificond";
/**
@@ -4261,6 +4294,15 @@
/**
* Use with {@link #getSystemService(String)} to retrieve a
+ * {@link android.telephony.MmsManager} to send/receive MMS messages.
+ *
+ * @see #getSystemService(String)
+ * @see android.telephony.MmsManager
+ */
+ public static final String MMS_SERVICE = "mms";
+
+ /**
+ * Use with {@link #getSystemService(String)} to retrieve a
* {@link android.content.ClipboardManager} for accessing and modifying
* the contents of the global clipboard.
*
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 7967708..ee75802 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -752,6 +752,22 @@
public static final String ACTION_PICK = "android.intent.action.PICK";
/**
+ * Activity Action: Creates a reminder.
+ * <p>Input: {@link #EXTRA_TITLE} The title of the reminder that will be shown to the user.
+ * {@link #EXTRA_TEXT} The reminder text that will be shown to the user. The intent should at
+ * least specify a title or a text. {@link #EXTRA_TIME} The time when the reminder will be shown
+ * to the user. The time is specified in milliseconds since the Epoch (optional).
+ * </p>
+ * <p>Output: Nothing.</p>
+ *
+ * @see #EXTRA_TITLE
+ * @see #EXTRA_TEXT
+ * @see #EXTRA_TIME
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_CREATE_REMINDER = "android.intent.action.CREATE_REMINDER";
+
+ /**
* Activity Action: Creates a shortcut.
* <p>Input: Nothing.</p>
* <p>Output: An Intent representing the {@link android.content.pm.ShortcutInfo} result.</p>
@@ -4224,9 +4240,10 @@
public static final String ACTION_SERVICE_STATE = "android.intent.action.SERVICE_STATE";
/**
- * Used for looking up a Data Loader Service providers.
+ * Used for looking up a Data Loader Service provider.
* @hide
*/
+ @SystemApi
@SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
public static final String ACTION_LOAD_DATA = "android.intent.action.LOAD_DATA";
@@ -5725,6 +5742,15 @@
= "android.intent.extra.SHUTDOWN_USERSPACE_ONLY";
/**
+ * Optional extra specifying a time in milliseconds since the Epoch. The value must be
+ * non-negative.
+ * <p>
+ * Type: long
+ * </p>
+ */
+ public static final String EXTRA_TIME = "android.intent.extra.TIME";
+
+ /**
* Optional int extra for {@link #ACTION_TIME_CHANGED} that indicates the
* user has set their time format preference. See {@link #EXTRA_TIME_PREF_VALUE_USE_12_HOUR},
* {@link #EXTRA_TIME_PREF_VALUE_USE_24_HOUR} and
@@ -6439,19 +6465,22 @@
*/
public static final int FLAG_RECEIVER_NO_ABORT = 0x08000000;
/**
- * If set, when sending a broadcast <i>before boot has completed</i> only
+ * If set, when sending a broadcast <i>before the system has fully booted up
+ * (which is even before {@link #ACTION_LOCKED_BOOT_COMPLETED} has been sent)"</i> only
* registered receivers will be called -- no BroadcastReceiver components
* will be launched. Sticky intent state will be recorded properly even
* if no receivers wind up being called. If {@link #FLAG_RECEIVER_REGISTERED_ONLY}
* is specified in the broadcast intent, this flag is unnecessary.
*
- * <p>This flag is only for use by system sevices as a convenience to
- * avoid having to implement a more complex mechanism around detection
+ * <p>This flag is only for use by system services (even services from mainline modules) as a
+ * convenience to avoid having to implement a more complex mechanism around detection
* of boot completion.
*
+ * <p>This is useful to system server mainline modules
+ *
* @hide
*/
- @UnsupportedAppUsage
+ @SystemApi
public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 0x04000000;
/**
* Set when this broadcast is for a boot upgrade, a special mode that
diff --git a/core/java/android/content/pm/CrossProfileApps.java b/core/java/android/content/pm/CrossProfileApps.java
index e897b91..9d57514 100644
--- a/core/java/android/content/pm/CrossProfileApps.java
+++ b/core/java/android/content/pm/CrossProfileApps.java
@@ -16,20 +16,25 @@
package android.content.pm;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
+import android.net.Uri;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
+import android.provider.Settings;
import com.android.internal.R;
import com.android.internal.util.UserIcons;
import java.util.List;
+import java.util.Set;
/**
* Class for handling cross profile operations. Apps can use this class to interact with its
@@ -169,6 +174,86 @@
}
}
+ /**
+ * Returns whether the calling package can request to interact across profiles.
+ *
+ * <p>The package's current ability to interact across profiles can be checked with
+ * {@link #canInteractAcrossProfiles()}.
+ *
+ * <p>Specifically, returns whether the following are all true:
+ * <ul>
+ * <li>{@link #getTargetUserProfiles()} returns a non-empty list for the calling user.</li>
+ * <li>The calling app has requested</li>
+ * {@code android.Manifest.permission.INTERACT_ACROSS_PROFILES} in its manifest.
+ * <li>The calling package has either been whitelisted by default by the OEM or has been
+ * explicitly whitelisted by the admin via
+ * {@link android.app.admin.DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)}.
+ * </li>
+ * </ul>
+ *
+ * @return true if the calling package can request to interact across profiles.
+ */
+ public boolean canRequestInteractAcrossProfiles() {
+ try {
+ return mService.canRequestInteractAcrossProfiles(mContext.getPackageName());
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns whether the calling package can interact across profiles.
+ *
+ * <p>The package's current ability to request to interact across profiles can be checked with
+ * {@link #canRequestInteractAcrossProfiles()}.
+ *
+ * <p>Specifically, returns whether the following are all true:
+ * <ul>
+ * <li>{@link #getTargetUserProfiles()} returns a non-empty list for the calling user.</li>
+ * <li>The user has previously consented to cross-profile communication for the calling
+ * package.</li>
+ * <li>The calling package has either been whitelisted by default by the OEM or has been
+ * explicitly whitelisted by the admin via
+ * {@link android.app.admin.DevicePolicyManager#setCrossProfilePackages(ComponentName, Set)}.
+ * </li>
+ * </ul>
+ *
+ * @return true if the calling package can interact across profiles.
+ * @throws SecurityException if {@code mContext.getPackageName()} does not belong to the
+ * calling UID.
+ */
+ public boolean canInteractAcrossProfiles() {
+ try {
+ return mService.canInteractAcrossProfiles(mContext.getPackageName());
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Returns an {@link Intent} to open the settings page that allows the user to decide whether
+ * the calling app can interact across profiles. The current state is given by
+ * {@link #canInteractAcrossProfiles()}.
+ *
+ * <p>Returns {@code null} if {@link #canRequestInteractAcrossProfiles()} is {@code false}.
+ *
+ * @return an {@link Intent} to open the settings page that allows the user to decide whether
+ * the app can interact across profiles
+ *
+ * @throws SecurityException if {@code mContext.getPackageName()} does not belong to the
+ * calling UID.
+ */
+ public @Nullable Intent createRequestInteractAcrossProfilesIntent() {
+ if (!canRequestInteractAcrossProfiles()) {
+ return null;
+ }
+ final Intent settingsIntent = new Intent();
+ settingsIntent.setAction(Settings.ACTION_MANAGE_CROSS_PROFILE_ACCESS);
+ final Uri packageUri = Uri.parse("package:" + mContext.getPackageName());
+ settingsIntent.setData(packageUri);
+ return settingsIntent;
+ }
+
private void verifyCanAccessUser(UserHandle userHandle) {
if (!getTargetUserProfiles().contains(userHandle)) {
throw new SecurityException("Not allowed to access " + userHandle);
diff --git a/core/java/android/content/pm/ICrossProfileApps.aidl b/core/java/android/content/pm/ICrossProfileApps.aidl
index d2d66cb..c5db0cc 100644
--- a/core/java/android/content/pm/ICrossProfileApps.aidl
+++ b/core/java/android/content/pm/ICrossProfileApps.aidl
@@ -30,4 +30,6 @@
void startActivityAsUser(in IApplicationThread caller, in String callingPackage,
in ComponentName component, int userId, boolean launchMainActivity);
List<UserHandle> getTargetUserProfiles(in String callingPackage);
+ boolean canInteractAcrossProfiles(in String callingPackage);
+ boolean canRequestInteractAcrossProfiles(in String callingPackage);
}
\ No newline at end of file
diff --git a/core/java/android/content/pm/InstallSourceInfo.java b/core/java/android/content/pm/InstallSourceInfo.java
index 4d235f1..c0fdcc9 100644
--- a/core/java/android/content/pm/InstallSourceInfo.java
+++ b/core/java/android/content/pm/InstallSourceInfo.java
@@ -29,32 +29,39 @@
@Nullable private final String mInitiatingPackageName;
+ @Nullable private final SigningInfo mInitiatingPackageSigningInfo;
+
@Nullable private final String mOriginatingPackageName;
@Nullable private final String mInstallingPackageName;
/** @hide */
public InstallSourceInfo(@Nullable String initiatingPackageName,
+ @Nullable SigningInfo initiatingPackageSigningInfo,
@Nullable String originatingPackageName, @Nullable String installingPackageName) {
- this.mInitiatingPackageName = initiatingPackageName;
- this.mOriginatingPackageName = originatingPackageName;
- this.mInstallingPackageName = installingPackageName;
+ mInitiatingPackageName = initiatingPackageName;
+ mInitiatingPackageSigningInfo = initiatingPackageSigningInfo;
+ mOriginatingPackageName = originatingPackageName;
+ mInstallingPackageName = installingPackageName;
}
@Override
public int describeContents() {
- return 0;
+ return mInitiatingPackageSigningInfo == null
+ ? 0 : mInitiatingPackageSigningInfo.describeContents();
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(mInitiatingPackageName);
+ dest.writeParcelable(mInitiatingPackageSigningInfo, flags);
dest.writeString(mOriginatingPackageName);
dest.writeString(mInstallingPackageName);
}
private InstallSourceInfo(Parcel source) {
mInitiatingPackageName = source.readString();
+ mInitiatingPackageSigningInfo = source.readParcelable(SigningInfo.class.getClassLoader());
mOriginatingPackageName = source.readString();
mInstallingPackageName = source.readString();
}
@@ -66,6 +73,14 @@
}
/**
+ * Information about the signing certificates used to sign the initiating package, if available.
+ */
+ @Nullable
+ public SigningInfo getInitiatingPackageSigningInfo() {
+ return mInitiatingPackageSigningInfo;
+ }
+
+ /**
* The name of the package on behalf of which the initiating package requested the installation,
* or null if not available.
* <p>
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index f792127..1f502a1 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -2323,6 +2323,13 @@
public static final String FEATURE_TELEPHONY_GSM = "android.hardware.telephony.gsm";
/**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device
+ * has a telephony radio that support data.
+ */
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_TELEPHONY_DATA = "android.hardware.telephony.data";
+
+ /**
* Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}:
* The device supports telephony carrier restriction mechanism.
*
@@ -2918,6 +2925,18 @@
public static final String FEATURE_IPSEC_TUNNELS = "android.software.ipsec_tunnels";
/**
+ * Feature for {@link #getSystemAvailableFeatures} and {@link #hasSystemFeature}: The device has
+ * the requisite hardware support to support reboot escrow of synthetic password for updates.
+ *
+ * <p>This feature implies that the device has the RebootEscrow HAL implementation.
+ *
+ * @hide
+ */
+ @SystemApi
+ @SdkConstant(SdkConstantType.FEATURE)
+ public static final String FEATURE_REBOOT_ESCROW = "android.hardware.reboot_escrow";
+
+ /**
* Extra field name for the URI to a verification file. Passed to a package
* verifier.
*
@@ -3360,6 +3379,10 @@
* etc. This library is versioned and backwards compatible. Clients
* should check its version via {@link android.ext.services.Version
* #getVersionCode()} and avoid calling APIs added in later versions.
+ * <p>
+ * This shared library no longer exists since Android R.
+ *
+ * @see #getServicesSystemSharedLibraryPackageName()
*
* @hide
*/
@@ -4370,6 +4393,18 @@
public abstract boolean shouldShowRequestPermissionRationale(@NonNull String permName);
/**
+ * Gets the string that is displayed on the button which corresponds to granting background
+ * location in settings. The intended use for this is to help apps instruct users how to
+ * grant a background permission by providing the string that users will see.
+ *
+ * @return the string shown on the button for granting background location
+ */
+ @NonNull
+ public CharSequence getBackgroundPermissionButtonLabel() {
+ return "";
+ }
+
+ /**
* Returns an {@link android.content.Intent} suitable for passing to
* {@link android.app.Activity#startActivityForResult(android.content.Intent, int)}
* which prompts the user to grant permissions to this application.
@@ -4714,6 +4749,9 @@
/**
* Get the name of the package hosting the services shared library.
+ * <p>
+ * Note that this package is no longer a shared library since Android R. It is now a package
+ * that hosts for a bunch of updatable services that the system binds to.
*
* @return The library host package.
*
@@ -6034,6 +6072,11 @@
* If the calling application does not hold the INSTALL_PACKAGES permission then
* the result will always return {@code null} from
* {@link InstallSourceInfo#getOriginatingPackageName()}.
+ * <p>
+ * If the package that requested the install has been uninstalled, then information about it
+ * will only be returned from {@link InstallSourceInfo#getInitiatingPackageName()} and
+ * {@link InstallSourceInfo#getInitiatingPackageSigningInfo()} if the calling package is
+ * requesting its own install information and is not an instant app.
*
* @param packageName The name of the package to query
* @throws NameNotFoundException if the given package name is not installed
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 32803ab..87acbc1 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -7803,7 +7803,7 @@
ai.category = FallbackCategoryProvider.getFallbackCategory(ai.packageName);
}
ai.seInfoUser = SELinuxUtil.assignSeinfoUser(state);
- ai.resourceDirs = state.overlayPaths;
+ ai.resourceDirs = state.getAllOverlayPaths();
ai.icon = (sUseRoundIcon && ai.roundIconRes != 0) ? ai.roundIconRes : ai.iconRes;
}
diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java
index f0f6753..da7623a 100644
--- a/core/java/android/content/pm/PackageUserState.java
+++ b/core/java/android/content/pm/PackageUserState.java
@@ -46,6 +46,8 @@
import java.io.IOException;
import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.Map;
import java.util.Objects;
/**
@@ -77,7 +79,9 @@
public ArraySet<String> disabledComponents;
public ArraySet<String> enabledComponents;
- public String[] overlayPaths;
+ private String[] overlayPaths;
+ private ArrayMap<String, String[]> sharedLibraryOverlayPaths; // Lib name to overlay paths
+ private String[] cachedOverlayPaths;
@UnsupportedAppUsage
public PackageUserState() {
@@ -112,9 +116,33 @@
enabledComponents = ArrayUtils.cloneOrNull(o.enabledComponents);
overlayPaths =
o.overlayPaths == null ? null : Arrays.copyOf(o.overlayPaths, o.overlayPaths.length);
+ if (o.sharedLibraryOverlayPaths != null) {
+ sharedLibraryOverlayPaths = new ArrayMap<>(o.sharedLibraryOverlayPaths);
+ }
harmfulAppWarning = o.harmfulAppWarning;
}
+ public String[] getOverlayPaths() {
+ return overlayPaths;
+ }
+
+ public void setOverlayPaths(String[] paths) {
+ overlayPaths = paths;
+ cachedOverlayPaths = null;
+ }
+
+ public Map<String, String[]> getSharedLibraryOverlayPaths() {
+ return sharedLibraryOverlayPaths;
+ }
+
+ public void setSharedLibraryOverlayPaths(String library, String[] paths) {
+ if (sharedLibraryOverlayPaths == null) {
+ sharedLibraryOverlayPaths = new ArrayMap<>();
+ }
+ sharedLibraryOverlayPaths.put(library, paths);
+ cachedOverlayPaths = null;
+ }
+
/**
* Test if this package is installed.
*/
@@ -235,6 +263,38 @@
return isComponentEnabled;
}
+ public String[] getAllOverlayPaths() {
+ if (overlayPaths == null && sharedLibraryOverlayPaths == null) {
+ return null;
+ }
+
+ if (cachedOverlayPaths != null) {
+ return cachedOverlayPaths;
+ }
+
+ final LinkedHashSet<String> paths = new LinkedHashSet<>();
+ if (overlayPaths != null) {
+ final int N = overlayPaths.length;
+ for (int i = 0; i < N; i++) {
+ paths.add(overlayPaths[i]);
+ }
+ }
+
+ if (sharedLibraryOverlayPaths != null) {
+ for (String[] libOverlayPaths : sharedLibraryOverlayPaths.values()) {
+ if (libOverlayPaths != null) {
+ final int N = libOverlayPaths.length;
+ for (int i = 0; i < N; i++) {
+ paths.add(libOverlayPaths[i]);
+ }
+ }
+ }
+ }
+
+ cachedOverlayPaths = paths.toArray(new String[0]);
+ return cachedOverlayPaths;
+ }
+
@Override
final public boolean equals(Object obj) {
if (!(obj instanceof PackageUserState)) {
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index df4ae09..0549c34 100644
--- a/core/java/android/content/pm/TEST_MAPPING
+++ b/core/java/android/content/pm/TEST_MAPPING
@@ -9,5 +9,11 @@
{
"path": "system/apex/tests"
}
+ ],
+ "presubmit": [
+ {
+ "name": "FrameworksInstantAppResolverTests",
+ "file_patterns": ["(/|^)InstantApp[^/]*"]
+ }
]
}
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index ac2e373..6639b3d 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -18,7 +18,7 @@
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
diff --git a/core/java/android/content/pm/parsing/ApkParseUtils.java b/core/java/android/content/pm/parsing/ApkParseUtils.java
index a001ada..38d3137 100644
--- a/core/java/android/content/pm/parsing/ApkParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkParseUtils.java
@@ -65,6 +65,7 @@
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.Trace;
+import android.os.ext.SdkExtensions;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -1615,11 +1616,72 @@
);
}
+ int type;
+ final int innerDepth = parser.getDepth();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+ if (parser.getName().equals("extension-sdk")) {
+ final ParseResult result =
+ parseExtensionSdk(parseInput, parsingPackage, res, parser);
+ if (!result.isSuccess()) {
+ return result;
+ }
+ } else {
+ Slog.w(TAG, "Unknown element under <uses-sdk>: " + parser.getName()
+ + " at " + parsingPackage.getBaseCodePath() + " "
+ + parser.getPositionDescription());
+ }
+ XmlUtils.skipCurrentTag(parser);
+ }
+
parsingPackage.setMinSdkVersion(minSdkVersion)
.setTargetSdkVersion(targetSdkVersion);
}
+ return parseInput.success(parsingPackage);
+ }
- XmlUtils.skipCurrentTag(parser);
+ private static ParseResult parseExtensionSdk(
+ ParseInput parseInput,
+ ParsingPackage parsingPackage,
+ Resources res,
+ XmlResourceParser parser
+ ) throws IOException, XmlPullParserException {
+ TypedArray sa = res.obtainAttributes(parser,
+ com.android.internal.R.styleable.AndroidManifestExtensionSdk);
+ int sdkVersion = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestExtensionSdk_sdkVersion, -1);
+ int minVersion = sa.getInt(
+ com.android.internal.R.styleable.AndroidManifestExtensionSdk_minExtensionVersion,
+ -1);
+ sa.recycle();
+
+ if (sdkVersion < 0) {
+ return parseInput.error(
+ PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+ "<extension-sdk> must specify an sdkVersion >= 0");
+ }
+ if (minVersion < 0) {
+ return parseInput.error(
+ PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+ "<extension-sdk> must specify minExtensionVersion >= 0");
+ }
+
+ try {
+ int version = SdkExtensions.getExtensionVersion(sdkVersion);
+ if (version < minVersion) {
+ return parseInput.error(
+ PackageManager.INSTALL_FAILED_OLDER_SDK,
+ "Package requires " + sdkVersion + " extension version " + minVersion
+ + " which exceeds device version " + version);
+ }
+ } catch (RuntimeException e) {
+ return parseInput.error(
+ PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED,
+ "Specified sdkVersion " + sdkVersion + " is not valid");
+ }
return parseInput.success(parsingPackage);
}
diff --git a/core/java/android/content/pm/parsing/ComponentParseUtils.java b/core/java/android/content/pm/parsing/ComponentParseUtils.java
index 7b24d3d..56ace5e 100644
--- a/core/java/android/content/pm/parsing/ComponentParseUtils.java
+++ b/core/java/android/content/pm/parsing/ComponentParseUtils.java
@@ -27,8 +27,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringRes;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityTaskManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
@@ -58,6 +58,7 @@
import android.view.Gravity;
import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.DataClass;
import com.android.internal.util.XmlUtils;
@@ -465,7 +466,8 @@
}
public void setPermission(String permission) {
- this.permission = TextUtils.safeIntern(permission);
+ // Empty string must be converted to null
+ this.permission = TextUtils.isEmpty(permission) ? null : permission.intern();
}
public String getPermission() {
@@ -813,6 +815,11 @@
return exported;
}
+ @VisibleForTesting
+ public void setExported(boolean exported) {
+ this.exported = exported;
+ }
+
public List<ParsedProviderIntentInfo> getIntents() {
return intents;
}
@@ -842,7 +849,9 @@
}
public void setReadPermission(String readPermission) {
- this.readPermission = TextUtils.safeIntern(readPermission);
+ // Empty string must be converted to null
+ this.readPermission = TextUtils.isEmpty(readPermission)
+ ? null : readPermission.intern();
}
public String getReadPermission() {
@@ -850,7 +859,9 @@
}
public void setWritePermission(String writePermission) {
- this.writePermission = TextUtils.safeIntern(writePermission);
+ // Empty string must be converted to null
+ this.writePermission = TextUtils.isEmpty(writePermission)
+ ? null : writePermission.intern();
}
public String getWritePermission() {
diff --git a/core/java/android/content/pm/parsing/PackageInfoUtils.java b/core/java/android/content/pm/parsing/PackageInfoUtils.java
index f2cf9a4..73a8d2a 100644
--- a/core/java/android/content/pm/parsing/PackageInfoUtils.java
+++ b/core/java/android/content/pm/parsing/PackageInfoUtils.java
@@ -41,9 +41,11 @@
import android.content.pm.parsing.ComponentParseUtils.ParsedInstrumentation;
import android.content.pm.parsing.ComponentParseUtils.ParsedPermission;
import android.content.pm.parsing.ComponentParseUtils.ParsedPermissionGroup;
+import android.util.ArraySet;
import com.android.internal.util.ArrayUtils;
+import java.util.LinkedHashSet;
import java.util.Set;
/** @hide */
@@ -545,7 +547,7 @@
ai.category = FallbackCategoryProvider.getFallbackCategory(ai.packageName);
}
ai.seInfoUser = SELinuxUtil.assignSeinfoUser(state);
- ai.resourceDirs = state.overlayPaths;
+ ai.resourceDirs = state.getAllOverlayPaths();
ai.icon = (PackageParser.sUseRoundIcon && ai.roundIconRes != 0)
? ai.roundIconRes : ai.iconRes;
}
diff --git a/core/java/android/content/res/TEST_MAPPING b/core/java/android/content/res/TEST_MAPPING
new file mode 100644
index 0000000..daf9a14
--- /dev/null
+++ b/core/java/android/content/res/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksResourceLoaderTests"
+ }
+ ]
+}
diff --git a/core/java/android/content/rollback/PackageRollbackInfo.java b/core/java/android/content/rollback/PackageRollbackInfo.java
index 6378db0..b273cd6 100644
--- a/core/java/android/content/rollback/PackageRollbackInfo.java
+++ b/core/java/android/content/rollback/PackageRollbackInfo.java
@@ -76,6 +76,11 @@
*/
private final boolean mIsApex;
+ /**
+ * Whether this instance represents the PackageRollbackInfo for an APK in APEX.
+ */
+ private final boolean mIsApkInApex;
+
/*
* The list of users for which snapshots have been saved.
*/
@@ -157,6 +162,10 @@
public @PackageManager.RollbackDataPolicy int getRollbackDataPolicy() {
return mRollbackDataPolicy;
}
+ /** @hide */
+ public boolean isApkInApex() {
+ return mIsApkInApex;
+ }
/** @hide */
public IntArray getSnapshottedUsers() {
@@ -190,17 +199,18 @@
public PackageRollbackInfo(VersionedPackage packageRolledBackFrom,
VersionedPackage packageRolledBackTo,
@NonNull IntArray pendingBackups, @NonNull ArrayList<RestoreInfo> pendingRestores,
- boolean isApex, @NonNull IntArray snapshottedUsers,
+ boolean isApex, boolean isApkInApex, @NonNull IntArray snapshottedUsers,
@NonNull SparseLongArray ceSnapshotInodes) {
this(packageRolledBackFrom, packageRolledBackTo, pendingBackups, pendingRestores, isApex,
- snapshottedUsers, ceSnapshotInodes, PackageManager.RollbackDataPolicy.RESTORE);
+ isApkInApex, snapshottedUsers, ceSnapshotInodes,
+ PackageManager.RollbackDataPolicy.RESTORE);
}
/** @hide */
public PackageRollbackInfo(VersionedPackage packageRolledBackFrom,
VersionedPackage packageRolledBackTo,
@NonNull IntArray pendingBackups, @NonNull ArrayList<RestoreInfo> pendingRestores,
- boolean isApex, @NonNull IntArray snapshottedUsers,
+ boolean isApex, boolean isApkInApex, @NonNull IntArray snapshottedUsers,
@NonNull SparseLongArray ceSnapshotInodes,
@PackageManager.RollbackDataPolicy int rollbackDataPolicy) {
this.mVersionRolledBackFrom = packageRolledBackFrom;
@@ -209,6 +219,7 @@
this.mPendingRestores = pendingRestores;
this.mIsApex = isApex;
this.mRollbackDataPolicy = rollbackDataPolicy;
+ this.mIsApkInApex = isApkInApex;
this.mSnapshottedUsers = snapshottedUsers;
this.mCeSnapshotInodes = ceSnapshotInodes;
}
@@ -217,6 +228,7 @@
this.mVersionRolledBackFrom = VersionedPackage.CREATOR.createFromParcel(in);
this.mVersionRolledBackTo = VersionedPackage.CREATOR.createFromParcel(in);
this.mIsApex = in.readBoolean();
+ this.mIsApkInApex = in.readBoolean();
this.mPendingRestores = null;
this.mPendingBackups = null;
this.mSnapshottedUsers = null;
@@ -234,6 +246,7 @@
mVersionRolledBackFrom.writeToParcel(out, flags);
mVersionRolledBackTo.writeToParcel(out, flags);
out.writeBoolean(mIsApex);
+ out.writeBoolean(mIsApkInApex);
}
public static final @NonNull Parcelable.Creator<PackageRollbackInfo> CREATOR =
diff --git a/core/java/android/database/AbstractCursor.java b/core/java/android/database/AbstractCursor.java
index 0a8547a..3effc5a 100644
--- a/core/java/android/database/AbstractCursor.java
+++ b/core/java/android/database/AbstractCursor.java
@@ -17,7 +17,7 @@
package android.database;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentResolver;
import android.net.Uri;
import android.os.Build;
diff --git a/core/java/android/database/AbstractWindowedCursor.java b/core/java/android/database/AbstractWindowedCursor.java
index a988f068..daf7d2b 100644
--- a/core/java/android/database/AbstractWindowedCursor.java
+++ b/core/java/android/database/AbstractWindowedCursor.java
@@ -16,7 +16,7 @@
package android.database;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* A base class for Cursors that store their data in {@link CursorWindow}s.
diff --git a/core/java/android/database/BulkCursorNative.java b/core/java/android/database/BulkCursorNative.java
index 77a13cf..8ea450c 100644
--- a/core/java/android/database/BulkCursorNative.java
+++ b/core/java/android/database/BulkCursorNative.java
@@ -16,7 +16,7 @@
package android.database;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
diff --git a/core/java/android/database/ContentObserver.java b/core/java/android/database/ContentObserver.java
index 798b783..69ca581 100644
--- a/core/java/android/database/ContentObserver.java
+++ b/core/java/android/database/ContentObserver.java
@@ -16,7 +16,7 @@
package android.database;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.net.Uri;
import android.os.Handler;
import android.os.UserHandle;
diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java
index 6873577..063a2d0 100644
--- a/core/java/android/database/CursorWindow.java
+++ b/core/java/android/database/CursorWindow.java
@@ -17,7 +17,7 @@
package android.database;
import android.annotation.BytesLong;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.Resources;
import android.database.sqlite.SQLiteClosable;
import android.database.sqlite.SQLiteException;
diff --git a/core/java/android/database/CursorWrapper.java b/core/java/android/database/CursorWrapper.java
index c9cafaf..4496f80 100644
--- a/core/java/android/database/CursorWrapper.java
+++ b/core/java/android/database/CursorWrapper.java
@@ -16,7 +16,7 @@
package android.database;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentResolver;
import android.net.Uri;
import android.os.Bundle;
diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java
index 992da31..4246b84 100644
--- a/core/java/android/database/DatabaseUtils.java
+++ b/core/java/android/database/DatabaseUtils.java
@@ -17,7 +17,7 @@
package android.database;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentValues;
import android.content.Context;
import android.content.OperationApplicationException;
diff --git a/core/java/android/database/MatrixCursor.java b/core/java/android/database/MatrixCursor.java
index b0d399c..050a49a 100644
--- a/core/java/android/database/MatrixCursor.java
+++ b/core/java/android/database/MatrixCursor.java
@@ -16,7 +16,7 @@
package android.database;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import java.util.ArrayList;
diff --git a/core/java/android/database/sqlite/DatabaseObjectNotClosedException.java b/core/java/android/database/sqlite/DatabaseObjectNotClosedException.java
index 2af06e1..ba546f3 100644
--- a/core/java/android/database/sqlite/DatabaseObjectNotClosedException.java
+++ b/core/java/android/database/sqlite/DatabaseObjectNotClosedException.java
@@ -16,7 +16,7 @@
package android.database.sqlite;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* An exception that indicates that garbage-collector is finalizing a database object
diff --git a/core/java/android/database/sqlite/SQLiteClosable.java b/core/java/android/database/sqlite/SQLiteClosable.java
index d6a71da..2fca729 100644
--- a/core/java/android/database/sqlite/SQLiteClosable.java
+++ b/core/java/android/database/sqlite/SQLiteClosable.java
@@ -16,7 +16,8 @@
package android.database.sqlite;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
import java.io.Closeable;
/**
diff --git a/core/java/android/database/sqlite/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java
index e3c4098..4559e91 100644
--- a/core/java/android/database/sqlite/SQLiteCursor.java
+++ b/core/java/android/database/sqlite/SQLiteCursor.java
@@ -16,7 +16,7 @@
package android.database.sqlite;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.database.AbstractWindowedCursor;
import android.database.CursorWindow;
import android.database.DatabaseUtils;
diff --git a/core/java/android/database/sqlite/SQLiteCustomFunction.java b/core/java/android/database/sqlite/SQLiteCustomFunction.java
index 41b78d3..1ace40d 100644
--- a/core/java/android/database/sqlite/SQLiteCustomFunction.java
+++ b/core/java/android/database/sqlite/SQLiteCustomFunction.java
@@ -16,7 +16,7 @@
package android.database.sqlite;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
/**
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index cad4e81..44c78aa 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -20,9 +20,9 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityManager;
import android.app.ActivityThread;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
diff --git a/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java b/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
index fcdaf0a..6a52b72 100644
--- a/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
+++ b/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java
@@ -16,7 +16,7 @@
package android.database.sqlite;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import java.util.ArrayList;
import java.util.Locale;
diff --git a/core/java/android/database/sqlite/SQLiteDebug.java b/core/java/android/database/sqlite/SQLiteDebug.java
index 3d0ac61..165f863 100644
--- a/core/java/android/database/sqlite/SQLiteDebug.java
+++ b/core/java/android/database/sqlite/SQLiteDebug.java
@@ -17,14 +17,13 @@
package android.database.sqlite;
import android.annotation.TestApi;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Process;
import android.os.SystemProperties;
import android.util.Log;
import android.util.Printer;
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
import java.util.ArrayList;
/**
diff --git a/core/java/android/database/sqlite/SQLiteOpenHelper.java b/core/java/android/database/sqlite/SQLiteOpenHelper.java
index 0608cb3..3341800 100644
--- a/core/java/android/database/sqlite/SQLiteOpenHelper.java
+++ b/core/java/android/database/sqlite/SQLiteOpenHelper.java
@@ -19,7 +19,7 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.database.DatabaseErrorHandler;
import android.database.SQLException;
diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java
index 8304133..de1c543 100644
--- a/core/java/android/database/sqlite/SQLiteProgram.java
+++ b/core/java/android/database/sqlite/SQLiteProgram.java
@@ -16,7 +16,7 @@
package android.database.sqlite;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.database.DatabaseUtils;
import android.os.CancellationSignal;
diff --git a/core/java/android/database/sqlite/SQLiteQueryBuilder.java b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
index f7137705..36ec67e 100644
--- a/core/java/android/database/sqlite/SQLiteQueryBuilder.java
+++ b/core/java/android/database/sqlite/SQLiteQueryBuilder.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.DatabaseUtils;
@@ -35,8 +35,8 @@
import libcore.util.EmptyArray;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Iterator;
-import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
@@ -56,7 +56,7 @@
"(?i)(AVG|COUNT|MAX|MIN|SUM|TOTAL|GROUP_CONCAT)\\((.+)\\)");
private Map<String, String> mProjectionMap = null;
- private List<Pattern> mProjectionGreylist = null;
+ private Collection<Pattern> mProjectionGreylist = null;
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
private String mTables = "";
@@ -196,20 +196,16 @@
* Sets a projection greylist of columns that will be allowed through, even
* when {@link #setStrict(boolean)} is enabled. This provides a way for
* abusive custom columns like {@code COUNT(*)} to continue working.
- *
- * @hide
*/
- public void setProjectionGreylist(@Nullable List<Pattern> projectionGreylist) {
+ public void setProjectionGreylist(@Nullable Collection<Pattern> projectionGreylist) {
mProjectionGreylist = projectionGreylist;
}
/**
* Gets the projection greylist for the query, as last configured by
- * {@link #setProjectionGreylist(List)}.
- *
- * @hide
+ * {@link #setProjectionGreylist}.
*/
- public @Nullable List<Pattern> getProjectionGreylist() {
+ public @Nullable Collection<Pattern> getProjectionGreylist() {
return mProjectionGreylist;
}
@@ -244,25 +240,27 @@
}
/**
- * When set, the selection is verified against malicious arguments.
- * When using this class to create a statement using
+ * When set, the selection is verified against malicious arguments. When
+ * using this class to create a statement using
* {@link #buildQueryString(boolean, String, String[], String, String, String, String, String)},
- * non-numeric limits will raise an exception. If a projection map is specified, fields
- * not in that map will be ignored.
- * If this class is used to execute the statement directly using
+ * non-numeric limits will raise an exception. If a projection map is
+ * specified, fields not in that map will be ignored. If this class is used
+ * to execute the statement directly using
* {@link #query(SQLiteDatabase, String[], String, String[], String, String, String)}
* or
* {@link #query(SQLiteDatabase, String[], String, String[], String, String, String, String)},
- * additionally also parenthesis escaping selection are caught.
- *
- * To summarize: To get maximum protection against malicious third party apps (for example
- * content provider consumers), make sure to do the following:
+ * additionally also parenthesis escaping selection are caught. To
+ * summarize: To get maximum protection against malicious third party apps
+ * (for example content provider consumers), make sure to do the following:
* <ul>
* <li>Set this value to true</li>
* <li>Use a projection map</li>
- * <li>Use one of the query overloads instead of getting the statement as a sql string</li>
+ * <li>Use one of the query overloads instead of getting the statement as a
+ * sql string</li>
* </ul>
- * By default, this value is false.
+ * <p>
+ * This feature is disabled by default on each newly constructed
+ * {@link SQLiteQueryBuilder} and needs to be manually enabled.
*/
public void setStrict(boolean strict) {
if (strict) {
@@ -287,6 +285,9 @@
* This enforcement applies to {@link #insert}, {@link #query}, and
* {@link #update} operations. Any enforcement failures will throw an
* {@link IllegalArgumentException}.
+ * <p>
+ * This feature is disabled by default on each newly constructed
+ * {@link SQLiteQueryBuilder} and needs to be manually enabled.
*/
public void setStrictColumns(boolean strictColumns) {
if (strictColumns) {
@@ -323,6 +324,9 @@
* {@link #delete} operations. This enforcement does not apply to trusted
* inputs, such as those provided by {@link #appendWhere}. Any enforcement
* failures will throw an {@link IllegalArgumentException}.
+ * <p>
+ * This feature is disabled by default on each newly constructed
+ * {@link SQLiteQueryBuilder} and needs to be manually enabled.
*/
public void setStrictGrammar(boolean strictGrammar) {
if (strictGrammar) {
diff --git a/core/java/android/database/sqlite/SQLiteSession.java b/core/java/android/database/sqlite/SQLiteSession.java
index a9ac9e7..24b62b8 100644
--- a/core/java/android/database/sqlite/SQLiteSession.java
+++ b/core/java/android/database/sqlite/SQLiteSession.java
@@ -16,7 +16,7 @@
package android.database.sqlite;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.database.CursorWindow;
import android.database.DatabaseUtils;
import android.os.CancellationSignal;
diff --git a/core/java/android/database/sqlite/SQLiteStatement.java b/core/java/android/database/sqlite/SQLiteStatement.java
index 42e7ac7..9fda8b0 100644
--- a/core/java/android/database/sqlite/SQLiteStatement.java
+++ b/core/java/android/database/sqlite/SQLiteStatement.java
@@ -16,7 +16,7 @@
package android.database.sqlite;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.ParcelFileDescriptor;
/**
diff --git a/core/java/android/database/sqlite/SqliteWrapper.java b/core/java/android/database/sqlite/SqliteWrapper.java
index e317164..f335fbd 100644
--- a/core/java/android/database/sqlite/SqliteWrapper.java
+++ b/core/java/android/database/sqlite/SqliteWrapper.java
@@ -17,12 +17,11 @@
package android.database.sqlite;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
-import android.database.sqlite.SQLiteException;
import android.net.Uri;
import android.util.Log;
import android.widget.Toast;
diff --git a/core/java/android/ddm/DdmHandleAppName.java b/core/java/android/ddm/DdmHandleAppName.java
index de7acba..4f55921 100644
--- a/core/java/android/ddm/DdmHandleAppName.java
+++ b/core/java/android/ddm/DdmHandleAppName.java
@@ -16,7 +16,7 @@
package android.ddm;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.util.Log;
import org.apache.harmony.dalvik.ddmc.Chunk;
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 8e8a81d..25279b3 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -16,14 +16,20 @@
package android.hardware;
-import static android.system.OsConstants.*;
+import static android.system.OsConstants.EACCES;
+import static android.system.OsConstants.EBUSY;
+import static android.system.OsConstants.EINVAL;
+import static android.system.OsConstants.ENODEV;
+import static android.system.OsConstants.ENOSYS;
+import static android.system.OsConstants.EOPNOTSUPP;
+import static android.system.OsConstants.EUSERS;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityThread;
import android.app.AppOpsManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.graphics.ImageFormat;
import android.graphics.Point;
diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java
index 5b25f5a..e97a5b9 100644
--- a/core/java/android/hardware/HardwareBuffer.java
+++ b/core/java/android/hardware/HardwareBuffer.java
@@ -20,7 +20,7 @@
import android.annotation.IntRange;
import android.annotation.LongDef;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.GraphicBuffer;
import android.os.Build;
import android.os.Parcel;
diff --git a/core/java/android/hardware/Sensor.java b/core/java/android/hardware/Sensor.java
index e78fb7f..a71a7b6 100644
--- a/core/java/android/hardware/Sensor.java
+++ b/core/java/android/hardware/Sensor.java
@@ -18,7 +18,7 @@
package android.hardware;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
/**
diff --git a/core/java/android/hardware/SensorEvent.java b/core/java/android/hardware/SensorEvent.java
index 8c910b2..64c45bf 100644
--- a/core/java/android/hardware/SensorEvent.java
+++ b/core/java/android/hardware/SensorEvent.java
@@ -16,7 +16,7 @@
package android.hardware;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* This class represents a {@link android.hardware.Sensor Sensor} event and
diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
index 524a0c4..6bf754f 100644
--- a/core/java/android/hardware/SensorManager.java
+++ b/core/java/android/hardware/SensorManager.java
@@ -18,7 +18,7 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Build;
import android.os.Handler;
diff --git a/core/java/android/hardware/SerialManager.java b/core/java/android/hardware/SerialManager.java
index 571c3cc..b51382e 100644
--- a/core/java/android/hardware/SerialManager.java
+++ b/core/java/android/hardware/SerialManager.java
@@ -17,7 +17,7 @@
package android.hardware;
import android.annotation.SystemService;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
diff --git a/core/java/android/hardware/SerialPort.java b/core/java/android/hardware/SerialPort.java
index 78ac3c0..0fcaa49 100644
--- a/core/java/android/hardware/SerialPort.java
+++ b/core/java/android/hardware/SerialPort.java
@@ -16,12 +16,11 @@
package android.hardware;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.ParcelFileDescriptor;
import java.io.FileDescriptor;
import java.io.IOException;
-
import java.nio.ByteBuffer;
/**
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index 7abfabf..974913b 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -16,7 +16,7 @@
package android.hardware;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
diff --git a/core/java/android/hardware/biometrics/BiometricAuthenticator.java b/core/java/android/hardware/biometrics/BiometricAuthenticator.java
index 698876b..11cf2d6 100644
--- a/core/java/android/hardware/biometrics/BiometricAuthenticator.java
+++ b/core/java/android/hardware/biometrics/BiometricAuthenticator.java
@@ -18,6 +18,7 @@
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
+import android.hardware.biometrics.BiometricPrompt.AuthenticationResultType;
import android.os.CancellationSignal;
import android.os.Parcelable;
@@ -119,6 +120,7 @@
class AuthenticationResult {
private Identifier mIdentifier;
private CryptoObject mCryptoObject;
+ private @AuthenticationResultType int mAuthenticationType;
private int mUserId;
/**
@@ -129,27 +131,41 @@
/**
* Authentication result
* @param crypto
+ * @param authenticationType
* @param identifier
* @param userId
* @hide
*/
- public AuthenticationResult(CryptoObject crypto, Identifier identifier,
+ public AuthenticationResult(CryptoObject crypto,
+ @AuthenticationResultType int authenticationType, Identifier identifier,
int userId) {
mCryptoObject = crypto;
+ mAuthenticationType = authenticationType;
mIdentifier = identifier;
mUserId = userId;
}
/**
- * Obtain the crypto object associated with this transaction
- * @return crypto object provided to {@link BiometricAuthenticator#authenticate(
- * CryptoObject, CancellationSignal, Executor, AuthenticationCallback)}
+ * Provides the crypto object associated with this transaction.
+ * @return The crypto object provided to {@link BiometricPrompt#authenticate(
+ * BiometricPrompt.CryptoObject, CancellationSignal, Executor,
+ * BiometricPrompt.AuthenticationCallback)}
*/
public CryptoObject getCryptoObject() {
return mCryptoObject;
}
/**
+ * Provides the type of authentication (e.g. device credential or biometric) that was
+ * requested from and successfully provided by the user.
+ *
+ * @return An integer value representing the authentication method used.
+ */
+ public @AuthenticationResultType int getAuthenticationType() {
+ return mAuthenticationType;
+ }
+
+ /**
* Obtain the biometric identifier associated with this operation. Applications are strongly
* discouraged from associating specific identifiers with specific applications or
* operations.
diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java
index d28b7c5..5a13651 100644
--- a/core/java/android/hardware/biometrics/BiometricConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricConstants.java
@@ -18,8 +18,7 @@
import static android.hardware.biometrics.BiometricManager.Authenticators;
-import android.annotation.UnsupportedAppUsage;
-
+import android.compat.annotation.UnsupportedAppUsage;
/**
* Interface containing all of the biometric modality agnostic constants.
diff --git a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
index b025508..5c74456 100644
--- a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
@@ -16,8 +16,8 @@
package android.hardware.biometrics;
-import android.annotation.UnsupportedAppUsage;
import android.app.KeyguardManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.hardware.fingerprint.FingerprintManager;
/**
diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java
index cb8fc8b..a695ce8 100644
--- a/core/java/android/hardware/biometrics/BiometricPrompt.java
+++ b/core/java/android/hardware/biometrics/BiometricPrompt.java
@@ -21,6 +21,7 @@
import static android.hardware.biometrics.BiometricManager.Authenticators;
import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -40,6 +41,8 @@
import com.android.internal.R;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.security.Signature;
import java.util.concurrent.Executor;
@@ -397,9 +400,11 @@
new IBiometricServiceReceiver.Stub() {
@Override
- public void onAuthenticationSucceeded() throws RemoteException {
+ public void onAuthenticationSucceeded(@AuthenticationResultType int authenticationType)
+ throws RemoteException {
mExecutor.execute(() -> {
- final AuthenticationResult result = new AuthenticationResult(mCryptoObject);
+ final AuthenticationResult result =
+ new AuthenticationResult(mCryptoObject, authenticationType);
mAuthenticationCallback.onAuthenticationSucceeded(result);
});
}
@@ -576,28 +581,62 @@
}
/**
- * Container for callback data from {@link #authenticate( CancellationSignal, Executor,
+ * Authentication type reported by {@link AuthenticationResult} when the user authenticated by
+ * entering their device PIN, pattern, or password.
+ */
+ public static final int AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL = 1;
+
+ /**
+ * Authentication type reported by {@link AuthenticationResult} when the user authenticated by
+ * presenting some form of biometric (e.g. fingerprint or face).
+ */
+ public static final int AUTHENTICATION_RESULT_TYPE_BIOMETRIC = 2;
+
+ /**
+ * An {@link IntDef} representing the type of auth, as reported by {@link AuthenticationResult}.
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL, AUTHENTICATION_RESULT_TYPE_BIOMETRIC})
+ public @interface AuthenticationResultType {
+ }
+
+ /**
+ * Container for callback data from {@link #authenticate(CancellationSignal, Executor,
* AuthenticationCallback)} and {@link #authenticate(CryptoObject, CancellationSignal, Executor,
- * AuthenticationCallback)}
+ * AuthenticationCallback)}.
*/
public static class AuthenticationResult extends BiometricAuthenticator.AuthenticationResult {
/**
* Authentication result
* @param crypto
+ * @param authenticationType
* @hide
*/
- public AuthenticationResult(CryptoObject crypto) {
+ public AuthenticationResult(CryptoObject crypto,
+ @AuthenticationResultType int authenticationType) {
// Identifier and userId is not used for BiometricPrompt.
- super(crypto, null /* identifier */, 0 /* userId */);
+ super(crypto, authenticationType, null /* identifier */, 0 /* userId */);
}
+
/**
- * Obtain the crypto object associated with this transaction
- * @return crypto object provided to {@link #authenticate( CryptoObject, CancellationSignal,
- * Executor, AuthenticationCallback)}
+ * Provides the crypto object associated with this transaction.
+ * @return The crypto object provided to {@link #authenticate(CryptoObject,
+ * CancellationSignal, Executor, AuthenticationCallback)}
*/
public CryptoObject getCryptoObject() {
return (CryptoObject) super.getCryptoObject();
}
+
+ /**
+ * Provides the type of authentication (e.g. device credential or biometric) that was
+ * requested from and successfully provided by the user.
+ *
+ * @return An integer value representing the authentication method used.
+ */
+ public @AuthenticationResultType int getAuthenticationType() {
+ return super.getAuthenticationType();
+ }
}
/**
diff --git a/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl b/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl
index c960049..1d43aa6 100644
--- a/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricServiceReceiver.aidl
@@ -20,8 +20,8 @@
* @hide
*/
oneway interface IBiometricServiceReceiver {
- // Notify BiometricPrompt that authentication was successful
- void onAuthenticationSucceeded();
+ // Notify BiometricPrompt that authentication was successful.
+ void onAuthenticationSucceeded(int authenticationType);
// Noties that authentication failed.
void onAuthenticationFailed();
// Notify BiometricPrompt that an error has occurred.
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index b605866..7bddc1d 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.PublicKey;
import android.hardware.camera2.impl.SyntheticKey;
@@ -1212,7 +1212,7 @@
/**
* <p>Minimum and maximum zoom ratios supported by this camera device.</p>
* <p>If the camera device supports zoom-out from 1x zoom, minZoom will be less than 1.0, and
- * setting android.control.zoomRation to values less than 1.0 increases the camera's field
+ * setting {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} to values less than 1.0 increases the camera's field
* of view.</p>
* <p><b>Units</b>: A pair of zoom ratio in floating points: (minZoom, maxZoom)</p>
* <p><b>Range of valid values:</b><br></p>
@@ -1222,6 +1222,7 @@
* Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the
* {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} key</p>
*
+ * @see CaptureRequest#CONTROL_ZOOM_RATIO
* @see CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL
*/
@PublicKey
@@ -1538,10 +1539,15 @@
* <p><code>p' = Rp</code></p>
* <p>where <code>p</code> is in the device sensor coordinate system, and
* <code>p'</code> is in the camera-oriented coordinate system.</p>
+ * <p>If {@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference} is UNDEFINED, the quaternion rotation cannot
+ * be accurately represented by the camera device, and will be represented by
+ * default values matching its default facing.</p>
* <p><b>Units</b>:
* Quaternion coefficients</p>
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
* <p><b>Permission {@link android.Manifest.permission#CAMERA } is needed to access this property</b></p>
+ *
+ * @see CameraCharacteristics#LENS_POSE_REFERENCE
*/
@PublicKey
@NonNull
@@ -1576,6 +1582,8 @@
* <p>When {@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference} is GYROSCOPE, then this position is relative to
* the center of the primary gyroscope on the device. The axis definitions are the same as
* with PRIMARY_CAMERA.</p>
+ * <p>When {@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference} is UNDEFINED, this position cannot be accurately
+ * represented by the camera device, and will be represented as <code>(0, 0, 0)</code>.</p>
* <p><b>Units</b>: Meters</p>
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
* <p><b>Permission {@link android.Manifest.permission#CAMERA } is needed to access this property</b></p>
@@ -1713,20 +1721,24 @@
new Key<float[]>("android.lens.radialDistortion", float[].class);
/**
- * <p>The origin for {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation}.</p>
+ * <p>The origin for {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation}, and the accuracy of
+ * {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation} and {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation}.</p>
* <p>Different calibration methods and use cases can produce better or worse results
* depending on the selected coordinate origin.</p>
* <p><b>Possible values:</b>
* <ul>
* <li>{@link #LENS_POSE_REFERENCE_PRIMARY_CAMERA PRIMARY_CAMERA}</li>
* <li>{@link #LENS_POSE_REFERENCE_GYROSCOPE GYROSCOPE}</li>
+ * <li>{@link #LENS_POSE_REFERENCE_UNDEFINED UNDEFINED}</li>
* </ul></p>
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
* <p><b>Permission {@link android.Manifest.permission#CAMERA } is needed to access this property</b></p>
*
+ * @see CameraCharacteristics#LENS_POSE_ROTATION
* @see CameraCharacteristics#LENS_POSE_TRANSLATION
* @see #LENS_POSE_REFERENCE_PRIMARY_CAMERA
* @see #LENS_POSE_REFERENCE_GYROSCOPE
+ * @see #LENS_POSE_REFERENCE_UNDEFINED
*/
@PublicKey
@NonNull
diff --git a/core/java/android/hardware/camera2/CameraConstrainedHighSpeedCaptureSession.java b/core/java/android/hardware/camera2/CameraConstrainedHighSpeedCaptureSession.java
index 07d2443..6ead64c 100644
--- a/core/java/android/hardware/camera2/CameraConstrainedHighSpeedCaptureSession.java
+++ b/core/java/android/hardware/camera2/CameraConstrainedHighSpeedCaptureSession.java
@@ -25,9 +25,12 @@
* A constrained high speed capture session for a {@link CameraDevice}, used for capturing high
* speed images from the {@link CameraDevice} for high speed video recording use case.
* <p>
- * A CameraHighSpeedCaptureSession is created by providing a set of target output surfaces to
- * {@link CameraDevice#createConstrainedHighSpeedCaptureSession}, Once created, the session is
- * active until a new session is created by the camera device, or the camera device is closed.
+ * A CameraConstrainedHighSpeedCaptureSession is created by providing a session configuration to
+ * {@link CameraDevice#createCaptureSession(SessionConfiguration)} with a type of
+ * {@link android.hardware.camera2.params.SessionConfiguration#SESSION_HIGH_SPEED}. The
+ * CameraCaptureSession returned from {@link CameraCaptureSession.StateCallback} can then be cast to
+ * a CameraConstrainedHighSpeedCaptureSession. Once created, the session is active until a new
+ * session is created by the camera device, or the camera device is closed.
* </p>
* <p>
* An active high speed capture session is a specialized capture session that is only targeted at
@@ -37,8 +40,8 @@
* accepts request lists created via {@link #createHighSpeedRequestList}, and the request list can
* only be submitted to this session via {@link CameraCaptureSession#captureBurst captureBurst}, or
* {@link CameraCaptureSession#setRepeatingBurst setRepeatingBurst}. See
- * {@link CameraDevice#createConstrainedHighSpeedCaptureSession} for more details of the
- * limitations.
+ * {@link CameraDevice#createCaptureSession(android.hardware.camera2.params.SessionConfiguration)}
+ * for more details of the limitations.
* </p>
* <p>
* Creating a session is an expensive operation and can take several hundred milliseconds, since it
@@ -50,13 +53,6 @@
* completed, then the {@link CameraCaptureSession.StateCallback#onConfigureFailed} is called, and
* the session will not become active.
* </p>
- * <!--
- * <p>
- * Any capture requests (repeating or non-repeating) submitted before the session is ready will be
- * queued up and will begin capture once the session becomes ready. In case the session cannot be
- * configured and {@link CameraCaptureSession.StateCallback#onConfigureFailed onConfigureFailed} is
- * called, all queued capture requests are discarded. </p>
- * -->
* <p>
* If a new session is created by the camera device, then the previous session is closed, and its
* associated {@link CameraCaptureSession.StateCallback#onClosed onClosed} callback will be
diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java
index fb1ece2..cc06681 100644
--- a/core/java/android/hardware/camera2/CameraDevice.java
+++ b/core/java/android/hardware/camera2/CameraDevice.java
@@ -16,24 +16,22 @@
package android.hardware.camera2;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.IntDef;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import static android.hardware.camera2.ICameraDeviceUser.NORMAL_MODE;
-import static android.hardware.camera2.ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE;
import android.hardware.camera2.params.InputConfiguration;
-import android.hardware.camera2.params.StreamConfigurationMap;
import android.hardware.camera2.params.OutputConfiguration;
import android.hardware.camera2.params.SessionConfiguration;
+import android.hardware.camera2.params.StreamConfigurationMap;
import android.os.Handler;
import android.view.Surface;
-import java.util.List;
-import java.util.Set;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import java.util.Set;
/**
* <p>The CameraDevice class is a representation of a single camera connected to an
@@ -220,6 +218,224 @@
* <p>Create a new camera capture session by providing the target output set of Surfaces to the
* camera device.</p>
*
+ * @param outputs The new set of Surfaces that should be made available as
+ * targets for captured image data.
+ * @param callback The callback to notify about the status of the new capture session.
+ * @param handler The handler on which the callback should be invoked, or {@code null} to use
+ * the current thread's {@link android.os.Looper looper}.
+ *
+ * @throws IllegalArgumentException if the set of output Surfaces do not meet the requirements,
+ * the callback is null, or the handler is null but the current
+ * thread has no looper.
+ * @throws CameraAccessException if the camera device is no longer connected or has
+ * encountered a fatal error
+ * @throws IllegalStateException if the camera device has been closed
+ *
+ * @see CameraCaptureSession
+ * @see StreamConfigurationMap#getOutputFormats()
+ * @see StreamConfigurationMap#getOutputSizes(int)
+ * @see StreamConfigurationMap#getOutputSizes(Class)
+ * @deprecated Please use @{link
+ * #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the
+ * full set of configuration options available.
+ */
+ @Deprecated
+ public abstract void createCaptureSession(@NonNull List<Surface> outputs,
+ @NonNull CameraCaptureSession.StateCallback callback, @Nullable Handler handler)
+ throws CameraAccessException;
+
+ /**
+ * <p>Create a new camera capture session by providing the target output set of Surfaces and
+ * its corresponding surface configuration to the camera device.</p>
+ *
+ * @see #createCaptureSession
+ * @see OutputConfiguration
+ * @deprecated Please use @{link
+ * #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the
+ * full set of configuration options available.
+ */
+ @Deprecated
+ public abstract void createCaptureSessionByOutputConfigurations(
+ List<OutputConfiguration> outputConfigurations,
+ CameraCaptureSession.StateCallback callback, @Nullable Handler handler)
+ throws CameraAccessException;
+ /**
+ * Create a new reprocessable camera capture session by providing the desired reprocessing
+ * input Surface configuration and the target output set of Surfaces to the camera device.
+ *
+ * @param inputConfig The configuration for the input {@link Surface}
+ * @param outputs The new set of Surfaces that should be made available as
+ * targets for captured image data.
+ * @param callback The callback to notify about the status of the new capture session.
+ * @param handler The handler on which the callback should be invoked, or {@code null} to use
+ * the current thread's {@link android.os.Looper looper}.
+ *
+ * @throws IllegalArgumentException if the input configuration is null or not supported, the set
+ * of output Surfaces do not meet the requirements, the
+ * callback is null, or the handler is null but the current
+ * thread has no looper.
+ * @throws CameraAccessException if the camera device is no longer connected or has
+ * encountered a fatal error
+ * @throws IllegalStateException if the camera device has been closed
+ *
+ * @see #createCaptureSession
+ * @see CameraCaptureSession
+ * @see StreamConfigurationMap#getInputFormats
+ * @see StreamConfigurationMap#getInputSizes
+ * @see StreamConfigurationMap#getValidOutputFormatsForInput
+ * @see StreamConfigurationMap#getOutputSizes
+ * @see android.media.ImageWriter
+ * @see android.media.ImageReader
+ * @deprecated Please use @{link
+ * #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the
+ * full set of configuration options available.
+ */
+ @Deprecated
+ public abstract void createReprocessableCaptureSession(@NonNull InputConfiguration inputConfig,
+ @NonNull List<Surface> outputs, @NonNull CameraCaptureSession.StateCallback callback,
+ @Nullable Handler handler)
+ throws CameraAccessException;
+
+ /**
+ * Create a new reprocessable camera capture session by providing the desired reprocessing
+ * input configuration and output {@link OutputConfiguration}
+ * to the camera device.
+ *
+ * @see #createReprocessableCaptureSession
+ * @see OutputConfiguration
+ * @deprecated Please use @{link
+ * #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the
+ * full set of configuration options available.
+ */
+ @Deprecated
+ public abstract void createReprocessableCaptureSessionByConfigurations(
+ @NonNull InputConfiguration inputConfig,
+ @NonNull List<OutputConfiguration> outputs,
+ @NonNull CameraCaptureSession.StateCallback callback,
+ @Nullable Handler handler)
+ throws CameraAccessException;
+
+ /**
+ * <p>Create a new constrained high speed capture session.</p>
+ *
+ * @param outputs The new set of Surfaces that should be made available as
+ * targets for captured high speed image data.
+ * @param callback The callback to notify about the status of the new capture session.
+ * @param handler The handler on which the callback should be invoked, or {@code null} to use
+ * the current thread's {@link android.os.Looper looper}.
+ *
+ * @throws IllegalArgumentException if the set of output Surfaces do not meet the requirements,
+ * the callback is null, or the handler is null but the current
+ * thread has no looper, or the camera device doesn't support
+ * high speed video capability.
+ * @throws CameraAccessException if the camera device is no longer connected or has
+ * encountered a fatal error
+ * @throws IllegalStateException if the camera device has been closed
+ *
+ * @see #createCaptureSession
+ * @see CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE
+ * @see StreamConfigurationMap#getHighSpeedVideoSizes
+ * @see StreamConfigurationMap#getHighSpeedVideoFpsRangesFor
+ * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
+ * @see CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO
+ * @see CameraCaptureSession#captureBurst
+ * @see CameraCaptureSession#setRepeatingBurst
+ * @see CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList
+ * @deprecated Please use @{link
+ * #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the
+ * full set of configuration options available.
+ */
+ @Deprecated
+ public abstract void createConstrainedHighSpeedCaptureSession(@NonNull List<Surface> outputs,
+ @NonNull CameraCaptureSession.StateCallback callback,
+ @Nullable Handler handler)
+ throws CameraAccessException;
+
+ /**
+ * Standard camera operation mode.
+ *
+ * @see #createCustomCaptureSession
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public static final int SESSION_OPERATION_MODE_NORMAL =
+ 0; // ICameraDeviceUser.NORMAL_MODE;
+
+ /**
+ * Constrained high-speed operation mode.
+ *
+ * @see #createCustomCaptureSession
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public static final int SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED =
+ 1; // ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE;
+
+ /**
+ * First vendor-specific operating mode
+ *
+ * @see #createCustomCaptureSession
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public static final int SESSION_OPERATION_MODE_VENDOR_START =
+ 0x8000; // ICameraDeviceUser.VENDOR_MODE_START;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"SESSION_OPERATION_MODE"}, value =
+ {SESSION_OPERATION_MODE_NORMAL,
+ SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED,
+ SESSION_OPERATION_MODE_VENDOR_START})
+ public @interface SessionOperatingMode {};
+
+ /**
+ * Create a new camera capture session with a custom operating mode.
+ *
+ * @param inputConfig The configuration for the input {@link Surface} if a reprocessing session
+ * is desired, or {@code null} otherwise.
+ * @param outputs The new set of {@link OutputConfiguration OutputConfigurations} that should be
+ * made available as targets for captured image data.
+ * @param operatingMode The custom operating mode to use; a nonnegative value, either a custom
+ * vendor value or one of the SESSION_OPERATION_MODE_* values.
+ * @param callback The callback to notify about the status of the new capture session.
+ * @param handler The handler on which the callback should be invoked, or {@code null} to use
+ * the current thread's {@link android.os.Looper looper}.
+ *
+ * @throws IllegalArgumentException if the input configuration is null or not supported, the set
+ * of output Surfaces do not meet the requirements, the
+ * callback is null, or the handler is null but the current
+ * thread has no looper.
+ * @throws CameraAccessException if the camera device is no longer connected or has
+ * encountered a fatal error
+ * @throws IllegalStateException if the camera device has been closed
+ *
+ * @see #createCaptureSession
+ * @see #createReprocessableCaptureSession
+ * @see CameraCaptureSession
+ * @see OutputConfiguration
+ * @deprecated Please use @{link
+ * #createCaptureSession(android.hardware.camera2.params.SessionConfiguration)} for the
+ * full set of configuration options available.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ @Deprecated
+ public abstract void createCustomCaptureSession(
+ InputConfiguration inputConfig,
+ @NonNull List<OutputConfiguration> outputs,
+ @SessionOperatingMode int operatingMode,
+ @NonNull CameraCaptureSession.StateCallback callback,
+ @Nullable Handler handler)
+ throws CameraAccessException;
+
+ /**
+ * <p>Create a new {@link CameraCaptureSession} using a {@link SessionConfiguration} helper
+ * object that aggregates all supported parameters.</p>
* <p>The active capture session determines the set of potential output Surfaces for
* the camera device for each capture request. A given request may use all
* or only some of the outputs. Once the CameraCaptureSession is created, requests can be
@@ -308,11 +524,15 @@
* <p>Configuring a session with an empty or null list will close the current session, if
* any. This can be used to release the current session's target surfaces for another use.</p>
*
+ * <h3>Regular capture</h3>
+ *
* <p>While any of the sizes from {@link StreamConfigurationMap#getOutputSizes} can be used when
* a single output stream is configured, a given camera device may not be able to support all
* combination of sizes, formats, and targets when multiple outputs are configured at once. The
* tables below list the maximum guaranteed resolutions for combinations of streams and targets,
- * given the capabilities of the camera device.</p>
+ * given the capabilities of the camera device. These are valid for when the
+ * {@link android.hardware.camera2.params.SessionConfiguration#setInputConfiguration
+ * input configuration} is not set and therefore no reprocessing is active.</p>
*
* <p>If an application tries to create a session using a set of targets that exceed the limits
* described in the below tables, one of three possibilities may occur. First, the session may
@@ -488,56 +708,22 @@
* (either width or height) might not be supported, and capture session creation will fail if it
* is not.</p>
*
- * @param outputs The new set of Surfaces that should be made available as
- * targets for captured image data.
- * @param callback The callback to notify about the status of the new capture session.
- * @param handler The handler on which the callback should be invoked, or {@code null} to use
- * the current thread's {@link android.os.Looper looper}.
- *
- * @throws IllegalArgumentException if the set of output Surfaces do not meet the requirements,
- * the callback is null, or the handler is null but the current
- * thread has no looper.
- * @throws CameraAccessException if the camera device is no longer connected or has
- * encountered a fatal error
- * @throws IllegalStateException if the camera device has been closed
- *
- * @see CameraCaptureSession
- * @see StreamConfigurationMap#getOutputFormats()
- * @see StreamConfigurationMap#getOutputSizes(int)
- * @see StreamConfigurationMap#getOutputSizes(Class)
- */
- public abstract void createCaptureSession(@NonNull List<Surface> outputs,
- @NonNull CameraCaptureSession.StateCallback callback, @Nullable Handler handler)
- throws CameraAccessException;
-
- /**
- * <p>Create a new camera capture session by providing the target output set of Surfaces and
- * its corresponding surface configuration to the camera device.</p>
- *
- * @see #createCaptureSession
- * @see OutputConfiguration
- */
- public abstract void createCaptureSessionByOutputConfigurations(
- List<OutputConfiguration> outputConfigurations,
- CameraCaptureSession.StateCallback callback, @Nullable Handler handler)
- throws CameraAccessException;
- /**
- * Create a new reprocessable camera capture session by providing the desired reprocessing
- * input Surface configuration and the target output set of Surfaces to the camera device.
+ * <h3>Reprocessing</h3>
*
* <p>If a camera device supports YUV reprocessing
* ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_YUV_REPROCESSING}) or PRIVATE
* reprocessing
- * ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING}), besides
- * the capture session created via {@link #createCaptureSession createCaptureSession}, the
+ * ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING}), the
* application can also create a reprocessable capture session to submit reprocess capture
- * requests in addition to regular capture requests. A reprocess capture request takes the next
- * available buffer from the session's input Surface, and sends it through the camera device's
- * processing pipeline again, to produce buffers for the request's target output Surfaces. No
- * new image data is captured for a reprocess request. However the input buffer provided by
- * the application must be captured previously by the same camera device in the same session
- * directly (e.g. for Zero-Shutter-Lag use case) or indirectly (e.g. combining multiple output
- * images).</p>
+ * requests in addition to regular capture requests, by setting an
+ * {@link android.hardware.camera2.params.SessionConfiguration#setInputConfiguration
+ * input configuration} for the session. A reprocess capture request takes the next available
+ * buffer from the
+ * session's input Surface, and sends it through the camera device's processing pipeline again,
+ * to produce buffers for the request's target output Surfaces. No new image data is captured
+ * for a reprocess request. However the input buffer provided by the application must be
+ * captured previously by the same camera device in the same session directly (e.g. for
+ * Zero-Shutter-Lag use case) or indirectly (e.g. combining multiple output images).</p>
*
* <p>The active reprocessable capture session determines an input {@link Surface} and the set
* of potential output Surfaces for the camera devices for each capture request. The application
@@ -570,10 +756,7 @@
* <p>Starting from API level 30, recreating a reprocessable capture session will flush all the
* queued but not yet processed buffers from the input surface.</p>
*
- * <p>The guaranteed stream configurations listed in
- * {@link #createCaptureSession createCaptureSession} are also guaranteed to work for
- * {@link #createReprocessableCaptureSession createReprocessableCaptureSession}. In addition,
- * the configurations in the tables below are also guaranteed for creating a reprocessable
+ * <p>The configurations in the tables below are guaranteed for creating a reprocessable
* capture session if the camera device supports YUV reprocessing or PRIVATE reprocessing.
* However, not all output targets used to create a reprocessable session may be used in a
* {@link CaptureRequest} simultaneously. For devices that support only 1 output target in a
@@ -602,7 +785,7 @@
* <p>LIMITED-level ({@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL}
* {@code == }{@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED LIMITED}) devices
* support at least the following stream combinations for creating a reprocessable capture
- * session in addition to those listed in {@link #createCaptureSession createCaptureSession} for
+ * session in addition to those listed earlier for regular captures for
* {@link CameraMetadata#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED LIMITED} devices:
*
* <table>
@@ -671,74 +854,30 @@
* </table><br>
* </p>
*
- * <p>Clients can access the above mandatory stream combination tables via
- * {@link android.hardware.camera2.params.MandatoryStreamCombination}.</p>
+ * <h3>Constrained high-speed recording</h3>
*
- * @param inputConfig The configuration for the input {@link Surface}
- * @param outputs The new set of Surfaces that should be made available as
- * targets for captured image data.
- * @param callback The callback to notify about the status of the new capture session.
- * @param handler The handler on which the callback should be invoked, or {@code null} to use
- * the current thread's {@link android.os.Looper looper}.
- *
- * @throws IllegalArgumentException if the input configuration is null or not supported, the set
- * of output Surfaces do not meet the requirements, the
- * callback is null, or the handler is null but the current
- * thread has no looper.
- * @throws CameraAccessException if the camera device is no longer connected or has
- * encountered a fatal error
- * @throws IllegalStateException if the camera device has been closed
- *
- * @see #createCaptureSession
- * @see CameraCaptureSession
- * @see StreamConfigurationMap#getInputFormats
- * @see StreamConfigurationMap#getInputSizes
- * @see StreamConfigurationMap#getValidOutputFormatsForInput
- * @see StreamConfigurationMap#getOutputSizes
- * @see android.media.ImageWriter
- * @see android.media.ImageReader
- */
- public abstract void createReprocessableCaptureSession(@NonNull InputConfiguration inputConfig,
- @NonNull List<Surface> outputs, @NonNull CameraCaptureSession.StateCallback callback,
- @Nullable Handler handler)
- throws CameraAccessException;
-
- /**
- * Create a new reprocessable camera capture session by providing the desired reprocessing
- * input configuration and output {@link OutputConfiguration}
- * to the camera device.
- *
- * @see #createReprocessableCaptureSession
- * @see OutputConfiguration
- *
- */
- public abstract void createReprocessableCaptureSessionByConfigurations(
- @NonNull InputConfiguration inputConfig,
- @NonNull List<OutputConfiguration> outputs,
- @NonNull CameraCaptureSession.StateCallback callback,
- @Nullable Handler handler)
- throws CameraAccessException;
-
- /**
- * <p>Create a new constrained high speed capture session.</p>
- *
- * <p>The application can use normal capture session (created via {@link #createCaptureSession})
+ * <p>The application can use a
+ * {@link android.hardware.camera2.params.SessionConfiguration#SESSION_REGULAR
+ * normal capture session}
* for high speed capture if the desired high speed FPS ranges are advertised by
* {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES}, in which case all API
* semantics associated with normal capture sessions applies.</p>
*
- * <p>The method creates a specialized capture session that is only targeted at high speed
- * video recording (>=120fps) use case if the camera device supports high speed video
- * capability (i.e., {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES} contains
- * {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO}).
- * Therefore, it has special characteristics compared with a normal capture session:</p>
+ * <p>A
+ * {@link android.hardware.camera2.params.SessionConfiguration#SESSION_HIGH_SPEED
+ * high-speed capture session}
+ * can be use for high speed video recording (>=120fps) when the camera device supports high
+ * speed video capability (i.e., {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES}
+ * contains {@link CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO}).
+ * A constrained high-speed capture session has special limitations compared with a normal
+ * capture session:</p>
*
* <ul>
*
- * <li>In addition to the output target Surface requirements specified by the
- * {@link #createCaptureSession} method, an active high speed capture session will support up
- * to 2 output Surfaces, though the application might choose to configure just one Surface
- * (e.g., preview only). All Surfaces must be either video encoder surfaces (acquired by
+ * <li>In addition to the output target Surface requirements specified above for regular
+ * captures, a high speed capture session will only support up to 2 output Surfaces, though
+ * the application might choose to configure just one Surface (e.g., preview only). All
+ * Surfaces must be either video encoder surfaces (acquired by
* {@link android.media.MediaRecorder#getSurface} or
* {@link android.media.MediaCodec#createInputSurface}) or preview surfaces (obtained from
* {@link android.view.SurfaceView}, {@link android.graphics.SurfaceTexture} via
@@ -774,116 +913,6 @@
*
* </ul>
*
- * @param outputs The new set of Surfaces that should be made available as
- * targets for captured high speed image data.
- * @param callback The callback to notify about the status of the new capture session.
- * @param handler The handler on which the callback should be invoked, or {@code null} to use
- * the current thread's {@link android.os.Looper looper}.
- *
- * @throws IllegalArgumentException if the set of output Surfaces do not meet the requirements,
- * the callback is null, or the handler is null but the current
- * thread has no looper, or the camera device doesn't support
- * high speed video capability.
- * @throws CameraAccessException if the camera device is no longer connected or has
- * encountered a fatal error
- * @throws IllegalStateException if the camera device has been closed
- *
- * @see #createCaptureSession
- * @see CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE
- * @see StreamConfigurationMap#getHighSpeedVideoSizes
- * @see StreamConfigurationMap#getHighSpeedVideoFpsRangesFor
- * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
- * @see CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_CONSTRAINED_HIGH_SPEED_VIDEO
- * @see CameraCaptureSession#captureBurst
- * @see CameraCaptureSession#setRepeatingBurst
- * @see CameraConstrainedHighSpeedCaptureSession#createHighSpeedRequestList
- */
- public abstract void createConstrainedHighSpeedCaptureSession(@NonNull List<Surface> outputs,
- @NonNull CameraCaptureSession.StateCallback callback,
- @Nullable Handler handler)
- throws CameraAccessException;
-
- /**
- * Standard camera operation mode.
- *
- * @see #createCustomCaptureSession
- * @hide
- */
- @SystemApi
- @TestApi
- public static final int SESSION_OPERATION_MODE_NORMAL =
- 0; // ICameraDeviceUser.NORMAL_MODE;
-
- /**
- * Constrained high-speed operation mode.
- *
- * @see #createCustomCaptureSession
- * @hide
- */
- @SystemApi
- @TestApi
- public static final int SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED =
- 1; // ICameraDeviceUser.CONSTRAINED_HIGH_SPEED_MODE;
-
- /**
- * First vendor-specific operating mode
- *
- * @see #createCustomCaptureSession
- * @hide
- */
- @SystemApi
- @TestApi
- public static final int SESSION_OPERATION_MODE_VENDOR_START =
- 0x8000; // ICameraDeviceUser.VENDOR_MODE_START;
-
- /** @hide */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = {"SESSION_OPERATION_MODE"}, value =
- {SESSION_OPERATION_MODE_NORMAL,
- SESSION_OPERATION_MODE_CONSTRAINED_HIGH_SPEED,
- SESSION_OPERATION_MODE_VENDOR_START})
- public @interface SessionOperatingMode {};
-
- /**
- * Create a new camera capture session with a custom operating mode.
- *
- * @param inputConfig The configuration for the input {@link Surface} if a reprocessing session
- * is desired, or {@code null} otherwise.
- * @param outputs The new set of {@link OutputConfiguration OutputConfigurations} that should be
- * made available as targets for captured image data.
- * @param operatingMode The custom operating mode to use; a nonnegative value, either a custom
- * vendor value or one of the SESSION_OPERATION_MODE_* values.
- * @param callback The callback to notify about the status of the new capture session.
- * @param handler The handler on which the callback should be invoked, or {@code null} to use
- * the current thread's {@link android.os.Looper looper}.
- *
- * @throws IllegalArgumentException if the input configuration is null or not supported, the set
- * of output Surfaces do not meet the requirements, the
- * callback is null, or the handler is null but the current
- * thread has no looper.
- * @throws CameraAccessException if the camera device is no longer connected or has
- * encountered a fatal error
- * @throws IllegalStateException if the camera device has been closed
- *
- * @see #createCaptureSession
- * @see #createReprocessableCaptureSession
- * @see CameraCaptureSession
- * @see OutputConfiguration
- * @hide
- */
- @SystemApi
- @TestApi
- public abstract void createCustomCaptureSession(
- InputConfiguration inputConfig,
- @NonNull List<OutputConfiguration> outputs,
- @SessionOperatingMode int operatingMode,
- @NonNull CameraCaptureSession.StateCallback callback,
- @Nullable Handler handler)
- throws CameraAccessException;
-
- /**
- * <p>Create a new {@link CameraCaptureSession} using a {@link SessionConfiguration} helper
- * object that aggregates all supported parameters.</p>
*
* @param config A session configuration (see {@link SessionConfiguration}).
*
@@ -997,7 +1026,7 @@
*
* @see CaptureRequest.Builder
* @see TotalCaptureResult
- * @see CameraDevice#createReprocessableCaptureSession
+ * @see CameraDevice#createCaptureSession(android.hardware.camera2.params.SessionConfiguration)
* @see android.media.ImageWriter
*/
@NonNull
@@ -1028,7 +1057,8 @@
* <p>This method performs a runtime check of a given {@link SessionConfiguration}. The result
* confirms whether or not the passed session configuration can be successfully used to
* create a camera capture session using
- * {@link CameraDevice#createCaptureSession(SessionConfiguration)}.
+ * {@link CameraDevice#createCaptureSession(
+ * android.hardware.camera2.params.SessionConfiguration)}.
* </p>
*
* <p>The method can be called at any point before, during and after active capture session.
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 8e0a46d..2377ccd 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -366,6 +366,20 @@
*/
public static final int LENS_POSE_REFERENCE_GYROSCOPE = 1;
+ /**
+ * <p>The camera device cannot represent the values of {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation}
+ * and {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation} accurately enough. One such example is a camera device
+ * on the cover of a foldable phone: in order to measure the pose translation and rotation,
+ * some kind of hinge position sensor would be needed.</p>
+ * <p>The value of {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation} must be all zeros, and
+ * {@link CameraCharacteristics#LENS_POSE_ROTATION android.lens.poseRotation} must be values matching its default facing.</p>
+ *
+ * @see CameraCharacteristics#LENS_POSE_ROTATION
+ * @see CameraCharacteristics#LENS_POSE_TRANSLATION
+ * @see CameraCharacteristics#LENS_POSE_REFERENCE
+ */
+ public static final int LENS_POSE_REFERENCE_UNDEFINED = 2;
+
//
// Enumeration values for CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES
//
@@ -1121,12 +1135,16 @@
//
/**
- * <p>Timestamps from {@link CaptureResult#SENSOR_TIMESTAMP android.sensor.timestamp} are in nanoseconds and monotonic,
- * but can not be compared to timestamps from other subsystems
- * (e.g. accelerometer, gyro etc.), or other instances of the same or different
- * camera devices in the same system. Timestamps between streams and results for
- * a single camera instance are comparable, and the timestamps for all buffers
- * and the result metadata generated by a single capture are identical.</p>
+ * <p>Timestamps from {@link CaptureResult#SENSOR_TIMESTAMP android.sensor.timestamp} are in nanoseconds and monotonic, but can
+ * not be compared to timestamps from other subsystems (e.g. accelerometer, gyro etc.),
+ * or other instances of the same or different camera devices in the same system with
+ * accuracy. However, the timestamps are roughly in the same timebase as
+ * {@link android.os.SystemClock#uptimeMillis }. The accuracy is sufficient for tasks
+ * like A/V synchronization for video recording, at least, and the timestamps can be
+ * directly used together with timestamps from the audio subsystem for that task.</p>
+ * <p>Timestamps between streams and results for a single camera instance are comparable,
+ * and the timestamps for all buffers and the result metadata generated by a single
+ * capture are identical.</p>
*
* @see CaptureResult#SENSOR_TIMESTAMP
* @see CameraCharacteristics#SENSOR_INFO_TIMESTAMP_SOURCE
@@ -1137,6 +1155,14 @@
* <p>Timestamps from {@link CaptureResult#SENSOR_TIMESTAMP android.sensor.timestamp} are in the same timebase as
* {@link android.os.SystemClock#elapsedRealtimeNanos },
* and they can be compared to other timestamps using that base.</p>
+ * <p>When buffers from a REALTIME device are passed directly to a video encoder from the
+ * camera, automatic compensation is done to account for differing timebases of the
+ * audio and camera subsystems. If the application is receiving buffers and then later
+ * sending them to a video encoder or other application where they are compared with
+ * audio subsystem timestamps or similar, this compensation is not present. In those
+ * cases, applications need to adjust the timestamps themselves. Since {@link android.os.SystemClock#elapsedRealtimeNanos } and {@link android.os.SystemClock#uptimeMillis } only diverge while the device is asleep, an
+ * offset between the two sources can be measured once per active session and applied
+ * to timestamps for sufficient accuracy for A/V sync.</p>
*
* @see CaptureResult#SENSOR_TIMESTAMP
* @see CameraCharacteristics#SENSOR_INFO_TIMESTAMP_SOURCE
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 6bf5783..b9af8f5 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.PublicKey;
import android.hardware.camera2.impl.SyntheticKey;
@@ -2154,7 +2154,7 @@
* stream combinations of LIMITED hardware level are guaranteed.</p>
* <p>For a logical multi-camera, bokeh may be implemented by stereo vision from sub-cameras
* with different field of view. As a result, when bokeh mode is enabled, the camera device
- * may override android.scaler.CropRegion, and the field of view will be smaller than when
+ * may override {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}, and the field of view will be smaller than when
* bokeh mode is off.</p>
* <p><b>Possible values:</b>
* <ul>
@@ -2163,6 +2163,8 @@
* <li>{@link #CONTROL_BOKEH_MODE_CONTINUOUS CONTINUOUS}</li>
* </ul></p>
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#SCALER_CROP_REGION
* @see #CONTROL_BOKEH_MODE_OFF
* @see #CONTROL_BOKEH_MODE_STILL_CAPTURE
* @see #CONTROL_BOKEH_MODE_CONTINUOUS
@@ -2740,32 +2742,70 @@
* <p>For devices not supporting {@link CaptureRequest#DISTORTION_CORRECTION_MODE android.distortionCorrection.mode} control, the coordinate
* system always follows that of {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}, with <code>(0, 0)</code> being
* the top-left pixel of the active array.</p>
- * <p>For devices supporting {@link CaptureRequest#DISTORTION_CORRECTION_MODE android.distortionCorrection.mode} control, the coordinate
- * system depends on the mode being set.
- * When the distortion correction mode is OFF, the coordinate system follows
- * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}, with
- * <code>(0, 0)</code> being the top-left pixel of the pre-correction active array.
- * When the distortion correction mode is not OFF, the coordinate system follows
- * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}, with
- * <code>(0, 0)</code> being the top-left pixel of the active array.</p>
- * <p>Output streams use this rectangle to produce their output,
- * cropping to a smaller region if necessary to maintain the
- * stream's aspect ratio, then scaling the sensor input to
+ * <p>For devices supporting {@link CaptureRequest#DISTORTION_CORRECTION_MODE android.distortionCorrection.mode} control, the coordinate system
+ * depends on the mode being set. When the distortion correction mode is OFF, the
+ * coordinate system follows {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}, with <code>(0,
+ * 0)</code> being the top-left pixel of the pre-correction active array. When the distortion
+ * correction mode is not OFF, the coordinate system follows
+ * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}, with <code>(0, 0)</code> being the top-left pixel of the
+ * active array.</p>
+ * <p>Output streams use this rectangle to produce their output, cropping to a smaller region
+ * if necessary to maintain the stream's aspect ratio, then scaling the sensor input to
* match the output's configured resolution.</p>
- * <p>The crop region is applied after the RAW to other color
- * space (e.g. YUV) conversion. Since raw streams
- * (e.g. RAW16) don't have the conversion stage, they are not
+ * <p>The crop region is applied after the RAW to other color space (e.g. YUV)
+ * conversion. Since raw streams (e.g. RAW16) don't have the conversion stage, they are not
* croppable. The crop region will be ignored by raw streams.</p>
- * <p>For non-raw streams, any additional per-stream cropping will
- * be done to maximize the final pixel area of the stream.</p>
- * <p>For example, if the crop region is set to a 4:3 aspect
- * ratio, then 4:3 streams will use the exact crop
- * region. 16:9 streams will further crop vertically
- * (letterbox).</p>
- * <p>Conversely, if the crop region is set to a 16:9, then 4:3
- * outputs will crop horizontally (pillarbox), and 16:9
- * streams will match exactly. These additional crops will
- * be centered within the crop region.</p>
+ * <p>For non-raw streams, any additional per-stream cropping will be done to maximize the
+ * final pixel area of the stream.</p>
+ * <p>For example, if the crop region is set to a 4:3 aspect ratio, then 4:3 streams will use
+ * the exact crop region. 16:9 streams will further crop vertically (letterbox).</p>
+ * <p>Conversely, if the crop region is set to a 16:9, then 4:3 outputs will crop horizontally
+ * (pillarbox), and 16:9 streams will match exactly. These additional crops will be
+ * centered within the crop region.</p>
+ * <p>To illustrate, here are several scenarios of different crop regions and output streams,
+ * for a hypothetical camera device with an active array of size <code>(2000,1500)</code>. Note that
+ * several of these examples use non-centered crop regions for ease of illustration; such
+ * regions are only supported on devices with FREEFORM capability
+ * ({@link CameraCharacteristics#SCALER_CROPPING_TYPE android.scaler.croppingType} <code>== FREEFORM</code>), but this does not affect the way the crop
+ * rules work otherwise.</p>
+ * <ul>
+ * <li>Camera Configuration:<ul>
+ * <li>Active array size: <code>2000x1500</code> (3 MP, 4:3 aspect ratio)</li>
+ * <li>Output stream #1: <code>640x480</code> (VGA, 4:3 aspect ratio)</li>
+ * <li>Output stream #2: <code>1280x720</code> (720p, 16:9 aspect ratio)</li>
+ * </ul>
+ * </li>
+ * <li>Case #1: 4:3 crop region with 2x digital zoom<ul>
+ * <li>Crop region: <code>Rect(500, 375, 1500, 1125) // (left, top, right, bottom)</code></li>
+ * <li><img alt="4:3 aspect ratio crop diagram" src="/reference/images/camera2/metadata/android.scaler.cropRegion/crop-region-43-ratio.png" /></li>
+ * <li><code>640x480</code> stream source area: <code>(500, 375, 1500, 1125)</code> (equal to crop region)</li>
+ * <li><code>1280x720</code> stream source area: <code>(500, 469, 1500, 1031)</code> (letterboxed)</li>
+ * </ul>
+ * </li>
+ * <li>Case #2: 16:9 crop region with ~1.5x digital zoom.<ul>
+ * <li>Crop region: <code>Rect(500, 375, 1833, 1125)</code></li>
+ * <li><img alt="16:9 aspect ratio crop diagram" src="/reference/images/camera2/metadata/android.scaler.cropRegion/crop-region-169-ratio.png" /></li>
+ * <li><code>640x480</code> stream source area: <code>(666, 375, 1666, 1125)</code> (pillarboxed)</li>
+ * <li><code>1280x720</code> stream source area: <code>(500, 375, 1833, 1125)</code> (equal to crop region)</li>
+ * </ul>
+ * </li>
+ * <li>Case #3: 1:1 crop region with ~2.6x digital zoom.<ul>
+ * <li>Crop region: <code>Rect(500, 375, 1250, 1125)</code></li>
+ * <li><img alt="1:1 aspect ratio crop diagram" src="/reference/images/camera2/metadata/android.scaler.cropRegion/crop-region-11-ratio.png" /></li>
+ * <li><code>640x480</code> stream source area: <code>(500, 469, 1250, 1031)</code> (letterboxed)</li>
+ * <li><code>1280x720</code> stream source area: <code>(500, 543, 1250, 957)</code> (letterboxed)</li>
+ * </ul>
+ * </li>
+ * <li>Case #4: Replace <code>640x480</code> stream with <code>1024x1024</code> stream, with 4:3 crop region:<ul>
+ * <li>Crop region: <code>Rect(500, 375, 1500, 1125)</code></li>
+ * <li><img alt="Square output, 4:3 aspect ratio crop diagram" src="/reference/images/camera2/metadata/android.scaler.cropRegion/crop-region-43-square-ratio.png" /></li>
+ * <li><code>1024x1024</code> stream source area: <code>(625, 375, 1375, 1125)</code> (pillarboxed)</li>
+ * <li><code>1280x720</code> stream source area: <code>(500, 469, 1500, 1031)</code> (letterboxed)</li>
+ * <li>Note that in this case, neither of the two outputs is a subset of the other, with
+ * each containing image data the other doesn't have.</li>
+ * </ul>
+ * </li>
+ * </ul>
* <p>If the coordinate system is {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}, the width and height
* of the crop region cannot be set to be smaller than
* <code>floor( activeArraySize.width / {@link CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM android.scaler.availableMaxDigitalZoom} )</code> and
@@ -2776,18 +2816,16 @@
* and
* <code>floor( preCorrectionActiveArraySize.height / {@link CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM android.scaler.availableMaxDigitalZoom} )</code>,
* respectively.</p>
- * <p>The camera device may adjust the crop region to account
- * for rounding and other hardware requirements; the final
- * crop region used will be included in the output capture
- * result.</p>
+ * <p>The camera device may adjust the crop region to account for rounding and other hardware
+ * requirements; the final crop region used will be included in the output capture result.</p>
* <p>Starting from API level 30, it's strongly recommended to use {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}
* to take advantage of better support for zoom with logical multi-camera. The benefits
* include better precision with optical-digital zoom combination, and ability to do
* zoom-out from 1.0x. When using {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for zoom, the crop region in
* the capture request must be either letterboxing or pillarboxing (but not both). The
* coordinate system is post-zoom, meaning that the activeArraySize or
- * preCorrectionActiveArraySize covers the camera device's field of view "after" zoom.
- * See {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for details.</p>
+ * preCorrectionActiveArraySize covers the camera device's field of view "after" zoom. See
+ * {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for details.</p>
* <p><b>Units</b>: Pixel coordinates relative to
* {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} or
* {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize} depending on distortion correction
@@ -2797,6 +2835,7 @@
* @see CaptureRequest#CONTROL_ZOOM_RATIO
* @see CaptureRequest#DISTORTION_CORRECTION_MODE
* @see CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM
+ * @see CameraCharacteristics#SCALER_CROPPING_TYPE
* @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
* @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
*/
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index c995623..6f0d135 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.hardware.camera2.impl.CameraMetadataNative;
import android.hardware.camera2.impl.CaptureResultExtras;
import android.hardware.camera2.impl.PublicKey;
@@ -2384,7 +2384,7 @@
* stream combinations of LIMITED hardware level are guaranteed.</p>
* <p>For a logical multi-camera, bokeh may be implemented by stereo vision from sub-cameras
* with different field of view. As a result, when bokeh mode is enabled, the camera device
- * may override android.scaler.CropRegion, and the field of view will be smaller than when
+ * may override {@link CaptureRequest#SCALER_CROP_REGION android.scaler.cropRegion}, and the field of view will be smaller than when
* bokeh mode is off.</p>
* <p><b>Possible values:</b>
* <ul>
@@ -2393,6 +2393,8 @@
* <li>{@link #CONTROL_BOKEH_MODE_CONTINUOUS CONTINUOUS}</li>
* </ul></p>
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
+ *
+ * @see CaptureRequest#SCALER_CROP_REGION
* @see #CONTROL_BOKEH_MODE_OFF
* @see #CONTROL_BOKEH_MODE_STILL_CAPTURE
* @see #CONTROL_BOKEH_MODE_CONTINUOUS
@@ -3025,10 +3027,15 @@
* <p><code>p' = Rp</code></p>
* <p>where <code>p</code> is in the device sensor coordinate system, and
* <code>p'</code> is in the camera-oriented coordinate system.</p>
+ * <p>If {@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference} is UNDEFINED, the quaternion rotation cannot
+ * be accurately represented by the camera device, and will be represented by
+ * default values matching its default facing.</p>
* <p><b>Units</b>:
* Quaternion coefficients</p>
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
* <p><b>Permission {@link android.Manifest.permission#CAMERA } is needed to access this property</b></p>
+ *
+ * @see CameraCharacteristics#LENS_POSE_REFERENCE
*/
@PublicKey
@NonNull
@@ -3063,6 +3070,8 @@
* <p>When {@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference} is GYROSCOPE, then this position is relative to
* the center of the primary gyroscope on the device. The axis definitions are the same as
* with PRIMARY_CAMERA.</p>
+ * <p>When {@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference} is UNDEFINED, this position cannot be accurately
+ * represented by the camera device, and will be represented as <code>(0, 0, 0)</code>.</p>
* <p><b>Units</b>: Meters</p>
* <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p>
* <p><b>Permission {@link android.Manifest.permission#CAMERA } is needed to access this property</b></p>
@@ -3379,32 +3388,70 @@
* <p>For devices not supporting {@link CaptureRequest#DISTORTION_CORRECTION_MODE android.distortionCorrection.mode} control, the coordinate
* system always follows that of {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}, with <code>(0, 0)</code> being
* the top-left pixel of the active array.</p>
- * <p>For devices supporting {@link CaptureRequest#DISTORTION_CORRECTION_MODE android.distortionCorrection.mode} control, the coordinate
- * system depends on the mode being set.
- * When the distortion correction mode is OFF, the coordinate system follows
- * {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}, with
- * <code>(0, 0)</code> being the top-left pixel of the pre-correction active array.
- * When the distortion correction mode is not OFF, the coordinate system follows
- * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}, with
- * <code>(0, 0)</code> being the top-left pixel of the active array.</p>
- * <p>Output streams use this rectangle to produce their output,
- * cropping to a smaller region if necessary to maintain the
- * stream's aspect ratio, then scaling the sensor input to
+ * <p>For devices supporting {@link CaptureRequest#DISTORTION_CORRECTION_MODE android.distortionCorrection.mode} control, the coordinate system
+ * depends on the mode being set. When the distortion correction mode is OFF, the
+ * coordinate system follows {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize}, with <code>(0,
+ * 0)</code> being the top-left pixel of the pre-correction active array. When the distortion
+ * correction mode is not OFF, the coordinate system follows
+ * {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}, with <code>(0, 0)</code> being the top-left pixel of the
+ * active array.</p>
+ * <p>Output streams use this rectangle to produce their output, cropping to a smaller region
+ * if necessary to maintain the stream's aspect ratio, then scaling the sensor input to
* match the output's configured resolution.</p>
- * <p>The crop region is applied after the RAW to other color
- * space (e.g. YUV) conversion. Since raw streams
- * (e.g. RAW16) don't have the conversion stage, they are not
+ * <p>The crop region is applied after the RAW to other color space (e.g. YUV)
+ * conversion. Since raw streams (e.g. RAW16) don't have the conversion stage, they are not
* croppable. The crop region will be ignored by raw streams.</p>
- * <p>For non-raw streams, any additional per-stream cropping will
- * be done to maximize the final pixel area of the stream.</p>
- * <p>For example, if the crop region is set to a 4:3 aspect
- * ratio, then 4:3 streams will use the exact crop
- * region. 16:9 streams will further crop vertically
- * (letterbox).</p>
- * <p>Conversely, if the crop region is set to a 16:9, then 4:3
- * outputs will crop horizontally (pillarbox), and 16:9
- * streams will match exactly. These additional crops will
- * be centered within the crop region.</p>
+ * <p>For non-raw streams, any additional per-stream cropping will be done to maximize the
+ * final pixel area of the stream.</p>
+ * <p>For example, if the crop region is set to a 4:3 aspect ratio, then 4:3 streams will use
+ * the exact crop region. 16:9 streams will further crop vertically (letterbox).</p>
+ * <p>Conversely, if the crop region is set to a 16:9, then 4:3 outputs will crop horizontally
+ * (pillarbox), and 16:9 streams will match exactly. These additional crops will be
+ * centered within the crop region.</p>
+ * <p>To illustrate, here are several scenarios of different crop regions and output streams,
+ * for a hypothetical camera device with an active array of size <code>(2000,1500)</code>. Note that
+ * several of these examples use non-centered crop regions for ease of illustration; such
+ * regions are only supported on devices with FREEFORM capability
+ * ({@link CameraCharacteristics#SCALER_CROPPING_TYPE android.scaler.croppingType} <code>== FREEFORM</code>), but this does not affect the way the crop
+ * rules work otherwise.</p>
+ * <ul>
+ * <li>Camera Configuration:<ul>
+ * <li>Active array size: <code>2000x1500</code> (3 MP, 4:3 aspect ratio)</li>
+ * <li>Output stream #1: <code>640x480</code> (VGA, 4:3 aspect ratio)</li>
+ * <li>Output stream #2: <code>1280x720</code> (720p, 16:9 aspect ratio)</li>
+ * </ul>
+ * </li>
+ * <li>Case #1: 4:3 crop region with 2x digital zoom<ul>
+ * <li>Crop region: <code>Rect(500, 375, 1500, 1125) // (left, top, right, bottom)</code></li>
+ * <li><img alt="4:3 aspect ratio crop diagram" src="/reference/images/camera2/metadata/android.scaler.cropRegion/crop-region-43-ratio.png" /></li>
+ * <li><code>640x480</code> stream source area: <code>(500, 375, 1500, 1125)</code> (equal to crop region)</li>
+ * <li><code>1280x720</code> stream source area: <code>(500, 469, 1500, 1031)</code> (letterboxed)</li>
+ * </ul>
+ * </li>
+ * <li>Case #2: 16:9 crop region with ~1.5x digital zoom.<ul>
+ * <li>Crop region: <code>Rect(500, 375, 1833, 1125)</code></li>
+ * <li><img alt="16:9 aspect ratio crop diagram" src="/reference/images/camera2/metadata/android.scaler.cropRegion/crop-region-169-ratio.png" /></li>
+ * <li><code>640x480</code> stream source area: <code>(666, 375, 1666, 1125)</code> (pillarboxed)</li>
+ * <li><code>1280x720</code> stream source area: <code>(500, 375, 1833, 1125)</code> (equal to crop region)</li>
+ * </ul>
+ * </li>
+ * <li>Case #3: 1:1 crop region with ~2.6x digital zoom.<ul>
+ * <li>Crop region: <code>Rect(500, 375, 1250, 1125)</code></li>
+ * <li><img alt="1:1 aspect ratio crop diagram" src="/reference/images/camera2/metadata/android.scaler.cropRegion/crop-region-11-ratio.png" /></li>
+ * <li><code>640x480</code> stream source area: <code>(500, 469, 1250, 1031)</code> (letterboxed)</li>
+ * <li><code>1280x720</code> stream source area: <code>(500, 543, 1250, 957)</code> (letterboxed)</li>
+ * </ul>
+ * </li>
+ * <li>Case #4: Replace <code>640x480</code> stream with <code>1024x1024</code> stream, with 4:3 crop region:<ul>
+ * <li>Crop region: <code>Rect(500, 375, 1500, 1125)</code></li>
+ * <li><img alt="Square output, 4:3 aspect ratio crop diagram" src="/reference/images/camera2/metadata/android.scaler.cropRegion/crop-region-43-square-ratio.png" /></li>
+ * <li><code>1024x1024</code> stream source area: <code>(625, 375, 1375, 1125)</code> (pillarboxed)</li>
+ * <li><code>1280x720</code> stream source area: <code>(500, 469, 1500, 1031)</code> (letterboxed)</li>
+ * <li>Note that in this case, neither of the two outputs is a subset of the other, with
+ * each containing image data the other doesn't have.</li>
+ * </ul>
+ * </li>
+ * </ul>
* <p>If the coordinate system is {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize}, the width and height
* of the crop region cannot be set to be smaller than
* <code>floor( activeArraySize.width / {@link CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM android.scaler.availableMaxDigitalZoom} )</code> and
@@ -3415,18 +3462,16 @@
* and
* <code>floor( preCorrectionActiveArraySize.height / {@link CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM android.scaler.availableMaxDigitalZoom} )</code>,
* respectively.</p>
- * <p>The camera device may adjust the crop region to account
- * for rounding and other hardware requirements; the final
- * crop region used will be included in the output capture
- * result.</p>
+ * <p>The camera device may adjust the crop region to account for rounding and other hardware
+ * requirements; the final crop region used will be included in the output capture result.</p>
* <p>Starting from API level 30, it's strongly recommended to use {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio}
* to take advantage of better support for zoom with logical multi-camera. The benefits
* include better precision with optical-digital zoom combination, and ability to do
* zoom-out from 1.0x. When using {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for zoom, the crop region in
* the capture request must be either letterboxing or pillarboxing (but not both). The
* coordinate system is post-zoom, meaning that the activeArraySize or
- * preCorrectionActiveArraySize covers the camera device's field of view "after" zoom.
- * See {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for details.</p>
+ * preCorrectionActiveArraySize covers the camera device's field of view "after" zoom. See
+ * {@link CaptureRequest#CONTROL_ZOOM_RATIO android.control.zoomRatio} for details.</p>
* <p><b>Units</b>: Pixel coordinates relative to
* {@link CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE android.sensor.info.activeArraySize} or
* {@link CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE android.sensor.info.preCorrectionActiveArraySize} depending on distortion correction
@@ -3436,6 +3481,7 @@
* @see CaptureRequest#CONTROL_ZOOM_RATIO
* @see CaptureRequest#DISTORTION_CORRECTION_MODE
* @see CameraCharacteristics#SCALER_AVAILABLE_MAX_DIGITAL_ZOOM
+ * @see CameraCharacteristics#SCALER_CROPPING_TYPE
* @see CameraCharacteristics#SENSOR_INFO_ACTIVE_ARRAY_SIZE
* @see CameraCharacteristics#SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE
*/
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 248c145..1fab666 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -16,13 +16,12 @@
package android.hardware.camera2.impl;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.ImageFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraMetadata;
-import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.marshal.MarshalQueryable;
@@ -54,7 +53,6 @@
import android.hardware.camera2.params.HighSpeedVideoConfiguration;
import android.hardware.camera2.params.LensShadingMap;
import android.hardware.camera2.params.MandatoryStreamCombination;
-import android.hardware.camera2.params.MandatoryStreamCombination.MandatoryStreamInformation;
import android.hardware.camera2.params.OisSample;
import android.hardware.camera2.params.RecommendedStreamConfiguration;
import android.hardware.camera2.params.RecommendedStreamConfigurationMap;
diff --git a/core/java/android/hardware/camera2/params/SessionConfiguration.java b/core/java/android/hardware/camera2/params/SessionConfiguration.java
index 555ff9a..47a897c 100644
--- a/core/java/android/hardware/camera2/params/SessionConfiguration.java
+++ b/core/java/android/hardware/camera2/params/SessionConfiguration.java
@@ -17,10 +17,11 @@
package android.hardware.camera2.params;
+import static com.android.internal.util.Preconditions.*;
+
import android.annotation.CallbackExecutor;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
@@ -32,14 +33,12 @@
import android.os.Parcelable;
import android.util.Log;
-import java.util.Collections;
-import java.util.List;
-import java.util.ArrayList;
-import java.util.concurrent.Executor;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-
-import static com.android.internal.util.Preconditions.*;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Executor;
/**
* A helper class that aggregates all supported arguments for capture session initialization.
@@ -61,6 +60,12 @@
* A high speed session type that can only contain instances of {@link OutputConfiguration}.
* The outputs can run using high speed FPS ranges. Calls to {@link #setInputConfiguration}
* are not supported.
+ * <p>
+ * When using this type, the CameraCaptureSession returned by
+ * {@link android.hardware.camera2.CameraCaptureSession.StateCallback} can be cast to a
+ * {@link android.hardware.camera2.CameraConstrainedHighSpeedCaptureSession} to access the extra
+ * methods for constrained high speed recording.
+ * </p>
*
* @see CameraDevice#createConstrainedHighSpeedCaptureSession
*/
diff --git a/core/java/android/hardware/camera2/utils/HashCodeHelpers.java b/core/java/android/hardware/camera2/utils/HashCodeHelpers.java
index 526f086..16f3f2a 100644
--- a/core/java/android/hardware/camera2/utils/HashCodeHelpers.java
+++ b/core/java/android/hardware/camera2/utils/HashCodeHelpers.java
@@ -16,7 +16,7 @@
package android.hardware.camera2.utils;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* Provide hashing functions using the Modified Bernstein hash
diff --git a/core/java/android/hardware/camera2/utils/SurfaceUtils.java b/core/java/android/hardware/camera2/utils/SurfaceUtils.java
index d3c4505..abe1372 100644
--- a/core/java/android/hardware/camera2/utils/SurfaceUtils.java
+++ b/core/java/android/hardware/camera2/utils/SurfaceUtils.java
@@ -16,7 +16,7 @@
package android.hardware.camera2.utils;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.ImageFormat;
import android.hardware.camera2.legacy.LegacyCameraDevice;
import android.hardware.camera2.legacy.LegacyExceptionUtils.BufferQueueAbandonedException;
diff --git a/core/java/android/hardware/camera2/utils/TypeReference.java b/core/java/android/hardware/camera2/utils/TypeReference.java
index d9ba31b..435ed15 100644
--- a/core/java/android/hardware/camera2/utils/TypeReference.java
+++ b/core/java/android/hardware/camera2/utils/TypeReference.java
@@ -16,7 +16,10 @@
package android.hardware.camera2.utils;
-import android.annotation.UnsupportedAppUsage;
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.compat.annotation.UnsupportedAppUsage;
+
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
@@ -24,8 +27,6 @@
import java.lang.reflect.TypeVariable;
import java.lang.reflect.WildcardType;
-import static com.android.internal.util.Preconditions.*;
-
/**
* Super type token; allows capturing generic types at runtime by forcing them to be reified.
*
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 0b25dbd..799dff9 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -23,8 +23,8 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.KeyguardManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.graphics.Point;
import android.media.projection.MediaProjection;
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index ea63776..2a58495 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.content.res.Resources;
diff --git a/core/java/android/hardware/display/WifiDisplay.java b/core/java/android/hardware/display/WifiDisplay.java
index 55e6051..5bbbbf9 100644
--- a/core/java/android/hardware/display/WifiDisplay.java
+++ b/core/java/android/hardware/display/WifiDisplay.java
@@ -16,7 +16,7 @@
package android.hardware.display;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/hardware/display/WifiDisplayStatus.java b/core/java/android/hardware/display/WifiDisplayStatus.java
index 1973e51..e2a825f 100644
--- a/core/java/android/hardware/display/WifiDisplayStatus.java
+++ b/core/java/android/hardware/display/WifiDisplayStatus.java
@@ -16,7 +16,7 @@
package android.hardware.display;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 22f8590..7bc4529 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -26,8 +26,8 @@
import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
import android.annotation.SystemService;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.PackageManager;
import android.hardware.biometrics.BiometricAuthenticator;
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 638d81b..6e1987c 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -90,4 +90,11 @@
/** Create an input monitor for gestures. */
InputMonitor monitorGestureInput(String name, int displayId);
+
+ // Add a runtime association between the input port and the display port. This overrides any
+ // static associations.
+ void addPortAssociation(in String inputPort, int displayPort);
+ // Remove the runtime association between the input port and the display port. Any existing
+ // static association for the cleared input port will be restored.
+ void removePortAssociation(in String inputPort);
}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 0c0f248..83f01a5 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -17,10 +17,11 @@
package android.hardware.input;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemService;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.media.AudioAttributes;
import android.os.Binder;
@@ -963,6 +964,41 @@
}
}
+ /**
+ * Add a runtime association between the input port and the display port. This overrides any
+ * static associations.
+ * @param inputPort The port of the input device.
+ * @param displayPort The physical port of the associated display.
+ * <p>
+ * Requires {@link android.Manifest.permissions.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT}.
+ * </p>
+ * @hide
+ */
+ public void addPortAssociation(@NonNull String inputPort, int displayPort) {
+ try {
+ mIm.addPortAssociation(inputPort, displayPort);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Remove the runtime association between the input port and the display port. Any existing
+ * static association for the cleared input port will be restored.
+ * @param inputPort The port of the input device to be cleared.
+ * <p>
+ * Requires {@link android.Manifest.permissions.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT}.
+ * </p>
+ * @hide
+ */
+ public void removePortAssociation(@NonNull String inputPort) {
+ try {
+ mIm.removePortAssociation(inputPort);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
+ }
+ }
+
private void populateInputDevicesLocked() {
if (mInputDevicesChangedListener == null) {
final InputDevicesChangedListener listener = new InputDevicesChangedListener();
diff --git a/core/java/android/hardware/location/GeofenceHardware.java b/core/java/android/hardware/location/GeofenceHardware.java
index 23d8d01..a1866af 100644
--- a/core/java/android/hardware/location/GeofenceHardware.java
+++ b/core/java/android/hardware/location/GeofenceHardware.java
@@ -16,7 +16,7 @@
package android.hardware.location;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.location.Location;
import android.os.Build;
import android.os.RemoteException;
diff --git a/core/java/android/hardware/soundtrigger/ConversionUtil.java b/core/java/android/hardware/soundtrigger/ConversionUtil.java
index 8231c58..d43a619 100644
--- a/core/java/android/hardware/soundtrigger/ConversionUtil.java
+++ b/core/java/android/hardware/soundtrigger/ConversionUtil.java
@@ -16,9 +16,11 @@
package android.hardware.soundtrigger;
+import android.annotation.Nullable;
import android.hardware.soundtrigger.ModelParams;
import android.media.AudioFormat;
import android.media.audio.common.AudioConfig;
+import android.media.soundtrigger_middleware.AudioCapabilities;
import android.media.soundtrigger_middleware.ConfidenceLevel;
import android.media.soundtrigger_middleware.ModelParameterRange;
import android.media.soundtrigger_middleware.Phrase;
@@ -32,8 +34,6 @@
import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
-import android.annotation.Nullable;
-
import java.util.Arrays;
import java.util.UUID;
@@ -48,6 +48,7 @@
properties.description,
properties.uuid,
properties.version,
+ properties.supportedModelArch,
properties.maxSoundModels,
properties.maxKeyPhrases,
properties.maxUsers,
@@ -56,7 +57,8 @@
properties.maxBufferMs,
properties.concurrentCapture,
properties.powerConsumptionMw,
- properties.triggerInEvent
+ properties.triggerInEvent,
+ aidl2apiAudioCapabilities(properties.audioCapabilities)
);
}
@@ -145,6 +147,7 @@
apiConfig.keyphrases[i]);
}
aidlConfig.data = Arrays.copyOf(apiConfig.data, apiConfig.data.length);
+ aidlConfig.audioCapabilities = api2aidlAudioCapabilities(apiConfig.audioCapabilities);
return aidlConfig;
}
@@ -326,4 +329,26 @@
}
return new SoundTrigger.ModelParamRange(aidlRange.minInclusive, aidlRange.maxInclusive);
}
+
+ public static int aidl2apiAudioCapabilities(int aidlCapabilities) {
+ int result = 0;
+ if ((aidlCapabilities & AudioCapabilities.ECHO_CANCELLATION) != 0) {
+ result |= SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION;
+ }
+ if ((aidlCapabilities & AudioCapabilities.NOISE_SUPPRESSION) != 0) {
+ result |= SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION;
+ }
+ return result;
+ }
+
+ public static int api2aidlAudioCapabilities(int apiCapabilities) {
+ int result = 0;
+ if ((apiCapabilities & SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION) != 0) {
+ result |= AudioCapabilities.ECHO_CANCELLATION;
+ }
+ if ((apiCapabilities & SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION) != 0) {
+ result |= AudioCapabilities.NOISE_SUPPRESSION;
+ }
+ return result;
+ }
}
diff --git a/core/java/android/hardware/soundtrigger/SoundTrigger.java b/core/java/android/hardware/soundtrigger/SoundTrigger.java
index 5484df4..1932f46 100644
--- a/core/java/android/hardware/soundtrigger/SoundTrigger.java
+++ b/core/java/android/hardware/soundtrigger/SoundTrigger.java
@@ -24,15 +24,17 @@
import static java.util.Objects.requireNonNull;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityThread;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.media.AudioFormat;
import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+import android.media.soundtrigger_middleware.Status;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -40,16 +42,17 @@
import android.os.Parcelable;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
import android.util.Log;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.List;
import java.util.UUID;
/**
- * The SoundTrigger class provides access via JNI to the native service managing
- * the sound trigger HAL.
+ * The SoundTrigger class provides access to the service managing the sound trigger HAL.
*
* @hide
*/
@@ -85,6 +88,30 @@
*
****************************************************************************/
public static final class ModuleProperties implements Parcelable {
+
+ /**
+ * Bit field values of AudioCapabilities supported by the implemented HAL
+ * driver.
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "AUDIO_CAPABILITY_" }, value = {
+ CAPABILITY_ECHO_CANCELLATION,
+ CAPABILITY_NOISE_SUPPRESSION
+ })
+ public @interface AudioCapabilities {}
+
+ /**
+ * If set the underlying module supports AEC.
+ * Describes bit field {@link ModuleProperties#audioCapabilities}
+ */
+ public static final int CAPABILITY_ECHO_CANCELLATION = 0x1;
+ /**
+ * If set, the underlying module supports noise suppression.
+ * Describes bit field {@link ModuleProperties#audioCapabilities}
+ */
+ public static final int CAPABILITY_NOISE_SUPPRESSION = 0x2;
+
/** Unique module ID provided by the native service */
public final int id;
@@ -103,6 +130,14 @@
/** Voice detection engine version */
public final int version;
+ /**
+ * String naming the architecture used for running the supported models.
+ * (eg. a platform running models on a DSP could implement this string to convey the DSP
+ * architecture used)
+ */
+ @NonNull
+ public final String supportedModelArch;
+
/** Maximum number of active sound models */
public final int maxSoundModels;
@@ -131,16 +166,25 @@
* recognition callback event */
public final boolean returnsTriggerInEvent;
+ /**
+ * Bit field encoding of the AudioCapabilities
+ * supported by the firmware.
+ */
+ @AudioCapabilities
+ public final int audioCapabilities;
+
ModuleProperties(int id, @NonNull String implementor, @NonNull String description,
- @NonNull String uuid, int version, int maxSoundModels, int maxKeyphrases,
- int maxUsers, int recognitionModes, boolean supportsCaptureTransition,
- int maxBufferMs, boolean supportsConcurrentCapture,
- int powerConsumptionMw, boolean returnsTriggerInEvent) {
+ @NonNull String uuid, int version, @NonNull String supportedModelArch,
+ int maxSoundModels, int maxKeyphrases, int maxUsers, int recognitionModes,
+ boolean supportsCaptureTransition, int maxBufferMs,
+ boolean supportsConcurrentCapture, int powerConsumptionMw,
+ boolean returnsTriggerInEvent, int audioCapabilities) {
this.id = id;
this.implementor = requireNonNull(implementor);
this.description = requireNonNull(description);
this.uuid = UUID.fromString(requireNonNull(uuid));
this.version = version;
+ this.supportedModelArch = requireNonNull(supportedModelArch);
this.maxSoundModels = maxSoundModels;
this.maxKeyphrases = maxKeyphrases;
this.maxUsers = maxUsers;
@@ -150,6 +194,7 @@
this.supportsConcurrentCapture = supportsConcurrentCapture;
this.powerConsumptionMw = powerConsumptionMw;
this.returnsTriggerInEvent = returnsTriggerInEvent;
+ this.audioCapabilities = audioCapabilities;
}
public static final @android.annotation.NonNull Parcelable.Creator<ModuleProperties> CREATOR
@@ -169,6 +214,7 @@
String description = in.readString();
String uuid = in.readString();
int version = in.readInt();
+ String supportedModelArch = in.readString();
int maxSoundModels = in.readInt();
int maxKeyphrases = in.readInt();
int maxUsers = in.readInt();
@@ -178,10 +224,11 @@
boolean supportsConcurrentCapture = in.readByte() == 1;
int powerConsumptionMw = in.readInt();
boolean returnsTriggerInEvent = in.readByte() == 1;
+ int audioCapabilities = in.readInt();
return new ModuleProperties(id, implementor, description, uuid, version,
- maxSoundModels, maxKeyphrases, maxUsers, recognitionModes,
+ supportedModelArch, maxSoundModels, maxKeyphrases, maxUsers, recognitionModes,
supportsCaptureTransition, maxBufferMs, supportsConcurrentCapture,
- powerConsumptionMw, returnsTriggerInEvent);
+ powerConsumptionMw, returnsTriggerInEvent, audioCapabilities);
}
@Override
@@ -191,6 +238,7 @@
dest.writeString(description);
dest.writeString(uuid.toString());
dest.writeInt(version);
+ dest.writeString(supportedModelArch);
dest.writeInt(maxSoundModels);
dest.writeInt(maxKeyphrases);
dest.writeInt(maxUsers);
@@ -200,6 +248,7 @@
dest.writeByte((byte) (supportsConcurrentCapture ? 1 : 0));
dest.writeInt(powerConsumptionMw);
dest.writeByte((byte) (returnsTriggerInEvent ? 1 : 0));
+ dest.writeInt(audioCapabilities);
}
@Override
@@ -210,13 +259,15 @@
@Override
public String toString() {
return "ModuleProperties [id=" + id + ", implementor=" + implementor + ", description="
- + description + ", uuid=" + uuid + ", version=" + version + ", maxSoundModels="
+ + description + ", uuid=" + uuid + ", version=" + version
+ + " , supportedModelArch=" + supportedModelArch + ", maxSoundModels="
+ maxSoundModels + ", maxKeyphrases=" + maxKeyphrases + ", maxUsers="
+ maxUsers + ", recognitionModes=" + recognitionModes
+ ", supportsCaptureTransition=" + supportsCaptureTransition + ", maxBufferMs="
+ maxBufferMs + ", supportsConcurrentCapture=" + supportsConcurrentCapture
+ ", powerConsumptionMw=" + powerConsumptionMw
- + ", returnsTriggerInEvent=" + returnsTriggerInEvent + "]";
+ + ", returnsTriggerInEvent=" + returnsTriggerInEvent
+ + ", audioCapabilities=" + audioCapabilities + "]";
}
}
@@ -254,16 +305,20 @@
@NonNull
public final UUID vendorUuid;
+ /** vendor specific version number of the model */
+ public final int version;
+
/** Opaque data. For use by vendor implementation and enrollment application */
@UnsupportedAppUsage
@NonNull
public final byte[] data;
public SoundModel(@NonNull UUID uuid, @Nullable UUID vendorUuid, int type,
- @Nullable byte[] data) {
+ @Nullable byte[] data, int version) {
this.uuid = requireNonNull(uuid);
this.vendorUuid = vendorUuid != null ? vendorUuid : new UUID(0, 0);
this.type = type;
+ this.version = version;
this.data = data != null ? data : new byte[0];
}
@@ -271,6 +326,7 @@
public int hashCode() {
final int prime = 31;
int result = 1;
+ result = prime * result + version;
result = prime * result + Arrays.hashCode(data);
result = prime * result + type;
result = prime * result + ((uuid == null) ? 0 : uuid.hashCode());
@@ -301,6 +357,8 @@
return false;
if (!Arrays.equals(data, other.data))
return false;
+ if (version != other.version)
+ return false;
return true;
}
}
@@ -450,14 +508,19 @@
@NonNull
public final Keyphrase[] keyphrases; // keyword phrases in model
- @UnsupportedAppUsage
public KeyphraseSoundModel(
@NonNull UUID uuid, @NonNull UUID vendorUuid, @Nullable byte[] data,
- @Nullable Keyphrase[] keyphrases) {
- super(uuid, vendorUuid, TYPE_KEYPHRASE, data);
+ @Nullable Keyphrase[] keyphrases, int version) {
+ super(uuid, vendorUuid, TYPE_KEYPHRASE, data, version);
this.keyphrases = keyphrases != null ? keyphrases : new Keyphrase[0];
}
+ @UnsupportedAppUsage
+ public KeyphraseSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid,
+ @Nullable byte[] data, @Nullable Keyphrase[] keyphrases) {
+ this(uuid, vendorUuid, data, keyphrases, -1);
+ }
+
public static final @android.annotation.NonNull Parcelable.Creator<KeyphraseSoundModel> CREATOR
= new Parcelable.Creator<KeyphraseSoundModel>() {
public KeyphraseSoundModel createFromParcel(Parcel in) {
@@ -476,9 +539,10 @@
if (length >= 0) {
vendorUuid = UUID.fromString(in.readString());
}
+ int version = in.readInt();
byte[] data = in.readBlob();
Keyphrase[] keyphrases = in.createTypedArray(Keyphrase.CREATOR);
- return new KeyphraseSoundModel(uuid, vendorUuid, data, keyphrases);
+ return new KeyphraseSoundModel(uuid, vendorUuid, data, keyphrases, version);
}
@Override
@@ -495,6 +559,7 @@
dest.writeInt(vendorUuid.toString().length());
dest.writeString(vendorUuid.toString());
}
+ dest.writeInt(version);
dest.writeBlob(data);
dest.writeTypedArray(keyphrases, flags);
}
@@ -503,7 +568,9 @@
public String toString() {
return "KeyphraseSoundModel [keyphrases=" + Arrays.toString(keyphrases)
+ ", uuid=" + uuid + ", vendorUuid=" + vendorUuid
- + ", type=" + type + ", data=" + (data == null ? 0 : data.length) + "]";
+ + ", type=" + type
+ + ", data=" + (data == null ? 0 : data.length)
+ + ", version=" + version + "]";
}
@Override
@@ -549,10 +616,15 @@
}
};
+ public GenericSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid,
+ @Nullable byte[] data, int version) {
+ super(uuid, vendorUuid, TYPE_GENERIC_SOUND, data, version);
+ }
+
@UnsupportedAppUsage
public GenericSoundModel(@NonNull UUID uuid, @NonNull UUID vendorUuid,
@Nullable byte[] data) {
- super(uuid, vendorUuid, TYPE_GENERIC_SOUND, data);
+ this(uuid, vendorUuid, data, -1);
}
@Override
@@ -568,7 +640,8 @@
vendorUuid = UUID.fromString(in.readString());
}
byte[] data = in.readBlob();
- return new GenericSoundModel(uuid, vendorUuid, data);
+ int version = in.readInt();
+ return new GenericSoundModel(uuid, vendorUuid, data, version);
}
@Override
@@ -581,28 +654,31 @@
dest.writeString(vendorUuid.toString());
}
dest.writeBlob(data);
+ dest.writeInt(version);
}
@Override
public String toString() {
return "GenericSoundModel [uuid=" + uuid + ", vendorUuid=" + vendorUuid
- + ", type=" + type + ", data=" + (data == null ? 0 : data.length) + "]";
+ + ", type=" + type
+ + ", data=" + (data == null ? 0 : data.length)
+ + ", version=" + version + "]";
}
}
- /*****************************************************************************
+ /**
* A ModelParamRange is a representation of supported parameter range for a
* given loaded model.
- ****************************************************************************/
+ */
public static final class ModelParamRange implements Parcelable {
/**
- * start of supported range inclusive
+ * The inclusive start of supported range.
*/
public final int start;
/**
- * end of supported range inclusive
+ * The inclusive end of supported range.
*/
public final int end;
@@ -611,31 +687,65 @@
this.end = end;
}
+ /** @hide */
private ModelParamRange(@NonNull Parcel in) {
this.start = in.readInt();
this.end = in.readInt();
}
@NonNull
- public static final Creator<ModelParamRange> CREATOR = new Creator<ModelParamRange>() {
- @Override
- @NonNull
- public ModelParamRange createFromParcel(@NonNull Parcel in) {
- return new ModelParamRange(in);
- }
+ public static final Creator<ModelParamRange> CREATOR =
+ new Creator<ModelParamRange>() {
+ @Override
+ @NonNull
+ public ModelParamRange createFromParcel(@NonNull Parcel in) {
+ return new ModelParamRange(in);
+ }
- @Override
- @NonNull
- public ModelParamRange[] newArray(int size) {
- return new ModelParamRange[size];
- }
- };
+ @Override
+ @NonNull
+ public ModelParamRange[] newArray(int size) {
+ return new ModelParamRange[size];
+ }
+ };
+ /** @hide */
@Override
public int describeContents() {
return 0;
}
+ /** @hide */
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (start);
+ result = prime * result + (end);
+ return result;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ ModelParamRange other = (ModelParamRange) obj;
+ if (start != other.start) {
+ return false;
+ }
+ if (end != other.end) {
+ return false;
+ }
+ return true;
+ }
+
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(start);
@@ -1004,13 +1114,27 @@
@NonNull
public final byte[] data;
- @UnsupportedAppUsage
+ /**
+ * Bit field encoding of the AudioCapabilities
+ * supported by the firmware.
+ */
+ @ModuleProperties.AudioCapabilities
+ public final int audioCapabilities;
+
public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
- @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) {
+ @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data,
+ int audioCapabilities) {
this.captureRequested = captureRequested;
this.allowMultipleTriggers = allowMultipleTriggers;
this.keyphrases = keyphrases != null ? keyphrases : new KeyphraseRecognitionExtra[0];
this.data = data != null ? data : new byte[0];
+ this.audioCapabilities = audioCapabilities;
+ }
+
+ @UnsupportedAppUsage
+ public RecognitionConfig(boolean captureRequested, boolean allowMultipleTriggers,
+ @Nullable KeyphraseRecognitionExtra[] keyphrases, @Nullable byte[] data) {
+ this(captureRequested, allowMultipleTriggers, keyphrases, data, 0);
}
public static final @android.annotation.NonNull Parcelable.Creator<RecognitionConfig> CREATOR
@@ -1030,7 +1154,9 @@
KeyphraseRecognitionExtra[] keyphrases =
in.createTypedArray(KeyphraseRecognitionExtra.CREATOR);
byte[] data = in.readBlob();
- return new RecognitionConfig(captureRequested, allowMultipleTriggers, keyphrases, data);
+ int audioCapabilities = in.readInt();
+ return new RecognitionConfig(captureRequested, allowMultipleTriggers, keyphrases, data,
+ audioCapabilities);
}
@Override
@@ -1039,6 +1165,7 @@
dest.writeByte((byte) (allowMultipleTriggers ? 1 : 0));
dest.writeTypedArray(keyphrases, flags);
dest.writeBlob(data);
+ dest.writeInt(audioCapabilities);
}
@Override
@@ -1050,7 +1177,8 @@
public String toString() {
return "RecognitionConfig [captureRequested=" + captureRequested
+ ", allowMultipleTriggers=" + allowMultipleTriggers + ", keyphrases="
- + Arrays.toString(keyphrases) + ", data=" + Arrays.toString(data) + "]";
+ + Arrays.toString(keyphrases) + ", data=" + Arrays.toString(data)
+ + ", audioCapabilities=" + Integer.toHexString(audioCapabilities) + "]";
}
}
@@ -1550,6 +1678,45 @@
}
/**
+ * Translate an exception thrown from interaction with the underlying service to an error code.
+ * Throws a runtime exception for unexpected conditions.
+ * @param e The caught exception.
+ * @return The error code.
+ *
+ * @hide
+ */
+ static int handleException(Exception e) {
+ Log.w(TAG, "Exception caught", e);
+ if (e instanceof RemoteException) {
+ return STATUS_DEAD_OBJECT;
+ }
+ if (e instanceof ServiceSpecificException) {
+ switch (((ServiceSpecificException) e).errorCode) {
+ case Status.OPERATION_NOT_SUPPORTED:
+ return STATUS_INVALID_OPERATION;
+ case Status.TEMPORARY_PERMISSION_DENIED:
+ return STATUS_PERMISSION_DENIED;
+ case Status.DEAD_OBJECT:
+ return STATUS_DEAD_OBJECT;
+ }
+ return STATUS_ERROR;
+ }
+ if (e instanceof SecurityException) {
+ return STATUS_PERMISSION_DENIED;
+ }
+ if (e instanceof IllegalStateException) {
+ return STATUS_INVALID_OPERATION;
+ }
+ if (e instanceof IllegalArgumentException || e instanceof NullPointerException) {
+ return STATUS_BAD_VALUE;
+ }
+ // This is not one of the conditions represented by our error code, escalate to a
+ // RuntimeException.
+ Log.e(TAG, "Escalating unexpected exception: ", e);
+ throw new RuntimeException(e);
+ }
+
+ /**
* Returns a list of descriptors for all hardware modules loaded.
* @param modules A ModuleProperties array where the list will be returned.
* @return - {@link #STATUS_OK} in case of success
@@ -1571,9 +1738,8 @@
modules.add(ConversionUtil.aidl2apiModuleDescriptor(desc));
}
return STATUS_OK;
- } catch (RemoteException e) {
- Log.e(TAG, "Exception caught", e);
- return STATUS_DEAD_OBJECT;
+ } catch (Exception e) {
+ return handleException(e);
}
}
diff --git a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
index 7cf5600..9bd3992 100644
--- a/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
+++ b/core/java/android/hardware/soundtrigger/SoundTriggerModule.java
@@ -18,11 +18,10 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.media.soundtrigger_middleware.ISoundTriggerCallback;
import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
import android.media.soundtrigger_middleware.ISoundTriggerModule;
-import android.media.soundtrigger_middleware.ModelParameterRange;
import android.media.soundtrigger_middleware.PhraseRecognitionEvent;
import android.media.soundtrigger_middleware.PhraseSoundModel;
import android.media.soundtrigger_middleware.RecognitionEvent;
@@ -79,7 +78,7 @@
mService = null;
}
} catch (Exception e) {
- handleException(e);
+ SoundTrigger.handleException(e);
}
}
@@ -116,7 +115,7 @@
}
return SoundTrigger.STATUS_BAD_VALUE;
} catch (Exception e) {
- return handleException(e);
+ return SoundTrigger.handleException(e);
}
}
@@ -138,7 +137,7 @@
mService.unloadModel(soundModelHandle);
return SoundTrigger.STATUS_OK;
} catch (Exception e) {
- return handleException(e);
+ return SoundTrigger.handleException(e);
}
}
@@ -167,7 +166,7 @@
ConversionUtil.api2aidlRecognitionConfig(config));
return SoundTrigger.STATUS_OK;
} catch (Exception e) {
- return handleException(e);
+ return SoundTrigger.handleException(e);
}
}
@@ -190,7 +189,7 @@
mService.stopRecognition(soundModelHandle);
return SoundTrigger.STATUS_OK;
} catch (Exception e) {
- return handleException(e);
+ return SoundTrigger.handleException(e);
}
}
@@ -215,7 +214,7 @@
mService.forceRecognitionEvent(soundModelHandle);
return SoundTrigger.STATUS_OK;
} catch (Exception e) {
- return handleException(e);
+ return SoundTrigger.handleException(e);
}
}
@@ -243,7 +242,7 @@
ConversionUtil.api2aidlModelParameter(modelParam), value);
return SoundTrigger.STATUS_OK;
} catch (Exception e) {
- return handleException(e);
+ return SoundTrigger.handleException(e);
}
}
@@ -297,23 +296,6 @@
}
}
- private int handleException(Exception e) {
- Log.e(TAG, "", e);
- if (e instanceof NullPointerException) {
- return SoundTrigger.STATUS_NO_INIT;
- }
- if (e instanceof RemoteException) {
- return SoundTrigger.STATUS_DEAD_OBJECT;
- }
- if (e instanceof IllegalArgumentException) {
- return SoundTrigger.STATUS_BAD_VALUE;
- }
- if (e instanceof IllegalStateException) {
- return SoundTrigger.STATUS_INVALID_OPERATION;
- }
- return SoundTrigger.STATUS_ERROR;
- }
-
private class EventHandlerDelegate extends ISoundTriggerCallback.Stub implements
IBinder.DeathRecipient {
private final Handler mHandler;
@@ -371,6 +353,12 @@
}
@Override
+ public synchronized void onModuleDied() {
+ Message m = mHandler.obtainMessage(EVENT_SERVICE_DIED);
+ mHandler.sendMessage(m);
+ }
+
+ @Override
public synchronized void binderDied() {
Message m = mHandler.obtainMessage(EVENT_SERVICE_DIED);
mHandler.sendMessage(m);
diff --git a/core/java/android/hardware/usb/UsbDevice.java b/core/java/android/hardware/usb/UsbDevice.java
index 9636b13..a0f64cd 100644
--- a/core/java/android/hardware/usb/UsbDevice.java
+++ b/core/java/android/hardware/usb/UsbDevice.java
@@ -18,14 +18,13 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityThread;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
import com.android.internal.util.Preconditions;
-import java.util.Objects;
import java.util.Objects;
diff --git a/core/java/android/hardware/usb/UsbDeviceConnection.java b/core/java/android/hardware/usb/UsbDeviceConnection.java
index 1a5cdd9..53a5785 100644
--- a/core/java/android/hardware/usb/UsbDeviceConnection.java
+++ b/core/java/android/hardware/usb/UsbDeviceConnection.java
@@ -20,7 +20,7 @@
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Build;
import android.os.ParcelFileDescriptor;
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index eb148b9..67fdda3 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -26,8 +26,8 @@
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
import android.annotation.SystemService;
-import android.annotation.UnsupportedAppUsage;
import android.app.PendingIntent;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -91,7 +91,7 @@
*
* {@hide}
*/
- @UnsupportedAppUsage
+ @SystemApi
public static final String ACTION_USB_STATE =
"android.hardware.usb.action.USB_STATE";
@@ -164,7 +164,7 @@
*
* {@hide}
*/
- @UnsupportedAppUsage
+ @SystemApi
public static final String USB_CONNECTED = "connected";
/**
@@ -181,6 +181,7 @@
*
* {@hide}
*/
+ @SystemApi
public static final String USB_CONFIGURED = "configured";
/**
@@ -217,6 +218,7 @@
*
* {@hide}
*/
+ @SystemApi
public static final String USB_FUNCTION_RNDIS = "rndis";
/**
@@ -319,6 +321,7 @@
* Code for the charging usb function. Passed into {@link #setCurrentFunctions(long)}
* {@hide}
*/
+ @SystemApi
public static final long FUNCTION_NONE = 0;
/**
@@ -337,6 +340,7 @@
* Code for the rndis usb function. Passed as a mask into {@link #setCurrentFunctions(long)}
* {@hide}
*/
+ @SystemApi
public static final long FUNCTION_RNDIS = GadgetFunction.RNDIS;
/**
@@ -698,6 +702,8 @@
*
* {@hide}
*/
+ @SystemApi
+ @RequiresPermission(Manifest.permission.MANAGE_USB)
public void setCurrentFunctions(long functions) {
try {
mService.setCurrentFunctions(functions);
@@ -737,6 +743,8 @@
*
* {@hide}
*/
+ @SystemApi
+ @RequiresPermission(Manifest.permission.MANAGE_USB)
public long getCurrentFunctions() {
try {
return mService.getCurrentFunctions();
diff --git a/core/java/android/hardware/usb/UsbRequest.java b/core/java/android/hardware/usb/UsbRequest.java
index d464ab5..473df71 100644
--- a/core/java/android/hardware/usb/UsbRequest.java
+++ b/core/java/android/hardware/usb/UsbRequest.java
@@ -17,7 +17,7 @@
package android.hardware.usb;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.util.Log;
diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
index 7d4849f..9327b24 100644
--- a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java
@@ -16,7 +16,7 @@
package android.inputmethodservice;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.graphics.Rect;
import android.os.Bundle;
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index 62f0196..4d5fabb 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -18,7 +18,7 @@
import android.annotation.BinderThread;
import android.annotation.MainThread;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageManager;
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index ffb97c0..8e52ee9 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -34,9 +34,9 @@
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityManager;
import android.app.Dialog;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.res.Configuration;
diff --git a/core/java/android/inputmethodservice/Keyboard.java b/core/java/android/inputmethodservice/Keyboard.java
index 3f25113..c85fcd9 100644
--- a/core/java/android/inputmethodservice/Keyboard.java
+++ b/core/java/android/inputmethodservice/Keyboard.java
@@ -16,8 +16,8 @@
package android.inputmethodservice;
-import android.annotation.UnsupportedAppUsage;
import android.annotation.XmlRes;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java
index 45f067b..b780b21 100644
--- a/core/java/android/inputmethodservice/KeyboardView.java
+++ b/core/java/android/inputmethodservice/KeyboardView.java
@@ -16,7 +16,7 @@
package android.inputmethodservice;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
diff --git a/core/java/android/net/CaptivePortal.java b/core/java/android/net/CaptivePortal.java
index a66fcae..fb35b4b 100644
--- a/core/java/android/net/CaptivePortal.java
+++ b/core/java/android/net/CaptivePortal.java
@@ -60,6 +60,18 @@
@SystemApi
@TestApi
public static final int APP_RETURN_WANTED_AS_IS = 2;
+ /** Event offset of request codes from captive portal application. */
+ private static final int APP_REQUEST_BASE = 100;
+ /**
+ * Request code from the captive portal application, indicating that the network condition may
+ * have changed and the network should be re-validated.
+ * @see ICaptivePortal#appRequest(int)
+ * @see android.net.INetworkMonitor#forceReevaluation(int)
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public static final int APP_REQUEST_REEVALUATION_REQUIRED = APP_REQUEST_BASE + 0;
private final IBinder mBinder;
@@ -136,6 +148,19 @@
}
/**
+ * Request that the system reevaluates the captive portal status.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public void reevaluateNetwork() {
+ try {
+ ICaptivePortal.Stub.asInterface(mBinder).appRequest(APP_REQUEST_REEVALUATION_REQUIRED);
+ } catch (RemoteException e) {
+ }
+ }
+
+ /**
* Log a captive portal login event.
* @param eventId one of the CAPTIVE_PORTAL_LOGIN_* constants in metrics_constants.proto.
* @param packageName captive portal application package name.
diff --git a/core/java/android/net/ConnectivityDiagnosticsManager.java b/core/java/android/net/ConnectivityDiagnosticsManager.java
new file mode 100644
index 0000000..6afdb5e
--- /dev/null
+++ b/core/java/android/net/ConnectivityDiagnosticsManager.java
@@ -0,0 +1,242 @@
+/*
+ * 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.net;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.PersistableBundle;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+
+/**
+ * Class that provides utilities for collecting network connectivity diagnostics information.
+ * Connectivity information is made available through triggerable diagnostics tools and by listening
+ * to System validations. Some diagnostics information may be permissions-restricted.
+ *
+ * <p>ConnectivityDiagnosticsManager is intended for use by applications offering network
+ * connectivity on a user device. These tools will provide several mechanisms for these applications
+ * to be alerted to network conditions as well as diagnose potential network issues themselves.
+ *
+ * <p>The primary responsibilities of this class are to:
+ *
+ * <ul>
+ * <li>Allow permissioned applications to register and unregister callbacks for network event
+ * notifications
+ * <li>Invoke callbacks for network event notifications, including:
+ * <ul>
+ * <li>Network validations
+ * <li>Data stalls
+ * <li>Connectivity reports from applications
+ * </ul>
+ * </ul>
+ */
+public class ConnectivityDiagnosticsManager {
+ public static final int DETECTION_METHOD_DNS_EVENTS = 1;
+ public static final int DETECTION_METHOD_TCP_METRICS = 2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = {"DETECTION_METHOD_"},
+ value = {DETECTION_METHOD_DNS_EVENTS, DETECTION_METHOD_TCP_METRICS})
+ public @interface DetectionMethod {}
+
+ /** @hide */
+ public ConnectivityDiagnosticsManager() {}
+
+ /** Class that includes connectivity information for a specific Network at a specific time. */
+ public static class ConnectivityReport {
+ /** The Network for which this ConnectivityReport applied */
+ @NonNull public final Network network;
+
+ /**
+ * The timestamp for the report. The timestamp is taken from {@link
+ * System#currentTimeMillis}.
+ */
+ public final long reportTimestamp;
+
+ /** LinkProperties available on the Network at the reported timestamp */
+ @NonNull public final LinkProperties linkProperties;
+
+ /** NetworkCapabilities available on the Network at the reported timestamp */
+ @NonNull public final NetworkCapabilities networkCapabilities;
+
+ /** PersistableBundle that may contain additional info about the report */
+ @NonNull public final PersistableBundle additionalInfo;
+
+ /**
+ * Constructor for ConnectivityReport.
+ *
+ * <p>Apps should obtain instances through {@link
+ * ConnectivityDiagnosticsCallback#onConnectivityReport} instead of instantiating their own
+ * instances (unless for testing purposes).
+ *
+ * @param network The Network for which this ConnectivityReport applies
+ * @param reportTimestamp The timestamp for the report
+ * @param linkProperties The LinkProperties available on network at reportTimestamp
+ * @param networkCapabilities The NetworkCapabilities available on network at
+ * reportTimestamp
+ * @param additionalInfo A PersistableBundle that may contain additional info about the
+ * report
+ */
+ public ConnectivityReport(
+ @NonNull Network network,
+ long reportTimestamp,
+ @NonNull LinkProperties linkProperties,
+ @NonNull NetworkCapabilities networkCapabilities,
+ @NonNull PersistableBundle additionalInfo) {
+ this.network = network;
+ this.reportTimestamp = reportTimestamp;
+ this.linkProperties = linkProperties;
+ this.networkCapabilities = networkCapabilities;
+ this.additionalInfo = additionalInfo;
+ }
+ }
+
+ /** Class that includes information for a suspected data stall on a specific Network */
+ public static class DataStallReport {
+ /** The Network for which this DataStallReport applied */
+ @NonNull public final Network network;
+
+ /**
+ * The timestamp for the report. The timestamp is taken from {@link
+ * System#currentTimeMillis}.
+ */
+ public final long reportTimestamp;
+
+ /** The detection method used to identify the suspected data stall */
+ @DetectionMethod public final int detectionMethod;
+
+ /** PersistableBundle that may contain additional information on the suspected data stall */
+ @NonNull public final PersistableBundle stallDetails;
+
+ /**
+ * Constructor for DataStallReport.
+ *
+ * <p>Apps should obtain instances through {@link
+ * ConnectivityDiagnosticsCallback#onDataStallSuspected} instead of instantiating their own
+ * instances (unless for testing purposes).
+ *
+ * @param network The Network for which this DataStallReport applies
+ * @param reportTimestamp The timestamp for the report
+ * @param detectionMethod The detection method used to identify this data stall
+ * @param stallDetails A PersistableBundle that may contain additional info about the report
+ */
+ public DataStallReport(
+ @NonNull Network network,
+ long reportTimestamp,
+ @DetectionMethod int detectionMethod,
+ @NonNull PersistableBundle stallDetails) {
+ this.network = network;
+ this.reportTimestamp = reportTimestamp;
+ this.detectionMethod = detectionMethod;
+ this.stallDetails = stallDetails;
+ }
+ }
+
+ /**
+ * Abstract base class for Connectivity Diagnostics callbacks. Used for notifications about
+ * network connectivity events. Must be extended by applications wanting notifications.
+ */
+ public abstract static class ConnectivityDiagnosticsCallback {
+ /**
+ * Called when the platform completes a data connectivity check. This will also be invoked
+ * upon registration with the latest report.
+ *
+ * <p>The Network specified in the ConnectivityReport may not be active any more when this
+ * method is invoked.
+ *
+ * @param report The ConnectivityReport containing information about a connectivity check
+ */
+ public void onConnectivityReport(@NonNull ConnectivityReport report) {}
+
+ /**
+ * Called when the platform suspects a data stall on some Network.
+ *
+ * <p>The Network specified in the DataStallReport may not be active any more when this
+ * method is invoked.
+ *
+ * @param report The DataStallReport containing information about the suspected data stall
+ */
+ public void onDataStallSuspected(@NonNull DataStallReport report) {}
+
+ /**
+ * Called when any app reports connectivity to the System.
+ *
+ * @param network The Network for which connectivity has been reported
+ * @param hasConnectivity The connectivity reported to the System
+ */
+ public void onNetworkConnectivityReported(
+ @NonNull Network network, boolean hasConnectivity) {}
+ }
+
+ /**
+ * Registers a ConnectivityDiagnosticsCallback with the System.
+ *
+ * <p>Only apps that offer network connectivity to the user are allowed to register callbacks.
+ * This includes:
+ *
+ * <ul>
+ * <li>Carrier apps with active subscriptions
+ * <li>Active VPNs
+ * <li>WiFi Suggesters
+ * </ul>
+ *
+ * <p>Callbacks will be limited to receiving notifications for networks over which apps provide
+ * connectivity.
+ *
+ * <p>If a registering app loses its relevant permissions, any callbacks it registered will
+ * silently stop receiving callbacks.
+ *
+ * <p>Each register() call <b>MUST</b> use a unique ConnectivityDiagnosticsCallback instance. If
+ * a single instance is registered with multiple NetworkRequests, an IllegalArgumentException
+ * will be thrown.
+ *
+ * @param request The NetworkRequest that will be used to match with Networks for which
+ * callbacks will be fired
+ * @param e The Executor to be used for running the callback method invocations
+ * @param callback The ConnectivityDiagnosticsCallback that the caller wants registered with the
+ * System
+ * @throws IllegalArgumentException if the same callback instance is registered with multiple
+ * NetworkRequests
+ * @throws SecurityException if the caller does not have appropriate permissions to register a
+ * callback
+ */
+ public void registerConnectivityDiagnosticsCallback(
+ @NonNull NetworkRequest request,
+ @NonNull Executor e,
+ @NonNull ConnectivityDiagnosticsCallback callback) {
+ // TODO(b/143187964): implement ConnectivityDiagnostics functionality
+ throw new UnsupportedOperationException("registerCallback() not supported yet");
+ }
+
+ /**
+ * Unregisters a ConnectivityDiagnosticsCallback with the System.
+ *
+ * <p>If the given callback is not currently registered with the System, this operation will be
+ * a no-op.
+ *
+ * @param callback The ConnectivityDiagnosticsCallback to be unregistered from the System.
+ */
+ public void unregisterConnectivityDiagnosticsCallback(
+ @NonNull ConnectivityDiagnosticsCallback callback) {
+ // TODO(b/143187964): implement ConnectivityDiagnostics functionality
+ throw new UnsupportedOperationException("registerCallback() not supported yet");
+ }
+}
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index fbd90bd..8ba3131 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -27,8 +27,8 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.PendingIntent;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
import android.net.IpSecManager.UdpEncapsulationSocket;
@@ -363,7 +363,7 @@
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
@UnsupportedAppUsage
public static final String ACTION_TETHER_STATE_CHANGED =
- "android.net.conn.TETHER_STATE_CHANGED";
+ TetheringManager.ACTION_TETHER_STATE_CHANGED;
/**
* @hide
@@ -371,14 +371,14 @@
* tethering and currently available for tethering.
*/
@UnsupportedAppUsage
- public static final String EXTRA_AVAILABLE_TETHER = "availableArray";
+ public static final String EXTRA_AVAILABLE_TETHER = TetheringManager.EXTRA_AVAILABLE_TETHER;
/**
* @hide
* gives a String[] listing all the interfaces currently in local-only
* mode (ie, has DHCPv4+IPv6-ULA support and no packet forwarding)
*/
- public static final String EXTRA_ACTIVE_LOCAL_ONLY = "localOnlyArray";
+ public static final String EXTRA_ACTIVE_LOCAL_ONLY = TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY;
/**
* @hide
@@ -386,7 +386,7 @@
* (ie, has DHCPv4 support and packets potentially forwarded/NATed)
*/
@UnsupportedAppUsage
- public static final String EXTRA_ACTIVE_TETHER = "tetherArray";
+ public static final String EXTRA_ACTIVE_TETHER = TetheringManager.EXTRA_ACTIVE_TETHER;
/**
* @hide
@@ -395,7 +395,7 @@
* for any interfaces listed here.
*/
@UnsupportedAppUsage
- public static final String EXTRA_ERRORED_TETHER = "erroredArray";
+ public static final String EXTRA_ERRORED_TETHER = TetheringManager.EXTRA_ERRORED_TETHER;
/**
* Broadcast Action: The captive portal tracker has finished its test.
@@ -445,7 +445,7 @@
* @see #startTethering(int, boolean, OnStartTetheringCallback)
* @hide
*/
- public static final int TETHERING_INVALID = -1;
+ public static final int TETHERING_INVALID = TetheringManager.TETHERING_INVALID;
/**
* Wifi tethering type.
@@ -453,7 +453,7 @@
* @hide
*/
@SystemApi
- public static final int TETHERING_WIFI = 0;
+ public static final int TETHERING_WIFI = TetheringManager.TETHERING_WIFI;
/**
* USB tethering type.
@@ -461,7 +461,7 @@
* @hide
*/
@SystemApi
- public static final int TETHERING_USB = 1;
+ public static final int TETHERING_USB = TetheringManager.TETHERING_USB;
/**
* Bluetooth tethering type.
@@ -469,7 +469,7 @@
* @hide
*/
@SystemApi
- public static final int TETHERING_BLUETOOTH = 2;
+ public static final int TETHERING_BLUETOOTH = TetheringManager.TETHERING_BLUETOOTH;
/**
* Wifi P2p tethering type.
@@ -477,47 +477,47 @@
* need to start from #startTethering(int, boolean, OnStartTetheringCallback).
* @hide
*/
- public static final int TETHERING_WIFI_P2P = 3;
+ public static final int TETHERING_WIFI_P2P = TetheringManager.TETHERING_WIFI_P2P;
/**
* Extra used for communicating with the TetherService. Includes the type of tethering to
* enable if any.
* @hide
*/
- public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType";
+ public static final String EXTRA_ADD_TETHER_TYPE = TetheringManager.EXTRA_ADD_TETHER_TYPE;
/**
* Extra used for communicating with the TetherService. Includes the type of tethering for
* which to cancel provisioning.
* @hide
*/
- public static final String EXTRA_REM_TETHER_TYPE = "extraRemTetherType";
+ public static final String EXTRA_REM_TETHER_TYPE = TetheringManager.EXTRA_REM_TETHER_TYPE;
/**
* Extra used for communicating with the TetherService. True to schedule a recheck of tether
* provisioning.
* @hide
*/
- public static final String EXTRA_SET_ALARM = "extraSetAlarm";
+ public static final String EXTRA_SET_ALARM = TetheringManager.EXTRA_SET_ALARM;
/**
* Tells the TetherService to run a provision check now.
* @hide
*/
- public static final String EXTRA_RUN_PROVISION = "extraRunProvision";
+ public static final String EXTRA_RUN_PROVISION = TetheringManager.EXTRA_RUN_PROVISION;
/**
* Extra used for communicating with the TetherService. Contains the {@link ResultReceiver}
* which will receive provisioning results. Can be left empty.
* @hide
*/
- public static final String EXTRA_PROVISION_CALLBACK = "extraProvisionCallback";
+ public static final String EXTRA_PROVISION_CALLBACK = TetheringManager.EXTRA_PROVISION_CALLBACK;
/**
* The absence of a connection type.
* @hide
*/
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 130143562)
+ @SystemApi
public static final int TYPE_NONE = -1;
/**
@@ -662,7 +662,7 @@
* {@hide}
*/
@Deprecated
- @UnsupportedAppUsage
+ @SystemApi
public static final int TYPE_WIFI_P2P = 13;
/**
@@ -3107,6 +3107,61 @@
}
}
+ /**
+ * Registers the specified {@link NetworkProvider}.
+ * Each listener must only be registered once. The listener can be unregistered with
+ * {@link #unregisterNetworkProvider}.
+ *
+ * @param provider the provider to register
+ * @return the ID of the provider. This ID must be used by the provider when registering
+ * {@link android.net.NetworkAgent}s.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+ public int registerNetworkProvider(@NonNull NetworkProvider provider) {
+ if (provider.getProviderId() != NetworkProvider.ID_NONE) {
+ throw new IllegalStateException("NetworkProviders can only be registered once");
+ }
+
+ try {
+ int providerId = mService.registerNetworkProvider(provider.getMessenger(),
+ provider.getName());
+ provider.setProviderId(providerId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return provider.getProviderId();
+ }
+
+ /**
+ * Unregisters the specified NetworkProvider.
+ *
+ * @param provider the provider to unregister
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+ public void unregisterNetworkProvider(@NonNull NetworkProvider provider) {
+ try {
+ mService.unregisterNetworkProvider(provider.getMessenger());
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ provider.setProviderId(NetworkProvider.ID_NONE);
+ }
+
+
+ /** @hide exposed via the NetworkProvider class. */
+ @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+ public void declareNetworkRequestUnfulfillable(@NonNull NetworkRequest request) {
+ try {
+ mService.declareNetworkRequestUnfulfillable(request);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
// TODO : remove this method. It is a stopgap measure to help sheperding a number
// of dependent changes that would conflict throughout the automerger graph. Having this
// temporarily helps with the process of going through with all these dependent changes across
@@ -3114,26 +3169,24 @@
/**
* @hide
* Register a NetworkAgent with ConnectivityService.
- * @return NetID corresponding to NetworkAgent.
+ * @return Network corresponding to NetworkAgent.
*/
@RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
- public int registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp,
- NetworkCapabilities nc, int score, NetworkMisc misc) {
- return registerNetworkAgent(messenger, ni, lp, nc, score, misc,
- NetworkFactory.SerialNumber.NONE);
+ public Network registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp,
+ NetworkCapabilities nc, int score, NetworkAgentConfig config) {
+ return registerNetworkAgent(messenger, ni, lp, nc, score, config, NetworkProvider.ID_NONE);
}
/**
* @hide
* Register a NetworkAgent with ConnectivityService.
- * @return NetID corresponding to NetworkAgent.
+ * @return Network corresponding to NetworkAgent.
*/
@RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
- public int registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp,
- NetworkCapabilities nc, int score, NetworkMisc misc, int factorySerialNumber) {
+ public Network registerNetworkAgent(Messenger messenger, NetworkInfo ni, LinkProperties lp,
+ NetworkCapabilities nc, int score, NetworkAgentConfig config, int providerId) {
try {
- return mService.registerNetworkAgent(messenger, ni, lp, nc, score, misc,
- factorySerialNumber);
+ return mService.registerNetworkAgent(messenger, ni, lp, nc, score, config, providerId);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -3569,14 +3622,26 @@
/**
* Helper function to request a network with a particular legacy type.
*
- * This is temporarily public @hide so it can be called by system code that uses the
- * NetworkRequest API to request networks but relies on CONNECTIVITY_ACTION broadcasts for
- * instead network notifications.
+ * @deprecated This is temporarily public for tethering to backwards compatibility that uses
+ * the NetworkRequest API to request networks with legacy type and relies on
+ * CONNECTIVITY_ACTION broadcasts instead of NetworkCallbacks. New caller should use
+ * {@link #requestNetwork(NetworkRequest, NetworkCallback, Handler)} instead.
*
* TODO: update said system code to rely on NetworkCallbacks and make this method private.
+
+ * @param request {@link NetworkRequest} describing this request.
+ * @param networkCallback The {@link NetworkCallback} to be utilized for this request. Note
+ * the callback must not be shared - it uniquely specifies this request.
+ * @param timeoutMs The time in milliseconds to attempt looking for a suitable network
+ * before {@link NetworkCallback#onUnavailable()} is called. The timeout must
+ * be a positive value (i.e. >0).
+ * @param legacyType to specify the network type(#TYPE_*).
+ * @param handler {@link Handler} to specify the thread upon which the callback will be invoked.
*
* @hide
*/
+ @SystemApi
+ @Deprecated
public void requestNetwork(@NonNull NetworkRequest request,
@NonNull NetworkCallback networkCallback, int timeoutMs, int legacyType,
@NonNull Handler handler) {
diff --git a/core/java/android/net/DhcpInfo.java b/core/java/android/net/DhcpInfo.java
index 98bab44..912df67 100644
--- a/core/java/android/net/DhcpInfo.java
+++ b/core/java/android/net/DhcpInfo.java
@@ -16,8 +16,8 @@
package android.net;
-import android.os.Parcelable;
import android.os.Parcel;
+import android.os.Parcelable;
/**
* A simple object for retrieving the results of a DHCP request.
@@ -67,12 +67,12 @@
buf.append(NetworkUtils.intToInetAddress(addr).getHostAddress());
}
- /** Implement the Parcelable interface {@hide} */
+ /** Implement the Parcelable interface */
public int describeContents() {
return 0;
}
- /** Implement the Parcelable interface {@hide} */
+ /** Implement the Parcelable interface */
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(ipAddress);
dest.writeInt(gateway);
@@ -83,7 +83,7 @@
dest.writeInt(leaseDuration);
}
- /** Implement the Parcelable interface {@hide} */
+ /** Implement the Parcelable interface */
public static final @android.annotation.NonNull Creator<DhcpInfo> CREATOR =
new Creator<DhcpInfo>() {
public DhcpInfo createFromParcel(Parcel in) {
diff --git a/core/java/android/net/DhcpResults.java b/core/java/android/net/DhcpResults.java
index 97be51a..059cd94 100644
--- a/core/java/android/net/DhcpResults.java
+++ b/core/java/android/net/DhcpResults.java
@@ -16,7 +16,7 @@
package android.net;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.net.shared.InetAddressUtils;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/net/EthernetManager.java b/core/java/android/net/EthernetManager.java
index 7256502..fd015b4 100644
--- a/core/java/android/net/EthernetManager.java
+++ b/core/java/android/net/EthernetManager.java
@@ -17,7 +17,7 @@
package android.net;
import android.annotation.SystemService;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
diff --git a/core/java/android/net/ICaptivePortal.aidl b/core/java/android/net/ICaptivePortal.aidl
index 707b4f6..fe21905 100644
--- a/core/java/android/net/ICaptivePortal.aidl
+++ b/core/java/android/net/ICaptivePortal.aidl
@@ -21,6 +21,7 @@
* @hide
*/
oneway interface ICaptivePortal {
+ void appRequest(int request);
void appResponse(int response);
void logEvent(int eventId, String packageName);
}
diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl
index 09c02ef..186196bd 100644
--- a/core/java/android/net/IConnectivityManager.aidl
+++ b/core/java/android/net/IConnectivityManager.aidl
@@ -20,9 +20,9 @@
import android.net.ConnectionInfo;
import android.net.LinkProperties;
import android.net.Network;
+import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
-import android.net.NetworkMisc;
import android.net.NetworkQuotaInfo;
import android.net.NetworkRequest;
import android.net.NetworkState;
@@ -142,14 +142,19 @@
void setAirplaneMode(boolean enable);
- int registerNetworkFactory(in Messenger messenger, in String name);
-
boolean requestBandwidthUpdate(in Network network);
+ int registerNetworkFactory(in Messenger messenger, in String name);
void unregisterNetworkFactory(in Messenger messenger);
- int registerNetworkAgent(in Messenger messenger, in NetworkInfo ni, in LinkProperties lp,
- in NetworkCapabilities nc, int score, in NetworkMisc misc, in int factorySerialNumber);
+ int registerNetworkProvider(in Messenger messenger, in String name);
+ void unregisterNetworkProvider(in Messenger messenger);
+
+ void declareNetworkRequestUnfulfillable(in NetworkRequest request);
+
+ Network registerNetworkAgent(in Messenger messenger, in NetworkInfo ni, in LinkProperties lp,
+ in NetworkCapabilities nc, int score, in NetworkAgentConfig config,
+ in int factorySerialNumber);
NetworkRequest requestNetwork(in NetworkCapabilities networkCapabilities,
in Messenger messenger, int timeoutSec, in IBinder binder, int legacy);
diff --git a/core/java/android/net/INetworkPolicyManager.aidl b/core/java/android/net/INetworkPolicyManager.aidl
index 385cb1d..72a6b39 100644
--- a/core/java/android/net/INetworkPolicyManager.aidl
+++ b/core/java/android/net/INetworkPolicyManager.aidl
@@ -57,9 +57,6 @@
@UnsupportedAppUsage
boolean getRestrictBackground();
- /** Callback used to change internal state on tethering */
- void onTetheringChanged(String iface, boolean tethering);
-
/** Gets the restrict background status based on the caller's UID:
1 - disabled
2 - whitelisted
diff --git a/core/java/android/net/INetworkStatsService.aidl b/core/java/android/net/INetworkStatsService.aidl
index 9994f9f..5fa515a 100644
--- a/core/java/android/net/INetworkStatsService.aidl
+++ b/core/java/android/net/INetworkStatsService.aidl
@@ -23,6 +23,8 @@
import android.net.NetworkStats;
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
+import android.net.netstats.provider.INetworkStatsProvider;
+import android.net.netstats.provider.INetworkStatsProviderCallback;
import android.os.IBinder;
import android.os.Messenger;
import com.android.internal.net.VpnInfo;
@@ -89,4 +91,7 @@
/** Get the total network stats information since boot */
long getTotalStats(int type);
+ /** Registers a network stats provider */
+ INetworkStatsProviderCallback registerNetworkStatsProvider(String tag,
+ in INetworkStatsProvider provider);
}
diff --git a/core/java/android/net/InterfaceConfiguration.java b/core/java/android/net/InterfaceConfiguration.java
index 1ae44e1..37425ff 100644
--- a/core/java/android/net/InterfaceConfiguration.java
+++ b/core/java/android/net/InterfaceConfiguration.java
@@ -16,7 +16,7 @@
package android.net;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/net/IpConfiguration.java b/core/java/android/net/IpConfiguration.java
index dddb64d..23d5ff7 100644
--- a/core/java/android/net/IpConfiguration.java
+++ b/core/java/android/net/IpConfiguration.java
@@ -20,8 +20,7 @@
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
-import android.net.StaticIpConfiguration;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index 45d0c73..09ec6c3 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -17,7 +17,6 @@
import static com.android.internal.util.Preconditions.checkNotNull;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
@@ -26,6 +25,7 @@
import android.annotation.TestApi;
import android.content.Context;
import android.content.pm.PackageManager;
+import android.net.annotations.PolicyDirection;
import android.os.Binder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
@@ -41,8 +41,6 @@
import java.io.FileDescriptor;
import java.io.IOException;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.Socket;
@@ -78,11 +76,6 @@
*/
public static final int DIRECTION_OUT = 1;
- /** @hide */
- @IntDef(value = {DIRECTION_IN, DIRECTION_OUT})
- @Retention(RetentionPolicy.SOURCE)
- public @interface PolicyDirection {}
-
/**
* The Security Parameter Index (SPI) 0 indicates an unknown or invalid index.
*
diff --git a/core/java/android/net/LinkAddress.java b/core/java/android/net/LinkAddress.java
index 93dd2e4..bf8b38f 100644
--- a/core/java/android/net/LinkAddress.java
+++ b/core/java/android/net/LinkAddress.java
@@ -30,7 +30,7 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/net/LinkProperties.java b/core/java/android/net/LinkProperties.java
index ed509cb..be8e561 100644
--- a/core/java/android/net/LinkProperties.java
+++ b/core/java/android/net/LinkProperties.java
@@ -20,7 +20,9 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.net.util.LinkPropertiesUtils;
+import android.net.util.LinkPropertiesUtils.CompareResult;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
@@ -63,6 +65,7 @@
private String mPrivateDnsServerName;
private String mDomains;
private ArrayList<RouteInfo> mRoutes = new ArrayList<>();
+ private Inet4Address mDhcpServerAddress;
private ProxyInfo mHttpProxy;
private int mMtu;
// in the format "rmem_min,rmem_def,rmem_max,wmem_min,wmem_def,wmem_max"
@@ -83,36 +86,6 @@
/**
* @hide
*/
- public static class CompareResult<T> {
- public final List<T> removed = new ArrayList<>();
- public final List<T> added = new ArrayList<>();
-
- public CompareResult() {}
-
- public CompareResult(Collection<T> oldItems, Collection<T> newItems) {
- if (oldItems != null) {
- removed.addAll(oldItems);
- }
- if (newItems != null) {
- for (T newItem : newItems) {
- if (!removed.remove(newItem)) {
- added.add(newItem);
- }
- }
- }
- }
-
- @Override
- public String toString() {
- return "removed=[" + TextUtils.join(",", removed)
- + "] added=[" + TextUtils.join(",", added)
- + "]";
- }
- }
-
- /**
- * @hide
- */
@UnsupportedAppUsage(implicitMember =
"values()[Landroid/net/LinkProperties$ProvisioningChange;")
public enum ProvisioningChange {
@@ -196,6 +169,7 @@
addStackedLink(l);
}
setMtu(source.mMtu);
+ setDhcpServerAddress(source.getDhcpServerAddress());
mTcpBufferSizes = source.mTcpBufferSizes;
mNat64Prefix = source.mNat64Prefix;
mWakeOnLanSupported = source.mWakeOnLanSupported;
@@ -460,6 +434,24 @@
}
/**
+ * Set DHCP server address.
+ *
+ * @param serverAddress the server address to set.
+ */
+ public void setDhcpServerAddress(@Nullable Inet4Address serverAddress) {
+ mDhcpServerAddress = serverAddress;
+ }
+
+ /**
+ * Get DHCP server address
+ *
+ * @return The current DHCP server address.
+ */
+ public @Nullable Inet4Address getDhcpServerAddress() {
+ return mDhcpServerAddress;
+ }
+
+ /**
* Returns the private DNS server name that is in use. If not {@code null},
* private DNS is in strict mode. In this mode, applications should ensure
* that all DNS queries are encrypted and sent to this hostname and that
@@ -851,6 +843,7 @@
mHttpProxy = null;
mStackedLinks.clear();
mMtu = 0;
+ mDhcpServerAddress = null;
mTcpBufferSizes = null;
mNat64Prefix = null;
mWakeOnLanSupported = false;
@@ -919,6 +912,11 @@
resultJoiner.add("WakeOnLanSupported: true");
}
+ if (mDhcpServerAddress != null) {
+ resultJoiner.add("ServerAddress:");
+ resultJoiner.add(mDhcpServerAddress.toString());
+ }
+
if (mTcpBufferSizes != null) {
resultJoiner.add("TcpBufferSizes:");
resultJoiner.add(mTcpBufferSizes);
@@ -1269,7 +1267,18 @@
*/
@UnsupportedAppUsage
public boolean isIdenticalInterfaceName(@NonNull LinkProperties target) {
- return TextUtils.equals(getInterfaceName(), target.getInterfaceName());
+ return LinkPropertiesUtils.isIdenticalInterfaceName(target, this);
+ }
+
+ /**
+ * Compares this {@code LinkProperties} DHCP server address against the target
+ *
+ * @param target LinkProperties to compare.
+ * @return {@code true} if both are identical, {@code false} otherwise.
+ * @hide
+ */
+ public boolean isIdenticalDhcpServerAddress(@NonNull LinkProperties target) {
+ return Objects.equals(mDhcpServerAddress, target.mDhcpServerAddress);
}
/**
@@ -1281,10 +1290,7 @@
*/
@UnsupportedAppUsage
public boolean isIdenticalAddresses(@NonNull LinkProperties target) {
- Collection<InetAddress> targetAddresses = target.getAddresses();
- Collection<InetAddress> sourceAddresses = getAddresses();
- return (sourceAddresses.size() == targetAddresses.size()) ?
- sourceAddresses.containsAll(targetAddresses) : false;
+ return LinkPropertiesUtils.isIdenticalAddresses(target, this);
}
/**
@@ -1296,15 +1302,7 @@
*/
@UnsupportedAppUsage
public boolean isIdenticalDnses(@NonNull LinkProperties target) {
- Collection<InetAddress> targetDnses = target.getDnsServers();
- String targetDomains = target.getDomains();
- if (mDomains == null) {
- if (targetDomains != null) return false;
- } else {
- if (!mDomains.equals(targetDomains)) return false;
- }
- return (mDnses.size() == targetDnses.size()) ?
- mDnses.containsAll(targetDnses) : false;
+ return LinkPropertiesUtils.isIdenticalDnses(target, this);
}
/**
@@ -1357,9 +1355,7 @@
*/
@UnsupportedAppUsage
public boolean isIdenticalRoutes(@NonNull LinkProperties target) {
- Collection<RouteInfo> targetRoutes = target.getRoutes();
- return (mRoutes.size() == targetRoutes.size()) ?
- mRoutes.containsAll(targetRoutes) : false;
+ return LinkPropertiesUtils.isIdenticalRoutes(target, this);
}
/**
@@ -1371,8 +1367,7 @@
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public boolean isIdenticalHttpProxy(@NonNull LinkProperties target) {
- return getHttpProxy() == null ? target.getHttpProxy() == null :
- getHttpProxy().equals(target.getHttpProxy());
+ return LinkPropertiesUtils.isIdenticalHttpProxy(target, this);
}
/**
@@ -1489,6 +1484,7 @@
*/
return isIdenticalInterfaceName(target)
&& isIdenticalAddresses(target)
+ && isIdenticalDhcpServerAddress(target)
&& isIdenticalDnses(target)
&& isIdenticalPrivateDns(target)
&& isIdenticalValidatedPrivateDnses(target)
@@ -1503,26 +1499,6 @@
}
/**
- * Compares the addresses in this LinkProperties with another
- * LinkProperties, examining only addresses on the base link.
- *
- * @param target a LinkProperties with the new list of addresses
- * @return the differences between the addresses.
- * @hide
- */
- public @NonNull CompareResult<LinkAddress> compareAddresses(@Nullable LinkProperties target) {
- /*
- * Duplicate the LinkAddresses into removed, we will be removing
- * address which are common between mLinkAddresses and target
- * leaving the addresses that are different. And address which
- * are in target but not in mLinkAddresses are placed in the
- * addedAddresses.
- */
- return new CompareResult<>(mLinkAddresses,
- target != null ? target.getLinkAddresses() : null);
- }
-
- /**
* Compares the DNS addresses in this LinkProperties with another
* LinkProperties, examining only DNS addresses on the base link.
*
@@ -1613,6 +1589,7 @@
+ mMtu * 51
+ ((null == mTcpBufferSizes) ? 0 : mTcpBufferSizes.hashCode())
+ (mUsePrivateDns ? 57 : 0)
+ + ((null == mDhcpServerAddress) ? 0 : mDhcpServerAddress.hashCode())
+ mPcscfs.size() * 67
+ ((null == mPrivateDnsServerName) ? 0 : mPrivateDnsServerName.hashCode())
+ Objects.hash(mNat64Prefix)
@@ -1635,6 +1612,7 @@
dest.writeString(mPrivateDnsServerName);
writeAddresses(dest, mPcscfs);
dest.writeString(mDomains);
+ writeAddress(dest, mDhcpServerAddress);
dest.writeInt(mMtu);
dest.writeString(mTcpBufferSizes);
dest.writeInt(mRoutes.size());
@@ -1663,8 +1641,9 @@
}
}
- private static void writeAddress(@NonNull Parcel dest, @NonNull InetAddress addr) {
- dest.writeByteArray(addr.getAddress());
+ private static void writeAddress(@NonNull Parcel dest, @Nullable InetAddress addr) {
+ byte[] addressBytes = (addr == null ? null : addr.getAddress());
+ dest.writeByteArray(addressBytes);
if (addr instanceof Inet6Address) {
final Inet6Address v6Addr = (Inet6Address) addr;
final boolean hasScopeId = v6Addr.getScopeId() != 0;
@@ -1673,9 +1652,11 @@
}
}
- @NonNull
+ @Nullable
private static InetAddress readAddress(@NonNull Parcel p) throws UnknownHostException {
final byte[] addr = p.createByteArray();
+ if (addr == null) return null;
+
if (addr.length == INET6_ADDR_LENGTH) {
final boolean hasScopeId = p.readBoolean();
final int scopeId = hasScopeId ? p.readInt() : 0;
@@ -1722,6 +1703,10 @@
} catch (UnknownHostException e) { }
}
netProp.setDomains(in.readString());
+ try {
+ netProp.setDhcpServerAddress((Inet4Address) InetAddress
+ .getByAddress(in.createByteArray()));
+ } catch (UnknownHostException e) { }
netProp.setMtu(in.readInt());
netProp.setTcpBufferSizes(in.readString());
addressCount = in.readInt();
diff --git a/core/java/android/net/LinkQualityInfo.java b/core/java/android/net/LinkQualityInfo.java
index 2e6915f..aa56cff 100644
--- a/core/java/android/net/LinkQualityInfo.java
+++ b/core/java/android/net/LinkQualityInfo.java
@@ -16,7 +16,7 @@
package android.net;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/net/LocalSocket.java b/core/java/android/net/LocalSocket.java
index 6a2031b..5b38f78 100644
--- a/core/java/android/net/LocalSocket.java
+++ b/core/java/android/net/LocalSocket.java
@@ -16,7 +16,8 @@
package android.net;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
import java.io.Closeable;
import java.io.FileDescriptor;
import java.io.IOException;
diff --git a/core/java/android/net/LocalSocketImpl.java b/core/java/android/net/LocalSocketImpl.java
index b066a15..e80e3a6 100644
--- a/core/java/android/net/LocalSocketImpl.java
+++ b/core/java/android/net/LocalSocketImpl.java
@@ -16,7 +16,7 @@
package android.net;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.system.ErrnoException;
import android.system.Int32Ref;
import android.system.Os;
diff --git a/core/java/android/net/MacAddress.java b/core/java/android/net/MacAddress.java
index 8729514..0e10c42 100644
--- a/core/java/android/net/MacAddress.java
+++ b/core/java/android/net/MacAddress.java
@@ -19,12 +19,12 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.net.util.MacAddressUtils;
import android.net.wifi.WifiInfo;
import android.os.Parcel;
import android.os.Parcelable;
-import com.android.internal.util.BitUtils;
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
@@ -33,7 +33,6 @@
import java.net.UnknownHostException;
import java.security.SecureRandom;
import java.util.Arrays;
-import java.util.Random;
/**
* Representation of a MAC address.
@@ -109,21 +108,13 @@
if (equals(BROADCAST_ADDRESS)) {
return TYPE_BROADCAST;
}
- if (isMulticastAddress()) {
+ if ((mAddr & MULTICAST_MASK) != 0) {
return TYPE_MULTICAST;
}
return TYPE_UNICAST;
}
/**
- * @return true if this MacAddress is a multicast address.
- * @hide
- */
- public boolean isMulticastAddress() {
- return (mAddr & MULTICAST_MASK) != 0;
- }
-
- /**
* @return true if this MacAddress is a locally assigned address.
*/
public boolean isLocallyAssigned() {
@@ -192,7 +183,7 @@
* @hide
*/
public static boolean isMacAddress(byte[] addr) {
- return addr != null && addr.length == ETHER_ADDR_LEN;
+ return MacAddressUtils.isMacAddress(addr);
}
/**
@@ -261,26 +252,11 @@
}
private static byte[] byteAddrFromLongAddr(long addr) {
- byte[] bytes = new byte[ETHER_ADDR_LEN];
- int index = ETHER_ADDR_LEN;
- while (index-- > 0) {
- bytes[index] = (byte) addr;
- addr = addr >> 8;
- }
- return bytes;
+ return MacAddressUtils.byteAddrFromLongAddr(addr);
}
private static long longAddrFromByteAddr(byte[] addr) {
- Preconditions.checkNotNull(addr);
- if (!isMacAddress(addr)) {
- throw new IllegalArgumentException(
- Arrays.toString(addr) + " was not a valid MAC address");
- }
- long longAddr = 0;
- for (byte b : addr) {
- longAddr = (longAddr << 8) + BitUtils.uint8(b);
- }
- return longAddr;
+ return MacAddressUtils.longAddrFromByteAddr(addr);
}
// Internal conversion function equivalent to longAddrFromByteAddr(byteAddrFromStringAddr(addr))
@@ -350,50 +326,7 @@
* @hide
*/
public static @NonNull MacAddress createRandomUnicastAddressWithGoogleBase() {
- return createRandomUnicastAddress(BASE_GOOGLE_MAC, new SecureRandom());
- }
-
- /**
- * Returns a generated MAC address whose 46 bits, excluding the locally assigned bit and the
- * unicast bit, are randomly selected.
- *
- * The locally assigned bit is always set to 1. The multicast bit is always set to 0.
- *
- * @return a random locally assigned, unicast MacAddress.
- *
- * @hide
- */
- public static @NonNull MacAddress createRandomUnicastAddress() {
- return createRandomUnicastAddress(null, new SecureRandom());
- }
-
- /**
- * Returns a randomly generated MAC address using the given Random object and the same
- * OUI values as the given MacAddress.
- *
- * The locally assigned bit is always set to 1. The multicast bit is always set to 0.
- *
- * @param base a base MacAddress whose OUI is used for generating the random address.
- * If base == null then the OUI will also be randomized.
- * @param r a standard Java Random object used for generating the random address.
- * @return a random locally assigned MacAddress.
- *
- * @hide
- */
- public static @NonNull MacAddress createRandomUnicastAddress(MacAddress base, Random r) {
- long addr;
- if (base == null) {
- addr = r.nextLong() & VALID_LONG_MASK;
- } else {
- addr = (base.mAddr & OUI_MASK) | (NIC_MASK & r.nextLong());
- }
- addr |= LOCALLY_ASSIGNED_MASK;
- addr &= ~MULTICAST_MASK;
- MacAddress mac = new MacAddress(addr);
- if (mac.equals(DEFAULT_MAC_ADDRESS)) {
- return createRandomUnicastAddress(base, r);
- }
- return mac;
+ return MacAddressUtils.createRandomUnicastAddress(BASE_GOOGLE_MAC, new SecureRandom());
}
// Convenience function for working around the lack of byte literals.
diff --git a/core/java/android/net/MobileLinkQualityInfo.java b/core/java/android/net/MobileLinkQualityInfo.java
index a10a14d..a65de6b 100644
--- a/core/java/android/net/MobileLinkQualityInfo.java
+++ b/core/java/android/net/MobileLinkQualityInfo.java
@@ -16,7 +16,7 @@
package android.net;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
/**
diff --git a/core/java/android/net/NattKeepalivePacketData.java b/core/java/android/net/NattKeepalivePacketData.java
index 3fb52f1..bd39c13 100644
--- a/core/java/android/net/NattKeepalivePacketData.java
+++ b/core/java/android/net/NattKeepalivePacketData.java
@@ -16,9 +16,11 @@
package android.net;
-import static android.net.SocketKeepalive.ERROR_INVALID_IP_ADDRESS;
-import static android.net.SocketKeepalive.ERROR_INVALID_PORT;
+import static android.net.InvalidPacketException.ERROR_INVALID_IP_ADDRESS;
+import static android.net.InvalidPacketException.ERROR_INVALID_PORT;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
import android.net.util.IpUtils;
import android.os.Parcel;
import android.os.Parcelable;
@@ -30,20 +32,22 @@
import java.nio.ByteOrder;
/** @hide */
+@SystemApi
public final class NattKeepalivePacketData extends KeepalivePacketData implements Parcelable {
private static final int IPV4_HEADER_LENGTH = 20;
private static final int UDP_HEADER_LENGTH = 8;
// This should only be constructed via static factory methods, such as
// nattKeepalivePacket
- private NattKeepalivePacketData(InetAddress srcAddress, int srcPort,
- InetAddress dstAddress, int dstPort, byte[] data) throws
+ public NattKeepalivePacketData(@NonNull InetAddress srcAddress, int srcPort,
+ @NonNull InetAddress dstAddress, int dstPort, @NonNull byte[] data) throws
InvalidPacketException {
super(srcAddress, srcPort, dstAddress, dstPort, data);
}
/**
* Factory method to create Nat-T keepalive packet structure.
+ * @hide
*/
public static NattKeepalivePacketData nattKeepalivePacket(
InetAddress srcAddress, int srcPort, InetAddress dstAddress, int dstPort)
@@ -87,7 +91,7 @@
}
/** Write to parcel */
- public void writeToParcel(Parcel out, int flags) {
+ public void writeToParcel(@NonNull Parcel out, int flags) {
out.writeString(srcAddress.getHostAddress());
out.writeString(dstAddress.getHostAddress());
out.writeInt(srcPort);
@@ -95,7 +99,7 @@
}
/** Parcelable Creator */
- public static final Parcelable.Creator<NattKeepalivePacketData> CREATOR =
+ public static final @NonNull Parcelable.Creator<NattKeepalivePacketData> CREATOR =
new Parcelable.Creator<NattKeepalivePacketData>() {
public NattKeepalivePacketData createFromParcel(Parcel in) {
final InetAddress srcAddress =
diff --git a/core/java/android/net/Network.java b/core/java/android/net/Network.java
index c6c73fe..c5681cb 100644
--- a/core/java/android/net/Network.java
+++ b/core/java/android/net/Network.java
@@ -19,7 +19,7 @@
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
import android.system.ErrnoException;
diff --git a/core/java/android/net/NetworkAgent.java b/core/java/android/net/NetworkAgent.java
index ff4bf2d..aae9fd4 100644
--- a/core/java/android/net/NetworkAgent.java
+++ b/core/java/android/net/NetworkAgent.java
@@ -17,7 +17,9 @@
package android.net;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Build;
import android.os.Bundle;
@@ -43,11 +45,15 @@
*
* @hide
*/
-public abstract class NetworkAgent extends Handler {
- // Guaranteed to be valid (not NETID_UNSET), otherwise registerNetworkAgent() would have thrown
- // an exception.
- public final int netId;
+@SystemApi
+public abstract class NetworkAgent {
+ /**
+ * The {@link Network} corresponding to this object.
+ */
+ @NonNull
+ public final Network network;
+ private final Handler mHandler;
private volatile AsyncChannel mAsyncChannel;
private final String LOG_TAG;
private static final boolean DBG = true;
@@ -56,9 +62,14 @@
private final ArrayList<Message>mPreConnectedQueue = new ArrayList<Message>();
private volatile long mLastBwRefreshTime = 0;
private static final long BW_REFRESH_MIN_WIN_MS = 500;
- private boolean mPollLceScheduled = false;
- private AtomicBoolean mPollLcePending = new AtomicBoolean(false);
- public final int mFactorySerialNumber;
+ private boolean mBandwidthUpdateScheduled = false;
+ private AtomicBoolean mBandwidthUpdatePending = new AtomicBoolean(false);
+
+ /**
+ * The ID of the {@link NetworkProvider} that created this object, or
+ * {@link NetworkProvider#ID_NONE} if unknown.
+ */
+ public final int providerId;
private static final int BASE = Protocol.BASE_NETWORK_AGENT;
@@ -66,6 +77,7 @@
* Sent by ConnectivityService to the NetworkAgent to inform it of
* suspected connectivity problems on its network. The NetworkAgent
* should take steps to verify and correct connectivity.
+ * @hide
*/
public static final int CMD_SUSPECT_BAD = BASE;
@@ -74,6 +86,7 @@
* ConnectivityService to pass the current NetworkInfo (connection state).
* Sent when the NetworkInfo changes, mainly due to change of state.
* obj = NetworkInfo
+ * @hide
*/
public static final int EVENT_NETWORK_INFO_CHANGED = BASE + 1;
@@ -81,6 +94,7 @@
* Sent by the NetworkAgent to ConnectivityService to pass the current
* NetworkCapabilties.
* obj = NetworkCapabilities
+ * @hide
*/
public static final int EVENT_NETWORK_CAPABILITIES_CHANGED = BASE + 2;
@@ -88,11 +102,14 @@
* Sent by the NetworkAgent to ConnectivityService to pass the current
* NetworkProperties.
* obj = NetworkProperties
+ * @hide
*/
public static final int EVENT_NETWORK_PROPERTIES_CHANGED = BASE + 3;
- /* centralize place where base network score, and network score scaling, will be
+ /**
+ * Centralize the place where base network score, and network score scaling, will be
* stored, so as we can consistently compare apple and oranges, or wifi, ethernet and LTE
+ * @hide
*/
public static final int WIFI_BASE_SCORE = 60;
@@ -100,6 +117,7 @@
* Sent by the NetworkAgent to ConnectivityService to pass the current
* network score.
* obj = network score Integer
+ * @hide
*/
public static final int EVENT_NETWORK_SCORE_CHANGED = BASE + 4;
@@ -112,12 +130,33 @@
* obj = Bundle containing map from {@code REDIRECT_URL_KEY} to {@code String}
* representing URL that Internet probe was redirect to, if it was redirected,
* or mapping to {@code null} otherwise.
+ * @hide
*/
public static final int CMD_REPORT_NETWORK_STATUS = BASE + 7;
+
+ /**
+ * Network validation suceeded.
+ * Corresponds to {@link NetworkCapabilities.NET_CAPABILITY_VALIDATED}.
+ */
+ public static final int VALIDATION_STATUS_VALID = 1;
+
+ /**
+ * Network validation was attempted and failed. This may be received more than once as
+ * subsequent validation attempts are made.
+ */
+ public static final int VALIDATION_STATUS_NOT_VALID = 2;
+
+ // TODO: remove.
+ /** @hide */
public static final int VALID_NETWORK = 1;
+ /** @hide */
public static final int INVALID_NETWORK = 2;
+ /**
+ * The key for the redirect URL in the Bundle argument of {@code CMD_REPORT_NETWORK_STATUS}.
+ * @hide
+ */
public static String REDIRECT_URL_KEY = "redirect URL";
/**
@@ -126,6 +165,7 @@
* CONNECTED so it can be given special treatment at that time.
*
* obj = boolean indicating whether to use this network even if unvalidated
+ * @hide
*/
public static final int EVENT_SET_EXPLICITLY_SELECTED = BASE + 8;
@@ -136,12 +176,14 @@
* responsibility to remember it.
*
* arg1 = 1 if true, 0 if false
+ * @hide
*/
public static final int CMD_SAVE_ACCEPT_UNVALIDATED = BASE + 9;
/**
* Sent by ConnectivityService to the NetworkAgent to inform the agent to pull
* the underlying network connection for updated bandwidth information.
+ * @hide
*/
public static final int CMD_REQUEST_BANDWIDTH_UPDATE = BASE + 10;
@@ -154,6 +196,7 @@
* obj = KeepalivePacketData object describing the data to be sent
*
* Also used internally by ConnectivityService / KeepaliveTracker, with different semantics.
+ * @hide
*/
public static final int CMD_START_SOCKET_KEEPALIVE = BASE + 11;
@@ -163,6 +206,7 @@
* arg1 = slot number of the keepalive to stop.
*
* Also used internally by ConnectivityService / KeepaliveTracker, with different semantics.
+ * @hide
*/
public static final int CMD_STOP_SOCKET_KEEPALIVE = BASE + 12;
@@ -176,6 +220,7 @@
*
* arg1 = slot number of the keepalive
* arg2 = error code
+ * @hide
*/
public static final int EVENT_SOCKET_KEEPALIVE = BASE + 13;
@@ -184,6 +229,7 @@
* that when crossed should trigger a system wakeup and a NetworkCapabilities update.
*
* obj = int[] describing signal strength thresholds.
+ * @hide
*/
public static final int CMD_SET_SIGNAL_STRENGTH_THRESHOLDS = BASE + 14;
@@ -191,6 +237,7 @@
* Sent by ConnectivityService to the NeworkAgent to inform the agent to avoid
* automatically reconnecting to this network (e.g. via autojoin). Happens
* when user selects "No" option on the "Stay connected?" dialog box.
+ * @hide
*/
public static final int CMD_PREVENT_AUTOMATIC_RECONNECT = BASE + 15;
@@ -203,6 +250,7 @@
* This does not happen with UDP, so this message is TCP-specific.
* arg1 = slot number of the keepalive to filter for.
* obj = the keepalive packet to send repeatedly.
+ * @hide
*/
public static final int CMD_ADD_KEEPALIVE_PACKET_FILTER = BASE + 16;
@@ -210,6 +258,7 @@
* Sent by the KeepaliveTracker to NetworkAgent to remove a packet filter. See
* {@link #CMD_ADD_KEEPALIVE_PACKET_FILTER}.
* arg1 = slot number of the keepalive packet filter to remove.
+ * @hide
*/
public static final int CMD_REMOVE_KEEPALIVE_PACKET_FILTER = BASE + 17;
@@ -217,27 +266,32 @@
// of dependent changes that would conflict throughout the automerger graph. Having these
// temporarily helps with the process of going through with all these dependent changes across
// the entire tree.
+ /** @hide TODO: decide which of these to expose. */
public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
NetworkCapabilities nc, LinkProperties lp, int score) {
- this(looper, context, logTag, ni, nc, lp, score, null, NetworkFactory.SerialNumber.NONE);
- }
- public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
- NetworkCapabilities nc, LinkProperties lp, int score, NetworkMisc misc) {
- this(looper, context, logTag, ni, nc, lp, score, misc, NetworkFactory.SerialNumber.NONE);
+ this(looper, context, logTag, ni, nc, lp, score, null, NetworkProvider.ID_NONE);
}
+ /** @hide TODO: decide which of these to expose. */
public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
- NetworkCapabilities nc, LinkProperties lp, int score, int factorySerialNumber) {
- this(looper, context, logTag, ni, nc, lp, score, null, factorySerialNumber);
+ NetworkCapabilities nc, LinkProperties lp, int score, NetworkAgentConfig config) {
+ this(looper, context, logTag, ni, nc, lp, score, config, NetworkProvider.ID_NONE);
}
+ /** @hide TODO: decide which of these to expose. */
public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
- NetworkCapabilities nc, LinkProperties lp, int score, NetworkMisc misc,
- int factorySerialNumber) {
- super(looper);
+ NetworkCapabilities nc, LinkProperties lp, int score, int providerId) {
+ this(looper, context, logTag, ni, nc, lp, score, null, providerId);
+ }
+
+ /** @hide TODO: decide which of these to expose. */
+ public NetworkAgent(Looper looper, Context context, String logTag, NetworkInfo ni,
+ NetworkCapabilities nc, LinkProperties lp, int score, NetworkAgentConfig config,
+ int providerId) {
+ mHandler = new NetworkAgentHandler(looper);
LOG_TAG = logTag;
mContext = context;
- mFactorySerialNumber = factorySerialNumber;
+ this.providerId = providerId;
if (ni == null || nc == null || lp == null) {
throw new IllegalArgumentException();
}
@@ -245,117 +299,126 @@
if (VDBG) log("Registering NetworkAgent");
ConnectivityManager cm = (ConnectivityManager)mContext.getSystemService(
Context.CONNECTIVITY_SERVICE);
- netId = cm.registerNetworkAgent(new Messenger(this), new NetworkInfo(ni),
- new LinkProperties(lp), new NetworkCapabilities(nc), score, misc,
- factorySerialNumber);
+ network = cm.registerNetworkAgent(new Messenger(mHandler), new NetworkInfo(ni),
+ new LinkProperties(lp), new NetworkCapabilities(nc), score, config,
+ providerId);
}
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
- if (mAsyncChannel != null) {
- log("Received new connection while already connected!");
- } else {
- if (VDBG) log("NetworkAgent fully connected");
- AsyncChannel ac = new AsyncChannel();
- ac.connected(null, this, msg.replyTo);
- ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
- AsyncChannel.STATUS_SUCCESSFUL);
- synchronized (mPreConnectedQueue) {
- mAsyncChannel = ac;
- for (Message m : mPreConnectedQueue) {
- ac.sendMessage(m);
- }
- mPreConnectedQueue.clear();
- }
- }
- break;
- }
- case AsyncChannel.CMD_CHANNEL_DISCONNECT: {
- if (VDBG) log("CMD_CHANNEL_DISCONNECT");
- if (mAsyncChannel != null) mAsyncChannel.disconnect();
- break;
- }
- case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
- if (DBG) log("NetworkAgent channel lost");
- // let the client know CS is done with us.
- unwanted();
- synchronized (mPreConnectedQueue) {
- mAsyncChannel = null;
- }
- break;
- }
- case CMD_SUSPECT_BAD: {
- log("Unhandled Message " + msg);
- break;
- }
- case CMD_REQUEST_BANDWIDTH_UPDATE: {
- long currentTimeMs = System.currentTimeMillis();
- if (VDBG) {
- log("CMD_REQUEST_BANDWIDTH_UPDATE request received.");
- }
- if (currentTimeMs >= (mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS)) {
- mPollLceScheduled = false;
- if (mPollLcePending.getAndSet(true) == false) {
- pollLceData();
- }
- } else {
- // deliver the request at a later time rather than discard it completely.
- if (!mPollLceScheduled) {
- long waitTime = mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS -
- currentTimeMs + 1;
- mPollLceScheduled = sendEmptyMessageDelayed(
- CMD_REQUEST_BANDWIDTH_UPDATE, waitTime);
- }
- }
- break;
- }
- case CMD_REPORT_NETWORK_STATUS: {
- String redirectUrl = ((Bundle)msg.obj).getString(REDIRECT_URL_KEY);
- if (VDBG) {
- log("CMD_REPORT_NETWORK_STATUS(" +
- (msg.arg1 == VALID_NETWORK ? "VALID, " : "INVALID, ") + redirectUrl);
- }
- networkStatus(msg.arg1, redirectUrl);
- break;
- }
- case CMD_SAVE_ACCEPT_UNVALIDATED: {
- saveAcceptUnvalidated(msg.arg1 != 0);
- break;
- }
- case CMD_START_SOCKET_KEEPALIVE: {
- startSocketKeepalive(msg);
- break;
- }
- case CMD_STOP_SOCKET_KEEPALIVE: {
- stopSocketKeepalive(msg);
- break;
- }
+ private class NetworkAgentHandler extends Handler {
+ NetworkAgentHandler(Looper looper) {
+ super(looper);
+ }
- case CMD_SET_SIGNAL_STRENGTH_THRESHOLDS: {
- ArrayList<Integer> thresholds =
- ((Bundle) msg.obj).getIntegerArrayList("thresholds");
- // TODO: Change signal strength thresholds API to use an ArrayList<Integer>
- // rather than convert to int[].
- int[] intThresholds = new int[(thresholds != null) ? thresholds.size() : 0];
- for (int i = 0; i < intThresholds.length; i++) {
- intThresholds[i] = thresholds.get(i);
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
+ if (mAsyncChannel != null) {
+ log("Received new connection while already connected!");
+ } else {
+ if (VDBG) log("NetworkAgent fully connected");
+ AsyncChannel ac = new AsyncChannel();
+ ac.connected(null, this, msg.replyTo);
+ ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
+ AsyncChannel.STATUS_SUCCESSFUL);
+ synchronized (mPreConnectedQueue) {
+ mAsyncChannel = ac;
+ for (Message m : mPreConnectedQueue) {
+ ac.sendMessage(m);
+ }
+ mPreConnectedQueue.clear();
+ }
+ }
+ break;
}
- setSignalStrengthThresholds(intThresholds);
- break;
- }
- case CMD_PREVENT_AUTOMATIC_RECONNECT: {
- preventAutomaticReconnect();
- break;
- }
- case CMD_ADD_KEEPALIVE_PACKET_FILTER: {
- addKeepalivePacketFilter(msg);
- break;
- }
- case CMD_REMOVE_KEEPALIVE_PACKET_FILTER: {
- removeKeepalivePacketFilter(msg);
- break;
+ case AsyncChannel.CMD_CHANNEL_DISCONNECT: {
+ if (VDBG) log("CMD_CHANNEL_DISCONNECT");
+ if (mAsyncChannel != null) mAsyncChannel.disconnect();
+ break;
+ }
+ case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
+ if (DBG) log("NetworkAgent channel lost");
+ // let the client know CS is done with us.
+ onNetworkUnwanted();
+ synchronized (mPreConnectedQueue) {
+ mAsyncChannel = null;
+ }
+ break;
+ }
+ case CMD_SUSPECT_BAD: {
+ log("Unhandled Message " + msg);
+ break;
+ }
+ case CMD_REQUEST_BANDWIDTH_UPDATE: {
+ long currentTimeMs = System.currentTimeMillis();
+ if (VDBG) {
+ log("CMD_REQUEST_BANDWIDTH_UPDATE request received.");
+ }
+ if (currentTimeMs >= (mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS)) {
+ mBandwidthUpdateScheduled = false;
+ if (!mBandwidthUpdatePending.getAndSet(true)) {
+ onBandwidthUpdateRequested();
+ }
+ } else {
+ // deliver the request at a later time rather than discard it completely.
+ if (!mBandwidthUpdateScheduled) {
+ long waitTime = mLastBwRefreshTime + BW_REFRESH_MIN_WIN_MS
+ - currentTimeMs + 1;
+ mBandwidthUpdateScheduled = sendEmptyMessageDelayed(
+ CMD_REQUEST_BANDWIDTH_UPDATE, waitTime);
+ }
+ }
+ break;
+ }
+ case CMD_REPORT_NETWORK_STATUS: {
+ String redirectUrl = ((Bundle) msg.obj).getString(REDIRECT_URL_KEY);
+ if (VDBG) {
+ log("CMD_REPORT_NETWORK_STATUS("
+ + (msg.arg1 == VALID_NETWORK ? "VALID, " : "INVALID, ")
+ + redirectUrl);
+ }
+ onValidationStatus(msg.arg1 /* status */, redirectUrl);
+ break;
+ }
+ case CMD_SAVE_ACCEPT_UNVALIDATED: {
+ onSaveAcceptUnvalidated(msg.arg1 != 0);
+ break;
+ }
+ case CMD_START_SOCKET_KEEPALIVE: {
+ onStartSocketKeepalive(msg.arg1 /* slot */, msg.arg2 /* interval */,
+ (KeepalivePacketData) msg.obj /* packet */);
+ break;
+ }
+ case CMD_STOP_SOCKET_KEEPALIVE: {
+ onStopSocketKeepalive(msg.arg1 /* slot */);
+ break;
+ }
+
+ case CMD_SET_SIGNAL_STRENGTH_THRESHOLDS: {
+ ArrayList<Integer> thresholds =
+ ((Bundle) msg.obj).getIntegerArrayList("thresholds");
+ // TODO: Change signal strength thresholds API to use an ArrayList<Integer>
+ // rather than convert to int[].
+ int[] intThresholds = new int[(thresholds != null) ? thresholds.size() : 0];
+ for (int i = 0; i < intThresholds.length; i++) {
+ intThresholds[i] = thresholds.get(i);
+ }
+ onSignalStrengthThresholdsUpdated(intThresholds);
+ break;
+ }
+ case CMD_PREVENT_AUTOMATIC_RECONNECT: {
+ onAutomaticReconnectDisabled();
+ break;
+ }
+ case CMD_ADD_KEEPALIVE_PACKET_FILTER: {
+ onAddKeepalivePacketFilter(msg.arg1 /* slot */,
+ (KeepalivePacketData) msg.obj /* packet */);
+ break;
+ }
+ case CMD_REMOVE_KEEPALIVE_PACKET_FILTER: {
+ onRemoveKeepalivePacketFilter(msg.arg1 /* slot */);
+ break;
+ }
}
}
}
@@ -388,14 +451,16 @@
}
/**
- * Called by the bearer code when it has new LinkProperties data.
+ * Must be called by the agent when the network's {@link LinkProperties} change.
+ * @param linkProperties the new LinkProperties.
*/
- public void sendLinkProperties(LinkProperties linkProperties) {
+ public void sendLinkProperties(@NonNull LinkProperties linkProperties) {
queueOrSendMessage(EVENT_NETWORK_PROPERTIES_CHANGED, new LinkProperties(linkProperties));
}
/**
- * Called by the bearer code when it has new NetworkInfo data.
+ * Must be called by the agent when it has a new NetworkInfo object.
+ * @hide TODO: expose something better.
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public void sendNetworkInfo(NetworkInfo networkInfo) {
@@ -403,17 +468,19 @@
}
/**
- * Called by the bearer code when it has new NetworkCapabilities data.
+ * Must be called by the agent when the network's {@link NetworkCapabilities} change.
+ * @param networkCapabilities the new NetworkCapabilities.
*/
- public void sendNetworkCapabilities(NetworkCapabilities networkCapabilities) {
- mPollLcePending.set(false);
+ public void sendNetworkCapabilities(@NonNull NetworkCapabilities networkCapabilities) {
+ mBandwidthUpdatePending.set(false);
mLastBwRefreshTime = System.currentTimeMillis();
queueOrSendMessage(EVENT_NETWORK_CAPABILITIES_CHANGED,
new NetworkCapabilities(networkCapabilities));
}
/**
- * Called by the bearer code when it has a new score for this network.
+ * Must be called by the agent to update the score of this network.
+ * @param score the new score.
*/
public void sendNetworkScore(int score) {
if (score < 0) {
@@ -425,14 +492,16 @@
}
/**
- * Called by the bearer code when it has a new NetworkScore for this network.
+ * Must be called by the agent when it has a new {@link NetworkScore} for this network.
+ * @param ns the new score.
+ * @hide TODO: unhide the NetworkScore class, and rename to sendNetworkScore.
*/
public void updateScore(@NonNull NetworkScore ns) {
queueOrSendMessage(EVENT_NETWORK_SCORE_CHANGED, new NetworkScore(ns));
}
/**
- * Called by the bearer to indicate this network was manually selected by the user.
+ * Must be called by the agent to indicate this network was manually selected by the user.
* This should be called before the NetworkInfo is marked CONNECTED so that this
* Network can be given special treatment at that time. If {@code acceptUnvalidated} is
* {@code true}, then the system will switch to this network. If it is {@code false} and the
@@ -441,15 +510,16 @@
* {@link #saveAcceptUnvalidated} to persist the user's choice. Thus, if the transport ever
* calls this method with {@code acceptUnvalidated} set to {@code false}, it must also implement
* {@link #saveAcceptUnvalidated} to respect the user's choice.
+ * @hide should move to NetworkAgentConfig.
*/
public void explicitlySelected(boolean acceptUnvalidated) {
explicitlySelected(true /* explicitlySelected */, acceptUnvalidated);
}
/**
- * Called by the bearer to indicate whether the network was manually selected by the user.
- * This should be called before the NetworkInfo is marked CONNECTED so that this
- * Network can be given special treatment at that time.
+ * Must be called by the agent to indicate whether the network was manually selected by the
+ * user. This should be called before the network becomes connected, so it can be given
+ * special treatment when it does.
*
* If {@code explicitlySelected} is {@code true}, and {@code acceptUnvalidated} is {@code true},
* then the system will switch to this network. If {@code explicitlySelected} is {@code true}
@@ -464,6 +534,7 @@
* {@code true}, the system will interpret this as the user having accepted partial connectivity
* on this network. Thus, the system will switch to the network and consider it validated even
* if it only provides partial connectivity, but the network is not otherwise treated specially.
+ * @hide should move to NetworkAgentConfig.
*/
public void explicitlySelected(boolean explicitlySelected, boolean acceptUnvalidated) {
queueOrSendMessage(EVENT_SET_EXPLICITLY_SELECTED,
@@ -477,73 +548,126 @@
* as well, either canceling NetworkRequests or altering their score such that this
* network won't be immediately requested again.
*/
- abstract protected void unwanted();
+ public void onNetworkUnwanted() {
+ unwanted();
+ }
+ /** @hide TODO delete once subclasses have moved to onNetworkUnwanted. */
+ protected void unwanted() {
+ }
/**
* Called when ConnectivityService request a bandwidth update. The parent factory
* shall try to overwrite this method and produce a bandwidth update if capable.
*/
+ public void onBandwidthUpdateRequested() {
+ pollLceData();
+ }
+ /** @hide TODO delete once subclasses have moved to onBandwidthUpdateRequested. */
protected void pollLceData() {
}
/**
* Called when the system determines the usefulness of this network.
*
- * Networks claiming internet connectivity will have their internet
- * connectivity verified.
+ * The system attempts to validate Internet connectivity on networks that provide the
+ * {@link NetworkCapabilities#NET_CAPABILITY_INTERNET} capability.
*
* Currently there are two possible values:
- * {@code VALID_NETWORK} if the system is happy with the connection,
- * {@code INVALID_NETWORK} if the system is not happy.
- * TODO - add indications of captive portal-ness and related success/failure,
- * ie, CAPTIVE_SUCCESS_NETWORK, CAPTIVE_NETWORK for successful login and detection
+ * {@code VALIDATION_STATUS_VALID} if Internet connectivity was validated,
+ * {@code VALIDATION_STATUS_NOT_VALID} if Internet connectivity was not validated.
*
- * This may be called multiple times as the network status changes and may
- * generate false negatives if we lose ip connectivity before the link is torn down.
+ * This may be called multiple times as network status changes, or if there are multiple
+ * subsequent attempts to validate connectivity that fail.
*
- * @param status one of {@code VALID_NETWORK} or {@code INVALID_NETWORK}.
- * @param redirectUrl If the Internet probe was redirected, this is the destination it was
- * redirected to, otherwise {@code null}.
+ * @param status one of {@code VALIDATION_STATUS_VALID} or {@code VALIDATION_STATUS_NOT_VALID}.
+ * @param redirectUrl If Internet connectivity is being redirected (e.g., on a captive portal),
+ * this is the destination the probes are being redirected to, otherwise {@code null}.
*/
+ public void onValidationStatus(int status, @Nullable String redirectUrl) {
+ networkStatus(status, redirectUrl);
+ }
+ /** @hide TODO delete once subclasses have moved to onValidationStatus */
protected void networkStatus(int status, String redirectUrl) {
}
+
/**
* Called when the user asks to remember the choice to use this network even if unvalidated.
* The transport is responsible for remembering the choice, and the next time the user connects
* to the network, should explicitlySelected with {@code acceptUnvalidated} set to {@code true}.
* This method will only be called if {@link #explicitlySelected} was called with
* {@code acceptUnvalidated} set to {@code false}.
+ * @param accept whether the user wants to use the network even if unvalidated.
*/
+ public void onSaveAcceptUnvalidated(boolean accept) {
+ saveAcceptUnvalidated(accept);
+ }
+ /** @hide TODO delete once subclasses have moved to onSaveAcceptUnvalidated */
protected void saveAcceptUnvalidated(boolean accept) {
}
/**
* Requests that the network hardware send the specified packet at the specified interval.
+ *
+ * @param slot the hardware slot on which to start the keepalive.
+ * @param intervalSeconds the interval between packets
+ * @param packet the packet to send.
*/
+ public void onStartSocketKeepalive(int slot, int intervalSeconds,
+ @NonNull KeepalivePacketData packet) {
+ Message msg = mHandler.obtainMessage(CMD_START_SOCKET_KEEPALIVE, slot, intervalSeconds,
+ packet);
+ startSocketKeepalive(msg);
+ msg.recycle();
+ }
+ /** @hide TODO delete once subclasses have moved to onStartSocketKeepalive */
protected void startSocketKeepalive(Message msg) {
onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_UNSUPPORTED);
}
/**
- * Requests that the network hardware send the specified packet at the specified interval.
+ * Requests that the network hardware stop a previously-started keepalive.
+ *
+ * @param slot the hardware slot on which to stop the keepalive.
*/
+ public void onStopSocketKeepalive(int slot) {
+ Message msg = mHandler.obtainMessage(CMD_STOP_SOCKET_KEEPALIVE, slot, 0, null);
+ stopSocketKeepalive(msg);
+ msg.recycle();
+ }
+ /** @hide TODO delete once subclasses have moved to onStopSocketKeepalive */
protected void stopSocketKeepalive(Message msg) {
onSocketKeepaliveEvent(msg.arg1, SocketKeepalive.ERROR_UNSUPPORTED);
}
/**
- * Called by the network when a socket keepalive event occurs.
+ * Must be called by the agent when a socket keepalive event occurs.
+ *
+ * @param slot the hardware slot on which the event occurred.
+ * @param event the event that occurred.
*/
+ public void sendSocketKeepaliveEvent(int slot, int event) {
+ queueOrSendMessage(EVENT_SOCKET_KEEPALIVE, slot, event);
+ }
+ /** @hide TODO delete once callers have moved to sendSocketKeepaliveEvent */
public void onSocketKeepaliveEvent(int slot, int reason) {
- queueOrSendMessage(EVENT_SOCKET_KEEPALIVE, slot, reason);
+ sendSocketKeepaliveEvent(slot, reason);
}
/**
* Called by ConnectivityService to add specific packet filter to network hardware to block
- * ACKs matching the sent keepalive packets. Implementations that support this feature must
- * override this method.
+ * replies (e.g., TCP ACKs) matching the sent keepalive packets. Implementations that support
+ * this feature must override this method.
+ *
+ * @param slot the hardware slot on which the keepalive should be sent.
+ * @param packet the packet that is being sent.
*/
+ public void onAddKeepalivePacketFilter(int slot, @NonNull KeepalivePacketData packet) {
+ Message msg = mHandler.obtainMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER, slot, 0, packet);
+ addKeepalivePacketFilter(msg);
+ msg.recycle();
+ }
+ /** @hide TODO delete once subclasses have moved to onAddKeepalivePacketFilter */
protected void addKeepalivePacketFilter(Message msg) {
}
@@ -551,14 +675,28 @@
* Called by ConnectivityService to remove a packet filter installed with
* {@link #addKeepalivePacketFilter(Message)}. Implementations that support this feature
* must override this method.
+ *
+ * @param slot the hardware slot on which the keepalive is being sent.
*/
+ public void onRemoveKeepalivePacketFilter(int slot) {
+ Message msg = mHandler.obtainMessage(CMD_REMOVE_KEEPALIVE_PACKET_FILTER, slot, 0, null);
+ removeKeepalivePacketFilter(msg);
+ msg.recycle();
+ }
+ /** @hide TODO delete once subclasses have moved to onRemoveKeepalivePacketFilter */
protected void removeKeepalivePacketFilter(Message msg) {
}
/**
* Called by ConnectivityService to inform this network transport of signal strength thresholds
* that when crossed should trigger a system wakeup and a NetworkCapabilities update.
+ *
+ * @param thresholds the array of thresholds that should trigger wakeups.
*/
+ public void onSignalStrengthThresholdsUpdated(@NonNull int[] thresholds) {
+ setSignalStrengthThresholds(thresholds);
+ }
+ /** @hide TODO delete once subclasses have moved to onSetSignalStrengthThresholds */
protected void setSignalStrengthThresholds(int[] thresholds) {
}
@@ -568,9 +706,14 @@
* responsible for making sure the device does not automatically reconnect to the same network
* after the {@code unwanted} call.
*/
+ public void onAutomaticReconnectDisabled() {
+ preventAutomaticReconnect();
+ }
+ /** @hide TODO delete once subclasses have moved to onAutomaticReconnectDisabled */
protected void preventAutomaticReconnect() {
}
+ /** @hide */
protected void log(String s) {
Log.d(LOG_TAG, "NetworkAgent: " + s);
}
diff --git a/core/java/android/net/NetworkMisc.aidl b/core/java/android/net/NetworkAgentConfig.aidl
similarity index 95%
rename from core/java/android/net/NetworkMisc.aidl
rename to core/java/android/net/NetworkAgentConfig.aidl
index c65583f..cb70bdd 100644
--- a/core/java/android/net/NetworkMisc.aidl
+++ b/core/java/android/net/NetworkAgentConfig.aidl
@@ -16,4 +16,4 @@
package android.net;
-parcelable NetworkMisc;
+parcelable NetworkAgentConfig;
diff --git a/core/java/android/net/NetworkAgentConfig.java b/core/java/android/net/NetworkAgentConfig.java
new file mode 100644
index 0000000..abc6b67
--- /dev/null
+++ b/core/java/android/net/NetworkAgentConfig.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2014 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.net;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+/**
+ * Allows a network transport to provide the system with policy and configuration information about
+ * a particular network when registering a {@link NetworkAgent}. This information cannot change once
+ * the agent is registered.
+ *
+ * @hide
+ */
+@SystemApi
+public final class NetworkAgentConfig implements Parcelable {
+
+ /**
+ * If the {@link Network} is a VPN, whether apps are allowed to bypass the
+ * VPN. This is set by a {@link VpnService} and used by
+ * {@link ConnectivityManager} when creating a VPN.
+ *
+ * @hide
+ */
+ public boolean allowBypass;
+
+ /**
+ * Set if the network was manually/explicitly connected to by the user either from settings
+ * or a 3rd party app. For example, turning on cell data is not explicit but tapping on a wifi
+ * ap in the wifi settings to trigger a connection is explicit. A 3rd party app asking to
+ * connect to a particular access point is also explicit, though this may change in the future
+ * as we want apps to use the multinetwork apis.
+ *
+ * @hide
+ */
+ public boolean explicitlySelected;
+
+ /**
+ * Set if the user desires to use this network even if it is unvalidated. This field has meaning
+ * only if {@link explicitlySelected} is true. If it is, this field must also be set to the
+ * appropriate value based on previous user choice.
+ *
+ * @hide
+ */
+ public boolean acceptUnvalidated;
+
+ /**
+ * Whether the user explicitly set that this network should be validated even if presence of
+ * only partial internet connectivity.
+ *
+ * @hide
+ */
+ public boolean acceptPartialConnectivity;
+
+ /**
+ * Set to avoid surfacing the "Sign in to network" notification.
+ * if carrier receivers/apps are registered to handle the carrier-specific provisioning
+ * procedure, a carrier specific provisioning notification will be placed.
+ * only one notification should be displayed. This field is set based on
+ * which notification should be used for provisioning.
+ *
+ * @hide
+ */
+ public boolean provisioningNotificationDisabled;
+
+ /**
+ *
+ * @return whether the sign in to network notification is enabled by this configuration.
+ */
+ public boolean isProvisioningNotificationEnabled() {
+ return !provisioningNotificationDisabled;
+ }
+
+ /**
+ * For mobile networks, this is the subscriber ID (such as IMSI).
+ *
+ * @hide
+ */
+ public String subscriberId;
+
+ /**
+ * @return the subscriber ID, or null if none.
+ */
+ @Nullable
+ public String getSubscriberId() {
+ return subscriberId;
+ }
+
+ /**
+ * Set to skip 464xlat. This means the device will treat the network as IPv6-only and
+ * will not attempt to detect a NAT64 via RFC 7050 DNS lookups.
+ *
+ * @hide
+ */
+ public boolean skip464xlat;
+
+ /**
+ * @return whether NAT64 prefix detection is enabled.
+ */
+ public boolean isNat64DetectionEnabled() {
+ return !skip464xlat;
+ }
+
+ /**
+ * Set to true if the PRIVATE_DNS_BROKEN notification has shown for this network.
+ * Reset this bit when private DNS mode is changed from strict mode to opportunistic/off mode.
+ *
+ * @hide
+ */
+ public boolean hasShownBroken;
+
+ /** @hide */
+ public NetworkAgentConfig() {
+ }
+
+ /** @hide */
+ public NetworkAgentConfig(@Nullable NetworkAgentConfig nac) {
+ if (nac != null) {
+ allowBypass = nac.allowBypass;
+ explicitlySelected = nac.explicitlySelected;
+ acceptUnvalidated = nac.acceptUnvalidated;
+ subscriberId = nac.subscriberId;
+ provisioningNotificationDisabled = nac.provisioningNotificationDisabled;
+ skip464xlat = nac.skip464xlat;
+ }
+ }
+
+ /**
+ * Builder class to facilitate constructing {@link NetworkAgentConfig} objects.
+ */
+ public static class Builder {
+ private final NetworkAgentConfig mConfig = new NetworkAgentConfig();
+
+ /**
+ * Sets the subscriber ID for this network.
+ *
+ * @return this builder, to facilitate chaining.
+ */
+ @NonNull
+ public Builder setSubscriberId(@Nullable String subscriberId) {
+ mConfig.subscriberId = subscriberId;
+ return this;
+ }
+
+ /**
+ * Disables active detection of NAT64 (e.g., via RFC 7050 DNS lookups). Used to save power
+ * and reduce idle traffic on networks that are known to be IPv6-only without a NAT64.
+ *
+ * @return this builder, to facilitate chaining.
+ */
+ @NonNull
+ public Builder disableNat64Detection() {
+ mConfig.skip464xlat = true;
+ return this;
+ }
+
+ /**
+ * Disables the "Sign in to network" notification. Used if the network transport will
+ * perform its own carrier-specific provisioning procedure.
+ *
+ * @return this builder, to facilitate chaining.
+ */
+ @NonNull
+ public Builder disableProvisioningNotification() {
+ mConfig.provisioningNotificationDisabled = true;
+ return this;
+ }
+
+ /**
+ * Returns the constructed {@link NetworkAgentConfig} object.
+ */
+ @NonNull
+ public NetworkAgentConfig build() {
+ return mConfig;
+ }
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ out.writeInt(allowBypass ? 1 : 0);
+ out.writeInt(explicitlySelected ? 1 : 0);
+ out.writeInt(acceptUnvalidated ? 1 : 0);
+ out.writeString(subscriberId);
+ out.writeInt(provisioningNotificationDisabled ? 1 : 0);
+ out.writeInt(skip464xlat ? 1 : 0);
+ }
+
+ public static final @NonNull Creator<NetworkAgentConfig> CREATOR =
+ new Creator<NetworkAgentConfig>() {
+ @Override
+ public NetworkAgentConfig createFromParcel(Parcel in) {
+ NetworkAgentConfig networkAgentConfig = new NetworkAgentConfig();
+ networkAgentConfig.allowBypass = in.readInt() != 0;
+ networkAgentConfig.explicitlySelected = in.readInt() != 0;
+ networkAgentConfig.acceptUnvalidated = in.readInt() != 0;
+ networkAgentConfig.subscriberId = in.readString();
+ networkAgentConfig.provisioningNotificationDisabled = in.readInt() != 0;
+ networkAgentConfig.skip464xlat = in.readInt() != 0;
+ return networkAgentConfig;
+ }
+
+ @Override
+ public NetworkAgentConfig[] newArray(int size) {
+ return new NetworkAgentConfig[size];
+ }
+ };
+}
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 4cee5f3..739e817 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -21,7 +21,7 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.net.ConnectivityManager.NetworkCallback;
import android.os.Build;
import android.os.Parcel;
@@ -587,15 +587,14 @@
}
/**
- * Removes the NET_CAPABILITY_NOT_RESTRICTED capability if all the capabilities it provides are
- * typically provided by restricted networks.
+ * Deduces that all the capabilities it provides are typically provided by restricted networks
+ * or not.
*
- * TODO: consider:
- * - Renaming it to guessRestrictedCapability and make it set the
- * restricted capability bit in addition to clearing it.
+ * @return {@code true} if the network should be restricted.
* @hide
*/
- public void maybeMarkCapabilitiesRestricted() {
+ @SystemApi
+ public boolean deduceRestrictedCapability() {
// Check if we have any capability that forces the network to be restricted.
final boolean forceRestrictedCapability =
(mNetworkCapabilities & FORCE_RESTRICTED_CAPABILITIES) != 0;
@@ -609,8 +608,17 @@
final boolean hasRestrictedCapabilities =
(mNetworkCapabilities & RESTRICTED_CAPABILITIES) != 0;
- if (forceRestrictedCapability
- || (hasRestrictedCapabilities && !hasUnrestrictedCapabilities)) {
+ return forceRestrictedCapability
+ || (hasRestrictedCapabilities && !hasUnrestrictedCapabilities);
+ }
+
+ /**
+ * Removes the NET_CAPABILITY_NOT_RESTRICTED capability if deducing the network is restricted.
+ *
+ * @hide
+ */
+ public void maybeMarkCapabilitiesRestricted() {
+ if (deduceRestrictedCapability()) {
removeCapability(NET_CAPABILITY_NOT_RESTRICTED);
}
}
diff --git a/core/java/android/net/NetworkFactory.java b/core/java/android/net/NetworkFactory.java
index 5b1d12c..e271037 100644
--- a/core/java/android/net/NetworkFactory.java
+++ b/core/java/android/net/NetworkFactory.java
@@ -16,7 +16,8 @@
package android.net;
-import android.annotation.UnsupportedAppUsage;
+import android.annotation.NonNull;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Build;
import android.os.Handler;
@@ -27,7 +28,6 @@
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.AsyncChannel;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Protocol;
@@ -52,7 +52,7 @@
* @hide
**/
public class NetworkFactory extends Handler {
- /** @hide */
+ /* TODO: delete when all callers have migrated to NetworkProvider IDs. */
public static class SerialNumber {
// Guard used by no network factory.
public static final int NONE = -1;
@@ -91,8 +91,8 @@
* with the same NetworkRequest but an updated score.
* Also, network conditions may change for this bearer
* allowing for a better score in the future.
- * msg.arg2 = the serial number of the factory currently responsible for the
- * NetworkAgent handling this request, or SerialNumber.NONE if none.
+ * msg.arg2 = the ID of the NetworkProvider currently responsible for the
+ * NetworkAgent handling this request, or NetworkProvider.ID_NONE if none.
*/
public static final int CMD_REQUEST_NETWORK = BASE;
@@ -115,16 +115,8 @@
*/
private static final int CMD_SET_FILTER = BASE + 3;
- /**
- * Sent by NetworkFactory to ConnectivityService to indicate that a request is
- * unfulfillable.
- * @see #releaseRequestAsUnfulfillableByAnyFactory(NetworkRequest).
- */
- public static final int EVENT_UNFULFILLABLE_REQUEST = BASE + 4;
-
private final Context mContext;
private final ArrayList<Message> mPreConnectedQueue = new ArrayList<Message>();
- private AsyncChannel mAsyncChannel;
private final String LOG_TAG;
private final SparseArray<NetworkRequestInfo> mNetworkRequests =
@@ -135,7 +127,8 @@
private int mRefCount = 0;
private Messenger mMessenger = null;
- private int mSerialNumber;
+ private NetworkProvider mProvider = null;
+ private int mProviderId;
@UnsupportedAppUsage
public NetworkFactory(Looper looper, Context context, String logTag,
@@ -147,55 +140,43 @@
}
public void register() {
- if (DBG) log("Registering NetworkFactory");
- if (mMessenger == null) {
- mMessenger = new Messenger(this);
- mSerialNumber = ConnectivityManager.from(mContext).registerNetworkFactory(mMessenger,
- LOG_TAG);
+ if (mProvider != null) {
+ Log.e(LOG_TAG, "Ignoring attempt to register already-registered NetworkFactory");
+ return;
}
+ if (DBG) log("Registering NetworkFactory");
+
+ mProvider = new NetworkProvider(mContext, NetworkFactory.this.getLooper(), LOG_TAG) {
+ @Override
+ public void onNetworkRequested(@NonNull NetworkRequest request, int score,
+ int servingProviderId) {
+ handleAddRequest((NetworkRequest) request, score, servingProviderId);
+ }
+
+ @Override
+ public void onRequestWithdrawn(@NonNull NetworkRequest request) {
+ handleRemoveRequest(request);
+ }
+ };
+
+ mMessenger = new Messenger(this);
+ mProviderId = ConnectivityManager.from(mContext).registerNetworkProvider(mProvider);
}
public void unregister() {
- if (DBG) log("Unregistering NetworkFactory");
- if (mMessenger != null) {
- ConnectivityManager.from(mContext).unregisterNetworkFactory(mMessenger);
- mMessenger = null;
+ if (mProvider == null) {
+ Log.e(LOG_TAG, "Ignoring attempt to unregister unregistered NetworkFactory");
+ return;
}
+ if (DBG) log("Unregistering NetworkFactory");
+
+ ConnectivityManager.from(mContext).unregisterNetworkProvider(mProvider);
+ mProvider = null;
}
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case AsyncChannel.CMD_CHANNEL_FULL_CONNECTION: {
- if (mAsyncChannel != null) {
- log("Received new connection while already connected!");
- break;
- }
- if (VDBG) log("NetworkFactory fully connected");
- AsyncChannel ac = new AsyncChannel();
- ac.connected(null, this, msg.replyTo);
- ac.replyToMessage(msg, AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED,
- AsyncChannel.STATUS_SUCCESSFUL);
- mAsyncChannel = ac;
- for (Message m : mPreConnectedQueue) {
- ac.sendMessage(m);
- }
- mPreConnectedQueue.clear();
- break;
- }
- case AsyncChannel.CMD_CHANNEL_DISCONNECT: {
- if (VDBG) log("CMD_CHANNEL_DISCONNECT");
- if (mAsyncChannel != null) {
- mAsyncChannel.disconnect();
- mAsyncChannel = null;
- }
- break;
- }
- case AsyncChannel.CMD_CHANNEL_DISCONNECTED: {
- if (DBG) log("NetworkFactory channel lost");
- mAsyncChannel = null;
- break;
- }
case CMD_REQUEST_NETWORK: {
handleAddRequest((NetworkRequest) msg.obj, msg.arg1, msg.arg2);
break;
@@ -219,13 +200,13 @@
public final NetworkRequest request;
public int score;
public boolean requested; // do we have a request outstanding, limited by score
- public int factorySerialNumber;
+ public int providerId;
- NetworkRequestInfo(NetworkRequest request, int score, int factorySerialNumber) {
+ NetworkRequestInfo(NetworkRequest request, int score, int providerId) {
this.request = request;
this.score = score;
this.requested = false;
- this.factorySerialNumber = factorySerialNumber;
+ this.providerId = providerId;
}
@Override
@@ -240,8 +221,6 @@
*
* @param request the request to handle.
* @param score the score of the NetworkAgent currently satisfying this request.
- * @param servingFactorySerialNumber the serial number of the NetworkFactory that
- * created the NetworkAgent currently satisfying this request.
*/
// TODO : remove this method. It is a stopgap measure to help sheperding a number
// of dependent changes that would conflict throughout the automerger graph. Having this
@@ -249,7 +228,7 @@
// the entire tree.
@VisibleForTesting
protected void handleAddRequest(NetworkRequest request, int score) {
- handleAddRequest(request, score, SerialNumber.NONE);
+ handleAddRequest(request, score, NetworkProvider.ID_NONE);
}
/**
@@ -258,27 +237,26 @@
*
* @param request the request to handle.
* @param score the score of the NetworkAgent currently satisfying this request.
- * @param servingFactorySerialNumber the serial number of the NetworkFactory that
- * created the NetworkAgent currently satisfying this request.
+ * @param servingProviderId the ID of the NetworkProvider that created the NetworkAgent
+ * currently satisfying this request.
*/
@VisibleForTesting
- protected void handleAddRequest(NetworkRequest request, int score,
- int servingFactorySerialNumber) {
+ protected void handleAddRequest(NetworkRequest request, int score, int servingProviderId) {
NetworkRequestInfo n = mNetworkRequests.get(request.requestId);
if (n == null) {
if (DBG) {
log("got request " + request + " with score " + score
- + " and serial " + servingFactorySerialNumber);
+ + " and providerId " + servingProviderId);
}
- n = new NetworkRequestInfo(request, score, servingFactorySerialNumber);
+ n = new NetworkRequestInfo(request, score, servingProviderId);
mNetworkRequests.put(n.request.requestId, n);
} else {
if (VDBG) {
log("new score " + score + " for exisiting request " + request
- + " with serial " + servingFactorySerialNumber);
+ + " and providerId " + servingProviderId);
}
n.score = score;
- n.factorySerialNumber = servingFactorySerialNumber;
+ n.providerId = servingProviderId;
}
if (VDBG) log(" my score=" + mScore + ", my filter=" + mCapabilityFilter);
@@ -333,8 +311,8 @@
log(" n.requests = " + n.requested);
log(" n.score = " + n.score);
log(" mScore = " + mScore);
- log(" n.factorySerialNumber = " + n.factorySerialNumber);
- log(" mSerialNumber = " + mSerialNumber);
+ log(" n.providerId = " + n.providerId);
+ log(" mProviderId = " + mProviderId);
}
if (shouldNeedNetworkFor(n)) {
if (VDBG) log(" needNetworkFor");
@@ -355,7 +333,7 @@
// If the score of this request is higher or equal to that of this factory and some
// other factory is responsible for it, then this factory should not track the request
// because it has no hope of satisfying it.
- && (n.score < mScore || n.factorySerialNumber == mSerialNumber)
+ && (n.score < mScore || n.providerId == mProviderId)
// If this factory can't satisfy the capability needs of this request, then it
// should not be tracked.
&& n.request.networkCapabilities.satisfiedByNetworkCapabilities(mCapabilityFilter)
@@ -373,7 +351,7 @@
// assigned to the factory
// - This factory can't satisfy the capability needs of the request
// - The concrete implementation of the factory rejects the request
- && ((n.score > mScore && n.factorySerialNumber != mSerialNumber)
+ && ((n.score > mScore && n.providerId != mProviderId)
|| !n.request.networkCapabilities.satisfiedByNetworkCapabilities(
mCapabilityFilter)
|| !acceptRequest(n.request, n.score));
@@ -408,12 +386,7 @@
protected void releaseRequestAsUnfulfillableByAnyFactory(NetworkRequest r) {
post(() -> {
if (DBG) log("releaseRequestAsUnfulfillableByAnyFactory: " + r);
- Message msg = obtainMessage(EVENT_UNFULFILLABLE_REQUEST, r);
- if (mAsyncChannel != null) {
- mAsyncChannel.sendMessage(msg);
- } else {
- mPreConnectedQueue.add(msg);
- }
+ ConnectivityManager.from(mContext).declareNetworkRequestUnfulfillable(r);
});
}
@@ -444,8 +417,13 @@
return mNetworkRequests.size();
}
+ /* TODO: delete when all callers have migrated to NetworkProvider IDs. */
public int getSerialNumber() {
- return mSerialNumber;
+ return mProviderId;
+ }
+
+ public int getProviderId() {
+ return mProviderId;
}
protected void log(String s) {
@@ -465,8 +443,8 @@
@Override
public String toString() {
- StringBuilder sb = new StringBuilder("{").append(LOG_TAG).append(" - mSerialNumber=")
- .append(mSerialNumber).append(", ScoreFilter=")
+ StringBuilder sb = new StringBuilder("{").append(LOG_TAG).append(" - mProviderId=")
+ .append(mProviderId).append(", ScoreFilter=")
.append(mScore).append(", Filter=").append(mCapabilityFilter).append(", requests=")
.append(mNetworkRequests.size()).append(", refCount=").append(mRefCount)
.append("}");
diff --git a/core/java/android/net/NetworkInfo.java b/core/java/android/net/NetworkInfo.java
index 92f105f..d0c5363 100644
--- a/core/java/android/net/NetworkInfo.java
+++ b/core/java/android/net/NetworkInfo.java
@@ -17,7 +17,7 @@
package android.net;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/net/NetworkMisc.java b/core/java/android/net/NetworkMisc.java
deleted file mode 100644
index 4ad52d5..0000000
--- a/core/java/android/net/NetworkMisc.java
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2014 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.net;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-/**
- * A grab-bag of information (metadata, policies, properties, etc) about a
- * {@link Network}. Since this contains PII, it should not be sent outside the
- * system.
- *
- * @hide
- */
-public class NetworkMisc implements Parcelable {
-
- /**
- * If the {@link Network} is a VPN, whether apps are allowed to bypass the
- * VPN. This is set by a {@link VpnService} and used by
- * {@link ConnectivityManager} when creating a VPN.
- */
- public boolean allowBypass;
-
- /**
- * Set if the network was manually/explicitly connected to by the user either from settings
- * or a 3rd party app. For example, turning on cell data is not explicit but tapping on a wifi
- * ap in the wifi settings to trigger a connection is explicit. A 3rd party app asking to
- * connect to a particular access point is also explicit, though this may change in the future
- * as we want apps to use the multinetwork apis.
- */
- public boolean explicitlySelected;
-
- /**
- * Set if the user desires to use this network even if it is unvalidated. This field has meaning
- * only if {@link explicitlySelected} is true. If it is, this field must also be set to the
- * appropriate value based on previous user choice.
- */
- public boolean acceptUnvalidated;
-
- /**
- * Whether the user explicitly set that this network should be validated even if presence of
- * only partial internet connectivity.
- */
- public boolean acceptPartialConnectivity;
-
- /**
- * Set to avoid surfacing the "Sign in to network" notification.
- * if carrier receivers/apps are registered to handle the carrier-specific provisioning
- * procedure, a carrier specific provisioning notification will be placed.
- * only one notification should be displayed. This field is set based on
- * which notification should be used for provisioning.
- */
- public boolean provisioningNotificationDisabled;
-
- /**
- * For mobile networks, this is the subscriber ID (such as IMSI).
- */
- public String subscriberId;
-
- /**
- * Set to skip 464xlat. This means the device will treat the network as IPv6-only and
- * will not attempt to detect a NAT64 via RFC 7050 DNS lookups.
- */
- public boolean skip464xlat;
-
- /**
- * Set to true if the PRIVATE_DNS_BROKEN notification has shown for this network.
- * Reset this bit when private DNS mode is changed from strict mode to opportunistic/off mode.
- */
- public boolean hasShownBroken;
-
- public NetworkMisc() {
- }
-
- public NetworkMisc(NetworkMisc nm) {
- if (nm != null) {
- allowBypass = nm.allowBypass;
- explicitlySelected = nm.explicitlySelected;
- acceptUnvalidated = nm.acceptUnvalidated;
- subscriberId = nm.subscriberId;
- provisioningNotificationDisabled = nm.provisioningNotificationDisabled;
- skip464xlat = nm.skip464xlat;
- }
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel out, int flags) {
- out.writeInt(allowBypass ? 1 : 0);
- out.writeInt(explicitlySelected ? 1 : 0);
- out.writeInt(acceptUnvalidated ? 1 : 0);
- out.writeString(subscriberId);
- out.writeInt(provisioningNotificationDisabled ? 1 : 0);
- out.writeInt(skip464xlat ? 1 : 0);
- }
-
- public static final @android.annotation.NonNull Creator<NetworkMisc> CREATOR = new Creator<NetworkMisc>() {
- @Override
- public NetworkMisc createFromParcel(Parcel in) {
- NetworkMisc networkMisc = new NetworkMisc();
- networkMisc.allowBypass = in.readInt() != 0;
- networkMisc.explicitlySelected = in.readInt() != 0;
- networkMisc.acceptUnvalidated = in.readInt() != 0;
- networkMisc.subscriberId = in.readString();
- networkMisc.provisioningNotificationDisabled = in.readInt() != 0;
- networkMisc.skip464xlat = in.readInt() != 0;
- return networkMisc;
- }
-
- @Override
- public NetworkMisc[] newArray(int size) {
- return new NetworkMisc[size];
- }
- };
-}
diff --git a/core/java/android/net/NetworkPolicy.java b/core/java/android/net/NetworkPolicy.java
index 33baebb..4f05c9b 100644
--- a/core/java/android/net/NetworkPolicy.java
+++ b/core/java/android/net/NetworkPolicy.java
@@ -16,7 +16,7 @@
package android.net;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.BackupUtils;
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 9150aae..de962f8 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -19,8 +19,8 @@
import static android.content.pm.PackageManager.GET_SIGNATURES;
import android.annotation.SystemService;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
diff --git a/core/java/android/net/NetworkProvider.java b/core/java/android/net/NetworkProvider.java
new file mode 100644
index 0000000..2c0e4aa
--- /dev/null
+++ b/core/java/android/net/NetworkProvider.java
@@ -0,0 +1,166 @@
+/*
+ * 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.net;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.util.Log;
+
+/**
+ * Base class for network providers such as telephony or Wi-Fi. NetworkProviders connect the device
+ * to networks and makes them available to to the core network stack by creating
+ * {@link NetworkAgent}s. The networks can then provide connectivity to apps and can be interacted
+ * with via networking APIs such as {@link ConnectivityManager}.
+ *
+ * Subclasses should implement {@link #onNetworkRequested} and {@link #onRequestWithdrawn} to
+ * receive {@link NetworkRequest}s sent by the system and by apps. A network that is not the
+ * best (highest-scoring) network for any request is generally not used by the system, and torn
+ * down.
+ *
+ * @hide
+ */
+@SystemApi
+public class NetworkProvider {
+ /**
+ * {@code providerId} value that indicates the absence of a provider. It is the providerId of
+ * any NetworkProvider that is not currently registered, and of any NetworkRequest that is not
+ * currently being satisfied by a network.
+ */
+ public static final int ID_NONE = -1;
+
+ /**
+ * A hardcoded ID for NetworkAgents representing VPNs. These agents are not created by any
+ * provider, so they use this constant for clarity instead of NONE.
+ * @hide only used by ConnectivityService.
+ */
+ public static final int ID_VPN = -2;
+
+ /**
+ * The first providerId value that will be allocated.
+ * @hide only used by ConnectivityService.
+ */
+ public static final int FIRST_PROVIDER_ID = 1;
+
+ /** @hide only used by ConnectivityService */
+ public static final int CMD_REQUEST_NETWORK = 1;
+ /** @hide only used by ConnectivityService */
+ public static final int CMD_CANCEL_REQUEST = 2;
+
+ private final Messenger mMessenger;
+ private final String mName;
+ private final ConnectivityManager mCm;
+
+ private int mProviderId = ID_NONE;
+
+ /**
+ * Constructs a new NetworkProvider.
+ *
+ * @param looper the Looper on which to run {@link #onNetworkRequested} and
+ * {@link #onRequestWithdrawn}.
+ * @param name the name of the listener, used only for debugging.
+ *
+ * @hide
+ */
+ @SystemApi
+ public NetworkProvider(@NonNull Context context, @NonNull Looper looper, @NonNull String name) {
+ mCm = ConnectivityManager.from(context);
+
+ Handler handler = new Handler(looper) {
+ @Override
+ public void handleMessage(Message m) {
+ switch (m.what) {
+ case CMD_REQUEST_NETWORK:
+ onNetworkRequested((NetworkRequest) m.obj, m.arg1, m.arg2);
+ break;
+ case CMD_CANCEL_REQUEST:
+ onRequestWithdrawn((NetworkRequest) m.obj);
+ break;
+ default:
+ Log.e(mName, "Unhandled message: " + m.what);
+ }
+ }
+ };
+ mMessenger = new Messenger(handler);
+ mName = name;
+ }
+
+ // TODO: consider adding a register() method so ConnectivityManager does not need to call this.
+ public @Nullable Messenger getMessenger() {
+ return mMessenger;
+ }
+
+ public @NonNull String getName() {
+ return mName;
+ }
+
+ /**
+ * Returns the ID of this provider. This is known only once the provider is registered via
+ * {@link ConnectivityManager#registerNetworkProvider()}, otherwise the ID is {@link #ID_NONE}.
+ * This ID must be used when registering any {@link NetworkAgent}s.
+ */
+ public int getProviderId() {
+ return mProviderId;
+ }
+
+ /** @hide */
+ public void setProviderId(int providerId) {
+ mProviderId = providerId;
+ }
+
+ /**
+ * Called when a NetworkRequest is received. The request may be a new request or an existing
+ * request with a different score.
+ *
+ * @param request the NetworkRequest being received
+ * @param score the score of the network currently satisfying the request, or 0 if none.
+ * @param providerId the ID of the provider that created the network currently satisfying this
+ * request, or {@link #ID_NONE} if none.
+ *
+ * @hide
+ */
+ @SystemApi
+ public void onNetworkRequested(@NonNull NetworkRequest request, int score, int providerId) {}
+
+ /**
+ * Called when a NetworkRequest is withdrawn.
+ * @hide
+ */
+ @SystemApi
+ public void onRequestWithdrawn(@NonNull NetworkRequest request) {}
+
+ /**
+ * Asserts that no provider will ever be able to satisfy the specified request. The provider
+ * must only call this method if it knows that it is the only provider on the system capable of
+ * satisfying this request, and that the request cannot be satisfied. The application filing the
+ * request will receive an {@link NetworkCallback#onUnavailable()} callback.
+ *
+ * @param request the request that cannot be fulfilled
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY)
+ public void declareNetworkRequestUnfulfillable(@NonNull NetworkRequest request) {
+ mCm.declareNetworkRequestUnfulfillable(request);
+ }
+}
diff --git a/core/java/android/net/NetworkQuotaInfo.java b/core/java/android/net/NetworkQuotaInfo.java
index a46cdde..2e52d9c 100644
--- a/core/java/android/net/NetworkQuotaInfo.java
+++ b/core/java/android/net/NetworkQuotaInfo.java
@@ -16,7 +16,7 @@
package android.net;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/net/NetworkRequest.java b/core/java/android/net/NetworkRequest.java
index 2992127..301d203 100644
--- a/core/java/android/net/NetworkRequest.java
+++ b/core/java/android/net/NetworkRequest.java
@@ -20,7 +20,7 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.net.NetworkCapabilities.NetCapability;
import android.net.NetworkCapabilities.Transport;
import android.os.Build;
@@ -247,9 +247,8 @@
* removing even the capabilities that are set by default when the object is constructed.
*
* @return The builder to facilitate chaining.
- * @hide
*/
- @UnsupportedAppUsage
+ @NonNull
public Builder clearCapabilities() {
mNetworkCapabilities.clearAll();
return this;
@@ -301,22 +300,34 @@
* this without a single transport set will generate an exception, as will
* subsequently adding or removing transports after this is set.
* </p>
- * The interpretation of this {@code String} is bearer specific and bearers that use
- * it should document their particulars. For example, Bluetooth may use some sort of
- * device id while WiFi could used ssid and/or bssid. Cellular may use carrier spn.
+ * If the {@code networkSpecifier} is provided, it shall be interpreted as follows:
+ * <ul>
+ * <li>If the specifier can be parsed as an integer, it will be treated as a
+ * {@link android.net TelephonyNetworkSpecifier}, and the provided integer will be
+ * interpreted as a SubscriptionId.
+ * <li>If the value is an ethernet interface name, it will be treated as such.
+ * <li>For all other cases, the behavior is undefined.
+ * </ul>
*
- * @param networkSpecifier An {@code String} of opaque format used to specify the bearer
- * specific network specifier where the bearer has a choice of
- * networks.
+ * @param networkSpecifier A {@code String} of either a SubscriptionId in cellular
+ * network request or an ethernet interface name in ethernet
+ * network request.
+ *
+ * @deprecated Use {@link #setNetworkSpecifier(NetworkSpecifier)} instead.
*/
+ @Deprecated
public Builder setNetworkSpecifier(String networkSpecifier) {
- /*
- * A StringNetworkSpecifier does not accept null or empty ("") strings. When network
- * specifiers were strings a null string and an empty string were considered equivalent.
- * Hence no meaning is attached to a null or empty ("") string.
- */
- return setNetworkSpecifier(TextUtils.isEmpty(networkSpecifier) ? null
- : new StringNetworkSpecifier(networkSpecifier));
+ try {
+ int subId = Integer.parseInt(networkSpecifier);
+ return setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
+ .setSubscriptionId(subId).build());
+ } catch (NumberFormatException nfe) {
+ // A StringNetworkSpecifier does not accept null or empty ("") strings. When network
+ // specifiers were strings a null string and an empty string were considered
+ // equivalent. Hence no meaning is attached to a null or empty ("") string.
+ return setNetworkSpecifier(TextUtils.isEmpty(networkSpecifier) ? null
+ : new StringNetworkSpecifier(networkSpecifier));
+ }
}
/**
@@ -456,6 +467,19 @@
}
/**
+ * Returns true iff. the capabilities requested in this NetworkRequest are satisfied by the
+ * provided {@link NetworkCapabilities}.
+ *
+ * @param nc Capabilities that should satisfy this NetworkRequest. null capabilities do not
+ * satisfy any request.
+ * @hide
+ */
+ @SystemApi
+ public boolean satisfiedBy(@Nullable NetworkCapabilities nc) {
+ return networkCapabilities.satisfiedByNetworkCapabilities(nc);
+ }
+
+ /**
* @see Builder#addTransportType(int)
*/
public boolean hasTransport(@Transport int transportType) {
diff --git a/core/java/android/net/NetworkScoreManager.java b/core/java/android/net/NetworkScoreManager.java
index f6dc525..c233ec0 100644
--- a/core/java/android/net/NetworkScoreManager.java
+++ b/core/java/android/net/NetworkScoreManager.java
@@ -163,27 +163,26 @@
public static final String EXTRA_NEW_SCORER = "newScorer";
/** @hide */
- @IntDef({CACHE_FILTER_NONE, CACHE_FILTER_CURRENT_NETWORK, CACHE_FILTER_SCAN_RESULTS})
+ @IntDef({SCORE_FILTER_NONE, SCORE_FILTER_CURRENT_NETWORK, SCORE_FILTER_SCAN_RESULTS})
@Retention(RetentionPolicy.SOURCE)
- public @interface CacheUpdateFilter {}
+ public @interface ScoreUpdateFilter {}
/**
- * Do not filter updates sent to the cache.
- * @hide
+ * Do not filter updates sent to the {@link NetworkScoreCallback}].
*/
- public static final int CACHE_FILTER_NONE = 0;
+ public static final int SCORE_FILTER_NONE = 0;
/**
- * Only send cache updates when the network matches the connected network.
- * @hide
+ * Only send updates to the {@link NetworkScoreCallback} when the network matches the connected
+ * network.
*/
- public static final int CACHE_FILTER_CURRENT_NETWORK = 1;
+ public static final int SCORE_FILTER_CURRENT_NETWORK = 1;
/**
- * Only send cache updates when the network is part of the current scan result set.
- * @hide
+ * Only send updates to the {@link NetworkScoreCallback} when the network is part of the
+ * current scan result set.
*/
- public static final int CACHE_FILTER_SCAN_RESULTS = 2;
+ public static final int SCORE_FILTER_SCAN_RESULTS = 2;
/** @hide */
@IntDef({RECOMMENDATIONS_ENABLED_FORCED_OFF, RECOMMENDATIONS_ENABLED_OFF,
@@ -404,13 +403,13 @@
* @throws SecurityException if the caller does not hold the
* {@link permission#REQUEST_NETWORK_SCORES} permission.
* @throws IllegalArgumentException if a score cache is already registered for this type.
- * @deprecated equivalent to registering for cache updates with CACHE_FILTER_NONE.
+ * @deprecated equivalent to registering for cache updates with {@link #SCORE_FILTER_NONE}.
* @hide
*/
@RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES)
@Deprecated // migrate to registerNetworkScoreCache(int, INetworkScoreCache, int)
public void registerNetworkScoreCache(int networkType, INetworkScoreCache scoreCache) {
- registerNetworkScoreCache(networkType, scoreCache, CACHE_FILTER_NONE);
+ registerNetworkScoreCache(networkType, scoreCache, SCORE_FILTER_NONE);
}
/**
@@ -418,7 +417,7 @@
*
* @param networkType the type of network this cache can handle. See {@link NetworkKey#type}
* @param scoreCache implementation of {@link INetworkScoreCache} to store the scores
- * @param filterType the {@link CacheUpdateFilter} to apply
+ * @param filterType the {@link ScoreUpdateFilter} to apply
* @throws SecurityException if the caller does not hold the
* {@link permission#REQUEST_NETWORK_SCORES} permission.
* @throws IllegalArgumentException if a score cache is already registered for this type.
@@ -426,7 +425,7 @@
*/
@RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES)
public void registerNetworkScoreCache(int networkType, INetworkScoreCache scoreCache,
- @CacheUpdateFilter int filterType) {
+ @ScoreUpdateFilter int filterType) {
try {
mService.registerNetworkScoreCache(networkType, scoreCache, filterType);
} catch (RemoteException e) {
@@ -510,7 +509,7 @@
* Register a network score callback.
*
* @param networkType the type of network this cache can handle. See {@link NetworkKey#type}
- * @param filterType the {@link CacheUpdateFilter} to apply
+ * @param filterType the {@link ScoreUpdateFilter} to apply
* @param callback implementation of {@link NetworkScoreCallback} that will be invoked when the
* scores change.
* @param executor The executor on which to execute the callbacks.
@@ -522,7 +521,7 @@
@SystemApi
@RequiresPermission(android.Manifest.permission.REQUEST_NETWORK_SCORES)
public void registerNetworkScoreCallback(@NetworkKey.NetworkType int networkType,
- @CacheUpdateFilter int filterType,
+ @ScoreUpdateFilter int filterType,
@NonNull @CallbackExecutor Executor executor,
@NonNull NetworkScoreCallback callback) throws SecurityException {
if (callback == null || executor == null) {
diff --git a/core/java/android/net/NetworkSpecifier.java b/core/java/android/net/NetworkSpecifier.java
index 2bc3eb5..cf31d21 100644
--- a/core/java/android/net/NetworkSpecifier.java
+++ b/core/java/android/net/NetworkSpecifier.java
@@ -16,6 +16,9 @@
package android.net;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
/**
* Describes specific properties of a requested network for use in a {@link NetworkRequest}.
*
@@ -31,7 +34,8 @@
*
* @hide
*/
- public abstract boolean satisfiedBy(NetworkSpecifier other);
+ @SystemApi
+ public abstract boolean satisfiedBy(@Nullable NetworkSpecifier other);
/**
* Optional method which can be overridden by concrete implementations of NetworkSpecifier to
@@ -45,6 +49,7 @@
*
* @hide
*/
+ @SystemApi
public void assertValidFromUid(int requestorUid) {
// empty
}
@@ -68,6 +73,8 @@
*
* @hide
*/
+ @SystemApi
+ @Nullable
public NetworkSpecifier redact() {
// TODO (b/122160111): convert default to null once all platform NetworkSpecifiers
// implement this method.
diff --git a/core/java/android/net/NetworkState.java b/core/java/android/net/NetworkState.java
index 292cf50..e449615 100644
--- a/core/java/android/net/NetworkState.java
+++ b/core/java/android/net/NetworkState.java
@@ -16,7 +16,7 @@
package android.net;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index 6c7aa13..96d7a80 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -19,7 +19,10 @@
import static android.os.Process.CLAT_UID;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
@@ -48,59 +51,104 @@
* @hide
*/
// @NotThreadSafe
-public class NetworkStats implements Parcelable {
+@SystemApi
+public final class NetworkStats implements Parcelable {
private static final String TAG = "NetworkStats";
+
/** {@link #iface} value when interface details unavailable. */
- public static final String IFACE_ALL = null;
+ @SuppressLint("CompileTimeConstant")
+ @Nullable public static final String IFACE_ALL = null;
+ /**
+ * Virtual network interface for video telephony. This is for VT data usage counting
+ * purpose.
+ */
+ public static final String IFACE_VT = "vt_data0";
+
/** {@link #uid} value when UID details unavailable. */
public static final int UID_ALL = -1;
- /** {@link #tag} value matching any tag. */
+ /** Special UID value for data usage by tethering. */
+ public static final int UID_TETHERING = -5;
+
+ /**
+ * {@link #tag} value matching any tag.
+ * @hide
+ */
// TODO: Rename TAG_ALL to TAG_ANY.
public static final int TAG_ALL = -1;
- /** {@link #set} value for all sets combined, not including debug sets. */
+ /**
+ * {@link #set} value for all sets combined, not including debug sets.
+ * @hide
+ */
public static final int SET_ALL = -1;
/** {@link #set} value where background data is accounted. */
public static final int SET_DEFAULT = 0;
/** {@link #set} value where foreground data is accounted. */
public static final int SET_FOREGROUND = 1;
- /** All {@link #set} value greater than SET_DEBUG_START are debug {@link #set} values. */
+ /**
+ * All {@link #set} value greater than SET_DEBUG_START are debug {@link #set} values.
+ * @hide
+ */
public static final int SET_DEBUG_START = 1000;
- /** Debug {@link #set} value when the VPN stats are moved in. */
+ /**
+ * Debug {@link #set} value when the VPN stats are moved in.
+ * @hide
+ */
public static final int SET_DBG_VPN_IN = 1001;
- /** Debug {@link #set} value when the VPN stats are moved out of a vpn UID. */
+ /**
+ * Debug {@link #set} value when the VPN stats are moved out of a vpn UID.
+ * @hide
+ */
public static final int SET_DBG_VPN_OUT = 1002;
- /** Include all interfaces when filtering */
- public static final String[] INTERFACES_ALL = null;
+ /**
+ * Include all interfaces when filtering
+ * @hide
+ */
+ public @Nullable static final String[] INTERFACES_ALL = null;
/** {@link #tag} value for total data across all tags. */
// TODO: Rename TAG_NONE to TAG_ALL.
public static final int TAG_NONE = 0;
- /** {@link #metered} value to account for all metered states. */
+ /**
+ * {@link #metered} value to account for all metered states.
+ * @hide
+ */
public static final int METERED_ALL = -1;
/** {@link #metered} value where native, unmetered data is accounted. */
public static final int METERED_NO = 0;
/** {@link #metered} value where metered data is accounted. */
public static final int METERED_YES = 1;
- /** {@link #roaming} value to account for all roaming states. */
+ /**
+ * {@link #roaming} value to account for all roaming states.
+ * @hide
+ */
public static final int ROAMING_ALL = -1;
/** {@link #roaming} value where native, non-roaming data is accounted. */
public static final int ROAMING_NO = 0;
/** {@link #roaming} value where roaming data is accounted. */
public static final int ROAMING_YES = 1;
- /** {@link #onDefaultNetwork} value to account for all default network states. */
+ /**
+ * {@link #onDefaultNetwork} value to account for all default network states.
+ * @hide
+ */
public static final int DEFAULT_NETWORK_ALL = -1;
/** {@link #onDefaultNetwork} value to account for usage while not the default network. */
public static final int DEFAULT_NETWORK_NO = 0;
/** {@link #onDefaultNetwork} value to account for usage while the default network. */
public static final int DEFAULT_NETWORK_YES = 1;
- /** Denotes a request for stats at the interface level. */
+ /**
+ * Denotes a request for stats at the interface level.
+ * @hide
+ */
public static final int STATS_PER_IFACE = 0;
- /** Denotes a request for stats at the interface and UID level. */
+ /**
+ * Denotes a request for stats at the interface and UID level.
+ * @hide
+ */
public static final int STATS_PER_UID = 1;
private static final String CLATD_INTERFACE_PREFIX = "v4-";
@@ -144,60 +192,78 @@
@UnsupportedAppUsage
private long[] operations;
+ /** @hide */
+ @SystemApi
public static class Entry {
+ /** @hide */
@UnsupportedAppUsage
public String iface;
+ /** @hide */
@UnsupportedAppUsage
public int uid;
+ /** @hide */
@UnsupportedAppUsage
public int set;
+ /** @hide */
@UnsupportedAppUsage
public int tag;
/**
* Note that this is only populated w/ the default value when read from /proc or written
* to disk. We merge in the correct value when reporting this value to clients of
* getSummary().
+ * @hide
*/
public int metered;
/**
* Note that this is only populated w/ the default value when read from /proc or written
* to disk. We merge in the correct value when reporting this value to clients of
* getSummary().
+ * @hide
*/
public int roaming;
/**
* Note that this is only populated w/ the default value when read from /proc or written
* to disk. We merge in the correct value when reporting this value to clients of
* getSummary().
+ * @hide
*/
public int defaultNetwork;
+ /** @hide */
@UnsupportedAppUsage
public long rxBytes;
+ /** @hide */
@UnsupportedAppUsage
public long rxPackets;
+ /** @hide */
@UnsupportedAppUsage
public long txBytes;
+ /** @hide */
@UnsupportedAppUsage
public long txPackets;
+ /** @hide */
+ @UnsupportedAppUsage
public long operations;
+ /** @hide */
@UnsupportedAppUsage
public Entry() {
this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, 0L, 0L, 0L, 0L, 0L);
}
+ /** @hide */
public Entry(long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
this(IFACE_ALL, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets,
operations);
}
+ /** @hide */
public Entry(String iface, int uid, int set, int tag, long rxBytes, long rxPackets,
long txBytes, long txPackets, long operations) {
this(iface, uid, set, tag, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO,
rxBytes, rxPackets, txBytes, txPackets, operations);
}
- public Entry(String iface, int uid, int set, int tag, int metered, int roaming,
+ public Entry(@Nullable String iface, int uid, int set, int tag, int metered, int roaming,
int defaultNetwork, long rxBytes, long rxPackets, long txBytes, long txPackets,
long operations) {
this.iface = iface;
@@ -214,15 +280,18 @@
this.operations = operations;
}
+ /** @hide */
public boolean isNegative() {
return rxBytes < 0 || rxPackets < 0 || txBytes < 0 || txPackets < 0 || operations < 0;
}
+ /** @hide */
public boolean isEmpty() {
return rxBytes == 0 && rxPackets == 0 && txBytes == 0 && txPackets == 0
&& operations == 0;
}
+ /** @hide */
public void add(Entry another) {
this.rxBytes += another.rxBytes;
this.rxPackets += another.rxPackets;
@@ -249,6 +318,7 @@
return builder.toString();
}
+ /** @hide */
@Override
public boolean equals(Object o) {
if (o instanceof Entry) {
@@ -262,13 +332,13 @@
return false;
}
+ /** @hide */
@Override
public int hashCode() {
return Objects.hash(uid, set, tag, metered, roaming, defaultNetwork, iface);
}
}
- @UnsupportedAppUsage
public NetworkStats(long elapsedRealtime, int initialSize) {
this.elapsedRealtime = elapsedRealtime;
this.size = 0;
@@ -292,6 +362,7 @@
}
}
+ /** @hide */
@UnsupportedAppUsage
public NetworkStats(Parcel parcel) {
elapsedRealtime = parcel.readLong();
@@ -312,7 +383,7 @@
}
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeLong(elapsedRealtime);
dest.writeInt(size);
dest.writeInt(capacity);
@@ -330,19 +401,23 @@
dest.writeLongArray(operations);
}
+ /**
+ * @hide
+ */
@Override
public NetworkStats clone() {
final NetworkStats clone = new NetworkStats(elapsedRealtime, size);
NetworkStats.Entry entry = null;
for (int i = 0; i < size; i++) {
entry = getValues(i, entry);
- clone.addValues(entry);
+ clone.addEntry(entry);
}
return clone;
}
/**
* Clear all data stored in this object.
+ * @hide
*/
public void clear() {
this.capacity = 0;
@@ -360,25 +435,28 @@
this.operations = EmptyArray.LONG;
}
+ /** @hide */
@VisibleForTesting
public NetworkStats addIfaceValues(
String iface, long rxBytes, long rxPackets, long txBytes, long txPackets) {
- return addValues(
+ return addEntry(
iface, UID_ALL, SET_DEFAULT, TAG_NONE, rxBytes, rxPackets, txBytes, txPackets, 0L);
}
+ /** @hide */
@VisibleForTesting
- public NetworkStats addValues(String iface, int uid, int set, int tag, long rxBytes,
+ public NetworkStats addEntry(String iface, int uid, int set, int tag, long rxBytes,
long rxPackets, long txBytes, long txPackets, long operations) {
- return addValues(new Entry(
+ return addEntry(new Entry(
iface, uid, set, tag, rxBytes, rxPackets, txBytes, txPackets, operations));
}
+ /** @hide */
@VisibleForTesting
- public NetworkStats addValues(String iface, int uid, int set, int tag, int metered, int roaming,
+ public NetworkStats addEntry(String iface, int uid, int set, int tag, int metered, int roaming,
int defaultNetwork, long rxBytes, long rxPackets, long txBytes, long txPackets,
long operations) {
- return addValues(new Entry(
+ return addEntry(new Entry(
iface, uid, set, tag, metered, roaming, defaultNetwork, rxBytes, rxPackets,
txBytes, txPackets, operations));
}
@@ -386,8 +464,9 @@
/**
* Add new stats entry, copying from given {@link Entry}. The {@link Entry}
* object can be recycled across multiple calls.
+ * @hide
*/
- public NetworkStats addValues(Entry entry) {
+ public NetworkStats addEntry(Entry entry) {
if (size >= capacity) {
final int newLength = Math.max(size, 10) * 3 / 2;
iface = Arrays.copyOf(iface, newLength);
@@ -428,6 +507,7 @@
/**
* Return specific stats entry.
+ * @hide
*/
@UnsupportedAppUsage
public Entry getValues(int i, Entry recycle) {
@@ -467,10 +547,12 @@
operations[dest] = operations[src];
}
+ /** @hide */
public long getElapsedRealtime() {
return elapsedRealtime;
}
+ /** @hide */
public void setElapsedRealtime(long time) {
elapsedRealtime = time;
}
@@ -478,21 +560,25 @@
/**
* Return age of this {@link NetworkStats} object with respect to
* {@link SystemClock#elapsedRealtime()}.
+ * @hide
*/
public long getElapsedRealtimeAge() {
return SystemClock.elapsedRealtime() - elapsedRealtime;
}
+ /** @hide */
@UnsupportedAppUsage
public int size() {
return size;
}
+ /** @hide */
@VisibleForTesting
public int internalSize() {
return capacity;
}
+ /** @hide */
@Deprecated
public NetworkStats combineValues(String iface, int uid, int tag, long rxBytes, long rxPackets,
long txBytes, long txPackets, long operations) {
@@ -501,6 +587,7 @@
txPackets, operations);
}
+ /** @hide */
public NetworkStats combineValues(String iface, int uid, int set, int tag,
long rxBytes, long rxPackets, long txBytes, long txPackets, long operations) {
return combineValues(new Entry(
@@ -509,16 +596,20 @@
/**
* Combine given values with an existing row, or create a new row if
- * {@link #findIndex(String, int, int, int, int)} is unable to find match. Can
- * also be used to subtract values from existing rows.
+ * {@link #findIndex(String, int, int, int, int, int, int)} is unable to find match. Can
+ * also be used to subtract values from existing rows. This method mutates the referencing
+ * {@link NetworkStats} object.
+ *
+ * @param entry the {@link Entry} to combine.
+ * @return a reference to this mutated {@link NetworkStats} object.
+ * @hide
*/
- @UnsupportedAppUsage
- public NetworkStats combineValues(Entry entry) {
+ public @NonNull NetworkStats combineValues(@NonNull Entry entry) {
final int i = findIndex(entry.iface, entry.uid, entry.set, entry.tag, entry.metered,
entry.roaming, entry.defaultNetwork);
if (i == -1) {
// only create new entry when positive contribution
- addValues(entry);
+ addEntry(entry);
} else {
rxBytes[i] += entry.rxBytes;
rxPackets[i] += entry.rxPackets;
@@ -530,10 +621,33 @@
}
/**
- * Combine all values from another {@link NetworkStats} into this object.
+ * Add given values with an existing row, or create a new row if
+ * {@link #findIndex(String, int, int, int, int, int, int)} is unable to find match. Can
+ * also be used to subtract values from existing rows.
+ *
+ * @param entry the {@link Entry} to add.
+ * @return a new constructed {@link NetworkStats} object that contains the result.
*/
- @UnsupportedAppUsage
- public void combineAllValues(NetworkStats another) {
+ public @NonNull NetworkStats addValues(@NonNull Entry entry) {
+ return this.clone().combineValues(entry);
+ }
+
+ /**
+ * Add the given {@link NetworkStats} objects.
+ *
+ * @return the sum of two objects.
+ */
+ public @NonNull NetworkStats add(@NonNull NetworkStats another) {
+ final NetworkStats ret = this.clone();
+ ret.combineAllValues(another);
+ return ret;
+ }
+
+ /**
+ * Combine all values from another {@link NetworkStats} into this object.
+ * @hide
+ */
+ public void combineAllValues(@NonNull NetworkStats another) {
NetworkStats.Entry entry = null;
for (int i = 0; i < another.size; i++) {
entry = another.getValues(i, entry);
@@ -543,6 +657,7 @@
/**
* Find first stats index that matches the requested parameters.
+ * @hide
*/
public int findIndex(String iface, int uid, int set, int tag, int metered, int roaming,
int defaultNetwork) {
@@ -560,6 +675,7 @@
/**
* Find first stats index that matches the requested parameters, starting
* search around the hinted index as an optimization.
+ * @hide
*/
@VisibleForTesting
public int findIndexHinted(String iface, int uid, int set, int tag, int metered, int roaming,
@@ -589,6 +705,7 @@
* Splice in {@link #operations} from the given {@link NetworkStats} based
* on matching {@link #uid} and {@link #tag} rows. Ignores {@link #iface},
* since operation counts are at data layer.
+ * @hide
*/
public void spliceOperationsFrom(NetworkStats stats) {
for (int i = 0; i < size; i++) {
@@ -604,6 +721,7 @@
/**
* Return list of unique interfaces known by this data structure.
+ * @hide
*/
public String[] getUniqueIfaces() {
final HashSet<String> ifaces = new HashSet<String>();
@@ -617,6 +735,7 @@
/**
* Return list of unique UIDs known by this data structure.
+ * @hide
*/
@UnsupportedAppUsage
public int[] getUniqueUids() {
@@ -636,6 +755,7 @@
/**
* Return total bytes represented by this snapshot object, usually used when
* checking if a {@link #subtract(NetworkStats)} delta passes a threshold.
+ * @hide
*/
@UnsupportedAppUsage
public long getTotalBytes() {
@@ -645,6 +765,7 @@
/**
* Return total of all fields represented by this snapshot object.
+ * @hide
*/
@UnsupportedAppUsage
public Entry getTotal(Entry recycle) {
@@ -654,6 +775,7 @@
/**
* Return total of all fields represented by this snapshot object matching
* the requested {@link #uid}.
+ * @hide
*/
@UnsupportedAppUsage
public Entry getTotal(Entry recycle, int limitUid) {
@@ -663,11 +785,13 @@
/**
* Return total of all fields represented by this snapshot object matching
* the requested {@link #iface}.
+ * @hide
*/
public Entry getTotal(Entry recycle, HashSet<String> limitIface) {
return getTotal(recycle, limitIface, UID_ALL, false);
}
+ /** @hide */
@UnsupportedAppUsage
public Entry getTotalIncludingTags(Entry recycle) {
return getTotal(recycle, null, UID_ALL, true);
@@ -717,6 +841,7 @@
/**
* Fast path for battery stats.
+ * @hide
*/
public long getTotalPackets() {
long total = 0;
@@ -729,9 +854,12 @@
/**
* Subtract the given {@link NetworkStats}, effectively leaving the delta
* between two snapshots in time. Assumes that statistics rows collect over
- * time, and that none of them have disappeared.
+ * time, and that none of them have disappeared. This method does not mutate
+ * the referencing object.
+ *
+ * @return the delta between two objects.
*/
- public NetworkStats subtract(NetworkStats right) {
+ public @NonNull NetworkStats subtract(@NonNull NetworkStats right) {
return subtract(this, right, null, null);
}
@@ -742,6 +870,7 @@
* <p>
* If counters have rolled backwards, they are clamped to {@code 0} and
* reported to the given {@link NonMonotonicObserver}.
+ * @hide
*/
public static <C> NetworkStats subtract(NetworkStats left, NetworkStats right,
NonMonotonicObserver<C> observer, C cookie) {
@@ -759,6 +888,7 @@
* If <var>recycle</var> is supplied, this NetworkStats object will be
* reused (and returned) as the result if it is large enough to contain
* the data.
+ * @hide
*/
public static <C> NetworkStats subtract(NetworkStats left, NetworkStats right,
NonMonotonicObserver<C> observer, C cookie, NetworkStats recycle) {
@@ -817,7 +947,7 @@
entry.operations = Math.max(entry.operations, 0);
}
- result.addValues(entry);
+ result.addEntry(entry);
}
return result;
@@ -847,6 +977,7 @@
* @param stackedTraffic Stats with traffic stacked on top of our ifaces. Will also be mutated.
* @param stackedIfaces Mapping ipv6if -> ipv4if interface where traffic is counted on both.
* @param useBpfStats True if eBPF is in use.
+ * @hide
*/
public static void apply464xlatAdjustments(NetworkStats baseTraffic,
NetworkStats stackedTraffic, Map<String, String> stackedIfaces, boolean useBpfStats) {
@@ -899,6 +1030,7 @@
* {@link #apply464xlatAdjustments(NetworkStats, NetworkStats, Map)} with {@code this} as
* base and stacked traffic.
* @param stackedIfaces Mapping ipv6if -> ipv4if interface where traffic is counted on both.
+ * @hide
*/
public void apply464xlatAdjustments(Map<String, String> stackedIfaces, boolean useBpfStats) {
apply464xlatAdjustments(this, this, stackedIfaces, useBpfStats);
@@ -907,6 +1039,7 @@
/**
* Return total statistics grouped by {@link #iface}; doesn't mutate the
* original structure.
+ * @hide
*/
public NetworkStats groupedByIface() {
final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
@@ -938,6 +1071,7 @@
/**
* Return total statistics grouped by {@link #uid}; doesn't mutate the
* original structure.
+ * @hide
*/
public NetworkStats groupedByUid() {
final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
@@ -968,6 +1102,7 @@
/**
* Remove all rows that match one of specified UIDs.
+ * @hide
*/
public void removeUids(int[] uids) {
int nextOutputEntry = 0;
@@ -989,6 +1124,7 @@
* @param limitUid UID to filter for, or {@link #UID_ALL}.
* @param limitIfaces Interfaces to filter for, or {@link #INTERFACES_ALL}.
* @param limitTag Tag to filter for, or {@link #TAG_ALL}.
+ * @hide
*/
public void filter(int limitUid, String[] limitIfaces, int limitTag) {
if (limitUid == UID_ALL && limitTag == TAG_ALL && limitIfaces == INTERFACES_ALL) {
@@ -1004,6 +1140,7 @@
* Only keep entries with {@link #set} value less than {@link #SET_DEBUG_START}.
*
* <p>This mutates the original structure in place.
+ * @hide
*/
public void filterDebugEntries() {
filter(e -> e.set < SET_DEBUG_START);
@@ -1024,6 +1161,7 @@
size = nextOutputEntry;
}
+ /** @hide */
public void dump(String prefix, PrintWriter pw) {
pw.print(prefix);
pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime);
@@ -1047,6 +1185,7 @@
/**
* Return text description of {@link #set} value.
+ * @hide
*/
public static String setToString(int set) {
switch (set) {
@@ -1067,6 +1206,7 @@
/**
* Return text description of {@link #set} value.
+ * @hide
*/
public static String setToCheckinString(int set) {
switch (set) {
@@ -1087,6 +1227,7 @@
/**
* @return true if the querySet matches the dataSet.
+ * @hide
*/
public static boolean setMatches(int querySet, int dataSet) {
if (querySet == dataSet) {
@@ -1098,6 +1239,7 @@
/**
* Return text description of {@link #tag} value.
+ * @hide
*/
public static String tagToString(int tag) {
return "0x" + Integer.toHexString(tag);
@@ -1105,6 +1247,7 @@
/**
* Return text description of {@link #metered} value.
+ * @hide
*/
public static String meteredToString(int metered) {
switch (metered) {
@@ -1121,6 +1264,7 @@
/**
* Return text description of {@link #roaming} value.
+ * @hide
*/
public static String roamingToString(int roaming) {
switch (roaming) {
@@ -1137,6 +1281,7 @@
/**
* Return text description of {@link #defaultNetwork} value.
+ * @hide
*/
public static String defaultNetworkToString(int defaultNetwork) {
switch (defaultNetwork) {
@@ -1151,6 +1296,7 @@
}
}
+ /** @hide */
@Override
public String toString() {
final CharArrayWriter writer = new CharArrayWriter();
@@ -1163,8 +1309,7 @@
return 0;
}
- @UnsupportedAppUsage
- public static final @android.annotation.NonNull Creator<NetworkStats> CREATOR = new Creator<NetworkStats>() {
+ public static final @NonNull Creator<NetworkStats> CREATOR = new Creator<NetworkStats>() {
@Override
public NetworkStats createFromParcel(Parcel in) {
return new NetworkStats(in);
@@ -1176,6 +1321,7 @@
}
};
+ /** @hide */
public interface NonMonotonicObserver<C> {
public void foundNonMonotonic(
NetworkStats left, int leftIndex, NetworkStats right, int rightIndex, C cookie);
@@ -1195,6 +1341,7 @@
* @param tunUid uid of the VPN application
* @param tunIface iface of the vpn tunnel
* @param underlyingIfaces underlying network ifaces used by the VPN application
+ * @hide
*/
public void migrateTun(int tunUid, @NonNull String tunIface,
@NonNull String[] underlyingIfaces) {
diff --git a/core/java/android/net/NetworkStatsHistory.java b/core/java/android/net/NetworkStatsHistory.java
index d96d2ee..6d46c20 100644
--- a/core/java/android/net/NetworkStatsHistory.java
+++ b/core/java/android/net/NetworkStatsHistory.java
@@ -30,7 +30,7 @@
import static com.android.internal.util.ArrayUtils.total;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
import android.service.NetworkStatsHistoryBucketProto;
diff --git a/core/java/android/net/NetworkTemplate.java b/core/java/android/net/NetworkTemplate.java
index 87c7118..5e6c47a 100644
--- a/core/java/android/net/NetworkTemplate.java
+++ b/core/java/android/net/NetworkTemplate.java
@@ -34,7 +34,7 @@
import static android.net.NetworkStats.ROAMING_YES;
import static android.net.wifi.WifiInfo.removeDoubleQuotes;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.BackupUtils;
diff --git a/core/java/android/net/NetworkUtils.java b/core/java/android/net/NetworkUtils.java
index d0f54b4..779f7bc 100644
--- a/core/java/android/net/NetworkUtils.java
+++ b/core/java/android/net/NetworkUtils.java
@@ -20,7 +20,7 @@
import static android.system.OsConstants.AF_INET6;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.net.shared.Inet4AddressUtils;
import android.os.Build;
import android.system.ErrnoException;
@@ -31,7 +31,6 @@
import java.io.FileDescriptor;
import java.math.BigInteger;
import java.net.Inet4Address;
-import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
@@ -61,13 +60,6 @@
public static native void detachBPFFilter(FileDescriptor fd) throws SocketException;
/**
- * Configures a socket for receiving ICMPv6 router solicitations and sending advertisements.
- * @param fd the socket's {@link FileDescriptor}.
- * @param ifIndex the interface index.
- */
- public native static void setupRaSocket(FileDescriptor fd, int ifIndex) throws SocketException;
-
- /**
* Binds the current process to the network designated by {@code netId}. All sockets created
* in the future (and not explicitly bound via a bound {@link SocketFactory} (see
* {@link Network#getSocketFactory}) will be bound to this network. Note that if this
@@ -320,15 +312,6 @@
}
/**
- * Check if IP address type is consistent between two InetAddress.
- * @return true if both are the same type. False otherwise.
- */
- public static boolean addressTypeMatches(InetAddress left, InetAddress right) {
- return (((left instanceof Inet4Address) && (right instanceof Inet4Address)) ||
- ((left instanceof Inet6Address) && (right instanceof Inet6Address)));
- }
-
- /**
* Convert a 32 char hex string into a Inet6Address.
* throws a runtime exception if the string isn't 32 chars, isn't hex or can't be
* made into an Inet6Address
diff --git a/core/java/android/net/OWNERS b/core/java/android/net/OWNERS
index a1c7fce..767b693 100644
--- a/core/java/android/net/OWNERS
+++ b/core/java/android/net/OWNERS
@@ -8,4 +8,4 @@
reminv@google.com
satk@google.com
-per-file SSL*, Uri*, Url* = flooey@google.com, narayan@google.com, tobiast@google.com
+per-file SSL*, Uri*, Url* = prb@google.com, dauletz@google.com, narayan@google.com, tobiast@google.com
diff --git a/core/java/android/net/Proxy.java b/core/java/android/net/Proxy.java
index 4600942..4ba7394 100644
--- a/core/java/android/net/Proxy.java
+++ b/core/java/android/net/Proxy.java
@@ -18,7 +18,7 @@
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
diff --git a/core/java/android/net/ProxyInfo.java b/core/java/android/net/ProxyInfo.java
index 9d92db4..ffe9ae9 100644
--- a/core/java/android/net/ProxyInfo.java
+++ b/core/java/android/net/ProxyInfo.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
diff --git a/core/java/android/net/RouteInfo.java b/core/java/android/net/RouteInfo.java
index 52d3fc4..e088094 100644
--- a/core/java/android/net/RouteInfo.java
+++ b/core/java/android/net/RouteInfo.java
@@ -21,7 +21,8 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.net.util.NetUtils;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
@@ -441,21 +442,7 @@
@UnsupportedAppUsage
@Nullable
public static RouteInfo selectBestRoute(Collection<RouteInfo> routes, InetAddress dest) {
- if ((routes == null) || (dest == null)) return null;
-
- RouteInfo bestRoute = null;
- // pick a longest prefix match under same address type
- for (RouteInfo route : routes) {
- if (NetworkUtils.addressTypeMatches(route.mDestination.getAddress(), dest)) {
- if ((bestRoute != null) &&
- (bestRoute.mDestination.getPrefixLength() >=
- route.mDestination.getPrefixLength())) {
- continue;
- }
- if (route.matches(dest)) bestRoute = route;
- }
- }
- return bestRoute;
+ return NetUtils.selectBestRoute(routes, dest);
}
/**
diff --git a/core/java/android/net/SSLCertificateSocketFactory.java b/core/java/android/net/SSLCertificateSocketFactory.java
index 39cb323..8b6ac42 100644
--- a/core/java/android/net/SSLCertificateSocketFactory.java
+++ b/core/java/android/net/SSLCertificateSocketFactory.java
@@ -16,7 +16,7 @@
package android.net;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.SystemProperties;
import android.util.Log;
diff --git a/core/java/android/net/SSLSessionCache.java b/core/java/android/net/SSLSessionCache.java
index 9667e82..944bc54 100644
--- a/core/java/android/net/SSLSessionCache.java
+++ b/core/java/android/net/SSLSessionCache.java
@@ -16,7 +16,7 @@
package android.net;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.util.Log;
diff --git a/core/java/android/net/SntpClient.java b/core/java/android/net/SntpClient.java
index f9c2def..8c6faf6 100644
--- a/core/java/android/net/SntpClient.java
+++ b/core/java/android/net/SntpClient.java
@@ -16,7 +16,7 @@
package android.net;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.SystemClock;
import android.util.Log;
diff --git a/core/java/android/net/SocketKeepalive.java b/core/java/android/net/SocketKeepalive.java
index fb224fb..fc9a8f6 100644
--- a/core/java/android/net/SocketKeepalive.java
+++ b/core/java/android/net/SocketKeepalive.java
@@ -20,6 +20,7 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.os.Binder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
@@ -53,7 +54,11 @@
public abstract class SocketKeepalive implements AutoCloseable {
static final String TAG = "SocketKeepalive";
- /** @hide */
+ /**
+ * No errors.
+ * @hide
+ */
+ @SystemApi
public static final int SUCCESS = 0;
/** @hide */
diff --git a/core/java/android/net/StaticIpConfiguration.java b/core/java/android/net/StaticIpConfiguration.java
index 990c114..f24a9bd 100644
--- a/core/java/android/net/StaticIpConfiguration.java
+++ b/core/java/android/net/StaticIpConfiguration.java
@@ -20,7 +20,7 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.net.shared.InetAddressUtils;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/net/TelephonyNetworkSpecifier.java b/core/java/android/net/TelephonyNetworkSpecifier.java
new file mode 100644
index 0000000..726f770
--- /dev/null
+++ b/core/java/android/net/TelephonyNetworkSpecifier.java
@@ -0,0 +1,146 @@
+/*
+ * 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.net;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * NetworkSpecifier object for cellular network request. Apps should use the
+ * {@link TelephonyNetworkSpecifier.Builder} class to create an instance.
+ */
+public final class TelephonyNetworkSpecifier extends NetworkSpecifier implements Parcelable {
+
+ private final int mSubId;
+
+ /**
+ * Return the subscription Id of current TelephonyNetworkSpecifier object.
+ *
+ * @return The subscription id.
+ */
+ public int getSubscriptionId() {
+ return mSubId;
+ }
+
+ /**
+ * @hide
+ */
+ public TelephonyNetworkSpecifier(int subId) {
+ this.mSubId = subId;
+ }
+
+ public static final @NonNull Creator<TelephonyNetworkSpecifier> CREATOR =
+ new Creator<TelephonyNetworkSpecifier>() {
+ @Override
+ public TelephonyNetworkSpecifier createFromParcel(Parcel in) {
+ int subId = in.readInt();
+ return new TelephonyNetworkSpecifier(subId);
+ }
+
+ @Override
+ public TelephonyNetworkSpecifier[] newArray(int size) {
+ return new TelephonyNetworkSpecifier[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mSubId);
+ }
+
+ @Override
+ public int hashCode() {
+ return mSubId;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof TelephonyNetworkSpecifier)) {
+ return false;
+ }
+
+ TelephonyNetworkSpecifier lhs = (TelephonyNetworkSpecifier) obj;
+ return mSubId == lhs.mSubId;
+ }
+
+ @Override
+ public String toString() {
+ return new StringBuilder()
+ .append("TelephonyNetworkSpecifier [")
+ .append("mSubId = ").append(mSubId)
+ .append("]")
+ .toString();
+ }
+
+ /** @hide */
+ @Override
+ public boolean satisfiedBy(NetworkSpecifier other) {
+ // Any generic requests should be satisfied by a specific telephony network.
+ // For simplicity, we treat null same as MatchAllNetworkSpecifier
+ return equals(other) || other == null || other instanceof MatchAllNetworkSpecifier;
+ }
+
+
+ /**
+ * Builder to create {@link TelephonyNetworkSpecifier} object.
+ */
+ public static final class Builder {
+ // Integer.MIN_VALUE which is not a valid subId, services as the sentinel to check if
+ // subId was set
+ private static final int SENTINEL_SUB_ID = Integer.MIN_VALUE;
+
+ private int mSubId;
+
+ public Builder() {
+ mSubId = SENTINEL_SUB_ID;
+ }
+
+ /**
+ * Set the subscription id.
+ *
+ * @param subId The subscription Id.
+ * @return Instance of {@link Builder} to enable the chaining of the builder method.
+ */
+ public @NonNull Builder setSubscriptionId(int subId) {
+ mSubId = subId;
+ return this;
+ }
+
+ /**
+ * Create a NetworkSpecifier for the cellular network request.
+ *
+ * @return TelephonyNetworkSpecifier object.
+ * @throws IllegalArgumentException when subscription Id is not provided through
+ * {@link #setSubscriptionId(int)}.
+ */
+ public @NonNull TelephonyNetworkSpecifier build() {
+ if (mSubId == SENTINEL_SUB_ID) {
+ throw new IllegalArgumentException("Subscription Id is not provided.");
+ }
+ return new TelephonyNetworkSpecifier(mSubId);
+ }
+ }
+}
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index bf4884a..8108cf0 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -20,10 +20,10 @@
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.DownloadManager;
import android.app.backup.BackupManager;
import android.app.usage.NetworkStatsManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.media.MediaPlayer;
import android.os.Build;
@@ -89,7 +89,7 @@
*
* @hide
*/
- public static final int UID_TETHERING = -5;
+ public static final int UID_TETHERING = NetworkStats.UID_TETHERING;
/**
* Tag values in this range are reserved for the network stack. The network stack is
diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java
index 44d977a..525bbfd 100644
--- a/core/java/android/net/Uri.java
+++ b/core/java/android/net/Uri.java
@@ -19,7 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Intent;
import android.os.Environment;
import android.os.Parcel;
diff --git a/core/java/android/net/VpnService.java b/core/java/android/net/VpnService.java
index ed7cddc..4b804b0 100644
--- a/core/java/android/net/VpnService.java
+++ b/core/java/android/net/VpnService.java
@@ -23,11 +23,11 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.Activity;
import android.app.PendingIntent;
import android.app.Service;
import android.app.admin.DevicePolicyManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
diff --git a/core/java/android/net/WebAddress.java b/core/java/android/net/WebAddress.java
index 994c794..aa3777d 100644
--- a/core/java/android/net/WebAddress.java
+++ b/core/java/android/net/WebAddress.java
@@ -20,7 +20,7 @@
import android.annotation.NonNull;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import java.util.Locale;
diff --git a/core/java/android/net/annotations/PolicyDirection.java b/core/java/android/net/annotations/PolicyDirection.java
new file mode 100644
index 0000000..febd9b4
--- /dev/null
+++ b/core/java/android/net/annotations/PolicyDirection.java
@@ -0,0 +1,35 @@
+/*
+ * 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.net.annotations;
+
+import android.annotation.IntDef;
+import android.net.IpSecManager;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * IPsec traffic direction.
+ *
+ * <p>Mainline modules cannot reference hidden @IntDef. Moving this annotation to a separate class
+ * to allow others to statically include it.
+ *
+ * @hide
+ */
+@IntDef(value = {IpSecManager.DIRECTION_IN, IpSecManager.DIRECTION_OUT})
+@Retention(RetentionPolicy.SOURCE)
+public @interface PolicyDirection {}
diff --git a/core/java/android/net/http/OWNERS b/core/java/android/net/http/OWNERS
index 6b8c9ed..3092612 100644
--- a/core/java/android/net/http/OWNERS
+++ b/core/java/android/net/http/OWNERS
@@ -1,3 +1,4 @@
-flooey@google.com
narayan@google.com
tobiast@google.com
+include platform/libcore:/OWNERS
+include platform/external/conscrypt:/OWNERS
diff --git a/core/java/android/net/http/SslCertificate.java b/core/java/android/net/http/SslCertificate.java
index 01dd08f..250cff2 100644
--- a/core/java/android/net/http/SslCertificate.java
+++ b/core/java/android/net/http/SslCertificate.java
@@ -17,7 +17,7 @@
package android.net.http;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Bundle;
import android.text.format.DateFormat;
diff --git a/core/java/android/net/http/SslError.java b/core/java/android/net/http/SslError.java
index b3f2fb7..d43c616 100644
--- a/core/java/android/net/http/SslError.java
+++ b/core/java/android/net/http/SslError.java
@@ -16,8 +16,9 @@
package android.net.http;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
+
import java.security.cert.X509Certificate;
/**
diff --git a/core/java/android/net/metrics/ApfProgramEvent.java b/core/java/android/net/metrics/ApfProgramEvent.java
index 8243be9..f93907a 100644
--- a/core/java/android/net/metrics/ApfProgramEvent.java
+++ b/core/java/android/net/metrics/ApfProgramEvent.java
@@ -21,7 +21,7 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
diff --git a/core/java/android/net/metrics/ApfStats.java b/core/java/android/net/metrics/ApfStats.java
index eac5579..b221cb9 100644
--- a/core/java/android/net/metrics/ApfStats.java
+++ b/core/java/android/net/metrics/ApfStats.java
@@ -20,7 +20,7 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/net/metrics/DhcpClientEvent.java b/core/java/android/net/metrics/DhcpClientEvent.java
index 5f9f507..8fc1ef8 100644
--- a/core/java/android/net/metrics/DhcpClientEvent.java
+++ b/core/java/android/net/metrics/DhcpClientEvent.java
@@ -20,7 +20,7 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
diff --git a/core/java/android/net/netstats/provider/AbstractNetworkStatsProvider.java b/core/java/android/net/netstats/provider/AbstractNetworkStatsProvider.java
new file mode 100644
index 0000000..740aa92
--- /dev/null
+++ b/core/java/android/net/netstats/provider/AbstractNetworkStatsProvider.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.netstats.provider;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+import android.net.NetworkStats;
+
+/**
+ * A base class that allows external modules to implement a custom network statistics provider.
+ * @hide
+ */
+@SystemApi
+public abstract class AbstractNetworkStatsProvider {
+ /**
+ * A value used by {@link #setLimit} and {@link #setAlert} indicates there is no limit.
+ */
+ public static final int QUOTA_UNLIMITED = -1;
+
+ /**
+ * Called by {@code NetworkStatsService} when global polling is needed. Custom
+ * implementation of providers MUST respond to it by calling
+ * {@link NetworkStatsProviderCallback#onStatsUpdated} within one minute. Responding
+ * later than this may cause the stats to be dropped.
+ *
+ * @param token a positive number identifying the new state of the system under which
+ * {@link NetworkStats} have to be gathered from now on. When this is called,
+ * custom implementations of providers MUST report the latest stats with the
+ * previous token, under which stats were being gathered so far.
+ */
+ public abstract void requestStatsUpdate(int token);
+
+ /**
+ * Called by {@code NetworkStatsService} when setting the interface quota for the specified
+ * upstream interface. When this is called, the custom implementation should block all egress
+ * packets on the {@code iface} associated with the provider when {@code quotaBytes} bytes have
+ * been reached, and MUST respond to it by calling
+ * {@link NetworkStatsProviderCallback#onLimitReached()}.
+ *
+ * @param iface the interface requiring the operation.
+ * @param quotaBytes the quota defined as the number of bytes, starting from zero and counting
+ * from now. A value of {@link #QUOTA_UNLIMITED} indicates there is no limit.
+ */
+ public abstract void setLimit(@NonNull String iface, long quotaBytes);
+
+ /**
+ * Called by {@code NetworkStatsService} when setting the alert bytes. Custom implementations
+ * MUST call {@link NetworkStatsProviderCallback#onAlertReached()} when {@code quotaBytes} bytes
+ * have been reached. Unlike {@link #setLimit(String, long)}, the custom implementation should
+ * not block all egress packets.
+ *
+ * @param quotaBytes the quota defined as the number of bytes, starting from zero and counting
+ * from now. A value of {@link #QUOTA_UNLIMITED} indicates there is no alert.
+ */
+ public abstract void setAlert(long quotaBytes);
+}
diff --git a/media/java/android/media/RouteSessionInfo.aidl b/core/java/android/net/netstats/provider/INetworkStatsProvider.aidl
similarity index 60%
copy from media/java/android/media/RouteSessionInfo.aidl
copy to core/java/android/net/netstats/provider/INetworkStatsProvider.aidl
index fb5d836..55b3d4e 100644
--- a/media/java/android/media/RouteSessionInfo.aidl
+++ b/core/java/android/net/netstats/provider/INetworkStatsProvider.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 The Android Open Source Project
+ * 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.
@@ -14,6 +14,15 @@
* limitations under the License.
*/
-package android.media;
+package android.net.netstats.provider;
-parcelable RouteSessionInfo;
+/**
+ * Interface for NetworkStatsService to query network statistics and set data limits.
+ *
+ * @hide
+ */
+oneway interface INetworkStatsProvider {
+ void requestStatsUpdate(int token);
+ void setLimit(String iface, long quotaBytes);
+ void setAlert(long quotaBytes);
+}
diff --git a/core/java/android/net/netstats/provider/INetworkStatsProviderCallback.aidl b/core/java/android/net/netstats/provider/INetworkStatsProviderCallback.aidl
new file mode 100644
index 0000000..3ea9318
--- /dev/null
+++ b/core/java/android/net/netstats/provider/INetworkStatsProviderCallback.aidl
@@ -0,0 +1,31 @@
+/*
+ * 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.net.netstats.provider;
+
+import android.net.NetworkStats;
+
+/**
+ * Interface for implementor of {@link INetworkStatsProviderCallback} to push events
+ * such as network statistics update or notify limit reached.
+ * @hide
+ */
+oneway interface INetworkStatsProviderCallback {
+ void onStatsUpdated(int token, in NetworkStats ifaceStats, in NetworkStats uidStats);
+ void onAlertReached();
+ void onLimitReached();
+ void unregister();
+}
diff --git a/core/java/android/net/netstats/provider/NetworkStatsProviderCallback.java b/core/java/android/net/netstats/provider/NetworkStatsProviderCallback.java
new file mode 100644
index 0000000..e17a8ee
--- /dev/null
+++ b/core/java/android/net/netstats/provider/NetworkStatsProviderCallback.java
@@ -0,0 +1,98 @@
+/*
+ * 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.net.netstats.provider;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.net.NetworkStats;
+import android.os.RemoteException;
+
+/**
+ * A callback class that allows callers to report events to the system.
+ * @hide
+ */
+@SystemApi
+@SuppressLint("CallbackMethodName")
+public class NetworkStatsProviderCallback {
+ @NonNull private final INetworkStatsProviderCallback mBinder;
+
+ /** @hide */
+ public NetworkStatsProviderCallback(@NonNull INetworkStatsProviderCallback binder) {
+ mBinder = binder;
+ }
+
+ /**
+ * Notify the system of new network statistics.
+ *
+ * Send the network statistics recorded since the last call to {@link #onStatsUpdated}. Must be
+ * called within one minute of {@link AbstractNetworkStatsProvider#requestStatsUpdate(int)}
+ * being called. The provider can also call this whenever it wants to reports new stats for any
+ * reason. Note that the system will not necessarily immediately propagate the statistics to
+ * reflect the update.
+ *
+ * @param token the token under which these stats were gathered. Providers can call this method
+ * with the current token as often as they want, until the token changes.
+ * {@see AbstractNetworkStatsProvider#requestStatsUpdate()}
+ * @param ifaceStats the {@link NetworkStats} per interface to be reported.
+ * The provider should not include any traffic that is already counted by
+ * kernel interface counters.
+ * @param uidStats the same stats as above, but counts {@link NetworkStats}
+ * per uid.
+ */
+ public void onStatsUpdated(int token, @NonNull NetworkStats ifaceStats,
+ @NonNull NetworkStats uidStats) {
+ try {
+ mBinder.onStatsUpdated(token, ifaceStats, uidStats);
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Notify system that the quota set by {@code setAlert} has been reached.
+ */
+ public void onAlertReached() {
+ try {
+ mBinder.onAlertReached();
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Notify system that the quota set by {@code setLimit} has been reached.
+ */
+ public void onLimitReached() {
+ try {
+ mBinder.onLimitReached();
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Unregister the provider and the referencing callback.
+ */
+ public void unregister() {
+ try {
+ mBinder.unregister();
+ } catch (RemoteException e) {
+ // Ignore error.
+ }
+ }
+}
diff --git a/core/java/android/net/netstats/provider/NetworkStatsProviderWrapper.java b/core/java/android/net/netstats/provider/NetworkStatsProviderWrapper.java
new file mode 100644
index 0000000..4bf7c9b
--- /dev/null
+++ b/core/java/android/net/netstats/provider/NetworkStatsProviderWrapper.java
@@ -0,0 +1,48 @@
+/*
+ * 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.net.netstats.provider;
+
+import android.annotation.NonNull;
+
+/**
+ * A wrapper class of {@link INetworkStatsProvider} that hides the binder interface from exposing
+ * to outer world.
+ *
+ * @hide
+ */
+public class NetworkStatsProviderWrapper extends INetworkStatsProvider.Stub {
+ @NonNull final AbstractNetworkStatsProvider mProvider;
+
+ public NetworkStatsProviderWrapper(AbstractNetworkStatsProvider provider) {
+ mProvider = provider;
+ }
+
+ @Override
+ public void requestStatsUpdate(int token) {
+ mProvider.requestStatsUpdate(token);
+ }
+
+ @Override
+ public void setLimit(@NonNull String iface, long quotaBytes) {
+ mProvider.setLimit(iface, quotaBytes);
+ }
+
+ @Override
+ public void setAlert(long quotaBytes) {
+ mProvider.setAlert(quotaBytes);
+ }
+}
diff --git a/core/java/android/net/nsd/DnsSdTxtRecord.java b/core/java/android/net/nsd/DnsSdTxtRecord.java
deleted file mode 100644
index e4a91c5..0000000
--- a/core/java/android/net/nsd/DnsSdTxtRecord.java
+++ /dev/null
@@ -1,325 +0,0 @@
-/* -*- Mode: Java; tab-width: 4 -*-
- *
- * Copyright (c) 2004 Apple Computer, Inc. All rights reserved.
- *
- * 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.
-
- To do:
- - implement remove()
- - fix set() to replace existing values
- */
-
-package android.net.nsd;
-
-import android.os.Parcelable;
-import android.os.Parcel;
-
-import java.util.Arrays;
-
-/**
- * This class handles TXT record data for DNS based service discovery as specified at
- * http://tools.ietf.org/html/draft-cheshire-dnsext-dns-sd-11
- *
- * DNS-SD specifies that a TXT record corresponding to an SRV record consist of
- * a packed array of bytes, each preceded by a length byte. Each string
- * is an attribute-value pair.
- *
- * The DnsSdTxtRecord object stores the entire TXT data as a single byte array, traversing it
- * as need be to implement its various methods.
- * @hide
- *
- */
-public class DnsSdTxtRecord implements Parcelable {
- private static final byte mSeperator = '=';
-
- private byte[] mData;
-
- /** Constructs a new, empty TXT record. */
- public DnsSdTxtRecord() {
- mData = new byte[0];
- }
-
- /** Constructs a new TXT record from a byte array in the standard format. */
- public DnsSdTxtRecord(byte[] data) {
- mData = (byte[]) data.clone();
- }
-
- /** Copy constructor */
- public DnsSdTxtRecord(DnsSdTxtRecord src) {
- if (src != null && src.mData != null) {
- mData = (byte[]) src.mData.clone();
- }
- }
-
- /**
- * Set a key/value pair. Setting an existing key will replace its value.
- * @param key Must be ascii with no '='
- * @param value matching value to key
- */
- public void set(String key, String value) {
- byte[] keyBytes;
- byte[] valBytes;
- int valLen;
-
- if (value != null) {
- valBytes = value.getBytes();
- valLen = valBytes.length;
- } else {
- valBytes = null;
- valLen = 0;
- }
-
- try {
- keyBytes = key.getBytes("US-ASCII");
- }
- catch (java.io.UnsupportedEncodingException e) {
- throw new IllegalArgumentException("key should be US-ASCII");
- }
-
- for (int i = 0; i < keyBytes.length; i++) {
- if (keyBytes[i] == '=') {
- throw new IllegalArgumentException("= is not a valid character in key");
- }
- }
-
- if (keyBytes.length + valLen >= 255) {
- throw new IllegalArgumentException("Key and Value length cannot exceed 255 bytes");
- }
-
- int currentLoc = remove(key);
- if (currentLoc == -1)
- currentLoc = keyCount();
-
- insert(keyBytes, valBytes, currentLoc);
- }
-
- /**
- * Get a value for a key
- *
- * @param key
- * @return The value associated with the key
- */
- public String get(String key) {
- byte[] val = this.getValue(key);
- return val != null ? new String(val) : null;
- }
-
- /** Remove a key/value pair. If found, returns the index or -1 if not found */
- public int remove(String key) {
- int avStart = 0;
-
- for (int i=0; avStart < mData.length; i++) {
- int avLen = mData[avStart];
- if (key.length() <= avLen &&
- (key.length() == avLen || mData[avStart + key.length() + 1] == mSeperator)) {
- String s = new String(mData, avStart + 1, key.length());
- if (0 == key.compareToIgnoreCase(s)) {
- byte[] oldBytes = mData;
- mData = new byte[oldBytes.length - avLen - 1];
- System.arraycopy(oldBytes, 0, mData, 0, avStart);
- System.arraycopy(oldBytes, avStart + avLen + 1, mData, avStart,
- oldBytes.length - avStart - avLen - 1);
- return i;
- }
- }
- avStart += (0xFF & (avLen + 1));
- }
- return -1;
- }
-
- /** Return the count of keys */
- public int keyCount() {
- int count = 0, nextKey;
- for (nextKey = 0; nextKey < mData.length; count++) {
- nextKey += (0xFF & (mData[nextKey] + 1));
- }
- return count;
- }
-
- /** Return true if key is present, false if not. */
- public boolean contains(String key) {
- String s = null;
- for (int i = 0; null != (s = this.getKey(i)); i++) {
- if (0 == key.compareToIgnoreCase(s)) return true;
- }
- return false;
- }
-
- /* Gets the size in bytes */
- public int size() {
- return mData.length;
- }
-
- /* Gets the raw data in bytes */
- public byte[] getRawData() {
- return (byte[]) mData.clone();
- }
-
- private void insert(byte[] keyBytes, byte[] value, int index) {
- byte[] oldBytes = mData;
- int valLen = (value != null) ? value.length : 0;
- int insertion = 0;
- int newLen, avLen;
-
- for (int i = 0; i < index && insertion < mData.length; i++) {
- insertion += (0xFF & (mData[insertion] + 1));
- }
-
- avLen = keyBytes.length + valLen + (value != null ? 1 : 0);
- newLen = avLen + oldBytes.length + 1;
-
- mData = new byte[newLen];
- System.arraycopy(oldBytes, 0, mData, 0, insertion);
- int secondHalfLen = oldBytes.length - insertion;
- System.arraycopy(oldBytes, insertion, mData, newLen - secondHalfLen, secondHalfLen);
- mData[insertion] = (byte) avLen;
- System.arraycopy(keyBytes, 0, mData, insertion + 1, keyBytes.length);
- if (value != null) {
- mData[insertion + 1 + keyBytes.length] = mSeperator;
- System.arraycopy(value, 0, mData, insertion + keyBytes.length + 2, valLen);
- }
- }
-
- /** Return a key in the TXT record by zero-based index. Returns null if index exceeds the total number of keys. */
- private String getKey(int index) {
- int avStart = 0;
-
- for (int i=0; i < index && avStart < mData.length; i++) {
- avStart += mData[avStart] + 1;
- }
-
- if (avStart < mData.length) {
- int avLen = mData[avStart];
- int aLen = 0;
-
- for (aLen=0; aLen < avLen; aLen++) {
- if (mData[avStart + aLen + 1] == mSeperator) break;
- }
- return new String(mData, avStart + 1, aLen);
- }
- return null;
- }
-
- /**
- * Look up a key in the TXT record by zero-based index and return its value.
- * Returns null if index exceeds the total number of keys.
- * Returns null if the key is present with no value.
- */
- private byte[] getValue(int index) {
- int avStart = 0;
- byte[] value = null;
-
- for (int i=0; i < index && avStart < mData.length; i++) {
- avStart += mData[avStart] + 1;
- }
-
- if (avStart < mData.length) {
- int avLen = mData[avStart];
- int aLen = 0;
-
- for (aLen=0; aLen < avLen; aLen++) {
- if (mData[avStart + aLen + 1] == mSeperator) {
- value = new byte[avLen - aLen - 1];
- System.arraycopy(mData, avStart + aLen + 2, value, 0, avLen - aLen - 1);
- break;
- }
- }
- }
- return value;
- }
-
- private String getValueAsString(int index) {
- byte[] value = this.getValue(index);
- return value != null ? new String(value) : null;
- }
-
- private byte[] getValue(String forKey) {
- String s = null;
- int i;
-
- for (i = 0; null != (s = this.getKey(i)); i++) {
- if (0 == forKey.compareToIgnoreCase(s)) {
- return this.getValue(i);
- }
- }
-
- return null;
- }
-
- /**
- * Return a string representation.
- * Example : {key1=value1},{key2=value2}..
- *
- * For a key say like "key3" with null value
- * {key1=value1},{key2=value2}{key3}
- */
- public String toString() {
- String a, result = null;
-
- for (int i = 0; null != (a = this.getKey(i)); i++) {
- String av = "{" + a;
- String val = this.getValueAsString(i);
- if (val != null)
- av += "=" + val + "}";
- else
- av += "}";
- if (result == null)
- result = av;
- else
- result = result + ", " + av;
- }
- return result != null ? result : "";
- }
-
- @Override
- public boolean equals(Object o) {
- if (o == this) {
- return true;
- }
- if (!(o instanceof DnsSdTxtRecord)) {
- return false;
- }
-
- DnsSdTxtRecord record = (DnsSdTxtRecord)o;
- return Arrays.equals(record.mData, mData);
- }
-
- @Override
- public int hashCode() {
- return Arrays.hashCode(mData);
- }
-
- /** Implement the Parcelable interface */
- public int describeContents() {
- return 0;
- }
-
- /** Implement the Parcelable interface */
- public void writeToParcel(Parcel dest, int flags) {
- dest.writeByteArray(mData);
- }
-
- /** Implement the Parcelable interface */
- public static final @android.annotation.NonNull Creator<DnsSdTxtRecord> CREATOR =
- new Creator<DnsSdTxtRecord>() {
- public DnsSdTxtRecord createFromParcel(Parcel in) {
- DnsSdTxtRecord info = new DnsSdTxtRecord();
- in.readByteArray(info.mData);
- return info;
- }
-
- public DnsSdTxtRecord[] newArray(int size) {
- return new DnsSdTxtRecord[size];
- }
- };
-}
diff --git a/core/java/android/os/BinderProxy.java b/core/java/android/os/BinderProxy.java
index b0c2546..ac70b52 100644
--- a/core/java/android/os/BinderProxy.java
+++ b/core/java/android/os/BinderProxy.java
@@ -631,10 +631,12 @@
}
}
- private static void sendDeathNotice(DeathRecipient recipient) {
- if (false) Log.v("JavaBinder", "sendDeathNotice to " + recipient);
+ private static void sendDeathNotice(DeathRecipient recipient, IBinder binderProxy) {
+ if (false) {
+ Log.v("JavaBinder", "sendDeathNotice to " + recipient + " for " + binderProxy);
+ }
try {
- recipient.binderDied();
+ recipient.binderDied(binderProxy);
} catch (RuntimeException exc) {
Log.w("BinderNative", "Uncaught exception from death notification",
exc);
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 3d44944..9f592e8 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -1888,10 +1888,13 @@
/**
* Note: currently only works when the requested pid has the same UID
* as the caller.
+ *
+ * @return true if the meminfo was read successfully, false if not (i.e., given pid has gone).
+ *
* @hide
*/
@UnsupportedAppUsage
- public static native void getMemoryInfo(int pid, MemoryInfo memoryInfo);
+ public static native boolean getMemoryInfo(int pid, MemoryInfo memoryInfo);
/**
* Retrieves the PSS memory used by the process as given by the
@@ -1904,6 +1907,8 @@
* array of up to 3 entries to also receive (up to 3 values in order): the Uss and SwapPss and
* Rss (only filled in as of {@link android.os.Build.VERSION_CODES#P}) of the process, and
* another array to also retrieve the separate memtrack size.
+ *
+ * @return The PSS memory usage, or 0 if failed to retrieve (i.e., given pid has gone).
* @hide
*/
public static native long getPss(int pid, long[] outUssSwapPssRss, long[] outMemtrack);
diff --git a/core/java/android/os/ExternalVibration.java b/core/java/android/os/ExternalVibration.java
index 37ca868..e62244f 100644
--- a/core/java/android/os/ExternalVibration.java
+++ b/core/java/android/os/ExternalVibration.java
@@ -84,6 +84,10 @@
return mAttrs;
}
+ public VibrationAttributes getVibrationAttributes() {
+ return new VibrationAttributes.Builder(mAttrs, null).build();
+ }
+
/**
* Mutes the external vibration if it's playing and unmuted.
*
@@ -157,7 +161,6 @@
out.writeInt(mUid);
out.writeString(mPkg);
writeAudioAttributes(mAttrs, out, flags);
- out.writeParcelable(mAttrs, flags);
out.writeStrongBinder(mController.asBinder());
out.writeStrongBinder(mToken);
}
diff --git a/core/java/android/os/IBinder.java b/core/java/android/os/IBinder.java
index f336fda..f5fe9c3 100644
--- a/core/java/android/os/IBinder.java
+++ b/core/java/android/os/IBinder.java
@@ -285,6 +285,13 @@
*/
public interface DeathRecipient {
public void binderDied();
+
+ /**
+ * @hide
+ */
+ default void binderDied(IBinder who) {
+ binderDied();
+ }
}
/**
diff --git a/core/java/android/os/IIncidentDumpCallback.aidl b/core/java/android/os/IIncidentDumpCallback.aidl
new file mode 100644
index 0000000..09b5b01
--- /dev/null
+++ b/core/java/android/os/IIncidentDumpCallback.aidl
@@ -0,0 +1,31 @@
+/**
+ * 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.os;
+
+import android.os.ParcelFileDescriptor;
+
+/**
+ * Callback from IIncidentManager to dump an extended section.
+ *
+ * @hide
+ */
+oneway interface IIncidentDumpCallback {
+ /**
+ * Dumps section data to the given ParcelFileDescriptor.
+ */
+ void onDumpSection(in ParcelFileDescriptor fd);
+}
diff --git a/core/java/android/os/IIncidentManager.aidl b/core/java/android/os/IIncidentManager.aidl
index 7e1b1e0..923234e 100644
--- a/core/java/android/os/IIncidentManager.aidl
+++ b/core/java/android/os/IIncidentManager.aidl
@@ -17,6 +17,7 @@
package android.os;
import android.os.IIncidentReportStatusListener;
+import android.os.IIncidentDumpCallback;
import android.os.IncidentManager;
import android.os.IncidentReportArgs;
@@ -52,6 +53,19 @@
@nullable IIncidentReportStatusListener listener);
/**
+ * Register a section callback with the given id and name. The callback function
+ * will be invoked when an incident report with all sections or sections matching
+ * the given id is being taken.
+ */
+ oneway void registerSection(int id, String name, IIncidentDumpCallback callback);
+
+ /**
+ * Unregister a section callback associated with the given id. The section must be
+ * previously registered with registerSection(int, String, IIncidentDumpCallback).
+ */
+ oneway void unregisterSection(int id);
+
+ /**
* Tell the incident daemon that the android system server is up and running.
*/
oneway void systemRunning();
diff --git a/core/java/android/os/IRecoverySystem.aidl b/core/java/android/os/IRecoverySystem.aidl
index c5ceecd..2561e1e 100644
--- a/core/java/android/os/IRecoverySystem.aidl
+++ b/core/java/android/os/IRecoverySystem.aidl
@@ -17,6 +17,7 @@
package android.os;
+import android.content.IntentSender;
import android.os.IRecoverySystemProgressListener;
/** @hide */
@@ -26,4 +27,7 @@
boolean setupBcb(in String command);
boolean clearBcb();
void rebootRecoveryWithCommand(in String command);
+ boolean requestLskf(in String updateToken, in IntentSender sender);
+ boolean clearLskf();
+ boolean rebootWithLskf(in String updateToken, in String reason);
}
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index e81a505..edaaf81 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -62,7 +62,7 @@
boolean canAddMoreManagedProfiles(int userId, boolean allowedToRemoveOne);
UserInfo getProfileParent(int userId);
boolean isSameProfileGroup(int userId, int otherUserHandle);
- String getUserTypeForUser(int userId);
+ boolean isUserOfType(int userId, in String userType);
@UnsupportedAppUsage
UserInfo getUserInfo(int userId);
String getUserAccount(int userId);
diff --git a/core/java/android/os/IVibratorService.aidl b/core/java/android/os/IVibratorService.aidl
index 6b881fe..416d692 100644
--- a/core/java/android/os/IVibratorService.aidl
+++ b/core/java/android/os/IVibratorService.aidl
@@ -16,17 +16,18 @@
package android.os;
-import android.media.AudioAttributes;
import android.os.VibrationEffect;
+import android.os.VibrationAttributes;
/** {@hide} */
interface IVibratorService
{
boolean hasVibrator();
boolean hasAmplitudeControl();
- boolean setAlwaysOnEffect(int id, in VibrationEffect effect, in AudioAttributes attributes);
- void vibrate(int uid, String opPkg, in VibrationEffect effect, in AudioAttributes attributes,
- String reason, IBinder token);
+ boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, in VibrationEffect effect,
+ in VibrationAttributes attributes);
+ void vibrate(int uid, String opPkg, in VibrationEffect effect,
+ in VibrationAttributes attributes, String reason, IBinder token);
void cancelVibrate(IBinder token);
}
diff --git a/core/java/android/os/IncidentManager.java b/core/java/android/os/IncidentManager.java
index 09e1c0f..f6563eb 100644
--- a/core/java/android/os/IncidentManager.java
+++ b/core/java/android/os/IncidentManager.java
@@ -31,6 +31,7 @@
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -421,6 +422,39 @@
}
/**
+ * Callback for dumping an extended (usually vendor-supplied) incident report section
+ *
+ * @see #registerSection
+ * @see #unregisterSection
+ *
+ * @hide
+ */
+ public static class DumpCallback {
+ private Executor mExecutor;
+
+ IIncidentDumpCallback.Stub mBinder = new IIncidentDumpCallback.Stub() {
+ @Override
+ public void onDumpSection(ParcelFileDescriptor pfd) {
+ if (mExecutor != null) {
+ mExecutor.execute(() -> {
+ DumpCallback.this.onDumpSection(
+ new ParcelFileDescriptor.AutoCloseOutputStream(pfd));
+ });
+ } else {
+ DumpCallback.this.onDumpSection(
+ new ParcelFileDescriptor.AutoCloseOutputStream(pfd));
+ }
+ }
+ };
+
+ /**
+ * Called when incidentd requests to dump this section.
+ */
+ public void onDumpSection(OutputStream out) {
+ }
+ }
+
+ /**
* @hide
*/
public IncidentManager(Context context) {
@@ -528,6 +562,61 @@
}
/**
+ * Register a callback to dump an extended incident report section with the given id and name.
+ * The callback function will be invoked when an incident report with all sections or sections
+ * matching the given id is being taken.
+ *
+ * @hide
+ */
+ public void registerSection(int id, String name, @NonNull DumpCallback callback) {
+ registerSection(id, name, mContext.getMainExecutor(), callback);
+ }
+
+ /**
+ * Register a callback to dump an extended incident report section with the given id and name,
+ * running on the supplied executor.
+ *
+ * @hide
+ */
+ public void registerSection(int id, String name, @NonNull @CallbackExecutor Executor executor,
+ @NonNull DumpCallback callback) {
+ try {
+ if (callback.mExecutor != null) {
+ throw new RuntimeException("Do not reuse DumpCallback objects when calling"
+ + " registerSection");
+ }
+ callback.mExecutor = executor;
+ final IIncidentManager service = getIIncidentManagerLocked();
+ if (service == null) {
+ Slog.e(TAG, "registerSection can't find incident binder service");
+ return;
+ }
+ service.registerSection(id, name, callback.mBinder);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "registerSection failed", ex);
+ }
+ }
+
+ /**
+ * Unregister an extended section dump function. The section must be previously registered with
+ * {@link #registerSection(int, String, DumpCallback)}
+ *
+ * @hide
+ */
+ public void unregisterSection(int id) {
+ try {
+ final IIncidentManager service = getIIncidentManagerLocked();
+ if (service == null) {
+ Slog.e(TAG, "unregisterSection can't find incident binder service");
+ return;
+ }
+ service.unregisterSection(id);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "unregisterSection failed", ex);
+ }
+ }
+
+ /**
* Get the incident reports that are available for upload for the supplied
* broadcast recevier.
*
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index c9ebed3..1a4dac7 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -1886,6 +1886,43 @@
public final void writeException(@NonNull Exception e) {
AppOpsManager.prefixParcelWithAppOpsIfNeeded(this);
+ int code = getExceptionCode(e);
+ writeInt(code);
+ StrictMode.clearGatheredViolations();
+ if (code == 0) {
+ if (e instanceof RuntimeException) {
+ throw (RuntimeException) e;
+ }
+ throw new RuntimeException(e);
+ }
+ writeString(e.getMessage());
+ final long timeNow = sParcelExceptionStackTrace ? SystemClock.elapsedRealtime() : 0;
+ if (sParcelExceptionStackTrace && (timeNow - sLastWriteExceptionStackTrace
+ > WRITE_EXCEPTION_STACK_TRACE_THRESHOLD_MS)) {
+ sLastWriteExceptionStackTrace = timeNow;
+ writeStackTrace(e);
+ } else {
+ writeInt(0);
+ }
+ switch (code) {
+ case EX_SERVICE_SPECIFIC:
+ writeInt(((ServiceSpecificException) e).errorCode);
+ break;
+ case EX_PARCELABLE:
+ // Write parceled exception prefixed by length
+ final int sizePosition = dataPosition();
+ writeInt(0);
+ writeParcelable((Parcelable) e, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
+ final int payloadPosition = dataPosition();
+ setDataPosition(sizePosition);
+ writeInt(payloadPosition - sizePosition);
+ setDataPosition(payloadPosition);
+ break;
+ }
+ }
+
+ /** @hide */
+ public static int getExceptionCode(@NonNull Throwable e) {
int code = 0;
if (e instanceof Parcelable
&& (e.getClass().getClassLoader() == Parcelable.class.getClassLoader())) {
@@ -1909,51 +1946,25 @@
} else if (e instanceof ServiceSpecificException) {
code = EX_SERVICE_SPECIFIC;
}
- writeInt(code);
- StrictMode.clearGatheredViolations();
- if (code == 0) {
- if (e instanceof RuntimeException) {
- throw (RuntimeException) e;
- }
- throw new RuntimeException(e);
+ return code;
+ }
+
+ /** @hide */
+ public void writeStackTrace(@NonNull Throwable e) {
+ final int sizePosition = dataPosition();
+ writeInt(0); // Header size will be filled in later
+ StackTraceElement[] stackTrace = e.getStackTrace();
+ final int truncatedSize = Math.min(stackTrace.length, 5);
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < truncatedSize; i++) {
+ sb.append("\tat ").append(stackTrace[i]).append('\n');
}
- writeString(e.getMessage());
- final long timeNow = sParcelExceptionStackTrace ? SystemClock.elapsedRealtime() : 0;
- if (sParcelExceptionStackTrace && (timeNow - sLastWriteExceptionStackTrace
- > WRITE_EXCEPTION_STACK_TRACE_THRESHOLD_MS)) {
- sLastWriteExceptionStackTrace = timeNow;
- final int sizePosition = dataPosition();
- writeInt(0); // Header size will be filled in later
- StackTraceElement[] stackTrace = e.getStackTrace();
- final int truncatedSize = Math.min(stackTrace.length, 5);
- StringBuilder sb = new StringBuilder();
- for (int i = 0; i < truncatedSize; i++) {
- sb.append("\tat ").append(stackTrace[i]).append('\n');
- }
- writeString(sb.toString());
- final int payloadPosition = dataPosition();
- setDataPosition(sizePosition);
- // Write stack trace header size. Used in native side to skip the header
- writeInt(payloadPosition - sizePosition);
- setDataPosition(payloadPosition);
- } else {
- writeInt(0);
- }
- switch (code) {
- case EX_SERVICE_SPECIFIC:
- writeInt(((ServiceSpecificException) e).errorCode);
- break;
- case EX_PARCELABLE:
- // Write parceled exception prefixed by length
- final int sizePosition = dataPosition();
- writeInt(0);
- writeParcelable((Parcelable) e, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
- final int payloadPosition = dataPosition();
- setDataPosition(sizePosition);
- writeInt(payloadPosition - sizePosition);
- setDataPosition(payloadPosition);
- break;
- }
+ writeString(sb.toString());
+ final int payloadPosition = dataPosition();
+ setDataPosition(sizePosition);
+ // Write stack trace header size. Used in native side to skip the header
+ writeInt(payloadPosition - sizePosition);
+ setDataPosition(payloadPosition);
}
/**
@@ -2069,14 +2080,7 @@
if (remoteStackTrace != null) {
RemoteException cause = new RemoteException(
"Remote stack trace:\n" + remoteStackTrace, null, false, false);
- try {
- Throwable rootCause = ExceptionUtils.getRootCause(e);
- if (rootCause != null) {
- rootCause.initCause(cause);
- }
- } catch (RuntimeException ex) {
- Log.e(TAG, "Cannot set cause " + cause + " for " + e, ex);
- }
+ ExceptionUtils.appendCause(e, cause);
}
SneakyThrow.sneakyThrow(e);
}
@@ -2088,6 +2092,14 @@
* @param msg The exception message.
*/
private Exception createException(int code, String msg) {
+ Exception exception = createExceptionOrNull(code, msg);
+ return exception != null
+ ? exception
+ : new RuntimeException("Unknown exception code: " + code + " msg " + msg);
+ }
+
+ /** @hide */
+ public Exception createExceptionOrNull(int code, String msg) {
switch (code) {
case EX_PARCELABLE:
if (readInt() > 0) {
@@ -2111,9 +2123,9 @@
return new UnsupportedOperationException(msg);
case EX_SERVICE_SPECIFIC:
return new ServiceSpecificException(readInt(), msg);
+ default:
+ return null;
}
- return new RuntimeException("Unknown exception code: " + code
- + " msg " + msg);
}
/**
diff --git a/core/java/android/os/ParcelFileDescriptor.java b/core/java/android/os/ParcelFileDescriptor.java
index 983053b..89ddf8c 100644
--- a/core/java/android/os/ParcelFileDescriptor.java
+++ b/core/java/android/os/ParcelFileDescriptor.java
@@ -31,6 +31,9 @@
import static android.system.OsConstants.S_ISREG;
import static android.system.OsConstants.S_IWOTH;
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.BroadcastReceiver;
@@ -253,6 +256,9 @@
* be opened with the requested mode.
* @see #parseMode(String)
*/
+ // We can't accept a generic Executor here, since we need to use
+ // MessageQueue.addOnFileDescriptorEventListener()
+ @SuppressLint("ExecutorRegistration")
public static ParcelFileDescriptor open(File file, int mode, Handler handler,
final OnCloseListener listener) throws IOException {
if (handler == null) {
@@ -268,9 +274,22 @@
return fromFd(fd, handler, listener);
}
- /** {@hide} */
- public static ParcelFileDescriptor fromPfd(ParcelFileDescriptor pfd, Handler handler,
- final OnCloseListener listener) throws IOException {
+ /**
+ * Create a new ParcelFileDescriptor wrapping an already-opened file.
+ *
+ * @param pfd The already-opened file.
+ * @param handler to call listener from.
+ * @param listener to be invoked when the returned descriptor has been
+ * closed.
+ * @return a new ParcelFileDescriptor pointing to the given file.
+ * @hide
+ */
+ @SystemApi
+ // We can't accept a generic Executor here, since we need to use
+ // MessageQueue.addOnFileDescriptorEventListener()
+ @SuppressLint("ExecutorRegistration")
+ public static @NonNull ParcelFileDescriptor wrap(@NonNull ParcelFileDescriptor pfd,
+ @NonNull Handler handler, @NonNull OnCloseListener listener) throws IOException {
final FileDescriptor original = new FileDescriptor();
original.setInt$(pfd.detachFd());
return fromFd(original, handler, listener);
diff --git a/core/java/android/os/PersistableBundle.java b/core/java/android/os/PersistableBundle.java
index b40283f..7a837e1 100644
--- a/core/java/android/os/PersistableBundle.java
+++ b/core/java/android/os/PersistableBundle.java
@@ -16,17 +16,24 @@
package android.os;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.ArrayMap;
import android.util.proto.ProtoOutputStream;
+import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
import java.util.ArrayList;
/**
@@ -339,4 +346,44 @@
proto.end(token);
}
+
+ /**
+ * Writes the content of the {@link PersistableBundle} to a {@link OutputStream}.
+ *
+ * <p>The content can be read by a {@link #readFromStream}.
+ *
+ * @see #readFromStream
+ */
+ public void writeToStream(@NonNull OutputStream outputStream) throws IOException {
+ FastXmlSerializer serializer = new FastXmlSerializer();
+ serializer.setOutput(outputStream, UTF_8.name());
+ serializer.startTag(null, "bundle");
+ try {
+ saveToXml(serializer);
+ } catch (XmlPullParserException e) {
+ throw new IOException(e);
+ }
+ serializer.endTag(null, "bundle");
+ serializer.flush();
+ }
+
+ /**
+ * Reads a {@link PersistableBundle} from an {@link InputStream}.
+ *
+ * <p>The stream must be generated by {@link #writeToStream}.
+ *
+ * @see #writeToStream
+ */
+ @NonNull
+ public static PersistableBundle readFromStream(@NonNull InputStream inputStream)
+ throws IOException {
+ try {
+ XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
+ parser.setInput(inputStream, UTF_8.name());
+ parser.next();
+ return PersistableBundle.restoreFromXml(parser);
+ } catch (XmlPullParserException e) {
+ throw new IOException(e);
+ }
+ }
}
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 94623bc..5d80ab6 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -68,10 +68,9 @@
public static final int LOG_UID = 1007;
/**
- * Defines the UID/GID for the WIFI supplicant process.
- * @hide
+ * Defines the UID/GID for the WIFI native processes like wificond, supplicant, hostapd,
+ * vendor HAL, etc.
*/
- @UnsupportedAppUsage
public static final int WIFI_UID = 1010;
/**
@@ -89,6 +88,12 @@
public static final int DRM_UID = 1019;
/**
+ * Defines the GID for the group that allows write access to the internal media storage.
+ * @hide
+ */
+ public static final int SDCARD_RW_GID = 1015;
+
+ /**
* Defines the UID/GID for the group that controls VPN services.
* @hide
*/
@@ -752,11 +757,12 @@
/**
* Set the priority of a thread, based on Linux priorities.
- *
- * @param tid The identifier of the thread/process to change.
+ *
+ * @param tid The identifier of the thread/process to change. It should be
+ * the native thread id but not the managed id of {@link java.lang.Thread}.
* @param priority A Linux priority level, from -20 for highest scheduling
* priority to 19 for lowest scheduling priority.
- *
+ *
* @throws IllegalArgumentException Throws IllegalArgumentException if
* <var>tid</var> does not exist.
* @throws SecurityException Throws SecurityException if your process does
diff --git a/core/java/android/os/RecoverySystem.java b/core/java/android/os/RecoverySystem.java
index 1901820..cdcb3ff 100644
--- a/core/java/android/os/RecoverySystem.java
+++ b/core/java/android/os/RecoverySystem.java
@@ -18,6 +18,8 @@
import static java.nio.charset.StandardCharsets.UTF_8;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
@@ -29,6 +31,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.provider.Settings;
import android.telephony.SubscriptionInfo;
@@ -624,22 +627,91 @@
}
/**
- * Schedule to install the given package on next boot. The caller needs to
- * ensure that the package must have been processed (uncrypt'd) if needed.
- * It sets up the command in BCB (bootloader control block), which will
- * be read by the bootloader and the recovery image.
+ * Prepare to apply an unattended update by asking the user for their Lock Screen Knowledge
+ * Factor (LSKF). If supplied, the {@code intentSender} will be called when the system is setup
+ * and ready to apply the OTA.
+ * <p>
+ * When the system is already prepared for update and this API is called again with the same
+ * {@code updateToken}, it will not call the intent sender nor request the user enter their Lock
+ * Screen Knowledge Factor.
+ * <p>
+ * When this API is called again with a different {@code updateToken}, the prepared-for-update
+ * status is reset and process repeats as though it's the initial call to this method as
+ * described in the first paragraph.
*
- * @param Context the Context to use.
- * @param packageFile the package to be installed.
- *
- * @throws IOException if there were any errors setting up the BCB.
- *
+ * @param context the Context to use.
+ * @param updateToken token used to indicate which update was prepared
+ * @param intentSender the intent to call when the update is prepared; may be {@code null}
+ * @throws IOException if there were any errors setting up unattended update
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.RECOVERY)
- public static void scheduleUpdateOnBoot(Context context, File packageFile)
+ public static void prepareForUnattendedUpdate(@NonNull Context context,
+ @NonNull String updateToken, @Nullable IntentSender intentSender) throws IOException {
+ if (updateToken == null) {
+ throw new NullPointerException("updateToken == null");
+ }
+ RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
+ if (!rs.requestLskf(updateToken, intentSender)) {
+ throw new IOException("preparation for update failed");
+ }
+ }
+
+ /**
+ * Request that any previously requested Lock Screen Knowledge Factor (LSKF) is cleared and
+ * the preparation for unattended update is reset.
+ *
+ * @param context the Context to use.
+ * @throws IOException if there were any errors setting up unattended update
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.RECOVERY)
+ public static boolean clearPrepareForUnattendedUpdate(@NonNull Context context)
throws IOException {
+ RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
+ return rs.clearLskf();
+ }
+
+ /**
+ * Request that the device reboot and apply the update that has been prepared. The
+ * {@code updateToken} must match what was given for {@link #prepareForUnattendedUpdate} or
+ * this will return {@code false}.
+ *
+ * @param context the Context to use.
+ * @param updateToken the token used to call {@link #prepareForUnattendedUpdate} before
+ * @param reason the reboot reason to give to the {@link PowerManager}
+ * @throws IOException if there were any errors setting up unattended update
+ * @return false if the reboot couldn't proceed because the device wasn't ready for an
+ * unattended reboot or if the {@code updateToken} did not match the previously
+ * given token
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.RECOVERY)
+ public static boolean rebootAndApply(@NonNull Context context, @NonNull String updateToken,
+ @NonNull String reason) throws IOException {
+ if (updateToken == null) {
+ throw new NullPointerException("updateToken == null");
+ }
+ RecoverySystem rs = (RecoverySystem) context.getSystemService(Context.RECOVERY_SERVICE);
+ return rs.rebootWithLskf(updateToken, reason);
+ }
+
+ /**
+ * Schedule to install the given package on next boot. The caller needs to ensure that the
+ * package must have been processed (uncrypt'd) if needed. It sets up the command in BCB
+ * (bootloader control block), which will be read by the bootloader and the recovery image.
+ *
+ * @param context the Context to use.
+ * @param packageFile the package to be installed.
+ * @throws IOException if there were any errors setting up the BCB.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.RECOVERY)
+ public static void scheduleUpdateOnBoot(Context context, File packageFile) throws IOException {
String filename = packageFile.getCanonicalPath();
boolean securityUpdate = filename.endsWith("_s.zip");
@@ -1204,6 +1276,49 @@
}
/**
+ * Begins the process of asking the user for the Lock Screen Knowledge Factor.
+ *
+ * @param updateToken token that will be used in calls to {@link #rebootAndApply} to ensure
+ * that the preparation was for the correct update
+ * @return true if the request was correct
+ * @throws IOException if the recovery system service could not be contacted
+ */
+ private boolean requestLskf(String updateToken, IntentSender sender) throws IOException {
+ try {
+ return mService.requestLskf(updateToken, sender);
+ } catch (RemoteException e) {
+ throw new IOException("could request update");
+ }
+ }
+
+ /**
+ * Calls the recovery system service and clears the setup for the OTA.
+ *
+ * @return true if the setup for OTA was cleared
+ * @throws IOException if the recovery system service could not be contacted
+ */
+ private boolean clearLskf() throws IOException {
+ try {
+ return mService.clearLskf();
+ } catch (RemoteException e) {
+ throw new IOException("could not clear LSKF");
+ }
+ }
+
+ /**
+ * Calls the recovery system service to reboot and apply update.
+ *
+ * @param updateToken the update token for which the update was prepared
+ */
+ private boolean rebootWithLskf(String updateToken, String reason) throws IOException {
+ try {
+ return mService.rebootWithLskf(updateToken, reason);
+ } catch (RemoteException e) {
+ throw new IOException("could not reboot for update");
+ }
+ }
+
+ /**
* Internally, recovery treats each line of the command file as a separate
* argv, so we only need to protect against newlines and nulls.
*/
diff --git a/core/java/android/os/SystemVibrator.java b/core/java/android/os/SystemVibrator.java
index f585c75..8050454 100644
--- a/core/java/android/os/SystemVibrator.java
+++ b/core/java/android/os/SystemVibrator.java
@@ -70,13 +70,15 @@
}
@Override
- public boolean setAlwaysOnEffect(int id, VibrationEffect effect, AudioAttributes attributes) {
+ public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, VibrationEffect effect,
+ AudioAttributes attributes) {
if (mService == null) {
Log.w(TAG, "Failed to set always-on effect; no vibrator service.");
return false;
}
try {
- return mService.setAlwaysOnEffect(id, effect, attributes);
+ VibrationAttributes atr = new VibrationAttributes.Builder(attributes, effect).build();
+ return mService.setAlwaysOnEffect(uid, opPkg, alwaysOnId, effect, atr);
} catch (RemoteException e) {
Log.w(TAG, "Failed to set always-on effect.", e);
}
@@ -91,7 +93,11 @@
return;
}
try {
- mService.vibrate(uid, opPkg, effect, attributes, reason, mToken);
+ if (attributes == null) {
+ attributes = new AudioAttributes.Builder().build();
+ }
+ VibrationAttributes atr = new VibrationAttributes.Builder(attributes, effect).build();
+ mService.vibrate(uid, opPkg, effect, atr, reason, mToken);
} catch (RemoteException e) {
Log.w(TAG, "Failed to vibrate.", e);
}
diff --git a/core/java/android/os/TelephonyServiceManager.java b/core/java/android/os/TelephonyServiceManager.java
index 1211dd6..064cf7d 100644
--- a/core/java/android/os/TelephonyServiceManager.java
+++ b/core/java/android/os/TelephonyServiceManager.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.content.Context;
/**
* Provides a way to register and obtain the system service binder objects managed by the telephony
@@ -51,8 +52,8 @@
/**
* Register a system server binding object for a service.
*/
- public void register(@NonNull IBinder binder) {
- ServiceManager.addService(mServiceName, binder);
+ public void register(@NonNull IBinder service) {
+ ServiceManager.addService(mServiceName, service);
}
/**
@@ -114,25 +115,123 @@
*/
@NonNull
public ServiceRegisterer getTelephonyServiceRegisterer() {
- return new ServiceRegisterer("phone");
+ return new ServiceRegisterer(Context.TELEPHONY_SERVICE);
}
+ /**
+ * Returns {@link ServiceRegisterer} for the telephony registry service.
+ */
+ @NonNull
+ public ServiceRegisterer getTelephonyRegistryServiceRegisterer() {
+ return new ServiceRegisterer("telephony.registry");
+ }
-// TODO: Add more services...
-//
-// /**
-// * Returns {@link ServiceRegisterer} for the "subscription" service.
-// */
-// @NonNull
-// public ServiceRegisterer getSubscriptionServiceRegisterer() {
-// return new ServiceRegisterer("isub");
-// }
-//
-// /**
-// * Returns {@link ServiceRegisterer} for the "SMS" service.
-// */
-// @NonNull
-// public ServiceRegisterer getSmsServiceRegisterer() {
-// return new ServiceRegisterer("isms");
-// }
+ /**
+ * Returns {@link ServiceRegisterer} for the telephony IMS service.
+ */
+ @NonNull
+ public ServiceRegisterer getTelephonyImsServiceRegisterer() {
+ return new ServiceRegisterer(Context.TELEPHONY_IMS_SERVICE);
+ }
+
+ /**
+ * Returns {@link ServiceRegisterer} for the telephony RCS message service.
+ */
+ @NonNull
+ public ServiceRegisterer getTelephonyRcsMessageServiceRegisterer() {
+ return new ServiceRegisterer(Context.TELEPHONY_RCS_MESSAGE_SERVICE);
+ }
+
+ /**
+ * Returns {@link ServiceRegisterer} for the subscription service.
+ */
+ @NonNull
+ public ServiceRegisterer getSubscriptionServiceRegisterer() {
+ return new ServiceRegisterer("isub");
+ }
+
+ /**
+ * Returns {@link ServiceRegisterer} for the network policy service.
+ */
+ @NonNull
+ public ServiceRegisterer getNetworkPolicyServiceRegisterer() {
+ return new ServiceRegisterer(Context.NETWORK_POLICY_SERVICE);
+ }
+
+ /**
+ * Returns {@link ServiceRegisterer} for the phone sub service.
+ */
+ @NonNull
+ public ServiceRegisterer getPhoneSubServiceRegisterer() {
+ return new ServiceRegisterer("iphonesubinfo");
+ }
+
+ /**
+ * Returns {@link ServiceRegisterer} for the opportunistic network service.
+ */
+ @NonNull
+ public ServiceRegisterer getOpportunisticNetworkServiceRegisterer() {
+ return new ServiceRegisterer("ions");
+ }
+
+ /**
+ * Returns {@link ServiceRegisterer} for the carrier config service.
+ */
+ @NonNull
+ public ServiceRegisterer getCarrierConfigServiceRegisterer() {
+ return new ServiceRegisterer(Context.CARRIER_CONFIG_SERVICE);
+ }
+
+ /**
+ * Returns {@link ServiceRegisterer} for the "SMS" service.
+ */
+ @NonNull
+ public ServiceRegisterer getSmsServiceRegisterer() {
+ return new ServiceRegisterer("isms");
+ }
+
+ /**
+ * Returns {@link ServiceRegisterer} for the eUICC controller service.
+ */
+ @NonNull
+ public ServiceRegisterer getEuiccControllerService() {
+ return new ServiceRegisterer("econtroller");
+ }
+
+ @NonNull
+ public ServiceRegisterer getEuiccCardControllerServiceRegisterer() {
+ return new ServiceRegisterer("euicc_card_controller");
+ }
+
+ /**
+ * Returns {@link ServiceRegisterer} for the package manager service.
+ */
+ @NonNull
+ public ServiceRegisterer getPackageManagerServiceRegisterer() {
+ return new ServiceRegisterer("package");
+ }
+
+ /**
+ * Returns {@link ServiceRegisterer} for the permission manager service.
+ */
+ @NonNull
+ public ServiceRegisterer getPermissionManagerServiceRegisterer() {
+ return new ServiceRegisterer("permissionmgr");
+ }
+
+ /**
+ * Returns {@link ServiceRegisterer} for the ICC phone book service.
+ */
+ @NonNull
+ public ServiceRegisterer getIccPhoneBookServiceRegisterer() {
+ return new ServiceRegisterer("simphonebook");
+ }
+
+ /**
+ * Returns {@link ServiceRegisterer} for the window service.
+ */
+ @NonNull
+ public ServiceRegisterer getWindowServiceRegisterer() {
+ return new ServiceRegisterer(Context.WINDOW_SERVICE);
+ }
}
diff --git a/core/java/android/util/TimestampedValue.java b/core/java/android/os/TimestampedValue.java
similarity index 88%
rename from core/java/android/util/TimestampedValue.java
rename to core/java/android/os/TimestampedValue.java
index 4505673..f4c87ac 100644
--- a/core/java/android/util/TimestampedValue.java
+++ b/core/java/android/os/TimestampedValue.java
@@ -14,13 +14,11 @@
* limitations under the License.
*/
-package android.util;
+package android.os;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.os.SystemClock;
+import android.annotation.SystemApi;
import java.util.Objects;
@@ -38,19 +36,27 @@
* @param <T> the type of the value with an associated timestamp
* @hide
*/
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public final class TimestampedValue<T> implements Parcelable {
private final long mReferenceTimeMillis;
+ @Nullable
private final T mValue;
- public TimestampedValue(long referenceTimeMillis, T value) {
+ public TimestampedValue(long referenceTimeMillis, @Nullable T value) {
mReferenceTimeMillis = referenceTimeMillis;
mValue = value;
}
+ /** Returns the reference time value. See {@link TimestampedValue} for more information. */
public long getReferenceTimeMillis() {
return mReferenceTimeMillis;
}
+ /**
+ * Returns the value associated with the timestamp. See {@link TimestampedValue} for more
+ * information.
+ */
+ @Nullable
public T getValue() {
return mValue;
}
@@ -89,6 +95,8 @@
return one.mReferenceTimeMillis - two.mReferenceTimeMillis;
}
+ /** @hide */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
public static final @NonNull Parcelable.Creator<TimestampedValue<?>> CREATOR =
new Parcelable.ClassLoaderCreator<TimestampedValue<?>>() {
diff --git a/core/java/android/os/UpdateEngine.java b/core/java/android/os/UpdateEngine.java
index a9ddffe..73e1adf 100644
--- a/core/java/android/os/UpdateEngine.java
+++ b/core/java/android/os/UpdateEngine.java
@@ -16,8 +16,10 @@
package android.os;
+import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.SystemApi;
+import android.content.res.AssetFileDescriptor;
import android.os.IUpdateEngine;
import android.os.IUpdateEngineCallback;
import android.os.RemoteException;
@@ -140,8 +142,43 @@
* {@code SWITCH_SLOT_ON_REBOOT=0}. See {@link #applyPayload}.
*/
public static final int UPDATED_BUT_NOT_ACTIVE = 52;
+
+ /**
+ * Error code: there is not enough space on the device to apply the update. User should
+ * be prompted to free up space and re-try the update.
+ *
+ * <p>See {@link UpdateEngine#allocateSpace}.
+ */
+ public static final int NOT_ENOUGH_SPACE = 60;
+
+ /**
+ * Error code: the device is corrupted and no further updates may be applied.
+ *
+ * <p>See {@link UpdateEngine#cleanupAppliedPayload}.
+ */
+ public static final int DEVICE_CORRUPTED = 61;
}
+ /** @hide */
+ @IntDef(value = {
+ ErrorCodeConstants.SUCCESS,
+ ErrorCodeConstants.ERROR,
+ ErrorCodeConstants.FILESYSTEM_COPIER_ERROR,
+ ErrorCodeConstants.POST_INSTALL_RUNNER_ERROR,
+ ErrorCodeConstants.PAYLOAD_MISMATCHED_TYPE_ERROR,
+ ErrorCodeConstants.INSTALL_DEVICE_OPEN_ERROR,
+ ErrorCodeConstants.KERNEL_DEVICE_OPEN_ERROR,
+ ErrorCodeConstants.DOWNLOAD_TRANSFER_ERROR,
+ ErrorCodeConstants.PAYLOAD_HASH_MISMATCH_ERROR,
+ ErrorCodeConstants.PAYLOAD_SIZE_MISMATCH_ERROR,
+ ErrorCodeConstants.DOWNLOAD_PAYLOAD_VERIFICATION_ERROR,
+ ErrorCodeConstants.PAYLOAD_TIMESTAMP_ERROR,
+ ErrorCodeConstants.UPDATED_BUT_NOT_ACTIVE,
+ ErrorCodeConstants.NOT_ENOUGH_SPACE,
+ ErrorCodeConstants.DEVICE_CORRUPTED,
+ })
+ public @interface ErrorCode {}
+
/**
* Status codes for update engine. Values must agree with the ones in
* {@code system/update_engine/client_library/include/update_engine/update_status.h}.
@@ -313,16 +350,17 @@
}
/**
- * Applies the payload passed as ParcelFileDescriptor {@code pfd} instead of
- * using the {@code file://} scheme.
+ * Applies the payload passed as AssetFileDescriptor {@code assetFd}
+ * instead of using the {@code file://} scheme.
*
* <p>See {@link #applyPayload(String)} for {@code offset}, {@code size} and
* {@code headerKeyValuePairs} parameters.
*/
- public void applyPayload(@NonNull ParcelFileDescriptor pfd, long offset, long size,
+ public void applyPayload(@NonNull AssetFileDescriptor assetFd,
@NonNull String[] headerKeyValuePairs) {
try {
- mUpdateEngine.applyPayloadFd(pfd, offset, size, headerKeyValuePairs);
+ mUpdateEngine.applyPayloadFd(assetFd.getParcelFileDescriptor(),
+ assetFd.getStartOffset(), assetFd.getLength(), headerKeyValuePairs);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -419,4 +457,138 @@
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Return value of {@link #allocateSpace.}
+ */
+ public static final class AllocateSpaceResult {
+ private @ErrorCode int mErrorCode = ErrorCodeConstants.SUCCESS;
+ private long mFreeSpaceRequired = 0;
+ private AllocateSpaceResult() {}
+ /**
+ * Error code.
+ *
+ * @return The following error codes:
+ * <ul>
+ * <li>{@link ErrorCodeConstants#SUCCESS} if space has been allocated
+ * successfully.</li>
+ * <li>{@link ErrorCodeConstants#NOT_ENOUGH_SPACE} if insufficient
+ * space.</li>
+ * <li>Other {@link ErrorCodeConstants} for other errors.</li>
+ * </ul>
+ */
+ @ErrorCode
+ public int errorCode() {
+ return mErrorCode;
+ }
+
+ /**
+ * Estimated total space that needs to be available on the userdata partition to apply the
+ * payload (in bytes).
+ *
+ * <p>
+ * Note that in practice, more space needs to be made available before applying the payload
+ * to keep the device working.
+ *
+ * @return The following values:
+ * <ul>
+ * <li>zero if {@link #errorCode} returns {@link ErrorCodeConstants#SUCCESS}</li>
+ * <li>non-zero if {@link #errorCode} returns {@link ErrorCodeConstants#NOT_ENOUGH_SPACE}.
+ * Value is the estimated total space required on userdata partition.</li>
+ * </ul>
+ * @throws IllegalStateException if {@link #errorCode} is not one of the above.
+ *
+ */
+ public long freeSpaceRequired() {
+ if (mErrorCode == ErrorCodeConstants.SUCCESS) {
+ return 0;
+ }
+ if (mErrorCode == ErrorCodeConstants.NOT_ENOUGH_SPACE) {
+ return mFreeSpaceRequired;
+ }
+ throw new IllegalStateException(String.format(
+ "freeSpaceRequired() is not available when error code is %d", mErrorCode));
+ }
+ }
+
+ /**
+ * Initialize partitions for a payload associated with the given payload
+ * metadata {@code payloadMetadataFilename} by preallocating required space.
+ *
+ * <p>This function should be called after payload has been verified after
+ * {@link #verifyPayloadMetadata}. This function does not verify whether
+ * the given payload is applicable or not.
+ *
+ * <p>Implementation of {@code allocateSpace} uses
+ * {@code headerKeyValuePairs} to determine whether space has been allocated
+ * for a different or same payload previously. If space has been allocated
+ * for a different payload before, space will be reallocated for the given
+ * payload. If space has been allocated for the same payload, no actions to
+ * storage devices are taken.
+ *
+ * <p>This function is synchronous and may take a non-trivial amount of
+ * time. Callers should call this function in a background thread.
+ *
+ * @param payloadMetadataFilename See {@link #verifyPayloadMetadata}.
+ * @param headerKeyValuePairs See {@link #applyPayload}.
+ * @return See {@link AllocateSpaceResult}.
+ */
+ @NonNull
+ public AllocateSpaceResult allocateSpace(
+ @NonNull String payloadMetadataFilename,
+ @NonNull String[] headerKeyValuePairs) {
+ AllocateSpaceResult result = new AllocateSpaceResult();
+ try {
+ result.mFreeSpaceRequired = mUpdateEngine.allocateSpaceForPayload(
+ payloadMetadataFilename,
+ headerKeyValuePairs);
+ result.mErrorCode = result.mFreeSpaceRequired == 0
+ ? ErrorCodeConstants.SUCCESS
+ : ErrorCodeConstants.NOT_ENOUGH_SPACE;
+ return result;
+ } catch (ServiceSpecificException e) {
+ result.mErrorCode = e.errorCode;
+ result.mFreeSpaceRequired = 0;
+ return result;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Cleanup files used by the previous update and free up space after the
+ * device has been booted successfully into the new build.
+ *
+ * <p>In particular, this function waits until delta files for snapshots for
+ * Virtual A/B update are merged to OS partitions, then delete these delta
+ * files.
+ *
+ * <p>This function is synchronous and may take a non-trivial amount of
+ * time. Callers should call this function in a background thread.
+ *
+ * <p>This function does not delete payload binaries downloaded for a
+ * non-streaming OTA update.
+ *
+ * @return One of the following:
+ * <ul>
+ * <li>{@link ErrorCodeConstants#SUCCESS} if execution is successful.</li>
+ * <li>{@link ErrorCodeConstants#ERROR} if a transient error has occurred.
+ * The device should be able to recover after a reboot. The function should
+ * be retried after the reboot.</li>
+ * <li>{@link ErrorCodeConstants#DEVICE_CORRUPTED} if a permanent error is
+ * encountered. Device is corrupted, and future updates must not be applied.
+ * The device cannot recover without flashing and factory resets.
+ * </ul>
+ *
+ * @throws ServiceSpecificException if other transient errors has occurred.
+ * A reboot may or may not help resolving the issue.
+ */
+ @ErrorCode
+ public int cleanupAppliedPayload() {
+ try {
+ return mUpdateEngine.cleanupSuccessfulUpdate();
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/android/os/UpdateEngineCallback.java b/core/java/android/os/UpdateEngineCallback.java
index f07294e..7fe7024 100644
--- a/core/java/android/os/UpdateEngineCallback.java
+++ b/core/java/android/os/UpdateEngineCallback.java
@@ -44,5 +44,6 @@
* unsuccessfully. The value of {@code errorCode} will be one of the
* values from {@link UpdateEngine.ErrorCodeConstants}.
*/
- public abstract void onPayloadApplicationComplete(int errorCode);
+ public abstract void onPayloadApplicationComplete(
+ @UpdateEngine.ErrorCode int errorCode);
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index fa9569b..2eaefca 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -192,7 +192,11 @@
/**
* Specifies if a user is disallowed from changing Wi-Fi
* access points. The default value is <code>false</code>.
- * <p>This restriction has no effect in a managed profile.
+ * <p>
+ * Device owner and profile owner can set this restriction, although the restriction has no
+ * effect in a managed profile. When it is set by the profile owner of an organization-owned
+ * managed profile on the parent profile, it will disallow the personal user from changing
+ * Wi-Fi access points.
*
* <p>Key for user restrictions.
* <p>Type: Boolean
@@ -242,8 +246,13 @@
/**
* Specifies if a user is disallowed from turning on location sharing.
* The default value is <code>false</code>.
- * <p>In a managed profile, location sharing always reflects the primary user's setting, but
+ * <p>
+ * In a managed profile, location sharing always reflects the primary user's setting, but
* can be overridden and forced off by setting this restriction to true in the managed profile.
+ * <p>
+ * Device owner and profile owner can set this restriction. When it is set by the profile
+ * owner of an organization-owned managed profile on the parent profile, it will prevent the
+ * user from turning on location sharing in the personal profile.
*
* <p>Key for user restrictions.
* <p>Type: Boolean
@@ -349,9 +358,14 @@
* Specifies if a user is disallowed from configuring bluetooth.
* This does <em>not</em> restrict the user from turning bluetooth on or off.
* The default value is <code>false</code>.
- * <p>This restriction doesn't prevent the user from using bluetooth. For disallowing usage of
+ * <p>
+ * This restriction doesn't prevent the user from using bluetooth. For disallowing usage of
* bluetooth completely on the device, use {@link #DISALLOW_BLUETOOTH}.
- * <p>This restriction has no effect in a managed profile.
+ * <p>
+ * Device owner and profile owner can set this restriction, although the restriction has no
+ * effect in a managed profile. When it is set by the profile owner of an organization-owned
+ * managed profile on the parent profile, it will disallow the personal user from configuring
+ * bluetooth.
*
* <p>Key for user restrictions.
* <p>Type: Boolean
@@ -364,8 +378,10 @@
/**
* Specifies if bluetooth is disallowed on the device.
*
- * <p> This restriction can only be set by the device owner and the profile owner on the
- * primary user and it applies globally - i.e. it disables bluetooth on the entire device.
+ * <p> This restriction can only be set by the device owner, the profile owner on the
+ * primary user or the profile owner of an organization-owned managed profile on the
+ * parent profile and it applies globally - i.e. it disables bluetooth on the entire
+ * device.
* <p>The default value is <code>false</code>.
* <p>Key for user restrictions.
* <p>Type: Boolean
@@ -377,8 +393,9 @@
/**
* Specifies if outgoing bluetooth sharing is disallowed on the device. Device owner and profile
- * owner can set this restriction. When it is set by device owner, all users on this device will
- * be affected.
+ * owner can set this restriction. When it is set by device owner or the profile owner of an
+ * organization-owned managed profile on the parent profile, all users on this device will be
+ * affected.
*
* <p>Default is <code>true</code> for managed profiles and false for otherwise. When a device
* upgrades to {@link android.os.Build.VERSION_CODES#O}, the system sets it for all existing
@@ -394,7 +411,8 @@
/**
* Specifies if a user is disallowed from transferring files over
- * USB. This can only be set by device owners and profile owners on the primary user.
+ * USB. This can only be set by device owners, profile owners on the primary user or
+ * profile owners of organization-owned managed profiles on the parent profile.
* The default value is <code>false</code>.
*
* <p>Key for user restrictions.
@@ -453,8 +471,9 @@
/**
* Specifies if a user is disallowed from enabling or accessing debugging features. When set on
- * the primary user, disables debugging features altogether, including USB debugging. When set
- * on a managed profile or a secondary user, blocks debugging for that user only, including
+ * the primary user or by the profile owner of an organization-owned managed profile on the
+ * parent profile, disables debugging features altogether, including USB debugging. When set on
+ * a managed profile or a secondary user, blocks debugging for that user only, including
* starting activities, making service calls, accessing content providers, sending broadcasts,
* installing/uninstalling packages, clearing user data, etc.
* The default value is <code>false</code>.
@@ -485,18 +504,19 @@
/**
* Specifies if a user is disallowed from enabling or disabling location providers. As a
- * result, user is disallowed from turning on or off location. Device owner and profile owners
- * can set this restriction and it only applies on the managed user.
+ * result, user is disallowed from turning on or off location.
*
- * <p>In a managed profile, location sharing is forced off when it's off on primary user, so
- * user can still turn off location sharing on managed profile when the restriction is set by
- * profile owner on managed profile.
- *
- * <p>This user restriction is different from {@link #DISALLOW_SHARE_LOCATION},
+ * <p>
+ * In a managed profile, location sharing is forced off when it is turned off on the primary
+ * user or by the profile owner of an organization-owned managed profile on the parent profile.
+ * The user can still turn off location sharing on a managed profile when the restriction is
+ * set by the profile owner on a managed profile.
+ * <p>
+ * This user restriction is different from {@link #DISALLOW_SHARE_LOCATION},
* as the device owner or profile owner can still enable or disable location mode via
* {@link DevicePolicyManager#setLocationEnabled} when this restriction is on.
- *
- * <p>The default value is <code>false</code>.
+ * <p>
+ * The default value is <code>false</code>.
*
* <p>Key for user restrictions.
* <p>Type: Boolean
@@ -510,7 +530,8 @@
/**
* Specifies if date, time and timezone configuring is disallowed.
*
- * <p>When restriction is set by device owners, it applies globally - i.e., it disables date,
+ * <p>When restriction is set by device owners or profile owners of organization-owned
+ * managed profiles on the parent profile, it applies globally - i.e., it disables date,
* time and timezone setting on the entire device and all users will be affected. When it's set
* by profile owners, it's only applied to the managed user.
* <p>The default value is <code>false</code>.
@@ -526,8 +547,9 @@
/**
* Specifies if a user is disallowed from configuring Tethering
- * & portable hotspots. This can only be set by device owners and profile owners on the
- * primary user. The default value is <code>false</code>.
+ * & portable hotspots. This can only be set by device owners, profile owners on the
+ * primary user or profile owners of organization-owned managed profiles on the parent profile.
+ * The default value is <code>false</code>.
* <p>In Android 9.0 or higher, if tethering is enabled when this restriction is set,
* tethering will be automatically turned off.
*
@@ -571,8 +593,8 @@
/**
* Specifies if a user is disallowed from adding new users. This can only be set by device
- * owners and profile owners on the primary user.
- * The default value is <code>false</code>.
+ * owners, profile owners on the primary user or profile owners of organization-owned managed
+ * profiles on the parent profile. The default value is <code>false</code>.
* <p>This restriction has no effect on secondary users and managed profiles since only the
* primary user can add other users.
*
@@ -621,7 +643,8 @@
/**
* Specifies if a user is disallowed from configuring cell
- * broadcasts. This can only be set by device owners and profile owners on the primary user.
+ * broadcasts. This can only be set by device owners, profile owners on the primary user or
+ * profile owners of organization-owned managed profiles on the parent profile.
* The default value is <code>false</code>.
* <p>This restriction has no effect on secondary users and managed profiles since only the
* primary user can configure cell broadcasts.
@@ -636,7 +659,8 @@
/**
* Specifies if a user is disallowed from configuring mobile
- * networks. This can only be set by device owners and profile owners on the primary user.
+ * networks. This can only be set by device owners, profile owners on the primary user or
+ * profile owners of organization-owned managed profiles on the parent profile.
* The default value is <code>false</code>.
* <p>This restriction has no effect on secondary users and managed profiles since only the
* primary user can configure mobile networks.
@@ -739,6 +763,10 @@
/**
* Specifies that the user is not allowed to send or receive
* SMS messages. The default value is <code>false</code>.
+ * <p>
+ * Device owner and profile owner can set this restriction. When it is set by the
+ * profile owner of an organization-owned managed profile on the parent profile,
+ * it will disable SMS in the personal profile.
*
* <p>Key for user restrictions.
* <p>Type: Boolean
@@ -857,7 +885,8 @@
/**
* Specifies if the user is not allowed to reboot the device into safe boot mode.
- * This can only be set by device owners and profile owners on the primary user.
+ * This can only be set by device owners, profile owners on the primary user or profile
+ * owners of organization-owned managed profiles on the parent profile.
* The default value is <code>false</code>.
*
* <p>Key for user restrictions.
@@ -896,6 +925,12 @@
/**
* Specifies if a user is not allowed to use the camera.
+ * <p>
+ * Device owner and profile owner can set this restriction. When the restriction is set by
+ * the device owner or the profile owner of an organization-owned managed profile on the
+ * parent profile, it is applied globally.
+ * <p>
+ * The default value is <code>false</code>.
*
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
@@ -916,7 +951,8 @@
/**
* Specifies if a user is not allowed to use cellular data when roaming. This can only be set by
- * device owners. The default value is <code>false</code>.
+ * device owners or profile owners of organization-owned managed profiles on the parent profile.
+ * The default value is <code>false</code>.
*
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
* @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
@@ -1011,8 +1047,9 @@
* Specifies if the contents of a user's screen is not allowed to be captured for artificial
* intelligence purposes.
*
- * <p>Device owner and profile owner can set this restriction. When it is set by device owner,
- * only the target user will be affected.
+ * <p>Device owner and profile owner can set this restriction. When it is set by the
+ * device owner or the profile owner of an organization-owned managed profile on the parent
+ * profile, only the target user will be affected.
*
* <p>The default value is <code>false</code>.
*
@@ -1026,8 +1063,9 @@
* Specifies if the current user is able to receive content suggestions for selections based on
* the contents of their screen.
*
- * <p>Device owner and profile owner can set this restriction. When it is set by device owner,
- * only the target user will be affected.
+ * <p>Device owner and profile owner can set this restriction. When it is set by the
+ * device owner or the profile owner of an organization-owned managed profile on the parent
+ * profile, only the target user will be affected.
*
* <p>The default value is <code>false</code>.
*
@@ -1093,7 +1131,9 @@
*
* <p>The default value is <code>false</code>.
*
- * <p>This user restriction can only be applied by the Device Owner.
+ * <p>This user restriction can only be applied by the device owner or the profile owner
+ * of an organization-owned managed profile on the parent profile.
+ *
* <p>Key for user restrictions.
* <p>Type: Boolean
* @see DevicePolicyManager#addUserRestriction(ComponentName, String)
@@ -1626,39 +1666,35 @@
}
/**
- * Returns the calling user's user type.
+ * Returns whether the current user is of the given user type, such as
+ * {@link UserManager#USER_TYPE_FULL_GUEST}.
*
- * // TODO(b/142482943): Decide on the appropriate permission requirements.
- *
- * @return the name of the user type, such as {@link UserManager#USER_TYPE_PROFILE_MANAGED}.
+ * @return true if the user is of the given user type.
* @hide
*/
- public @NonNull String getUserType() {
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ public boolean isUserOfType(@NonNull String userType) {
try {
- return mService.getUserTypeForUser(UserHandle.myUserId());
+ return mService.isUserOfType(UserHandle.myUserId(), userType);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
}
/**
- * Returns the given user's user type.
- *
- * // TODO(b/142482943): Decide on the appropriate permission requirements.
- * Requires {@link android.Manifest.permission#MANAGE_USERS} or
- * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission, otherwise the caller
- * must be in the same profile group of specified user.
+ * Returns whether the given user is of the given user type, such as
+ * {@link UserManager#USER_TYPE_FULL_GUEST}.
*
* @param userHandle the user handle of the user whose type is being requested.
- * @return the name of the user's user type, e.g. {@link UserManager#USER_TYPE_PROFILE_MANAGED},
- * or {@code null} if there is no such user.
+ * @param userType the name of the user's user type, e.g.
+ * {@link UserManager#USER_TYPE_PROFILE_MANAGED}.
+ * @return true if the userHandle user is of type userType
* @hide
*/
- @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
- android.Manifest.permission.INTERACT_ACROSS_USERS}, conditional = true)
- public @Nullable String getUserTypeForUser(@NonNull UserHandle userHandle) {
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+ public boolean isUserOfType(@NonNull UserHandle userHandle, @NonNull String userType) {
try {
- return mService.getUserTypeForUser(userHandle.getIdentifier());
+ return mService.isUserOfType(userHandle.getIdentifier(), userType);
} catch (RemoteException re) {
throw re.rethrowFromSystemServer();
}
@@ -1854,10 +1890,7 @@
* Checks if the calling app is running in a managed profile.
*
* @return whether the caller is in a managed profile.
- * @hide
*/
- @SystemApi
- @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
public boolean isManagedProfile() {
// No need for synchronization. Once it becomes non-null, it'll be non-null forever.
// Worst case we might end up calling the AIDL method multiple times but that's fine.
@@ -2420,6 +2453,13 @@
* by {@link #createUser(String, String, int)} or {@link #createGuest(Context, String)}), it
* takes less time.
*
+ * <p>This method completes the majority of work necessary for user creation: it
+ * creates user data, CE and DE encryption keys, app data directories, initializes the user and
+ * grants default permissions. When pre-created users become "real" users, only then are
+ * components notified of new user creation by firing user creation broadcasts.
+ *
+ * <p>All pre-created users are removed during system upgrade.
+ *
* <p>Requires {@link android.Manifest.permission#MANAGE_USERS} permission.
*
* @param userType the type of user, such as {@link UserManager#USER_TYPE_FULL_GUEST}.
@@ -2431,6 +2471,7 @@
*
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
public @Nullable UserInfo preCreateUser(@NonNull String userType) {
try {
return mService.preCreateUser(userType);
diff --git a/core/java/android/os/VibrationAttributes.aidl b/core/java/android/os/VibrationAttributes.aidl
new file mode 100644
index 0000000..5c05a4e
--- /dev/null
+++ b/core/java/android/os/VibrationAttributes.aidl
@@ -0,0 +1,18 @@
+/* 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.os;
+
+parcelable VibrationAttributes;
diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java
new file mode 100644
index 0000000..3e16640
--- /dev/null
+++ b/core/java/android/os/VibrationAttributes.java
@@ -0,0 +1,401 @@
+/*
+ * 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.os;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.media.AudioAttributes;
+import android.util.Slog;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * A class to encapsulate a collection of attributes describing information about a vibration
+ */
+public final class VibrationAttributes implements Parcelable {
+ private static final String TAG = "VibrationAttributes";
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = { "USAGE_CLASS_" }, value = {
+ USAGE_CLASS_UNKNOWN,
+ USAGE_CLASS_ALARM,
+ USAGE_CLASS_FEEDBACK,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface UsageClass{}
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = { "USAGE_" }, value = {
+ USAGE_UNKNOWN,
+ USAGE_ALARM,
+ USAGE_RINGTONE,
+ USAGE_NOTIFICATION,
+ USAGE_COMMUNICATION_REQUEST,
+ USAGE_TOUCH,
+ USAGE_PHYSICAL_EMULATION,
+ USAGE_HARDWARE_FEEDBACK,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Usage{}
+
+ /**
+ * Vibration usage class value to use when the vibration usage class is unknown.
+ */
+ public static final int USAGE_CLASS_UNKNOWN = 0x0;
+ /**
+ * Vibration usage class value to use when the vibration is initiated to catch user's
+ * attention, such as alarm, ringtone, and notification vibrations.
+ */
+ public static final int USAGE_CLASS_ALARM = 0x1;
+ /**
+ * Vibration usage class value to use when the vibration is initiated as a response to user's
+ * actions, such as emulation of physical effects, and texting feedback vibration.
+ */
+ public static final int USAGE_CLASS_FEEDBACK = 0x2;
+
+ /**
+ * Mask for vibration usage class value.
+ */
+ public static final int USAGE_CLASS_MASK = 0xF;
+
+ /**
+ * Usage value to use when usage is unknown.
+ */
+ public static final int USAGE_UNKNOWN = 0x0 | USAGE_CLASS_UNKNOWN;
+ /**
+ * Usage value to use for alarm vibrations.
+ */
+ public static final int USAGE_ALARM = 0x10 | USAGE_CLASS_ALARM;
+ /**
+ * Usage value to use for ringtone vibrations.
+ */
+ public static final int USAGE_RINGTONE = 0x20 | USAGE_CLASS_ALARM;
+ /**
+ * Usage value to use for notification vibrations.
+ */
+ public static final int USAGE_NOTIFICATION = 0x30 | USAGE_CLASS_ALARM;
+ /**
+ * Usage value to use for vibrations which mean a request to enter/end a
+ * communication, such as a VoIP communication or video-conference.
+ */
+ public static final int USAGE_COMMUNICATION_REQUEST = 0x40 | USAGE_CLASS_ALARM;
+ /**
+ * Usage value to use for touch vibrations.
+ */
+ public static final int USAGE_TOUCH = 0x10 | USAGE_CLASS_FEEDBACK;
+ /**
+ * Usage value to use for vibrations which emulate physical effects, such as edge squeeze.
+ */
+ public static final int USAGE_PHYSICAL_EMULATION = 0x20 | USAGE_CLASS_FEEDBACK;
+ /**
+ * Usage value to use for vibrations which provide a feedback for hardware interaction,
+ * such as a fingerprint sensor.
+ */
+ public static final int USAGE_HARDWARE_FEEDBACK = 0x30 | USAGE_CLASS_FEEDBACK;
+
+ /**
+ * @hide
+ */
+ @IntDef(prefix = { "FLAG_" }, value = {
+ FLAG_BYPASS_INTERRUPTION_POLICY,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Flag{}
+
+ /**
+ * Flag requesting vibration effect to be played even under limited interruptions.
+ */
+ public static final int FLAG_BYPASS_INTERRUPTION_POLICY = 0x1;
+
+ // If a vibration is playing for longer than 5s, it's probably not haptic feedback
+ private static final long MAX_HAPTIC_FEEDBACK_DURATION = 5000;
+
+ private final int mUsage;
+ private final int mFlags;
+
+ private final AudioAttributes mAudioAttributes;
+
+ private VibrationAttributes(int usage, int flags, @NonNull AudioAttributes audio) {
+ mUsage = usage;
+ mFlags = flags;
+ mAudioAttributes = audio;
+ }
+
+ /**
+ * Return the vibration usage class.
+ * @return USAGE_CLASS_ALARM, USAGE_CLASS_FEEDBACK or USAGE_CLASS_UNKNOWN
+ */
+ public int getUsageClass() {
+ return mUsage & USAGE_CLASS_MASK;
+ }
+
+ /**
+ * Return the vibration usage.
+ * @return one of the values that can be set in {@link Builder#setUsage(int)}
+ */
+ public int getUsage() {
+ return mUsage;
+ }
+
+ /**
+ * Return the flags.
+ * @return a combined mask of all flags
+ */
+ public int getFlags() {
+ return mFlags;
+ }
+
+ /**
+ * Check whether a flag is set
+ * @return true if a flag is set and false otherwise
+ */
+ public boolean isFlagSet(int flag) {
+ return (mFlags & flag) > 0;
+ }
+
+ /**
+ * Return AudioAttributes equivalent to this VibrationAttributes.
+ * @deprecated Temporary support of AudioAttributes, will be removed when out of WIP
+ * @hide
+ */
+ @Deprecated
+ @TestApi
+ public @NonNull AudioAttributes getAudioAttributes() {
+ return mAudioAttributes;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mUsage);
+ dest.writeInt(mFlags);
+ dest.writeParcelable(mAudioAttributes, flags);
+ }
+
+ private VibrationAttributes(Parcel src) {
+ mUsage = src.readInt();
+ mFlags = src.readInt();
+ mAudioAttributes = (AudioAttributes) src.readParcelable(
+ AudioAttributes.class.getClassLoader());
+ }
+
+ public static final @NonNull Parcelable.Creator<VibrationAttributes>
+ CREATOR = new Parcelable.Creator<VibrationAttributes>() {
+ public VibrationAttributes createFromParcel(Parcel p) {
+ return new VibrationAttributes(p);
+ }
+ public VibrationAttributes[] newArray(int size) {
+ return new VibrationAttributes[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ VibrationAttributes rhs = (VibrationAttributes) o;
+ return mUsage == rhs.mUsage && mFlags == rhs.mFlags;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mUsage, mFlags);
+ }
+
+ @Override
+ public String toString() {
+ return "VibrationAttributes:"
+ + " Usage=" + usageToString()
+ + " Flags=" + mFlags;
+ }
+
+ /** @hide */
+ public String usageToString() {
+ return usageToString(mUsage);
+ }
+
+ /** @hide */
+ public String usageToString(int usage) {
+ switch (usage) {
+ case USAGE_UNKNOWN:
+ return "UNKNOWN";
+ case USAGE_ALARM:
+ return "ALARM";
+ case USAGE_RINGTONE:
+ return "RIGNTONE";
+ case USAGE_NOTIFICATION:
+ return "NOTIFICATION";
+ case USAGE_COMMUNICATION_REQUEST:
+ return "COMMUNICATION_REQUEST";
+ case USAGE_TOUCH:
+ return "TOUCH";
+ case USAGE_PHYSICAL_EMULATION:
+ return "PHYSICAL_EMULATION";
+ case USAGE_HARDWARE_FEEDBACK:
+ return "HARDWARE_FEEDBACK";
+ default:
+ return "unknown usage " + usage;
+ }
+ }
+
+ /**
+ * Builder class for {@link VibrationAttributes} objects.
+ * By default, all information is set to UNKNOWN.
+ */
+ public static final class Builder {
+ private int mUsage = USAGE_UNKNOWN;
+ private int mFlags = 0x0;
+
+ private AudioAttributes mAudioAttributes = new AudioAttributes.Builder().build();
+
+ /**
+ * Constructs a new Builder with the defaults.
+ */
+ public Builder() {
+ }
+
+ /**
+ * Constructs a new Builder from a given VibrationAttributes.
+ */
+ public Builder(@Nullable VibrationAttributes vib) {
+ if (vib != null) {
+ mUsage = vib.mUsage;
+ mFlags = vib.mFlags;
+ mAudioAttributes = vib.mAudioAttributes;
+ }
+ }
+
+ /**
+ * Constructs a new Builder from AudioAttributes.
+ * @hide
+ */
+ @TestApi
+ public Builder(@NonNull AudioAttributes audio,
+ @Nullable VibrationEffect effect) {
+ mAudioAttributes = audio;
+ setUsage(audio);
+ applyHapticFeedbackHeuristics(effect);
+ }
+
+ private void applyHapticFeedbackHeuristics(@Nullable VibrationEffect effect) {
+ if (effect != null) {
+ if (mUsage == USAGE_UNKNOWN && effect instanceof VibrationEffect.Prebaked) {
+ VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
+ switch (prebaked.getId()) {
+ case VibrationEffect.EFFECT_CLICK:
+ case VibrationEffect.EFFECT_DOUBLE_CLICK:
+ case VibrationEffect.EFFECT_HEAVY_CLICK:
+ case VibrationEffect.EFFECT_TEXTURE_TICK:
+ case VibrationEffect.EFFECT_TICK:
+ case VibrationEffect.EFFECT_POP:
+ case VibrationEffect.EFFECT_THUD:
+ mUsage = USAGE_TOUCH;
+ break;
+ default:
+ Slog.w(TAG, "Unknown prebaked vibration effect, assuming it isn't "
+ + "haptic feedback");
+ }
+ }
+ final long duration = effect.getDuration();
+ if (mUsage == USAGE_UNKNOWN && duration >= 0
+ && duration < MAX_HAPTIC_FEEDBACK_DURATION) {
+ mUsage = USAGE_TOUCH;
+ }
+ }
+ }
+
+ private void setUsage(@NonNull AudioAttributes audio) {
+ switch (audio.getUsage()) {
+ case AudioAttributes.USAGE_NOTIFICATION:
+ case AudioAttributes.USAGE_NOTIFICATION_EVENT:
+ case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
+ case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
+ mUsage = USAGE_NOTIFICATION;
+ break;
+ case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
+ case AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY:
+ mUsage = USAGE_COMMUNICATION_REQUEST;
+ break;
+ case AudioAttributes.USAGE_NOTIFICATION_RINGTONE:
+ mUsage = USAGE_RINGTONE;
+ break;
+ case AudioAttributes.USAGE_ASSISTANCE_SONIFICATION:
+ mUsage = USAGE_TOUCH;
+ break;
+ case AudioAttributes.USAGE_ALARM:
+ mUsage = USAGE_ALARM;
+ break;
+ default:
+ mUsage = USAGE_UNKNOWN;
+ }
+ }
+
+ /**
+ * Combines all of the attributes that have been set and returns a new
+ * {@link VibrationAttributes} object.
+ * @return a new {@link VibrationAttributes} object
+ */
+ public @NonNull VibrationAttributes build() {
+ VibrationAttributes ans = new VibrationAttributes(mUsage, mFlags,
+ mAudioAttributes);
+ return ans;
+ }
+
+ /**
+ * Sets the attribute describing the type of corresponding vibration.
+ * @param usage one of {@link VibrationAttributes#USAGE_ALARM},
+ * {@link VibrationAttributes#USAGE_RINGTONE},
+ * {@link VibrationAttributes#USAGE_NOTIFICATION},
+ * {@link VibrationAttributes#USAGE_COMMUNICATION_REQUEST},
+ * {@link VibrationAttributes#USAGE_TOUCH},
+ * {@link VibrationAttributes#USAGE_PHYSICAL_EMULATION},
+ * {@link VibrationAttributes#USAGE_HARDWARE_FEEDBACK}.
+ * @return the same Builder instance.
+ */
+ public @NonNull Builder setUsage(int usage) {
+ mUsage = usage;
+ return this;
+ }
+
+ /**
+ * Replaces flags
+ * @param flags any combination of flags.
+ * @return the same Builder instance.
+ */
+ public @NonNull Builder replaceFlags(int flags) {
+ mFlags = flags;
+ return this;
+ }
+ }
+}
+
diff --git a/core/java/android/os/Vibrator.java b/core/java/android/os/Vibrator.java
index ccbb0f1..ae75f3d 100644
--- a/core/java/android/os/Vibrator.java
+++ b/core/java/android/os/Vibrator.java
@@ -155,7 +155,7 @@
/**
* Configure an always-on haptics effect.
*
- * @param id The board-specific always-on ID to configure.
+ * @param alwaysOnId The board-specific always-on ID to configure.
* @param effect Vibration effect to assign to always-on id. Passing null will disable it.
* @param attributes {@link AudioAttributes} corresponding to the vibration. For example,
* specify {@link AudioAttributes#USAGE_ALARM} for alarm vibrations or
@@ -164,8 +164,17 @@
* @hide
*/
@RequiresPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)
- public boolean setAlwaysOnEffect(int id, @Nullable VibrationEffect effect,
- @Nullable AudioAttributes attributes) {
+ public boolean setAlwaysOnEffect(int alwaysOnId, @Nullable VibrationEffect effect,
+ @Nullable AudioAttributes attributes) {
+ return setAlwaysOnEffect(Process.myUid(), mPackageName, alwaysOnId, effect, attributes);
+ }
+
+ /**
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)
+ public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId,
+ @Nullable VibrationEffect effect, @Nullable AudioAttributes attributes) {
Log.w(TAG, "Always-on effects aren't supported");
return false;
}
diff --git a/core/java/android/os/ZygoteProcess.java b/core/java/android/os/ZygoteProcess.java
index 62b8953..3846f89 100644
--- a/core/java/android/os/ZygoteProcess.java
+++ b/core/java/android/os/ZygoteProcess.java
@@ -594,6 +594,8 @@
argsForZygote.add("--mount-external-legacy");
} else if (mountExternal == Zygote.MOUNT_EXTERNAL_PASS_THROUGH) {
argsForZygote.add("--mount-external-pass-through");
+ } else if (mountExternal == Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE) {
+ argsForZygote.add("--mount-external-android-writable");
}
argsForZygote.add("--target-sdk-version=" + targetSdkVersion);
diff --git a/core/java/android/os/image/DynamicSystemClient.java b/core/java/android/os/image/DynamicSystemClient.java
index 921f0f2..5cb3361 100644
--- a/core/java/android/os/image/DynamicSystemClient.java
+++ b/core/java/android/os/image/DynamicSystemClient.java
@@ -256,9 +256,13 @@
mService.send(msg);
} catch (RemoteException e) {
Slog.e(TAG, "Unable to get status from installation service");
- mExecutor.execute(() -> {
+ if (mExecutor != null) {
+ mExecutor.execute(() -> {
+ mListener.onStatusChanged(STATUS_UNKNOWN, CAUSE_ERROR_IPC, 0, e);
+ });
+ } else {
mListener.onStatusChanged(STATUS_UNKNOWN, CAUSE_ERROR_IPC, 0, e);
- });
+ }
}
}
diff --git a/core/java/android/os/image/DynamicSystemManager.java b/core/java/android/os/image/DynamicSystemManager.java
index 4c92c28..cbf531c 100644
--- a/core/java/android/os/image/DynamicSystemManager.java
+++ b/core/java/android/os/image/DynamicSystemManager.java
@@ -106,9 +106,9 @@
* @return true if the call succeeds
*/
@RequiresPermission(android.Manifest.permission.MANAGE_DYNAMIC_SYSTEM)
- public boolean startInstallation() {
+ public boolean startInstallation(String dsuSlot) {
try {
- return mService.startInstallation();
+ return mService.startInstallation(dsuSlot);
} catch (RemoteException e) {
throw new RuntimeException(e.toString());
}
diff --git a/core/java/android/os/image/IDynamicSystemService.aidl b/core/java/android/os/image/IDynamicSystemService.aidl
index 69cbab2..cc32f99 100644
--- a/core/java/android/os/image/IDynamicSystemService.aidl
+++ b/core/java/android/os/image/IDynamicSystemService.aidl
@@ -22,9 +22,10 @@
{
/**
* Start DynamicSystem installation.
+ * @param dsuSlot Name used to identify this installation
* @return true if the call succeeds
*/
- boolean startInstallation();
+ boolean startInstallation(@utf8InCpp String dsuSlot);
/**
* Create a DSU partition. This call may take 60~90 seconds. The caller
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 8959fcf..f0a1174 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -31,6 +31,7 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import android.annotation.BytesLong;
+import android.annotation.CallbackExecutor;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -72,6 +73,7 @@
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.os.SystemProperties;
+import android.os.UserHandle;
import android.provider.MediaStore;
import android.provider.Settings;
import android.sysprop.VoldProperties;
@@ -93,7 +95,6 @@
import com.android.internal.os.FuseAppLoop;
import com.android.internal.os.FuseUnavailableMountException;
import com.android.internal.os.RoSystemProperties;
-import com.android.internal.os.SomeArgs;
import com.android.internal.util.Preconditions;
import dalvik.system.BlockGuard;
@@ -114,6 +115,7 @@
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@@ -305,109 +307,85 @@
private final Looper mLooper;
private final AtomicInteger mNextNonce = new AtomicInteger(0);
+ @GuardedBy("mDelegates")
private final ArrayList<StorageEventListenerDelegate> mDelegates = new ArrayList<>();
- private static class StorageEventListenerDelegate extends IStorageEventListener.Stub implements
- Handler.Callback {
- private static final int MSG_STORAGE_STATE_CHANGED = 1;
- private static final int MSG_VOLUME_STATE_CHANGED = 2;
- private static final int MSG_VOLUME_RECORD_CHANGED = 3;
- private static final int MSG_VOLUME_FORGOTTEN = 4;
- private static final int MSG_DISK_SCANNED = 5;
- private static final int MSG_DISK_DESTROYED = 6;
+ private class StorageEventListenerDelegate extends IStorageEventListener.Stub {
+ final Executor mExecutor;
+ final StorageEventListener mListener;
+ final StorageVolumeCallback mCallback;
- final StorageEventListener mCallback;
- final Handler mHandler;
-
- public StorageEventListenerDelegate(StorageEventListener callback, Looper looper) {
+ public StorageEventListenerDelegate(@NonNull Executor executor,
+ @NonNull StorageEventListener listener, @NonNull StorageVolumeCallback callback) {
+ mExecutor = executor;
+ mListener = listener;
mCallback = callback;
- mHandler = new Handler(looper, this);
- }
-
- @Override
- public boolean handleMessage(Message msg) {
- final SomeArgs args = (SomeArgs) msg.obj;
- switch (msg.what) {
- case MSG_STORAGE_STATE_CHANGED:
- mCallback.onStorageStateChanged((String) args.arg1, (String) args.arg2,
- (String) args.arg3);
- args.recycle();
- return true;
- case MSG_VOLUME_STATE_CHANGED:
- mCallback.onVolumeStateChanged((VolumeInfo) args.arg1, args.argi2, args.argi3);
- args.recycle();
- return true;
- case MSG_VOLUME_RECORD_CHANGED:
- mCallback.onVolumeRecordChanged((VolumeRecord) args.arg1);
- args.recycle();
- return true;
- case MSG_VOLUME_FORGOTTEN:
- mCallback.onVolumeForgotten((String) args.arg1);
- args.recycle();
- return true;
- case MSG_DISK_SCANNED:
- mCallback.onDiskScanned((DiskInfo) args.arg1, args.argi2);
- args.recycle();
- return true;
- case MSG_DISK_DESTROYED:
- mCallback.onDiskDestroyed((DiskInfo) args.arg1);
- args.recycle();
- return true;
- }
- args.recycle();
- return false;
}
@Override
public void onUsbMassStorageConnectionChanged(boolean connected) throws RemoteException {
- // Ignored
+ mExecutor.execute(() -> {
+ mListener.onUsbMassStorageConnectionChanged(connected);
+ });
}
@Override
public void onStorageStateChanged(String path, String oldState, String newState) {
- final SomeArgs args = SomeArgs.obtain();
- args.arg1 = path;
- args.arg2 = oldState;
- args.arg3 = newState;
- mHandler.obtainMessage(MSG_STORAGE_STATE_CHANGED, args).sendToTarget();
+ mExecutor.execute(() -> {
+ mListener.onStorageStateChanged(path, oldState, newState);
+
+ if (path != null) {
+ for (StorageVolume sv : getStorageVolumes()) {
+ if (Objects.equals(path, sv.getPath())) {
+ mCallback.onStateChanged(sv);
+ }
+ }
+ }
+ });
}
@Override
public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
- final SomeArgs args = SomeArgs.obtain();
- args.arg1 = vol;
- args.argi2 = oldState;
- args.argi3 = newState;
- mHandler.obtainMessage(MSG_VOLUME_STATE_CHANGED, args).sendToTarget();
+ mExecutor.execute(() -> {
+ mListener.onVolumeStateChanged(vol, oldState, newState);
+
+ final File path = vol.getPathForUser(UserHandle.myUserId());
+ if (path != null) {
+ for (StorageVolume sv : getStorageVolumes()) {
+ if (Objects.equals(path.getAbsolutePath(), sv.getPath())) {
+ mCallback.onStateChanged(sv);
+ }
+ }
+ }
+ });
}
@Override
public void onVolumeRecordChanged(VolumeRecord rec) {
- final SomeArgs args = SomeArgs.obtain();
- args.arg1 = rec;
- mHandler.obtainMessage(MSG_VOLUME_RECORD_CHANGED, args).sendToTarget();
+ mExecutor.execute(() -> {
+ mListener.onVolumeRecordChanged(rec);
+ });
}
@Override
public void onVolumeForgotten(String fsUuid) {
- final SomeArgs args = SomeArgs.obtain();
- args.arg1 = fsUuid;
- mHandler.obtainMessage(MSG_VOLUME_FORGOTTEN, args).sendToTarget();
+ mExecutor.execute(() -> {
+ mListener.onVolumeForgotten(fsUuid);
+ });
}
@Override
public void onDiskScanned(DiskInfo disk, int volumeCount) {
- final SomeArgs args = SomeArgs.obtain();
- args.arg1 = disk;
- args.argi2 = volumeCount;
- mHandler.obtainMessage(MSG_DISK_SCANNED, args).sendToTarget();
+ mExecutor.execute(() -> {
+ mListener.onDiskScanned(disk, volumeCount);
+ });
}
@Override
public void onDiskDestroyed(DiskInfo disk) throws RemoteException {
- final SomeArgs args = SomeArgs.obtain();
- args.arg1 = disk;
- mHandler.obtainMessage(MSG_DISK_DESTROYED, args).sendToTarget();
+ mExecutor.execute(() -> {
+ mListener.onDiskDestroyed(disk);
+ });
}
}
@@ -525,8 +503,8 @@
@UnsupportedAppUsage
public void registerListener(StorageEventListener listener) {
synchronized (mDelegates) {
- final StorageEventListenerDelegate delegate = new StorageEventListenerDelegate(listener,
- mLooper);
+ final StorageEventListenerDelegate delegate = new StorageEventListenerDelegate(
+ mContext.getMainExecutor(), listener, new StorageVolumeCallback());
try {
mStorageManager.registerListener(delegate);
} catch (RemoteException e) {
@@ -548,7 +526,76 @@
synchronized (mDelegates) {
for (Iterator<StorageEventListenerDelegate> i = mDelegates.iterator(); i.hasNext();) {
final StorageEventListenerDelegate delegate = i.next();
- if (delegate.mCallback == listener) {
+ if (delegate.mListener == listener) {
+ try {
+ mStorageManager.unregisterListener(delegate);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ i.remove();
+ }
+ }
+ }
+ }
+
+ /**
+ * Callback that delivers {@link StorageVolume} related events.
+ * <p>
+ * For example, this can be used to detect when a volume changes to the
+ * {@link Environment#MEDIA_MOUNTED} or {@link Environment#MEDIA_UNMOUNTED}
+ * states.
+ *
+ * @see StorageManager#registerStorageVolumeCallback
+ * @see StorageManager#unregisterStorageVolumeCallback
+ */
+ public static class StorageVolumeCallback {
+ /**
+ * Called when {@link StorageVolume#getState()} changes, such as
+ * changing to the {@link Environment#MEDIA_MOUNTED} or
+ * {@link Environment#MEDIA_UNMOUNTED} states.
+ * <p>
+ * The given argument is a snapshot in time and can be used to process
+ * events in the order they occurred, or you can call
+ * {@link StorageManager#getStorageVolumes()} to observe the latest
+ * value.
+ */
+ public void onStateChanged(@NonNull StorageVolume volume) { }
+ }
+
+ /**
+ * Registers the given callback to listen for {@link StorageVolume} changes.
+ * <p>
+ * For example, this can be used to detect when a volume changes to the
+ * {@link Environment#MEDIA_MOUNTED} or {@link Environment#MEDIA_UNMOUNTED}
+ * states.
+ *
+ * @see StorageManager#unregisterStorageVolumeCallback
+ */
+ public void registerStorageVolumeCallback(@CallbackExecutor @NonNull Executor executor,
+ @NonNull StorageVolumeCallback callback) {
+ synchronized (mDelegates) {
+ final StorageEventListenerDelegate delegate = new StorageEventListenerDelegate(
+ executor, new StorageEventListener(), callback);
+ try {
+ mStorageManager.registerListener(delegate);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ mDelegates.add(delegate);
+ }
+ }
+
+ /**
+ * Unregisters the given callback from listening for {@link StorageVolume}
+ * changes.
+ *
+ * @see StorageManager#registerStorageVolumeCallback
+ */
+ public void unregisterStorageVolumeCallback(@NonNull StorageVolumeCallback callback) {
+ synchronized (mDelegates) {
+ for (Iterator<StorageEventListenerDelegate> i = mDelegates.iterator(); i.hasNext();) {
+ final StorageEventListenerDelegate delegate = i.next();
+ if (delegate.mCallback == callback) {
try {
mStorageManager.unregisterListener(delegate);
} catch (RemoteException e) {
@@ -829,7 +876,14 @@
*/
public @NonNull UUID getUuidForPath(@NonNull File path) throws IOException {
Preconditions.checkNotNull(path);
- final String pathString = path.getCanonicalPath();
+ String pathString = path.getCanonicalPath();
+ if (path.getPath().startsWith("/sdcard")) {
+ // On FUSE enabled devices, realpath(2) /sdcard is /mnt/user/<userid>/emulated/<userid>
+ // as opposed to /storage/emulated/<userid>.
+ // And vol.path below expects to match with a path starting with /storage
+ pathString = pathString.replaceFirst("^/mnt/user/[0-9]+/", "/storage/");
+ }
+
if (FileUtils.contains(Environment.getDataDirectory().getAbsolutePath(), pathString)) {
return UUID_DEFAULT;
}
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index 2ab226f..e251f80 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
@@ -162,9 +163,13 @@
mState = in.readString();
}
- /** {@hide} */
- @UnsupportedAppUsage
- public String getId() {
+ /**
+ * Return an opaque ID that can be used to identify this volume.
+ *
+ * @hide
+ */
+ @SystemApi
+ public @NonNull String getId() {
return mId;
}
diff --git a/core/java/android/permission/PermissionManager.java b/core/java/android/permission/PermissionManager.java
index 2583292..5a1ba7f 100644
--- a/core/java/android/permission/PermissionManager.java
+++ b/core/java/android/permission/PermissionManager.java
@@ -160,6 +160,7 @@
* Grant default permissions to currently active LUI app
* @param packageName The package name for the LUI app
* @param user The user handle
+ * @param executor The executor for the callback
* @param callback The callback provided by caller to be notified when grant completes
* @hide
*/
@@ -181,6 +182,7 @@
* Revoke default permissions to currently active LUI app
* @param packageNames The package names for the LUI apps
* @param user The user handle
+ * @param executor The executor for the callback
* @param callback The callback provided by caller to be notified when grant completes
* @hide
*/
@@ -198,6 +200,72 @@
}
}
+ /**
+ * Grant default permissions to currently active Ims services
+ * @param packageNames The package names for the Ims services
+ * @param user The user handle
+ * @param executor The executor for the callback
+ * @param callback The callback provided by caller to be notified when grant completes
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS)
+ public void grantDefaultPermissionsToEnabledImsServices(
+ @NonNull String[] packageNames, @NonNull UserHandle user,
+ @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
+ try {
+ mPermissionManager.grantDefaultPermissionsToEnabledImsServices(
+ packageNames, user.getIdentifier());
+ executor.execute(() -> callback.accept(true));
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Grant default permissions to currently enabled telephony data services
+ * @param packageNames The package name for the services
+ * @param user The user handle
+ * @param executor The executor for the callback
+ * @param callback The callback provided by caller to be notified when grant completes
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS)
+ public void grantDefaultPermissionsToEnabledTelephonyDataServices(
+ @NonNull String[] packageNames, @NonNull UserHandle user,
+ @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
+ try {
+ mPermissionManager.grantDefaultPermissionsToEnabledTelephonyDataServices(
+ packageNames, user.getIdentifier());
+ executor.execute(() -> callback.accept(true));
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
+ * Revoke default permissions to currently active telephony data services
+ * @param packageNames The package name for the services
+ * @param user The user handle
+ * @param executor The executor for the callback
+ * @param callback The callback provided by caller to be notified when revoke completes
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS_TO_TELEPHONY_DEFAULTS)
+ public void revokeDefaultPermissionsFromDisabledTelephonyDataServices(
+ @NonNull String[] packageNames, @NonNull UserHandle user,
+ @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
+ try {
+ mPermissionManager.revokeDefaultPermissionsFromDisabledTelephonyDataServices(
+ packageNames, user.getIdentifier());
+ executor.execute(() -> callback.accept(true));
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
private List<SplitPermissionInfo> splitPermissionInfoListToNonParcelableList(
List<SplitPermissionInfoParcelable> parcelableList) {
final int size = parcelableList.size();
diff --git a/core/java/android/provider/ContactsInternal.java b/core/java/android/provider/ContactsInternal.java
index 69c4b9b..aa2a89c 100644
--- a/core/java/android/provider/ContactsInternal.java
+++ b/core/java/android/provider/ContactsInternal.java
@@ -15,15 +15,14 @@
*/
package android.provider;
-import android.annotation.UnsupportedAppUsage;
import android.app.admin.DevicePolicyManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ActivityNotFoundException;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.UriMatcher;
import android.net.Uri;
-import android.os.Process;
import android.os.UserHandle;
import android.text.TextUtils;
import android.widget.Toast;
diff --git a/core/java/android/provider/DocumentsContract.java b/core/java/android/provider/DocumentsContract.java
index eb09930..1453608 100644
--- a/core/java/android/provider/DocumentsContract.java
+++ b/core/java/android/provider/DocumentsContract.java
@@ -22,7 +22,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentInterface;
import android.content.ContentResolver;
import android.content.Context;
@@ -363,7 +363,7 @@
* <p>
* Type: INTEGER (int)
*
- * @see #FLAG_DIR_BLOCKS_TREE
+ * @see #FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE
* @see #FLAG_DIR_PREFERS_GRID
* @see #FLAG_DIR_PREFERS_LAST_MODIFIED
* @see #FLAG_DIR_SUPPORTS_CREATE
@@ -553,7 +553,9 @@
/**
* Flag indicating that a document is a directory that wants to block itself
* from being selected when the user launches an {@link Intent#ACTION_OPEN_DOCUMENT_TREE}
- * intent. Only valid when {@link #COLUMN_MIME_TYPE} is {@link #MIME_TYPE_DIR}.
+ * intent. Individual files can still be selected when launched via other intents
+ * like {@link Intent#ACTION_OPEN_DOCUMENT} and {@link Intent#ACTION_GET_CONTENT}.
+ * Only valid when {@link #COLUMN_MIME_TYPE} is {@link #MIME_TYPE_DIR}.
* <p>
* Note that this flag <em>only</em> applies to the single directory to which it is
* applied. It does <em>not</em> block the user from selecting either a parent or
@@ -565,7 +567,7 @@
* @see Intent#ACTION_OPEN_DOCUMENT_TREE
* @see #COLUMN_FLAGS
*/
- public static final int FLAG_DIR_BLOCKS_TREE = 1 << 15;
+ public static final int FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE = 1 << 15;
}
/**
diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java
index 9a384c6..48410a7 100644
--- a/core/java/android/provider/Downloads.java
+++ b/core/java/android/provider/Downloads.java
@@ -16,8 +16,8 @@
package android.provider;
-import android.annotation.UnsupportedAppUsage;
import android.app.DownloadManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.net.NetworkPolicyManager;
import android.net.Uri;
diff --git a/core/java/android/provider/SearchIndexablesContract.java b/core/java/android/provider/SearchIndexablesContract.java
index 298628e..8fc13b7 100644
--- a/core/java/android/provider/SearchIndexablesContract.java
+++ b/core/java/android/provider/SearchIndexablesContract.java
@@ -98,16 +98,14 @@
/**
- * Dynamic indexable raw data names.
- *
- * @hide
+ * The raw data name of dynamic index. This is used to compose the index path of provider
+ * for dynamic index.
*/
public static final String DYNAMIC_INDEXABLES_RAW = "dynamic_indexables_raw";
/**
- * ContentProvider path for dynamic indexable raw data.
- *
- * @hide
+ * ContentProvider path for dynamic index. This is used to get the raw data of dynamic index
+ * from provider.
*/
public static final String DYNAMIC_INDEXABLES_RAW_PATH =
SETTINGS + "/" + DYNAMIC_INDEXABLES_RAW;
diff --git a/core/java/android/provider/SearchIndexablesProvider.java b/core/java/android/provider/SearchIndexablesProvider.java
index 68284b4..f4d0cb4 100644
--- a/core/java/android/provider/SearchIndexablesProvider.java
+++ b/core/java/android/provider/SearchIndexablesProvider.java
@@ -204,11 +204,9 @@
* @param projection list of {@link android.provider.SearchIndexablesContract.RawData} columns
* to put into the cursor. If {@code null} all supported columns should be
* included.
- *
- * @hide
*/
@Nullable
- public Cursor queryDynamicRawData(String[] projection) {
+ public Cursor queryDynamicRawData(@Nullable String[] projection) {
// By default no-op;
return null;
}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 7bf7004..f663320 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -26,7 +26,6 @@
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.annotation.UserIdInt;
import android.app.ActivityThread;
import android.app.AppOpsManager;
@@ -36,6 +35,7 @@
import android.app.NotificationManager;
import android.app.SearchManager;
import android.app.WallpaperManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentValues;
@@ -68,7 +68,6 @@
import android.os.ServiceManager;
import android.os.UserHandle;
import android.speech.tts.TextToSpeech;
-import android.telephony.SubscriptionManager;
import android.text.TextUtils;
import android.util.AndroidException;
import android.util.ArrayMap;
@@ -96,6 +95,7 @@
* The Settings provider contains global system-level device preferences.
*/
public final class Settings {
+ private static final boolean DEFAULT_OVERRIDEABLE_BY_RESTORE = false;
// Intent actions for Settings
@@ -219,7 +219,9 @@
* @hide
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
- public static final String ACTION_TETHER_PROVISIONING =
+ @SystemApi
+ @TestApi
+ public static final String ACTION_TETHER_PROVISIONING_UI =
"android.settings.TETHER_PROVISIONING_UI";
/**
@@ -252,6 +254,9 @@
/** @hide */
public static final String EXTRA_NETWORK_TEMPLATE = "network_template";
+ /** @hide */
+ public static final String KEY_CONFIG_SET_RETURN = "config_set_return";
+
/**
* An int extra specifying a subscription ID.
*
@@ -385,6 +390,21 @@
"android.settings.MANAGE_UNKNOWN_APP_SOURCES";
/**
+ * Activity Action: Show settings to allow configuration of cross-profile access for apps
+ *
+ * Input: Optionally, the Intent's data URI can specify the application package name to
+ * directly invoke the management GUI specific to the package name. For example
+ * "package:com.my.app".
+ * <p>
+ * Output: Nothing.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_MANAGE_CROSS_PROFILE_ACCESS =
+ "android.settings.MANAGE_CROSS_PROFILE_ACCESS";
+
+ /**
* Activity Action: Show the "Open by Default" page in a particular application's details page.
* <p>
* In some cases, a matching Activity may not exist, so ensure you safeguard against this.
@@ -491,28 +511,33 @@
"android.settings.WIFI_IP_SETTINGS";
/**
- * Activity Action: Show setting page to process an Easy Connect (Wi-Fi DPP) QR code and start
+ * Activity Action: Show setting page to process a Wi-Fi Easy Connect (aka DPP) URI and start
* configuration. This intent should be used when you want to use this device to take on the
- * configurator role for an IoT/other device. When provided with a valid DPP URI string Settings
- * will open a wifi selection screen for the user to indicate which network they would like to
- * configure the device specified in the DPP URI string for and carry them through the rest of
- * the flow for provisioning the device.
+ * configurator role for an IoT/other device. When provided with a valid DPP URI
+ * string, Settings will open a Wi-Fi selection screen for the user to indicate which network
+ * they would like to configure the device specified in the DPP URI string and
+ * carry them through the rest of the flow for provisioning the device.
* <p>
- * In some cases, a matching Activity may not exist, so ensure you safeguard against this by
- * checking WifiManager.isEasyConnectSupported();
+ * In some cases, a matching Activity may not exist, so ensure to safeguard against this by
+ * checking {@link WifiManager#isEasyConnectSupported()}.
* <p>
* Input: The Intent's data URI specifies bootstrapping information for authenticating and
- * provisioning the peer, with the "DPP" scheme.
+ * provisioning the peer, and uses a "DPP" scheme. The URI should be attached to the intent
+ * using {@link Intent#setData(Uri)}. The calling app can obtain a DPP URI in any
+ * way, e.g. by scanning a QR code or other out-of-band methods. The calling app may also
+ * attach the {@link #EXTRA_EASY_CONNECT_BAND_LIST} extra to provide information
+ * about the bands supported by the enrollee device.
* <p>
- * Output: After {@code startActivityForResult}, the callback {@code onActivityResult} will have
- * resultCode {@link android.app.Activity#RESULT_OK} if Wi-Fi Easy Connect configuration succeeded
- * and the user tapped 'Done' button, or {@link android.app.Activity#RESULT_CANCELED} if operation
- * failed and user tapped 'Cancel'. In case the operation has failed, a status code from {@link
- * android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode} will be returned as
- * Extra {@link #EXTRA_EASY_CONNECT_ERROR_CODE}. Easy Connect R2 Enrollees report additional
- * details about the error they encountered, which will be provided in the {@link
- * #EXTRA_EASY_CONNECT_ATTEMPTED_SSID}, {@link #EXTRA_EASY_CONNECT_CHANNEL_LIST}, and {@link
- * #EXTRA_EASY_CONNECT_BAND_LIST}.
+ * Output: After calling {@link android.app.Activity#startActivityForResult}, the callback
+ * {@code onActivityResult} will have resultCode {@link android.app.Activity#RESULT_OK} if
+ * the Wi-Fi Easy Connect configuration succeeded and the user tapped the 'Done' button, or
+ * {@link android.app.Activity#RESULT_CANCELED} if the operation failed and user tapped the
+ * 'Cancel' button. In case the operation has failed, a status code from
+ * {@link android.net.wifi.EasyConnectStatusCallback} {@code EASY_CONNECT_EVENT_FAILURE_*} will
+ * be returned as an Extra {@link #EXTRA_EASY_CONNECT_ERROR_CODE}. Easy Connect R2
+ * Enrollees report additional details about the error they encountered, which will be
+ * provided in the {@link #EXTRA_EASY_CONNECT_ATTEMPTED_SSID},
+ * {@link #EXTRA_EASY_CONNECT_CHANNEL_LIST}, and {@link #EXTRA_EASY_CONNECT_BAND_LIST}.
*/
@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_PROCESS_WIFI_EASY_CONNECT_URI =
@@ -521,12 +546,15 @@
/**
* Activity Extra: The Easy Connect operation error code
* <p>
- * An extra returned on the result intent received when using the {@link
- * #ACTION_PROCESS_WIFI_EASY_CONNECT_URI} intent to launch the Easy Connect Operation. This
- * extra contains the error code of the operation - one of
- * {@link android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode}.
- * If there is no error, i.e. if the operation returns {@link android.app.Activity#RESULT_OK},
+ * An extra returned on the result intent received when using the
+ * {@link #ACTION_PROCESS_WIFI_EASY_CONNECT_URI} intent to launch the Easy Connect Operation.
+ * This extra contains the integer error code of the operation - one of
+ * {@link android.net.wifi.EasyConnectStatusCallback} {@code EASY_CONNECT_EVENT_FAILURE_*}. If
+ * there is no error, i.e. if the operation returns {@link android.app.Activity#RESULT_OK},
* then this extra is not attached to the result intent.
+ * <p>
+ * Use the {@link Intent#hasExtra(String)} to determine whether the extra is attached and
+ * {@link Intent#getIntExtra(String, int)} to obtain the error code data.
*/
public static final String EXTRA_EASY_CONNECT_ERROR_CODE =
"android.provider.extra.EASY_CONNECT_ERROR_CODE";
@@ -538,11 +566,13 @@
* #ACTION_PROCESS_WIFI_EASY_CONNECT_URI} intent to launch the Easy Connect Operation. This
* extra contains the SSID of the Access Point that the remote Enrollee tried to connect to.
* This value is populated only by remote R2 devices, and only for the following error codes:
- * {@link android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode#EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK}
- * {@link android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode#EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION}.
+ * {@link android.net.wifi.EasyConnectStatusCallback#EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK}
+ * {@link android.net.wifi.EasyConnectStatusCallback#EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION}.
* Therefore, always check if this extra is available using {@link Intent#hasExtra(String)}. If
* there is no error, i.e. if the operation returns {@link android.app.Activity#RESULT_OK}, then
* this extra is not attached to the result intent.
+ * <p>
+ * Use the {@link Intent#getStringExtra(String)} to obtain the SSID.
*/
public static final String EXTRA_EASY_CONNECT_ATTEMPTED_SSID =
"android.provider.extra.EASY_CONNECT_ATTEMPTED_SSID";
@@ -552,13 +582,15 @@
* <p>
* An extra returned on the result intent received when using the {@link
* #ACTION_PROCESS_WIFI_EASY_CONNECT_URI} intent to launch the Easy Connect Operation. This
- * extra contains the list channels the Enrollee used to scan for a network. This value is
+ * extra contains the channel list that the Enrollee scanned for a network. This value is
* populated only by remote R2 devices, and only for the following error code: {@link
- * android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode#EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK}.
+ * android.net.wifi.EasyConnectStatusCallback#EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK}.
* Therefore, always check if this extra is available using {@link Intent#hasExtra(String)}. If
* there is no error, i.e. if the operation returns {@link android.app.Activity#RESULT_OK}, then
* this extra is not attached to the result intent. The list is JSON formatted, as an array
* (Wi-Fi global operating classes) of arrays (Wi-Fi channels).
+ * <p>
+ * Use the {@link Intent#getStringExtra(String)} to obtain the list.
*/
public static final String EXTRA_EASY_CONNECT_CHANNEL_LIST =
"android.provider.extra.EASY_CONNECT_CHANNEL_LIST";
@@ -566,17 +598,31 @@
/**
* Activity Extra: The Band List that the Enrollee supports.
* <p>
- * An extra returned on the result intent received when using the {@link
- * #ACTION_PROCESS_WIFI_EASY_CONNECT_URI} intent to launch the Easy Connect Operation. This
- * extra contains the bands the Enrollee supports, expressed as the Global Operating Class,
- * see Table E-4 in IEEE Std 802.11-2016 -Global operating classes. This value is populated only
- * by remote R2 devices, and only for the following error codes: {@link
- * android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode#EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK}
- * {@link android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode#EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION}
- * {@link android.net.wifi.EasyConnectStatusCallback.EasyConnectFailureStatusCode#EASY_CONNECT_EVENT_FAILURE_ENROLLEE_REJECTED_CONFIGURATION}.
- * Therefore, always check if this extra is available using {@link Intent#hasExtra(String)}. If
- * there is no error, i.e. if the operation returns {@link android.app.Activity#RESULT_OK}, then
- * this extra is not attached to the result intent.
+ * This extra contains the bands the Enrollee supports, expressed as the Global Operating
+ * Class, see Table E-4 in IEEE Std 802.11-2016 Global operating classes. It is used both as
+ * input, to configure the Easy Connect operation and as output of the operation.
+ * <p>
+ * As input: an optional extra to be attached to the
+ * {@link #ACTION_PROCESS_WIFI_EASY_CONNECT_URI}. If attached, it indicates the bands which
+ * the remote device (enrollee, device-to-be-configured) supports. The Settings operation
+ * may take this into account when presenting the user with list of networks configurations
+ * to be used. The calling app may obtain this information in any out-of-band method. The
+ * information should be attached as an array of raw integers - using the
+ * {@link Intent#putExtra(String, int[])}.
+ * <p>
+ * As output: an extra returned on the result intent received when using the
+ * {@link #ACTION_PROCESS_WIFI_EASY_CONNECT_URI} intent to launch the Easy Connect Operation
+ * . This value is populated only by remote R2 devices, and only for the following error
+ * codes:
+ * {@link android.net.wifi.EasyConnectStatusCallback#EASY_CONNECT_EVENT_FAILURE_CANNOT_FIND_NETWORK},
+ * {@link android.net.wifi.EasyConnectStatusCallback#EASY_CONNECT_EVENT_FAILURE_ENROLLEE_AUTHENTICATION},
+ * or
+ * {@link android.net.wifi.EasyConnectStatusCallback#EASY_CONNECT_EVENT_FAILURE_ENROLLEE_REJECTED_CONFIGURATION}.
+ * Therefore, always check if this extra is available using {@link Intent#hasExtra(String)}.
+ * If there is no error, i.e. if the operation returns {@link android.app.Activity#RESULT_OK}
+ * , then this extra is not attached to the result intent.
+ * <p>
+ * Use the {@link Intent#getIntArrayExtra(String)} to obtain the list.
*/
public static final String EXTRA_EASY_CONNECT_BAND_LIST =
"android.provider.extra.EASY_CONNECT_BAND_LIST";
@@ -2104,6 +2150,11 @@
*/
public static final String CALL_METHOD_FLAGS_KEY = "_flags";
+ /**
+ * @hide - String argument extra to the fast-path call()-based requests
+ */
+ public static final String CALL_METHOD_OVERRIDEABLE_BY_RESTORE_KEY = "_overrideable_by_restore";
+
/** @hide - Private call() method to write to 'system' table */
public static final String CALL_METHOD_PUT_SYSTEM = "PUT_system";
@@ -2472,7 +2523,8 @@
}
public boolean putStringForUser(ContentResolver cr, String name, String value,
- String tag, boolean makeDefault, final int userHandle) {
+ String tag, boolean makeDefault, final int userHandle,
+ boolean overrideableByRestore) {
try {
Bundle arg = new Bundle();
arg.putString(Settings.NameValueTable.VALUE, value);
@@ -2483,6 +2535,9 @@
if (makeDefault) {
arg.putBoolean(CALL_METHOD_MAKE_DEFAULT_KEY, true);
}
+ if (overrideableByRestore) {
+ arg.putBoolean(CALL_METHOD_OVERRIDEABLE_BY_RESTORE_KEY, true);
+ }
IContentProvider cp = mProviderHolder.getProvider(cr);
cp.call(cr.getPackageName(), cr.getFeatureId(),
mProviderHolder.mUri.getAuthority(), mCallSetCommand, name, arg);
@@ -2504,13 +2559,14 @@
args.putString(CALL_METHOD_PREFIX_KEY, prefix);
args.putSerializable(CALL_METHOD_FLAGS_KEY, keyValues);
IContentProvider cp = mProviderHolder.getProvider(cr);
- cp.call(cr.getPackageName(), cr.getFeatureId(), mProviderHolder.mUri.getAuthority(),
+ Bundle bundle = cp.call(cr.getPackageName(), cr.getFeatureId(),
+ mProviderHolder.mUri.getAuthority(),
mCallSetAllCommand, null, args);
+ return bundle.getBoolean(KEY_CONFIG_SET_RETURN);
} catch (RemoteException e) {
// Not supported by the remote side
return false;
}
- return true;
}
@UnsupportedAppUsage
@@ -3034,10 +3090,36 @@
return putStringForUser(resolver, name, value, resolver.getUserId());
}
+ /**
+ * Store a name/value pair into the database. Values written by this method will be
+ * overridden if a restore happens in the future.
+ *
+ * @param resolver to access the database with
+ * @param name to store
+ * @param value to associate with the name
+ *
+ * @return true if the value was set, false on database errors
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE)
+ @SystemApi
+ public static boolean putString(@NonNull ContentResolver resolver,
+ @NonNull String name, @Nullable String value, boolean overrideableByRestore) {
+ return putStringForUser(resolver, name, value, resolver.getUserId(),
+ overrideableByRestore);
+ }
+
/** @hide */
@UnsupportedAppUsage
public static boolean putStringForUser(ContentResolver resolver, String name, String value,
int userHandle) {
+ return putStringForUser(resolver, name, value, userHandle,
+ DEFAULT_OVERRIDEABLE_BY_RESTORE);
+ }
+
+ private static boolean putStringForUser(ContentResolver resolver, String name, String value,
+ int userHandle, boolean overrideableByRestore) {
if (MOVED_TO_SECURE.contains(name)) {
Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.System"
+ " to android.provider.Settings.Secure, value is unchanged.");
@@ -3048,7 +3130,8 @@
+ " to android.provider.Settings.Global, value is unchanged.");
return false;
}
- return sNameValueCache.putStringForUser(resolver, name, value, null, false, userHandle);
+ return sNameValueCache.putStringForUser(resolver, name, value, null, false, userHandle,
+ overrideableByRestore);
}
/**
@@ -3372,7 +3455,7 @@
// need to store the adjusted configuration as the initial settings.
Settings.System.putStringForUser(
cr, SYSTEM_LOCALES, outConfig.getLocales().toLanguageTags(),
- userHandle);
+ userHandle, DEFAULT_OVERRIDEABLE_BY_RESTORE);
}
}
}
@@ -3405,7 +3488,8 @@
int userHandle) {
return Settings.System.putFloatForUser(cr, FONT_SCALE, config.fontScale, userHandle) &&
Settings.System.putStringForUser(
- cr, SYSTEM_LOCALES, config.getLocales().toLanguageTags(), userHandle);
+ cr, SYSTEM_LOCALES, config.getLocales().toLanguageTags(), userHandle,
+ DEFAULT_OVERRIDEABLE_BY_RESTORE);
}
/** @hide */
@@ -5093,7 +5177,6 @@
MOVED_TO_GLOBAL.add(Settings.Global.WIFI_P2P_DEVICE_NAME);
MOVED_TO_GLOBAL.add(Settings.Global.WIFI_SAVED_STATE);
MOVED_TO_GLOBAL.add(Settings.Global.WIFI_SUPPLICANT_SCAN_INTERVAL_MS);
- MOVED_TO_GLOBAL.add(Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED);
MOVED_TO_GLOBAL.add(Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED);
MOVED_TO_GLOBAL.add(Settings.Global.WIFI_ENHANCED_AUTO_JOIN);
MOVED_TO_GLOBAL.add(Settings.Global.WIFI_NETWORK_SHOW_RSSI);
@@ -5208,6 +5291,24 @@
}
/**
+ * Store a name/value pair into the database. Values written by this method will be
+ * overridden if a restore happens in the future.
+ *
+ * @param resolver to access the database with
+ * @param name to store
+ * @param value to associate with the name
+ * @return true if the value was set, false on database errors
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE)
+ public static boolean putString(ContentResolver resolver, String name,
+ String value, boolean overrideableByRestore) {
+ return putStringForUser(resolver, name, value, /* tag */ null, /* makeDefault */ false,
+ resolver.getUserId(), overrideableByRestore);
+ }
+
+ /**
* Store a name/value pair into the database.
* @param resolver to access the database with
* @param name to store
@@ -5222,22 +5323,23 @@
@UnsupportedAppUsage
public static boolean putStringForUser(ContentResolver resolver, String name, String value,
int userHandle) {
- return putStringForUser(resolver, name, value, null, false, userHandle);
+ return putStringForUser(resolver, name, value, null, false, userHandle,
+ DEFAULT_OVERRIDEABLE_BY_RESTORE);
}
/** @hide */
@UnsupportedAppUsage
public static boolean putStringForUser(@NonNull ContentResolver resolver,
@NonNull String name, @Nullable String value, @Nullable String tag,
- boolean makeDefault, @UserIdInt int userHandle) {
+ boolean makeDefault, @UserIdInt int userHandle, boolean overrideableByRestore) {
if (MOVED_TO_GLOBAL.contains(name)) {
Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.Secure"
+ " to android.provider.Settings.Global");
return Global.putStringForUser(resolver, name, value,
- tag, makeDefault, userHandle);
+ tag, makeDefault, userHandle, DEFAULT_OVERRIDEABLE_BY_RESTORE);
}
return sNameValueCache.putStringForUser(resolver, name, value, tag,
- makeDefault, userHandle);
+ makeDefault, userHandle, overrideableByRestore);
}
/**
@@ -5286,7 +5388,7 @@
@NonNull String name, @Nullable String value, @Nullable String tag,
boolean makeDefault) {
return putStringForUser(resolver, name, value, tag, makeDefault,
- resolver.getUserId());
+ resolver.getUserId(), DEFAULT_OVERRIDEABLE_BY_RESTORE);
}
/**
@@ -6376,6 +6478,15 @@
"accessibility_button_target_component";
/**
+ * The system class name of magnification controller which is a target to be toggled via
+ * accessibility shortcut or accessibility button.
+ *
+ * @hide
+ */
+ public static final String ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER =
+ "com.android.server.accessibility.MagnificationController";
+
+ /**
* If touch exploration is enabled.
*/
public static final String TOUCH_EXPLORATION_ENABLED = "touch_exploration_enabled";
@@ -8767,9 +8878,9 @@
* added to both AIRPLANE_MODE_RADIOS and AIRPLANE_MODE_TOGGLEABLE_RADIOS, then Wifi
* will be turned off when entering airplane mode, but the user will be able to reenable
* Wifi in the Settings app.
- *
- * {@hide}
+ * @hide
*/
+ @SystemApi
public static final String AIRPLANE_MODE_TOGGLEABLE_RADIOS = "airplane_mode_toggleable_radios";
/**
@@ -9696,6 +9807,8 @@
* is interpreted as |false|.
* @hide
*/
+ @SystemApi
+ @TestApi
public static final String TETHER_OFFLOAD_DISABLED = "tether_offload_disabled";
/**
@@ -9939,24 +10052,17 @@
* Setting to allow scans to be enabled even wifi is turned off for connectivity.
* @hide
*/
+ @SystemApi
public static final String WIFI_SCAN_ALWAYS_AVAILABLE =
"wifi_scan_always_enabled";
/**
- * The interval in milliseconds at which wifi rtt ranging requests will be throttled when
- * they are coming from the background.
- *
- * @hide
- */
- public static final String WIFI_RTT_BACKGROUND_EXEC_GAP_MS =
- "wifi_rtt_background_exec_gap_ms";
-
- /**
* Indicate whether factory reset request is pending.
*
* Type: int (0 for false, 1 for true)
* @hide
*/
+ @SystemApi
public static final String WIFI_P2P_PENDING_FACTORY_RESET =
"wifi_p2p_pending_factory_reset";
@@ -9966,6 +10072,7 @@
* Type: int (0 for false, 1 for true)
* @hide
*/
+ @SystemApi
public static final String SOFT_AP_TIMEOUT_ENABLED = "soft_ap_timeout_enabled";
/**
@@ -10009,10 +10116,10 @@
* enabled state.
* @hide
*/
+ @SystemApi
public static final String NETWORK_RECOMMENDATIONS_ENABLED =
"network_recommendations_enabled";
-
/**
* Which package name to use for network recommendations. If null, network recommendations
* will neither be requested nor accepted.
@@ -10037,17 +10144,6 @@
public static final String USE_OPEN_WIFI_PACKAGE = "use_open_wifi_package";
/**
- * The number of milliseconds the {@link com.android.server.NetworkScoreService}
- * will give a recommendation request to complete before returning a default response.
- *
- * Type: long
- * @hide
- * @deprecated to be removed
- */
- public static final String NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS =
- "network_recommendation_request_timeout_ms";
-
- /**
* The expiration time in milliseconds for the {@link android.net.WifiKey} request cache in
* {@link com.android.server.wifi.RecommendedNetworkEvaluator}.
*
@@ -10065,6 +10161,7 @@
* Type: int (0 for false, 1 for true)
* @hide
*/
+ @SystemApi
public static final String WIFI_SCAN_THROTTLE_ENABLED = "wifi_scan_throttle_enabled";
/**
@@ -10173,18 +10270,11 @@
"wifi_watchdog_poor_network_test_enabled";
/**
- * Setting to turn on suspend optimizations at screen off on Wi-Fi. Enabled by default and
- * needs to be set to 0 to disable it.
- * @hide
- */
- public static final String WIFI_SUSPEND_OPTIMIZATIONS_ENABLED =
- "wifi_suspend_optimizations_enabled";
-
- /**
* Setting to enable verbose logging in Wi-Fi; disabled by default, and setting to 1
* will enable it. In the future, additional values may be supported.
* @hide
*/
+ @SystemApi
public static final String WIFI_VERBOSE_LOGGING_ENABLED =
"wifi_verbose_logging_enabled";
@@ -10210,69 +10300,10 @@
* Errors in the parameters will cause the entire setting to be ignored.
* @hide
*/
+ @SystemApi
public static final String WIFI_SCORE_PARAMS =
"wifi_score_params";
- /**
- * Setting to enable logging WifiIsUnusableEvent in metrics
- * which gets triggered when wifi becomes unusable.
- * Disabled by default, and setting it to 1 will enable it.
- * @hide
- */
- public static final String WIFI_IS_UNUSABLE_EVENT_METRICS_ENABLED =
- "wifi_is_unusable_event_metrics_enabled";
-
- /**
- * The minimum number of txBad the framework has to observe
- * to trigger a wifi data stall.
- * @hide
- */
- public static final String WIFI_DATA_STALL_MIN_TX_BAD =
- "wifi_data_stall_min_tx_bad";
-
- /**
- * The minimum number of txSuccess the framework has to observe
- * to trigger a wifi data stall when rxSuccess is 0.
- * @hide
- */
- public static final String WIFI_DATA_STALL_MIN_TX_SUCCESS_WITHOUT_RX =
- "wifi_data_stall_min_tx_success_without_rx";
-
- /**
- * Setting to enable logging Wifi LinkSpeedCounts in metrics.
- * Disabled by default, and setting it to 1 will enable it.
- * @hide
- */
- public static final String WIFI_LINK_SPEED_METRICS_ENABLED =
- "wifi_link_speed_metrics_enabled";
-
- /**
- * Setting to enable the PNO frequency culling optimization.
- * Disabled by default, and setting it to 1 will enable it.
- * The value is boolean (0 or 1).
- * @hide
- */
- public static final String WIFI_PNO_FREQUENCY_CULLING_ENABLED =
- "wifi_pno_frequency_culling_enabled";
-
- /**
- * Setting to enable including recency information when determining pno network priorities.
- * Disabled by default, and setting it to 1 will enable it.
- * The value is boolean (0 or 1).
- * @hide
- */
- public static final String WIFI_PNO_RECENCY_SORTING_ENABLED =
- "wifi_pno_recency_sorting_enabled";
-
- /**
- * Setting to enable the Wi-Fi link probing.
- * Enabled by default, and setting it to 0 will disable it.
- * The value is boolean (0 or 1).
- * @hide
- */
- public static final String WIFI_LINK_PROBING_ENABLED =
- "wifi_link_probing_enabled";
-
/**
* The maximum number of times we will retry a connection to an access
* point for which we have failed in acquiring an IP address from DHCP.
@@ -10312,6 +10343,7 @@
* The Wi-Fi peer-to-peer device name
* @hide
*/
+ @SystemApi
public static final String WIFI_P2P_DEVICE_NAME = "wifi_p2p_device_name";
/**
@@ -12457,16 +12489,17 @@
/**
* Whether the Volte is enabled. If this setting is not set then we use the Carrier Config
- * value {@link CarrierConfigManager#KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL}.
+ * value
+ * {@link android.telephony.CarrierConfigManager#KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL}.
* <p>
* Type: int (0 for false, 1 for true)
* @hide
- * @deprecated Use {@link android.telephony.SubscriptionManager#ENHANCED_4G_MODE_ENABLED}
+ * @deprecated Use {@link android.provider.Telephony.SimInfo#ENHANCED_4G_MODE_ENABLED}
* instead.
*/
@Deprecated
public static final String ENHANCED_4G_MODE_ENABLED =
- SubscriptionManager.ENHANCED_4G_MODE_ENABLED;
+ Telephony.SimInfo.ENHANCED_4G_MODE_ENABLED;
/**
* Whether VT (Video Telephony over IMS) is enabled
@@ -12474,10 +12507,10 @@
* Type: int (0 for false, 1 for true)
*
* @hide
- * @deprecated Use {@link android.telephony.SubscriptionManager#VT_IMS_ENABLED} instead.
+ * @deprecated Use {@link android.provider.Telephony.SimInfo#VT_IMS_ENABLED} instead.
*/
@Deprecated
- public static final String VT_IMS_ENABLED = SubscriptionManager.VT_IMS_ENABLED;
+ public static final String VT_IMS_ENABLED = Telephony.SimInfo.VT_IMS_ENABLED;
/**
* Whether WFC is enabled
@@ -12485,10 +12518,10 @@
* Type: int (0 for false, 1 for true)
*
* @hide
- * @deprecated Use {@link android.telephony.SubscriptionManager#WFC_IMS_ENABLED} instead.
+ * @deprecated Use {@link android.provider.Telephony.SimInfo#WFC_IMS_ENABLED} instead.
*/
@Deprecated
- public static final String WFC_IMS_ENABLED = SubscriptionManager.WFC_IMS_ENABLED;
+ public static final String WFC_IMS_ENABLED = Telephony.SimInfo.WFC_IMS_ENABLED;
/**
* WFC mode on home/non-roaming network.
@@ -12496,10 +12529,10 @@
* Type: int - 2=Wi-Fi preferred, 1=Cellular preferred, 0=Wi-Fi only
*
* @hide
- * @deprecated Use {@link android.telephony.SubscriptionManager#WFC_IMS_MODE} instead.
+ * @deprecated Use {@link android.provider.Telephony.SimInfo#WFC_IMS_MODE} instead.
*/
@Deprecated
- public static final String WFC_IMS_MODE = SubscriptionManager.WFC_IMS_MODE;
+ public static final String WFC_IMS_MODE = Telephony.SimInfo.WFC_IMS_MODE;
/**
* WFC mode on roaming network.
@@ -12507,11 +12540,11 @@
* Type: int - see {@link #WFC_IMS_MODE} for values
*
* @hide
- * @deprecated Use {@link android.telephony.SubscriptionManager#WFC_IMS_ROAMING_MODE}
+ * @deprecated Use {@link android.provider.Telephony.SimInfo#WFC_IMS_ROAMING_MODE}
* instead.
*/
@Deprecated
- public static final String WFC_IMS_ROAMING_MODE = SubscriptionManager.WFC_IMS_ROAMING_MODE;
+ public static final String WFC_IMS_ROAMING_MODE = Telephony.SimInfo.WFC_IMS_ROAMING_MODE;
/**
* Whether WFC roaming is enabled
@@ -12519,12 +12552,12 @@
* Type: int (0 for false, 1 for true)
*
* @hide
- * @deprecated Use {@link android.telephony.SubscriptionManager#WFC_IMS_ROAMING_ENABLED}
+ * @deprecated Use {@link android.provider.Telephony.SimInfo#WFC_IMS_ROAMING_ENABLED}
* instead
*/
@Deprecated
public static final String WFC_IMS_ROAMING_ENABLED =
- SubscriptionManager.WFC_IMS_ROAMING_ENABLED;
+ Telephony.SimInfo.WFC_IMS_ROAMING_ENABLED;
/**
* Whether user can enable/disable LTE as a preferred network. A carrier might control
@@ -12966,7 +12999,29 @@
*/
public static boolean putString(ContentResolver resolver,
String name, String value) {
- return putStringForUser(resolver, name, value, null, false, resolver.getUserId());
+ return putStringForUser(resolver, name, value, null, false, resolver.getUserId(),
+ DEFAULT_OVERRIDEABLE_BY_RESTORE);
+ }
+
+ /**
+ * Store a name/value pair into the database.
+ *
+ * @param resolver to access the database with
+ * @param name to store
+ * @param value to associate with the name
+ * @param tag to associated with the setting.
+ * @param makeDefault whether to make the value the default one.
+ * @param overrideableByRestore whether restore can override this value
+ * @return true if the value was set, false on database errors
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE)
+ public static boolean putString(@NonNull ContentResolver resolver,
+ @NonNull String name, @Nullable String value, @Nullable String tag,
+ boolean makeDefault, boolean overrideableByRestore) {
+ return putStringForUser(resolver, name, value, tag, makeDefault,
+ resolver.getUserId(), overrideableByRestore);
}
/**
@@ -13015,7 +13070,7 @@
@NonNull String name, @Nullable String value, @Nullable String tag,
boolean makeDefault) {
return putStringForUser(resolver, name, value, tag, makeDefault,
- resolver.getUserId());
+ resolver.getUserId(), DEFAULT_OVERRIDEABLE_BY_RESTORE);
}
/**
@@ -13077,13 +13132,14 @@
@UnsupportedAppUsage
public static boolean putStringForUser(ContentResolver resolver,
String name, String value, int userHandle) {
- return putStringForUser(resolver, name, value, null, false, userHandle);
+ return putStringForUser(resolver, name, value, null, false, userHandle,
+ DEFAULT_OVERRIDEABLE_BY_RESTORE);
}
/** @hide */
public static boolean putStringForUser(@NonNull ContentResolver resolver,
@NonNull String name, @Nullable String value, @Nullable String tag,
- boolean makeDefault, @UserIdInt int userHandle) {
+ boolean makeDefault, @UserIdInt int userHandle, boolean overrideableByRestore) {
if (LOCAL_LOGV) {
Log.v(TAG, "Global.putString(name=" + name + ", value=" + value
+ " for " + userHandle);
@@ -13093,10 +13149,10 @@
Log.w(TAG, "Setting " + name + " has moved from android.provider.Settings.Global"
+ " to android.provider.Settings.Secure, value is unchanged.");
return Secure.putStringForUser(resolver, name, value, tag,
- makeDefault, userHandle);
+ makeDefault, userHandle, overrideableByRestore);
}
return sNameValueCache.putStringForUser(resolver, name, value, tag,
- makeDefault, userHandle);
+ makeDefault, userHandle, overrideableByRestore);
}
/**
@@ -13711,14 +13767,6 @@
"backup_agent_timeout_parameters";
/**
- * Whether the backup system service supports multiple users (0 = disabled, 1 = enabled). If
- * disabled, the service will only be active for the system user.
- *
- * @hide
- */
- public static final String BACKUP_MULTI_USER_ENABLED = "backup_multi_user_enabled";
-
- /**
* Blacklist of GNSS satellites.
*
* This is a list of integers separated by commas to represent pairs of (constellation,
@@ -13971,7 +14019,8 @@
static boolean putString(@NonNull ContentResolver resolver, @NonNull String namespace,
@NonNull String name, @Nullable String value, boolean makeDefault) {
return sNameValueCache.putStringForUser(resolver, createCompositeName(namespace, name),
- value, null, makeDefault, resolver.getUserId());
+ value, null, makeDefault, resolver.getUserId(),
+ DEFAULT_OVERRIDEABLE_BY_RESTORE);
}
/**
@@ -13987,14 +14036,18 @@
*/
@RequiresPermission(Manifest.permission.WRITE_DEVICE_CONFIG)
static boolean setStrings(@NonNull ContentResolver resolver, @NonNull String namespace,
- @NonNull Map<String, String> keyValues) {
+ @NonNull Map<String, String> keyValues) throws DeviceConfig.BadConfigException {
HashMap<String, String> compositeKeyValueMap = new HashMap<>(keyValues.keySet().size());
for (Map.Entry<String, String> entry : keyValues.entrySet()) {
compositeKeyValueMap.put(
createCompositeName(namespace, entry.getKey()), entry.getValue());
}
- return sNameValueCache.setStringsForPrefix(resolver, createPrefix(namespace),
- compositeKeyValueMap);
+ // If can't set given configuration that means it's bad
+ if (!sNameValueCache.setStringsForPrefix(resolver, createPrefix(namespace),
+ compositeKeyValueMap)) {
+ throw new DeviceConfig.BadConfigException();
+ }
+ return true;
}
/**
@@ -14402,6 +14455,42 @@
};
/**
+ * Activity Action: Show screen for controlling which apps have access to manage external
+ * storage.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you safeguard against this.
+ * <p>
+ * If you want to control a specific app's access to manage external storage, use
+ * {@link #ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION} instead.
+ * <p>
+ * Output: Nothing.
+ * @see #ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION =
+ "android.settings.MANAGE_ALL_FILES_ACCESS_PERMISSION";
+
+ /**
+ * Activity Action: Show screen for controlling if the app specified in the data URI of the
+ * intent can manage external storage.
+ * <p>
+ * Launching the corresponding activity requires the permission
+ * {@link Manifest.permission#MANAGE_EXTERNAL_STORAGE}.
+ * <p>
+ * In some cases, a matching Activity may not exist, so ensure you safeguard against this.
+ * <p>
+ * Input: The Intent's data URI MUST specify the application package name whose ability of
+ * managing external storage you want to control.
+ * For example "package:com.my.app".
+ * <p>
+ * Output: Nothing.
+ * @see #ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION
+ */
+ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+ public static final String ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION =
+ "android.settings.MANAGE_APP_ALL_FILES_ACCESS_PERMISSION";
+
+ /**
* Performs a strict and comprehensive check of whether a calling package is allowed to
* write/modify system settings, as the condition differs for pre-M, M+, and
* privileged/preinstalled apps. If the provided uid does not match the
@@ -14427,8 +14516,9 @@
* current time.
* @hide
*/
- public static boolean checkAndNoteWriteSettingsOperation(Context context, int uid,
- String callingPackage, boolean throwException) {
+ @SystemApi
+ public static boolean checkAndNoteWriteSettingsOperation(@NonNull Context context, int uid,
+ @NonNull String callingPackage, boolean throwException) {
return isCallingPackageAllowedToPerformAppOpsProtectedOperation(context, uid,
callingPackage, throwException, AppOpsManager.OP_WRITE_SETTINGS,
PM_WRITE_SETTINGS, true);
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index c95e132..f369064 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -24,8 +24,8 @@
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.compat.annotation.ChangeId;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentValues;
@@ -36,12 +36,15 @@
import android.database.sqlite.SqliteWrapper;
import android.net.Uri;
import android.os.Build;
+import android.os.Bundle;
import android.os.Parcel;
+import android.telephony.CarrierConfigManager;
import android.telephony.Rlog;
import android.telephony.ServiceState;
import android.telephony.SmsMessage;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import android.telephony.UiccAccessRule;
import android.text.TextUtils;
import android.util.Patterns;
@@ -4105,10 +4108,112 @@
public static final Uri MESSAGE_HISTORY_URI = Uri.parse("content://cellbroadcasts/history");
/**
+ * The authority for the legacy cellbroadcast provider.
+ * This is used for OEM data migration. OEMs want to migrate message history or
+ * sharepreference data to mainlined cellbroadcastreceiver app, should have a
+ * contentprovider with authority: cellbroadcast-legacy. Mainlined cellbroadcastreceiver
+ * will interact with this URI to retrieve data and persists to mainlined cellbroadcast app.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final @NonNull String AUTHORITY_LEGACY = "cellbroadcast-legacy";
+
+ /**
+ * A content:// style uri to the authority for the legacy cellbroadcast provider.
+ * @hide
+ */
+ @SystemApi
+ public static final @NonNull Uri AUTHORITY_LEGACY_URI =
+ Uri.parse("content://cellbroadcast-legacy");
+
+ /**
+ * Method name to {@link android.content.ContentProvider#call(String, String, Bundle)
+ * for {@link #AUTHORITY_LEGACY}. Used to query cellbroadcast {@link Preference},
+ * containing following supported entries
+ * <ul>
+ * <li>{@link #ENABLE_AREA_UPDATE_INFO_PREF}</li>
+ * <li>{@link #ENABLE_TEST_ALERT_PREF}</li>
+ * <li>{@link #ENABLE_STATE_LOCAL_TEST_PREF}</li>
+ * <li>{@link #ENABLE_PUBLIC_SAFETY_PREF}</li>
+ * <li>{@link #ENABLE_CMAS_AMBER_PREF}</li>
+ * <li>{@link #ENABLE_CMAS_SEVERE_THREAT_PREF}</li>
+ * <li>{@link #ENABLE_CMAS_EXTREME_THREAT_PREF}</li>
+ * <li>{@link #ENABLE_CMAS_PRESIDENTIAL_PREF}</li>
+ * <li>{@link #ENABLE_ALERT_VIBRATION_PREF}</li>
+ * <li>{@link #ENABLE_EMERGENCY_PERF}</li>
+ * <li>{@link #ENABLE_CMAS_IN_SECOND_LANGUAGE_PREF}</li>
+ * </ul>
+ * @hide
+ */
+ @SystemApi
+ public static final @NonNull String CALL_METHOD_GET_PREFERENCE = "get_preference";
+
+ /**
+ * Arg name to {@link android.content.ContentProvider#call(String, String, Bundle)}
+ * for {@link #AUTHORITY_LEGACY}.
+ * Contains all supported shared preferences for cellbroadcast.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final class Preference {
+ /**
+ * Not Instantiatable.
+ * @hide
+ */
+ private Preference() {}
+
+ /** Preference to enable area update info alert */
+ public static final @NonNull String ENABLE_AREA_UPDATE_INFO_PREF =
+ "enable_area_update_info_alerts";
+
+ /** Preference to enable test alert */
+ public static final @NonNull String ENABLE_TEST_ALERT_PREF =
+ "enable_test_alerts";
+
+ /** Preference to enable state local test alert */
+ public static final @NonNull String ENABLE_STATE_LOCAL_TEST_PREF
+ = "enable_state_local_test_alerts";
+
+ /** Preference to enable public safety alert */
+ public static final @NonNull String ENABLE_PUBLIC_SAFETY_PREF
+ = "enable_public_safety_messages";
+
+ /** Preference to enable amber alert */
+ public static final @NonNull String ENABLE_CMAS_AMBER_PREF
+ = "enable_cmas_amber_alerts";
+
+ /** Preference to enable severe threat alert */
+ public static final @NonNull String ENABLE_CMAS_SEVERE_THREAT_PREF
+ = "enable_cmas_severe_threat_alerts";
+
+ /** Preference to enable extreme threat alert */
+ public static final @NonNull String ENABLE_CMAS_EXTREME_THREAT_PREF =
+ "enable_cmas_extreme_threat_alerts";
+
+ /** Preference to enable presidential alert */
+ public static final @NonNull String ENABLE_CMAS_PRESIDENTIAL_PREF =
+ "enable_cmas_presidential_alerts";
+
+ /** Preference to enable alert vibration */
+ public static final @NonNull String ENABLE_ALERT_VIBRATION_PREF =
+ "enable_alert_vibrate";
+
+ /** Preference to enable emergency alert */
+ public static final @NonNull String ENABLE_EMERGENCY_PERF =
+ "enable_emergency_alerts";
+
+ /** Preference to enable receive alerts in second language */
+ public static final @NonNull String ENABLE_CMAS_IN_SECOND_LANGUAGE_PREF =
+ "receive_cmas_in_second_language";
+ }
+
+ /**
* The subscription which received this cell broadcast message.
* <P>Type: INTEGER</P>
*/
- public static final String SUB_ID = "sub_id";
+ public static final String SUBSCRIPTION_ID = "sub_id";
/**
* The slot which received this cell broadcast message.
@@ -4366,7 +4471,7 @@
public static final String[] QUERY_COLUMNS_FWK = {
_ID,
SLOT_INDEX,
- SUB_ID,
+ SUBSCRIPTION_ID,
GEOGRAPHICAL_SCOPE,
PLMN,
LAC,
@@ -4872,5 +4977,402 @@
*/
@NonNull
public static final Uri CONTENT_URI = Uri.parse("content://telephony/siminfo");
+
+ /**
+ * TelephonyProvider unique key column name is the subscription id.
+ * <P>Type: TEXT (String)</P>
+ */
+ public static final String UNIQUE_KEY_SUBSCRIPTION_ID = "_id";
+
+ /**
+ * TelephonyProvider column name for a unique identifier for the subscription within the
+ * specific subscription type. For example, it contains SIM ICC Identifier subscriptions
+ * on Local SIMs. and Mac-address for Remote-SIM Subscriptions for Bluetooth devices.
+ * <P>Type: TEXT (String)</P>
+ */
+ public static final String ICC_ID = "icc_id";
+
+ /**
+ * TelephonyProvider column name for user SIM_SlOT_INDEX
+ * <P>Type: INTEGER (int)</P>
+ */
+ public static final String SIM_SLOT_INDEX = "sim_id";
+
+ /**
+ * SIM is not inserted
+ */
+ public static final int SIM_NOT_INSERTED = -1;
+
+ /**
+ * TelephonyProvider column name Subscription-type.
+ * <P>Type: INTEGER (int)</P> {@link #SUBSCRIPTION_TYPE_LOCAL_SIM} for Local-SIM
+ * Subscriptions, {@link #SUBSCRIPTION_TYPE_REMOTE_SIM} for Remote-SIM Subscriptions.
+ * Default value is 0.
+ */
+ public static final String SUBSCRIPTION_TYPE = "subscription_type";
+
+ /**
+ * This constant is to designate a subscription as a Local-SIM Subscription.
+ * <p> A Local-SIM can be a physical SIM inserted into a sim-slot in the device, or eSIM on
+ * the device.
+ * </p>
+ */
+ public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0;
+
+ /**
+ * This constant is to designate a subscription as a Remote-SIM Subscription.
+ * <p>
+ * A Remote-SIM subscription is for a SIM on a phone connected to this device via some
+ * connectivity mechanism, for example bluetooth. Similar to Local SIM, this subscription
+ * can be used for SMS, Voice and data by proxying data through the connected device.
+ * Certain data of the SIM, such as IMEI, are not accessible for Remote SIMs.
+ * </p>
+ *
+ * <p>
+ * A Remote-SIM is available only as long the phone stays connected to this device.
+ * When the phone disconnects, Remote-SIM subscription is removed from this device and is
+ * no longer known. All data associated with the subscription, such as stored SMS, call
+ * logs, contacts etc, are removed from this device.
+ * </p>
+ *
+ * <p>
+ * If the phone re-connects to this device, a new Remote-SIM subscription is created for
+ * the phone. The Subscription Id associated with the new subscription is different from
+ * the Subscription Id of the previous Remote-SIM subscription created (and removed) for the
+ * phone; i.e., new Remote-SIM subscription treats the reconnected phone as a Remote-SIM
+ * that was never seen before.
+ * </p>
+ */
+ public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1;
+
+ /**
+ * TelephonyProvider column name data_enabled_override_rules.
+ * It's a list of rules for overriding data enabled settings. The syntax is
+ * For example, "mms=nonDefault" indicates enabling data for mms in non-default
+ * subscription.
+ * "default=nonDefault&inVoiceCall" indicates enabling data for internet in non-default
+ * subscription and while is in voice call.
+ *
+ * Default value is empty string.
+ */
+ public static final String DATA_ENABLED_OVERRIDE_RULES = "data_enabled_override_rules";
+
+ /**
+ * TelephonyProvider column name for user displayed name.
+ * <P>Type: TEXT (String)</P>
+ */
+ public static final String DISPLAY_NAME = "display_name";
+
+ /**
+ * TelephonyProvider column name for the service provider name for the SIM.
+ * <P>Type: TEXT (String)</P>
+ */
+ public static final String CARRIER_NAME = "carrier_name";
+
+ /**
+ * TelephonyProvider column name for source of the user displayed name.
+ * <P>Type: INT (int)</P> with one of the NAME_SOURCE_XXXX values below
+ */
+ public static final String NAME_SOURCE = "name_source";
+
+ /** The name_source is the default, which is from the carrier id. */
+ public static final int NAME_SOURCE_DEFAULT = 0;
+
+ /**
+ * The name_source is from SIM EF_SPN.
+ */
+ public static final int NAME_SOURCE_SIM_SPN = 1;
+
+ /**
+ * The name_source is from user input
+ */
+ public static final int NAME_SOURCE_USER_INPUT = 2;
+
+ /**
+ * The name_source is carrier (carrier app, carrier config, etc.)
+ */
+ public static final int NAME_SOURCE_CARRIER = 3;
+
+ /**
+ * The name_source is from SIM EF_PNN.
+ */
+ public static final int NAME_SOURCE_SIM_PNN = 4;
+
+ /**
+ * TelephonyProvider column name for the color of a SIM.
+ * <P>Type: INTEGER (int)</P>
+ */
+ public static final String COLOR = "color";
+
+ /** TelephonyProvider column name for the default color of a SIM {@hide} */
+ public static final int COLOR_DEFAULT = 0;
+
+ /**
+ * TelephonyProvider column name for the phone number of a SIM.
+ * <P>Type: TEXT (String)</P>
+ */
+ public static final String NUMBER = "number";
+
+ /**
+ * TelephonyProvider column name for the number display format of a SIM.
+ * <P>Type: INTEGER (int)</P>
+ * @hide
+ */
+ public static final String DISPLAY_NUMBER_FORMAT = "display_number_format";
+
+ /**
+ * TelephonyProvider column name for the default display format of a SIM
+ * @hide
+ */
+ public static final int DISPLAY_NUMBER_DEFAULT = 1;
+
+ /**
+ * TelephonyProvider column name for whether data roaming is enabled.
+ * <P>Type: INTEGER (int)</P>
+ */
+ public static final String DATA_ROAMING = "data_roaming";
+
+ /** Indicates that data roaming is enabled for a subscription */
+ public static final int DATA_ROAMING_ENABLE = 1;
+
+ /** Indicates that data roaming is disabled for a subscription */
+ public static final int DATA_ROAMING_DISABLE = 0;
+
+ /** TelephonyProvider column name for default data roaming setting: disable */
+ public static final int DATA_ROAMING_DEFAULT = DATA_ROAMING_DISABLE;
+
+ /**
+ * TelephonyProvider column name for subscription carrier id.
+ * @see TelephonyManager#getSimCarrierId()
+ * <p>Type: INTEGER (int) </p>
+ */
+ public static final String CARRIER_ID = "carrier_id";
+
+ /**
+ * A comma-separated list of EHPLMNs associated with the subscription
+ * <P>Type: TEXT (String)</P>
+ */
+ public static final String EHPLMNS = "ehplmns";
+
+ /**
+ * A comma-separated list of HPLMNs associated with the subscription
+ * <P>Type: TEXT (String)</P>
+ */
+ public static final String HPLMNS = "hplmns";
+
+ /**
+ * TelephonyProvider column name for the MCC associated with a SIM, stored as a string.
+ * <P>Type: TEXT (String)</P>
+ */
+ public static final String MCC_STRING = "mcc_string";
+
+ /**
+ * TelephonyProvider column name for the MNC associated with a SIM, stored as a string.
+ * <P>Type: TEXT (String)</P>
+ */
+ public static final String MNC_STRING = "mnc_string";
+
+ /**
+ * TelephonyProvider column name for the MCC associated with a SIM.
+ * <P>Type: INTEGER (int)</P>
+ */
+ public static final String MCC = "mcc";
+
+ /**
+ * TelephonyProvider column name for the MNC associated with a SIM.
+ * <P>Type: INTEGER (int)</P>
+ */
+ public static final String MNC = "mnc";
+
+ /**
+ * TelephonyProvider column name for the iso country code associated with a SIM.
+ * <P>Type: TEXT (String)</P>
+ */
+ public static final String ISO_COUNTRY_CODE = "iso_country_code";
+
+ /**
+ * TelephonyProvider column name for the sim provisioning status associated with a SIM.
+ * <P>Type: INTEGER (int)</P>
+ * @hide
+ */
+ public static final String SIM_PROVISIONING_STATUS = "sim_provisioning_status";
+
+ /** The sim is provisioned {@hide} */
+ public static final int SIM_PROVISIONED = 0;
+
+ /**
+ * TelephonyProvider column name for whether a subscription is embedded (that is, present on
+ * an eSIM).
+ * <p>Type: INTEGER (int), 1 for embedded or 0 for non-embedded.
+ */
+ public static final String IS_EMBEDDED = "is_embedded";
+
+ /**
+ * TelephonyProvider column name for SIM card identifier. For UICC card it is the ICCID of
+ * the current enabled profile on the card, while for eUICC card it is the EID of the card.
+ * <P>Type: TEXT (String)</P>
+ */
+ public static final String CARD_ID = "card_id";
+
+ /**
+ * TelephonyProvider column name for the encoded {@link UiccAccessRule}s from
+ * {@link UiccAccessRule#encodeRules}. Only present if {@link #IS_EMBEDDED} is 1.
+ * <p>TYPE: BLOB
+ */
+ public static final String ACCESS_RULES = "access_rules";
+
+ /**
+ * TelephonyProvider column name for the encoded {@link UiccAccessRule}s from
+ * {@link UiccAccessRule#encodeRules} but for the rules that come from CarrierConfigs.
+ * Only present if there are access rules in CarrierConfigs
+ * <p>TYPE: BLOB
+ */
+ public static final String ACCESS_RULES_FROM_CARRIER_CONFIGS =
+ "access_rules_from_carrier_configs";
+
+ /**
+ * TelephonyProvider column name identifying whether an embedded subscription is on a
+ * removable card. Such subscriptions are marked inaccessible as soon as the current card
+ * is removed. Otherwise, they will remain accessible unless explicitly deleted. Only
+ * present if {@link #IS_EMBEDDED} is 1.
+ * <p>TYPE: INTEGER (int), 1 for removable or 0 for non-removable.
+ */
+ public static final String IS_REMOVABLE = "is_removable";
+
+ /** TelephonyProvider column name for extreme threat in CB settings */
+ public static final String CB_EXTREME_THREAT_ALERT = "enable_cmas_extreme_threat_alerts";
+
+ /** TelephonyProvider column name for severe threat in CB settings */
+ public static final String CB_SEVERE_THREAT_ALERT = "enable_cmas_severe_threat_alerts";
+
+ /** TelephonyProvider column name for amber alert in CB settings */
+ public static final String CB_AMBER_ALERT = "enable_cmas_amber_alerts";
+
+ /** TelephonyProvider column name for emergency alert in CB settings */
+ public static final String CB_EMERGENCY_ALERT = "enable_emergency_alerts";
+
+ /** TelephonyProvider column name for alert sound duration in CB settings */
+ public static final String CB_ALERT_SOUND_DURATION = "alert_sound_duration";
+
+ /** TelephonyProvider column name for alert reminder interval in CB settings */
+ public static final String CB_ALERT_REMINDER_INTERVAL = "alert_reminder_interval";
+
+ /** TelephonyProvider column name for enabling vibrate in CB settings */
+ public static final String CB_ALERT_VIBRATE = "enable_alert_vibrate";
+
+ /** TelephonyProvider column name for enabling alert speech in CB settings */
+ public static final String CB_ALERT_SPEECH = "enable_alert_speech";
+
+ /** TelephonyProvider column name for ETWS test alert in CB settings */
+ public static final String CB_ETWS_TEST_ALERT = "enable_etws_test_alerts";
+
+ /** TelephonyProvider column name for enable channel50 alert in CB settings */
+ public static final String CB_CHANNEL_50_ALERT = "enable_channel_50_alerts";
+
+ /** TelephonyProvider column name for CMAS test alert in CB settings */
+ public static final String CB_CMAS_TEST_ALERT = "enable_cmas_test_alerts";
+
+ /** TelephonyProvider column name for Opt out dialog in CB settings */
+ public static final String CB_OPT_OUT_DIALOG = "show_cmas_opt_out_dialog";
+
+ /**
+ * TelephonyProvider column name for enable Volte.
+ *
+ * If this setting is not initialized (set to -1) then we use the Carrier Config value
+ * {@link CarrierConfigManager#KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL}.
+ */
+ public static final String ENHANCED_4G_MODE_ENABLED = "volte_vt_enabled";
+
+ /** TelephonyProvider column name for enable VT (Video Telephony over IMS) */
+ public static final String VT_IMS_ENABLED = "vt_ims_enabled";
+
+ /** TelephonyProvider column name for enable Wifi calling */
+ public static final String WFC_IMS_ENABLED = "wfc_ims_enabled";
+
+ /** TelephonyProvider column name for Wifi calling mode */
+ public static final String WFC_IMS_MODE = "wfc_ims_mode";
+
+ /** TelephonyProvider column name for Wifi calling mode in roaming */
+ public static final String WFC_IMS_ROAMING_MODE = "wfc_ims_roaming_mode";
+
+ /** TelephonyProvider column name for enable Wifi calling in roaming */
+ public static final String WFC_IMS_ROAMING_ENABLED = "wfc_ims_roaming_enabled";
+
+ /**
+ * TelephonyProvider column name for whether a subscription is opportunistic, that is,
+ * whether the network it connects to is limited in functionality or coverage.
+ * For example, CBRS.
+ * <p>Type: INTEGER (int), 1 for opportunistic or 0 for non-opportunistic.
+ */
+ public static final String IS_OPPORTUNISTIC = "is_opportunistic";
+
+ /**
+ * TelephonyProvider column name for group ID. Subscriptions with same group ID
+ * are considered bundled together, and should behave as a single subscription at
+ * certain scenarios.
+ */
+ public static final String GROUP_UUID = "group_uuid";
+
+ /**
+ * TelephonyProvider column name for group owner. It's the package name who created
+ * the subscription group.
+ */
+ public static final String GROUP_OWNER = "group_owner";
+
+ /**
+ * TelephonyProvider column name for whether a subscription is metered or not, that is,
+ * whether the network it connects to charges for subscription or not. For example, paid
+ * CBRS or unpaid.
+ * @hide
+ */
+ public static final String IS_METERED = "is_metered";
+
+ /**
+ * TelephonyProvider column name for the profile class of a subscription
+ * Only present if {@link #IS_EMBEDDED} is 1.
+ * <P>Type: INTEGER (int)</P>
+ */
+ public static final String PROFILE_CLASS = "profile_class";
+
+ /**
+ * A testing profile can be pre-loaded or downloaded onto
+ * the eUICC and provides connectivity to test equipment
+ * for the purpose of testing the device and the eUICC. It
+ * is not intended to store any operator credentials.
+ */
+ public static final int PROFILE_CLASS_TESTING = 0;
+
+ /**
+ * A provisioning profile is pre-loaded onto the eUICC and
+ * provides connectivity to a mobile network solely for the
+ * purpose of provisioning profiles.
+ */
+ public static final int PROFILE_CLASS_PROVISIONING = 1;
+
+ /**
+ * An operational profile can be pre-loaded or downloaded
+ * onto the eUICC and provides services provided by the
+ * operator.
+ */
+ public static final int PROFILE_CLASS_OPERATIONAL = 2;
+
+ /**
+ * The profile class is unset. This occurs when profile class
+ * info is not available. The subscription either has no profile
+ * metadata or the profile metadata did not encode profile class.
+ */
+ public static final int PROFILE_CLASS_UNSET = -1;
+
+ /** Default profile class */
+ public static final int PROFILE_CLASS_DEFAULT = PROFILE_CLASS_UNSET;
+
+ /**
+ * IMSI (International Mobile Subscriber Identity).
+ * <P>Type: TEXT </P>
+ */
+ public static final String IMSI = "imsi";
+
+ /** Whether uicc applications is set to be enabled or disabled. By default it's enabled. */
+ public static final String UICC_APPLICATIONS_ENABLED = "uicc_applications_enabled";
}
}
diff --git a/core/java/android/se/omapi/SEService.java b/core/java/android/se/omapi/SEService.java
index d646e23..00060ab 100644
--- a/core/java/android/se/omapi/SEService.java
+++ b/core/java/android/se/omapi/SEService.java
@@ -22,14 +22,11 @@
package android.se.omapi;
-import android.app.ActivityThread;
import android.annotation.NonNull;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageManager;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
@@ -143,10 +140,6 @@
throw new NullPointerException("Arguments must not be null");
}
- if (!hasOMAPIReaders()) {
- throw new UnsupportedOperationException("Device does not support any OMAPI reader");
- }
-
mContext = context;
mSEListener.mListener = listener;
mSEListener.mExecutor = executor;
@@ -277,23 +270,4 @@
throw new IllegalStateException(e.getMessage());
}
}
-
- /**
- * Helper to check if this device support any OMAPI readers
- */
- private static boolean hasOMAPIReaders() {
- IPackageManager pm = ActivityThread.getPackageManager();
- if (pm == null) {
- Log.e(TAG, "Cannot get package manager, assuming OMAPI readers supported");
- return true;
- }
- try {
- return pm.hasSystemFeature(PackageManager.FEATURE_SE_OMAPI_UICC, 0)
- || pm.hasSystemFeature(PackageManager.FEATURE_SE_OMAPI_ESE, 0)
- || pm.hasSystemFeature(PackageManager.FEATURE_SE_OMAPI_SD, 0);
- } catch (RemoteException e) {
- Log.e(TAG, "Package manager query failed, assuming OMAPI readers supported", e);
- return true;
- }
- }
}
diff --git a/core/java/android/security/KeystoreArguments.java b/core/java/android/security/KeystoreArguments.java
index e634234..a59c4e0 100644
--- a/core/java/android/security/KeystoreArguments.java
+++ b/core/java/android/security/KeystoreArguments.java
@@ -16,7 +16,7 @@
package android.security;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/security/keymaster/ExportResult.java b/core/java/android/security/keymaster/ExportResult.java
index 1be5ae9..037b852 100644
--- a/core/java/android/security/keymaster/ExportResult.java
+++ b/core/java/android/security/keymaster/ExportResult.java
@@ -16,7 +16,7 @@
package android.security.keymaster;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/security/keymaster/KeyCharacteristics.java b/core/java/android/security/keymaster/KeyCharacteristics.java
index 2eb2cef..d8382fa 100644
--- a/core/java/android/security/keymaster/KeyCharacteristics.java
+++ b/core/java/android/security/keymaster/KeyCharacteristics.java
@@ -16,7 +16,7 @@
package android.security.keymaster;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/security/keymaster/KeymasterArguments.java b/core/java/android/security/keymaster/KeymasterArguments.java
index d2dbdec..e009e12 100644
--- a/core/java/android/security/keymaster/KeymasterArguments.java
+++ b/core/java/android/security/keymaster/KeymasterArguments.java
@@ -16,7 +16,7 @@
package android.security.keymaster;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/security/keymaster/KeymasterBlob.java b/core/java/android/security/keymaster/KeymasterBlob.java
index 6a2024f..68365bf 100644
--- a/core/java/android/security/keymaster/KeymasterBlob.java
+++ b/core/java/android/security/keymaster/KeymasterBlob.java
@@ -16,7 +16,7 @@
package android.security.keymaster;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/security/keymaster/KeymasterBlobArgument.java b/core/java/android/security/keymaster/KeymasterBlobArgument.java
index fc562bd..81b08c5 100644
--- a/core/java/android/security/keymaster/KeymasterBlobArgument.java
+++ b/core/java/android/security/keymaster/KeymasterBlobArgument.java
@@ -16,7 +16,7 @@
package android.security.keymaster;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
/**
diff --git a/core/java/android/security/keymaster/KeymasterBooleanArgument.java b/core/java/android/security/keymaster/KeymasterBooleanArgument.java
index 4286aa0..25b2ac4 100644
--- a/core/java/android/security/keymaster/KeymasterBooleanArgument.java
+++ b/core/java/android/security/keymaster/KeymasterBooleanArgument.java
@@ -16,7 +16,7 @@
package android.security.keymaster;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
/**
diff --git a/core/java/android/security/keymaster/KeymasterDateArgument.java b/core/java/android/security/keymaster/KeymasterDateArgument.java
index 3e04c15..218f488 100644
--- a/core/java/android/security/keymaster/KeymasterDateArgument.java
+++ b/core/java/android/security/keymaster/KeymasterDateArgument.java
@@ -16,8 +16,9 @@
package android.security.keymaster;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
+
import java.util.Date;
/**
diff --git a/core/java/android/security/keymaster/KeymasterIntArgument.java b/core/java/android/security/keymaster/KeymasterIntArgument.java
index 4aadce4..01d38c7 100644
--- a/core/java/android/security/keymaster/KeymasterIntArgument.java
+++ b/core/java/android/security/keymaster/KeymasterIntArgument.java
@@ -16,7 +16,7 @@
package android.security.keymaster;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
/**
diff --git a/core/java/android/security/keymaster/KeymasterLongArgument.java b/core/java/android/security/keymaster/KeymasterLongArgument.java
index bc2255e..3ac27cc 100644
--- a/core/java/android/security/keymaster/KeymasterLongArgument.java
+++ b/core/java/android/security/keymaster/KeymasterLongArgument.java
@@ -16,7 +16,7 @@
package android.security.keymaster;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
/**
diff --git a/core/java/android/security/keymaster/OperationResult.java b/core/java/android/security/keymaster/OperationResult.java
index c278eb3..b4e155a 100644
--- a/core/java/android/security/keymaster/OperationResult.java
+++ b/core/java/android/security/keymaster/OperationResult.java
@@ -16,7 +16,7 @@
package android.security.keymaster;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/security/net/config/RootTrustManager.java b/core/java/android/security/net/config/RootTrustManager.java
index d8936d9..58dc4ba 100644
--- a/core/java/android/security/net/config/RootTrustManager.java
+++ b/core/java/android/security/net/config/RootTrustManager.java
@@ -16,15 +16,16 @@
package android.security.net.config;
+import android.compat.annotation.UnsupportedAppUsage;
+
import java.net.Socket;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.List;
-import android.annotation.UnsupportedAppUsage;
-import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
import javax.net.ssl.X509ExtendedTrustManager;
/**
diff --git a/core/java/android/service/autofill/Dataset.java b/core/java/android/service/autofill/Dataset.java
index d53e62a..262d989 100644
--- a/core/java/android/service/autofill/Dataset.java
+++ b/core/java/android/service/autofill/Dataset.java
@@ -20,6 +20,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
import android.content.IntentSender;
import android.os.Parcel;
import android.os.Parcelable;
@@ -56,6 +58,10 @@
* all datasets pairs that have that view's {@link AutofillId}. Then, when the user selects a
* dataset from the UI, all views in that dataset are autofilled.
*
+ * <p>If both the current Input Method and autofill service supports inline suggestions, the Dataset
+ * can be shown by the keyboard as a suggestion. To use this feature, the Dataset should contain
+ * an {@link InlinePresentation} representing how the inline suggestion UI will be rendered.
+ *
* <a name="Authentication"></a>
* <h3>Dataset authentication</h3>
*
@@ -99,8 +105,10 @@
private final ArrayList<AutofillId> mFieldIds;
private final ArrayList<AutofillValue> mFieldValues;
private final ArrayList<RemoteViews> mFieldPresentations;
+ private final ArrayList<InlinePresentation> mFieldInlinePresentations;
private final ArrayList<DatasetFieldFilter> mFieldFilters;
private final RemoteViews mPresentation;
+ @Nullable private final InlinePresentation mInlinePresentation;
private final IntentSender mAuthentication;
@Nullable String mId;
@@ -108,8 +116,10 @@
mFieldIds = builder.mFieldIds;
mFieldValues = builder.mFieldValues;
mFieldPresentations = builder.mFieldPresentations;
+ mFieldInlinePresentations = builder.mFieldInlinePresentations;
mFieldFilters = builder.mFieldFilters;
mPresentation = builder.mPresentation;
+ mInlinePresentation = builder.mInlinePresentation;
mAuthentication = builder.mAuthentication;
mId = builder.mId;
}
@@ -132,6 +142,13 @@
/** @hide */
@Nullable
+ public InlinePresentation getFieldInlinePresentation(int index) {
+ final InlinePresentation inlinePresentation = mFieldInlinePresentations.get(index);
+ return inlinePresentation != null ? inlinePresentation : mInlinePresentation;
+ }
+
+ /** @hide */
+ @Nullable
public DatasetFieldFilter getFilter(int index) {
return mFieldFilters.get(index);
}
@@ -165,7 +182,9 @@
}
if (mFieldPresentations != null) {
builder.append(", fieldPresentations=").append(mFieldPresentations.size());
-
+ }
+ if (mFieldInlinePresentations != null) {
+ builder.append(", fieldInlinePresentations=").append(mFieldInlinePresentations.size());
}
if (mFieldFilters != null) {
builder.append(", fieldFilters=").append(mFieldFilters.size());
@@ -173,6 +192,9 @@
if (mPresentation != null) {
builder.append(", hasPresentation");
}
+ if (mInlinePresentation != null) {
+ builder.append(", hasInlinePresentation");
+ }
if (mAuthentication != null) {
builder.append(", hasAuthentication");
}
@@ -198,8 +220,10 @@
private ArrayList<AutofillId> mFieldIds;
private ArrayList<AutofillValue> mFieldValues;
private ArrayList<RemoteViews> mFieldPresentations;
+ private ArrayList<InlinePresentation> mFieldInlinePresentations;
private ArrayList<DatasetFieldFilter> mFieldFilters;
private RemoteViews mPresentation;
+ @Nullable private InlinePresentation mInlinePresentation;
private IntentSender mAuthentication;
private boolean mDestroyed;
@Nullable private String mId;
@@ -208,6 +232,22 @@
* Creates a new builder.
*
* @param presentation The presentation used to visualize this dataset.
+ * @param inlinePresentation The {@link InlinePresentation} used to visualize this dataset
+ * as inline suggestions. If the dataset supports inline suggestions,
+ * this should not be null.
+ */
+ public Builder(@NonNull RemoteViews presentation,
+ @NonNull InlinePresentation inlinePresentation) {
+ Preconditions.checkNotNull(presentation, "presentation must be non-null");
+ Preconditions.checkNotNull(inlinePresentation, "inlinePresentation must be non-null");
+ mPresentation = presentation;
+ mInlinePresentation = inlinePresentation;
+ }
+
+ /**
+ * Creates a new builder.
+ *
+ * @param presentation The presentation used to visualize this dataset.
*/
public Builder(@NonNull RemoteViews presentation) {
Preconditions.checkNotNull(presentation, "presentation must be non-null");
@@ -215,6 +255,23 @@
}
/**
+ * Creates a new builder.
+ *
+ * <p>Only called by augmented autofill.
+ *
+ * @param inlinePresentation The {@link InlinePresentation} used to visualize this dataset
+ * as inline suggestions. If the dataset supports inline suggestions,
+ * this should not be null.
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public Builder(@NonNull InlinePresentation inlinePresentation) {
+ Preconditions.checkNotNull(inlinePresentation, "inlinePresentation must be non-null");
+ mInlinePresentation = inlinePresentation;
+ }
+
+ /**
* Creates a new builder for a dataset where each field will be visualized independently.
*
* <p>When using this constructor, fields must be set through
@@ -325,7 +382,7 @@
*/
public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value) {
throwIfDestroyed();
- setLifeTheUniverseAndEverything(id, value, null, null);
+ setLifeTheUniverseAndEverything(id, value, null, null, null);
return this;
}
@@ -353,7 +410,7 @@
@NonNull RemoteViews presentation) {
throwIfDestroyed();
Preconditions.checkNotNull(presentation, "presentation cannot be null");
- setLifeTheUniverseAndEverything(id, value, presentation, null);
+ setLifeTheUniverseAndEverything(id, value, presentation, null, null);
return this;
}
@@ -389,7 +446,7 @@
throwIfDestroyed();
Preconditions.checkState(mPresentation != null,
"Dataset presentation not set on constructor");
- setLifeTheUniverseAndEverything(id, value, null, new DatasetFieldFilter(filter));
+ setLifeTheUniverseAndEverything(id, value, null, null, new DatasetFieldFilter(filter));
return this;
}
@@ -424,13 +481,120 @@
@Nullable Pattern filter, @NonNull RemoteViews presentation) {
throwIfDestroyed();
Preconditions.checkNotNull(presentation, "presentation cannot be null");
- setLifeTheUniverseAndEverything(id, value, presentation,
+ setLifeTheUniverseAndEverything(id, value, presentation, null,
+ new DatasetFieldFilter(filter));
+ return this;
+ }
+
+ /**
+ * Sets the value of a field, using a custom {@link RemoteViews presentation} to
+ * visualize it and an {@link InlinePresentation} to visualize it as an inline suggestion.
+ *
+ * <p><b>Note:</b> If the dataset requires authentication but the service knows its text
+ * value it's easier to filter by calling
+ * {@link #setValue(AutofillId, AutofillValue, RemoteViews)} and using the value to filter.
+ *
+ * @param id id returned by {@link
+ * android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
+ * @param value the value to be autofilled. Pass {@code null} if you do not have the value
+ * but the target view is a logical part of the dataset. For example, if
+ * the dataset needs authentication and you have no access to the value.
+ * @param presentation the presentation used to visualize this field.
+ * @param inlinePresentation The {@link InlinePresentation} used to visualize this dataset
+ * as inline suggestions. If the dataset supports inline suggestions,
+ * this should not be null.
+ *
+ * @return this builder.
+ */
+ public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
+ @NonNull RemoteViews presentation, @NonNull InlinePresentation inlinePresentation) {
+ throwIfDestroyed();
+ Preconditions.checkNotNull(presentation, "presentation cannot be null");
+ Preconditions.checkNotNull(inlinePresentation, "inlinePresentation cannot be null");
+ setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation, null);
+ return this;
+ }
+
+ /**
+ * Sets the value of a field, using a custom {@link RemoteViews presentation} to
+ * visualize it and a <a href="#Filtering">explicit filter</a>, and an
+ * {@link InlinePresentation} to visualize it as an inline suggestion.
+ *
+ * <p>This method is typically used when the dataset requires authentication and the service
+ * does not know its value but wants to hide the dataset after the user enters a minimum
+ * number of characters. For example, if the dataset represents a credit card number and the
+ * service does not want to show the "Tap to authenticate" message until the user tapped
+ * 4 digits, in which case the filter would be {@code Pattern.compile("\\d.{4,}")}.
+ *
+ * <p><b>Note:</b> If the dataset requires authentication but the service knows its text
+ * value it's easier to filter by calling
+ * {@link #setValue(AutofillId, AutofillValue, RemoteViews)} and using the value to filter.
+ *
+ * @param id id returned by {@link
+ * android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
+ * @param value the value to be autofilled. Pass {@code null} if you do not have the value
+ * but the target view is a logical part of the dataset. For example, if
+ * the dataset needs authentication and you have no access to the value.
+ * @param filter regex used to determine if the dataset should be shown in the autofill UI;
+ * when {@code null}, it disables filtering on that dataset (this is the recommended
+ * approach when {@code value} is not {@code null} and field contains sensitive data
+ * such as passwords).
+ * @param presentation the presentation used to visualize this field.
+ * @param inlinePresentation The {@link InlinePresentation} used to visualize this dataset
+ * as inline suggestions. If the dataset supports inline suggestions, this
+ * should not be null.
+ *
+ * @return this builder.
+ */
+ public @NonNull Builder setValue(@NonNull AutofillId id, @Nullable AutofillValue value,
+ @Nullable Pattern filter, @NonNull RemoteViews presentation,
+ @NonNull InlinePresentation inlinePresentation) {
+ throwIfDestroyed();
+ Preconditions.checkNotNull(presentation, "presentation cannot be null");
+ Preconditions.checkNotNull(inlinePresentation, "inlinePresentation cannot be null");
+ setLifeTheUniverseAndEverything(id, value, presentation, inlinePresentation,
+ new DatasetFieldFilter(filter));
+ return this;
+ }
+
+ /**
+ * Sets the value of a field with an <a href="#Filtering">explicit filter</a>, and using an
+ * {@link InlinePresentation} to visualize it as an inline suggestion.
+ *
+ * <p>Only called by augmented autofill.
+ *
+ * @param id id returned by {@link
+ * android.app.assist.AssistStructure.ViewNode#getAutofillId()}.
+ * @param value the value to be autofilled. Pass {@code null} if you do not have the value
+ * but the target view is a logical part of the dataset. For example, if
+ * the dataset needs authentication and you have no access to the value.
+ * @param filter regex used to determine if the dataset should be shown in the autofill UI;
+ * when {@code null}, it disables filtering on that dataset (this is the recommended
+ * approach when {@code value} is not {@code null} and field contains sensitive data
+ * such as passwords).
+ * @param inlinePresentation The {@link InlinePresentation} used to visualize this dataset
+ * as inline suggestions. If the dataset supports inline suggestions, this
+ * should not be null.
+ *
+ * @return this builder.
+ *
+ * @hide
+ */
+ @SystemApi
+ @TestApi
+ public @NonNull Builder setInlinePresentation(@NonNull AutofillId id,
+ @Nullable AutofillValue value, @Nullable Pattern filter,
+ @NonNull InlinePresentation inlinePresentation) {
+ throwIfDestroyed();
+ Preconditions.checkNotNull(inlinePresentation, "inlinePresentation cannot be null");
+ setLifeTheUniverseAndEverything(id, value, null, inlinePresentation,
new DatasetFieldFilter(filter));
return this;
}
private void setLifeTheUniverseAndEverything(@NonNull AutofillId id,
@Nullable AutofillValue value, @Nullable RemoteViews presentation,
+ @Nullable InlinePresentation inlinePresentation,
@Nullable DatasetFieldFilter filter) {
Preconditions.checkNotNull(id, "id cannot be null");
if (mFieldIds != null) {
@@ -438,6 +602,7 @@
if (existingIdx >= 0) {
mFieldValues.set(existingIdx, value);
mFieldPresentations.set(existingIdx, presentation);
+ mFieldInlinePresentations.set(existingIdx, inlinePresentation);
mFieldFilters.set(existingIdx, filter);
return;
}
@@ -445,11 +610,13 @@
mFieldIds = new ArrayList<>();
mFieldValues = new ArrayList<>();
mFieldPresentations = new ArrayList<>();
+ mFieldInlinePresentations = new ArrayList<>();
mFieldFilters = new ArrayList<>();
}
mFieldIds.add(id);
mFieldValues.add(value);
mFieldPresentations.add(presentation);
+ mFieldInlinePresentations.add(inlinePresentation);
mFieldFilters.add(filter);
}
@@ -460,7 +627,8 @@
*
* @throws IllegalStateException if no field was set (through
* {@link #setValue(AutofillId, AutofillValue)} or
- * {@link #setValue(AutofillId, AutofillValue, RemoteViews)}).
+ * {@link #setValue(AutofillId, AutofillValue, RemoteViews)} or
+ * {@link #setValue(AutofillId, AutofillValue, RemoteViews, InlinePresentation)}).
*
* @return The built dataset.
*/
@@ -492,38 +660,51 @@
@Override
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeParcelable(mPresentation, flags);
+ parcel.writeParcelable(mInlinePresentation, flags);
parcel.writeTypedList(mFieldIds, flags);
parcel.writeTypedList(mFieldValues, flags);
parcel.writeTypedList(mFieldPresentations, flags);
+ parcel.writeTypedList(mFieldInlinePresentations, flags);
parcel.writeTypedList(mFieldFilters, flags);
parcel.writeParcelable(mAuthentication, flags);
parcel.writeString(mId);
}
- public static final @android.annotation.NonNull Creator<Dataset> CREATOR = new Creator<Dataset>() {
+ public static final @NonNull Creator<Dataset> CREATOR = new Creator<Dataset>() {
@Override
public Dataset createFromParcel(Parcel parcel) {
// Always go through the builder to ensure the data ingested by
// the system obeys the contract of the builder to avoid attacks
// using specially crafted parcels.
final RemoteViews presentation = parcel.readParcelable(null);
- final Builder builder = (presentation == null)
- ? new Builder()
- : new Builder(presentation);
+ final InlinePresentation inlinePresentation = parcel.readParcelable(null);
+ final Builder builder = presentation != null
+ ? inlinePresentation == null
+ ? new Builder(presentation)
+ : new Builder(presentation, inlinePresentation)
+ : inlinePresentation == null
+ ? new Builder()
+ : new Builder(inlinePresentation);
final ArrayList<AutofillId> ids =
parcel.createTypedArrayList(AutofillId.CREATOR);
final ArrayList<AutofillValue> values =
parcel.createTypedArrayList(AutofillValue.CREATOR);
final ArrayList<RemoteViews> presentations =
parcel.createTypedArrayList(RemoteViews.CREATOR);
+ final ArrayList<InlinePresentation> inlinePresentations =
+ parcel.createTypedArrayList(InlinePresentation.CREATOR);
final ArrayList<DatasetFieldFilter> filters =
parcel.createTypedArrayList(DatasetFieldFilter.CREATOR);
+ final int inlinePresentationsSize = inlinePresentations.size();
for (int i = 0; i < ids.size(); i++) {
final AutofillId id = ids.get(i);
final AutofillValue value = values.get(i);
final RemoteViews fieldPresentation = presentations.get(i);
+ final InlinePresentation fieldInlinePresentation =
+ i < inlinePresentationsSize ? inlinePresentations.get(i) : null;
final DatasetFieldFilter filter = filters.get(i);
- builder.setLifeTheUniverseAndEverything(id, value, fieldPresentation, filter);
+ builder.setLifeTheUniverseAndEverything(id, value, fieldPresentation,
+ fieldInlinePresentation, filter);
}
builder.setAuthentication(parcel.readParcelable(null));
builder.setId(parcel.readString());
diff --git a/core/java/android/service/autofill/FillResponse.java b/core/java/android/service/autofill/FillResponse.java
index 02a6390..939ae87 100644
--- a/core/java/android/service/autofill/FillResponse.java
+++ b/core/java/android/service/autofill/FillResponse.java
@@ -25,7 +25,6 @@
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.app.Activity;
-import android.app.slice.Slice;
import android.content.IntentSender;
import android.content.pm.ParceledListSlice;
import android.os.Bundle;
@@ -87,7 +86,7 @@
private int mRequestId;
private final @Nullable UserData mUserData;
private final @Nullable int[] mCancelIds;
- private final @Nullable ParceledListSlice<Slice> mInlineSuggestionSlices;
+ private final boolean mSupportsInlineSuggestions;
private FillResponse(@NonNull Builder builder) {
mDatasets = (builder.mDatasets != null) ? new ParceledListSlice<>(builder.mDatasets) : null;
@@ -105,8 +104,7 @@
mRequestId = INVALID_REQUEST_ID;
mUserData = builder.mUserData;
mCancelIds = builder.mCancelIds;
- mInlineSuggestionSlices = (builder.mInlineSuggestionSlices != null)
- ? new ParceledListSlice<>(builder.mInlineSuggestionSlices) : null;
+ mSupportsInlineSuggestions = builder.mSupportsInlineSuggestions;
}
/** @hide */
@@ -200,8 +198,8 @@
}
/** @hide */
- public List<Slice> getInlineSuggestionSlices() {
- return (mInlineSuggestionSlices != null) ? mInlineSuggestionSlices.getList() : null;
+ public boolean supportsInlineSuggestions() {
+ return mSupportsInlineSuggestions;
}
/**
@@ -224,7 +222,7 @@
private boolean mDestroyed;
private UserData mUserData;
private int[] mCancelIds;
- private ArrayList<Slice> mInlineSuggestionSlices;
+ private boolean mSupportsInlineSuggestions;
/**
* Triggers a custom UI before before autofilling the screen with any data set in this
@@ -580,20 +578,6 @@
}
/**
- * TODO(b/137800469): add javadoc
- */
- @NonNull
- public Builder addInlineSuggestionSlice(@NonNull Slice inlineSuggestionSlice) {
- throwIfDestroyed();
- throwIfAuthenticationCalled();
- if (mInlineSuggestionSlices == null) {
- mInlineSuggestionSlices = new ArrayList<>();
- }
- mInlineSuggestionSlices.add(inlineSuggestionSlice);
- return this;
- }
-
- /**
* Builds a new {@link FillResponse} instance.
*
* @throws IllegalStateException if any of the following conditions occur:
@@ -624,6 +608,16 @@
throw new IllegalStateException(
"must add at least 1 dataset when using header or footer");
}
+
+ if (mDatasets != null) {
+ for (final Dataset dataset : mDatasets) {
+ if (dataset.getFieldInlinePresentation(0) != null) {
+ mSupportsInlineSuggestions = true;
+ break;
+ }
+ }
+ }
+
mDestroyed = true;
return new FillResponse(this);
}
@@ -694,9 +688,6 @@
if (mCancelIds != null) {
builder.append(", mCancelIds=").append(mCancelIds.length);
}
- if (mInlineSuggestionSlices != null) {
- builder.append(", inlinedSuggestions=").append(mInlineSuggestionSlices.getList());
- }
return builder.append("]").toString();
}
@@ -725,7 +716,6 @@
parcel.writeParcelableArray(mFieldClassificationIds, flags);
parcel.writeInt(mFlags);
parcel.writeIntArray(mCancelIds);
- parcel.writeParcelable(mInlineSuggestionSlices, flags);
parcel.writeInt(mRequestId);
}
@@ -781,16 +771,6 @@
final int[] cancelIds = parcel.createIntArray();
builder.setCancelTargetIds(cancelIds);
- final ParceledListSlice<Slice> parceledInlineSuggestionSlices =
- parcel.readParcelable(null);
- if (parceledInlineSuggestionSlices != null) {
- final List<Slice> inlineSuggestionSlices = parceledInlineSuggestionSlices.getList();
- final int size = inlineSuggestionSlices.size();
- for (int i = 0; i < size; i++) {
- builder.addInlineSuggestionSlice(inlineSuggestionSlices.get(i));
- }
- }
-
final FillResponse response = builder.build();
response.setRequestId(parcel.readInt());
diff --git a/core/java/android/net/NetworkMisc.aidl b/core/java/android/service/autofill/InlinePresentation.aidl
similarity index 82%
copy from core/java/android/net/NetworkMisc.aidl
copy to core/java/android/service/autofill/InlinePresentation.aidl
index c65583f..eeddcc0 100644
--- a/core/java/android/net/NetworkMisc.aidl
+++ b/core/java/android/service/autofill/InlinePresentation.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 The Android Open Source Project
+ * 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.
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.net;
+package android.service.autofill;
-parcelable NetworkMisc;
+parcelable InlinePresentation;
diff --git a/core/java/android/service/autofill/InlinePresentation.java b/core/java/android/service/autofill/InlinePresentation.java
new file mode 100644
index 0000000..1568fb3
--- /dev/null
+++ b/core/java/android/service/autofill/InlinePresentation.java
@@ -0,0 +1,181 @@
+/*
+ * 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.service.autofill;
+
+import android.annotation.NonNull;
+import android.app.slice.Slice;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.inline.InlinePresentationSpec;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Wrapper class holding a {@link Slice} and an {@link InlinePresentationSpec} for rendering UI
+ * for an Inline Suggestion.
+ */
+@DataClass(
+ genToString = true,
+ genHiddenConstDefs = true,
+ genEqualsHashCode = true)
+public final class InlinePresentation implements Parcelable {
+
+ private final @NonNull Slice mSlice;
+
+ private final @NonNull InlinePresentationSpec mInlinePresentationSpec;
+
+
+
+ // Code below generated by codegen v1.0.14.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/service/autofill/InlinePresentation.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ @DataClass.Generated.Member
+ public InlinePresentation(
+ @NonNull Slice slice,
+ @NonNull InlinePresentationSpec inlinePresentationSpec) {
+ this.mSlice = slice;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mSlice);
+ this.mInlinePresentationSpec = inlinePresentationSpec;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mInlinePresentationSpec);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull Slice getSlice() {
+ return mSlice;
+ }
+
+ @DataClass.Generated.Member
+ public @NonNull InlinePresentationSpec getInlinePresentationSpec() {
+ return mInlinePresentationSpec;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "InlinePresentation { " +
+ "slice = " + mSlice + ", " +
+ "inlinePresentationSpec = " + mInlinePresentationSpec +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@android.annotation.Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(InlinePresentation other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ InlinePresentation that = (InlinePresentation) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mSlice, that.mSlice)
+ && java.util.Objects.equals(mInlinePresentationSpec, that.mInlinePresentationSpec);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mSlice);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mInlinePresentationSpec);
+ return _hash;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ dest.writeTypedObject(mSlice, flags);
+ dest.writeTypedObject(mInlinePresentationSpec, flags);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ InlinePresentation(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ Slice slice = (Slice) in.readTypedObject(Slice.CREATOR);
+ InlinePresentationSpec inlinePresentationSpec = (InlinePresentationSpec) in.readTypedObject(InlinePresentationSpec.CREATOR);
+
+ this.mSlice = slice;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mSlice);
+ this.mInlinePresentationSpec = inlinePresentationSpec;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mInlinePresentationSpec);
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<InlinePresentation> CREATOR
+ = new Parcelable.Creator<InlinePresentation>() {
+ @Override
+ public InlinePresentation[] newArray(int size) {
+ return new InlinePresentation[size];
+ }
+
+ @Override
+ public InlinePresentation createFromParcel(@NonNull Parcel in) {
+ return new InlinePresentation(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1578081082387L,
+ codegenVersion = "1.0.14",
+ sourceFile = "frameworks/base/core/java/android/service/autofill/InlinePresentation.java",
+ inputSignatures = "private final @android.annotation.NonNull android.app.slice.Slice mSlice\nprivate final @android.annotation.NonNull android.view.inline.InlinePresentationSpec mInlinePresentationSpec\nclass InlinePresentation extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstDefs=true, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 38de794..de4a551 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -21,9 +21,9 @@
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
-import android.annotation.UnsupportedAppUsage;
import android.app.AlarmManager;
import android.app.Service;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.graphics.drawable.ColorDrawable;
diff --git a/core/java/android/service/notification/INotificationListener.aidl b/core/java/android/service/notification/INotificationListener.aidl
index 5977baf..4ead3fc 100644
--- a/core/java/android/service/notification/INotificationListener.aidl
+++ b/core/java/android/service/notification/INotificationListener.aidl
@@ -49,6 +49,9 @@
void onNotificationEnqueuedWithChannel(in IStatusBarNotificationHolder notificationHolder, in NotificationChannel channel);
void onNotificationSnoozedUntilContext(in IStatusBarNotificationHolder notificationHolder, String snoozeCriterionId);
void onNotificationsSeen(in List<String> keys);
+ void onPanelRevealed(int items);
+ void onPanelHidden();
+ void onNotificationVisibilityChanged(String key, boolean isVisible);
void onNotificationExpansionChanged(String key, boolean userAction, boolean expanded);
void onNotificationDirectReply(String key);
void onSuggestedReplySent(String key, in CharSequence reply, int source);
diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java
index da40254..e976e18 100644
--- a/core/java/android/service/notification/NotificationAssistantService.java
+++ b/core/java/android/service/notification/NotificationAssistantService.java
@@ -182,6 +182,32 @@
}
/**
+ * Implement this to know when the notification panel is revealed
+ *
+ * @param items Number of items on the panel at time of opening
+ */
+ public void onPanelRevealed(int items) {
+
+ }
+
+ /**
+ * Implement this to know when the notification panel is hidden
+ */
+ public void onPanelHidden() {
+
+ }
+
+ /**
+ * Implement this to know when a notification becomes visible or hidden from the user.
+ *
+ * @param key the notification key
+ * @param isVisible whether the notification is visible.
+ */
+ public void onNotificationVisibilityChanged(@NonNull String key, boolean isVisible) {
+
+ }
+
+ /**
* Implement this to know when a notification change (expanded / collapsed) is visible to user.
*
* @param key the notification key
@@ -337,6 +363,30 @@
}
@Override
+ public void onPanelRevealed(int items) {
+ SomeArgs args = SomeArgs.obtain();
+ args.argi1 = items;
+ mHandler.obtainMessage(MyHandler.MSG_ON_PANEL_REVEALED,
+ args).sendToTarget();
+ }
+
+ @Override
+ public void onPanelHidden() {
+ SomeArgs args = SomeArgs.obtain();
+ mHandler.obtainMessage(MyHandler.MSG_ON_PANEL_HIDDEN,
+ args).sendToTarget();
+ }
+
+ @Override
+ public void onNotificationVisibilityChanged(String key, boolean isVisible) {
+ SomeArgs args = SomeArgs.obtain();
+ args.arg1 = key;
+ args.argi1 = isVisible ? 1 : 0;
+ mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_VISIBILITY_CHANGED,
+ args).sendToTarget();
+ }
+
+ @Override
public void onNotificationExpansionChanged(String key, boolean isUserAction,
boolean isExpanded) {
SomeArgs args = SomeArgs.obtain();
@@ -394,6 +444,9 @@
public static final int MSG_ON_SUGGESTED_REPLY_SENT = 6;
public static final int MSG_ON_ACTION_INVOKED = 7;
public static final int MSG_ON_ALLOWED_ADJUSTMENTS_CHANGED = 8;
+ public static final int MSG_ON_PANEL_REVEALED = 9;
+ public static final int MSG_ON_PANEL_HIDDEN = 10;
+ public static final int MSG_ON_NOTIFICATION_VISIBILITY_CHANGED = 11;
public MyHandler(Looper looper) {
super(looper, null, false);
@@ -480,6 +533,25 @@
onAllowedAdjustmentsChanged();
break;
}
+ case MSG_ON_PANEL_REVEALED: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ int items = args.argi1;
+ args.recycle();
+ onPanelRevealed(items);
+ break;
+ }
+ case MSG_ON_PANEL_HIDDEN: {
+ onPanelHidden();
+ break;
+ }
+ case MSG_ON_NOTIFICATION_VISIBILITY_CHANGED: {
+ SomeArgs args = (SomeArgs) msg.obj;
+ String key = (String) args.arg1;
+ boolean isVisible = args.argi1 == 1;
+ args.recycle();
+ onNotificationVisibilityChanged(key, isVisible);
+ break;
+ }
}
}
}
diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java
index c04ac59..fd04f49 100644
--- a/core/java/android/service/notification/NotificationListenerService.java
+++ b/core/java/android/service/notification/NotificationListenerService.java
@@ -22,7 +22,6 @@
import android.annotation.SdkConstant;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityManager;
import android.app.INotificationManager;
import android.app.Notification;
@@ -33,6 +32,7 @@
import android.app.Person;
import android.app.Service;
import android.companion.CompanionDeviceManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -1383,6 +1383,22 @@
}
@Override
+ public void onPanelRevealed(int items) throws RemoteException {
+ // no-op in the listener
+ }
+
+ @Override
+ public void onPanelHidden() throws RemoteException {
+ // no-op in the listener
+ }
+
+ @Override
+ public void onNotificationVisibilityChanged(
+ String key, boolean isVisible) {
+ // no-op in the listener
+ }
+
+ @Override
public void onNotificationSnoozedUntilContext(
IStatusBarNotificationHolder notificationHolder, String snoozeCriterionId)
throws RemoteException {
diff --git a/core/java/android/service/notification/StatusBarNotification.java b/core/java/android/service/notification/StatusBarNotification.java
index b8378a3..389040c 100644
--- a/core/java/android/service/notification/StatusBarNotification.java
+++ b/core/java/android/service/notification/StatusBarNotification.java
@@ -17,11 +17,10 @@
package android.service.notification;
import android.annotation.NonNull;
-import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Person;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index dedc3b7..3f9462c 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -20,11 +20,11 @@
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_LIGHTS;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.NotificationManager;
import android.app.NotificationManager.Policy;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
diff --git a/core/java/android/service/voice/AlwaysOnHotwordDetector.java b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
index cf56eae..0f33998 100644
--- a/core/java/android/service/voice/AlwaysOnHotwordDetector.java
+++ b/core/java/android/service/voice/AlwaysOnHotwordDetector.java
@@ -19,7 +19,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
import android.hardware.soundtrigger.IRecognitionStatusCallback;
@@ -119,7 +119,9 @@
@IntDef(flag = true, prefix = { "RECOGNITION_FLAG_" }, value = {
RECOGNITION_FLAG_NONE,
RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO,
- RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS
+ RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS,
+ RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION,
+ RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION,
})
public @interface RecognitionFlags {}
@@ -144,6 +146,26 @@
*/
public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 0x2;
+ /**
+ * Audio capabilities flag for {@link #startRecognition(int)} that indicates
+ * if the underlying recognition should use AEC.
+ * This capability may or may not be supported by the system, and support can be queried
+ * by calling {@link #getSupportedAudioCapabilities()}. The corresponding capabilities field for
+ * this flag is {@link #AUDIO_CAPABILITY_ECHO_CANCELLATION}. If this flag is passed without the
+ * audio capability supported, there will be no audio effect applied.
+ */
+ public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 0x4;
+
+ /**
+ * Audio capabilities flag for {@link #startRecognition(int)} that indicates
+ * if the underlying recognition should use noise suppression.
+ * This capability may or may not be supported by the system, and support can be queried
+ * by calling {@link #getSupportedAudioCapabilities()}. The corresponding capabilities field for
+ * this flag is {@link #AUDIO_CAPABILITY_NOISE_SUPPRESSION}. If this flag is passed without the
+ * audio capability supported, there will be no audio effect applied.
+ */
+ public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 0x8;
+
//---- Recognition mode flags. Return codes for getSupportedRecognitionModes() ----//
// Must be kept in sync with the related attribute defined as searchKeyphraseRecognitionFlags.
@@ -168,6 +190,46 @@
public static final int RECOGNITION_MODE_USER_IDENTIFICATION
= SoundTrigger.RECOGNITION_MODE_USER_IDENTIFICATION;
+ //-- Audio capabilities. Values in returned bit field for getSupportedAudioCapabilities() --//
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "AUDIO_CAPABILITY_" }, value = {
+ AUDIO_CAPABILITY_ECHO_CANCELLATION,
+ AUDIO_CAPABILITY_NOISE_SUPPRESSION,
+ })
+ public @interface AudioCapabilities {}
+
+ /**
+ * If set the underlying module supports AEC.
+ * Returned by {@link #getSupportedAudioCapabilities()}
+ */
+ public static final int AUDIO_CAPABILITY_ECHO_CANCELLATION =
+ SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION;
+
+ /**
+ * If set, the underlying module supports noise suppression.
+ * Returned by {@link #getSupportedAudioCapabilities()}
+ */
+ public static final int AUDIO_CAPABILITY_NOISE_SUPPRESSION =
+ SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true, prefix = { "MODEL_PARAM_" }, value = {
+ MODEL_PARAM_THRESHOLD_FACTOR,
+ })
+ public @interface ModelParams {}
+
+ /**
+ * Controls the sensitivity threshold adjustment factor for a given model.
+ * Negative value corresponds to less sensitive model (high threshold) and
+ * a positive value corresponds to a more sensitive model (low threshold).
+ * Default value is 0.
+ */
+ public static final int MODEL_PARAM_THRESHOLD_FACTOR =
+ android.hardware.soundtrigger.ModelParams.THRESHOLD_FACTOR;
+
static final String TAG = "AlwaysOnHotwordDetector";
static final boolean DBG = false;
@@ -198,6 +260,53 @@
private int mAvailability = STATE_NOT_READY;
/**
+ * A ModelParamRange is a representation of supported parameter range for a
+ * given loaded model.
+ */
+ public static final class ModelParamRange {
+ private final SoundTrigger.ModelParamRange mModelParamRange;
+
+ /** @hide */
+ ModelParamRange(SoundTrigger.ModelParamRange modelParamRange) {
+ mModelParamRange = modelParamRange;
+ }
+
+ /**
+ * The inclusive start of supported range.
+ *
+ * @return start of range
+ */
+ public int start() {
+ return mModelParamRange.start;
+ }
+
+ /**
+ * The inclusive end of supported range.
+ *
+ * @return end of range
+ */
+ public int end() {
+ return mModelParamRange.end;
+ }
+
+ @Override
+ @NonNull
+ public String toString() {
+ return mModelParamRange.toString();
+ }
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ return mModelParamRange.equals(obj);
+ }
+
+ @Override
+ public int hashCode() {
+ return mModelParamRange.hashCode();
+ }
+ }
+
+ /**
* Additional payload for {@link Callback#onDetected}.
*/
public static class EventPayload {
@@ -385,6 +494,37 @@
}
/**
+ * Get the audio capabilities supported by the platform which can be enabled when
+ * starting a recognition.
+ *
+ * @see #AUDIO_CAPABILITY_ECHO_CANCELLATION
+ * @see #AUDIO_CAPABILITY_NOISE_SUPPRESSION
+ *
+ * @return Bit field encoding of the AudioCapabilities supported.
+ */
+ @AudioCapabilities
+ public int getSupportedAudioCapabilities() {
+ if (DBG) Slog.d(TAG, "getSupportedAudioCapabilities()");
+ synchronized (mLock) {
+ return getSupportedAudioCapabilitiesLocked();
+ }
+ }
+
+ private int getSupportedAudioCapabilitiesLocked() {
+ try {
+ ModuleProperties properties =
+ mModelManagementService.getDspModuleProperties(mVoiceInteractionService);
+ if (properties != null) {
+ return properties.audioCapabilities;
+ }
+
+ return 0;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Starts recognition for the associated keyphrase.
*
* @see #RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO
@@ -445,6 +585,83 @@
}
/**
+ * Set a model specific {@link ModelParams} with the given value. This
+ * parameter will keep its value for the duration the model is loaded regardless of starting and
+ * stopping recognition. Once the model is unloaded, the value will be lost.
+ * {@link AlwaysOnHotwordDetector#queryParameter} should be checked first before calling this
+ * method.
+ *
+ * @param modelParam {@link ModelParams}
+ * @param value Value to set
+ * @return - {@link SoundTrigger#STATUS_OK} in case of success
+ * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
+ * - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter
+ * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or
+ * if API is not supported by HAL
+ */
+ public int setParameter(@ModelParams int modelParam, int value) {
+ if (DBG) {
+ Slog.d(TAG, "setParameter(" + modelParam + ", " + value + ")");
+ }
+
+ synchronized (mLock) {
+ if (mAvailability == STATE_INVALID) {
+ throw new IllegalStateException("setParameter called on an invalid detector");
+ }
+
+ return setParameterLocked(modelParam, value);
+ }
+ }
+
+ /**
+ * Get a model specific {@link ModelParams}. This parameter will keep its value
+ * for the duration the model is loaded regardless of starting and stopping recognition.
+ * Once the model is unloaded, the value will be lost. If the value is not set, a default
+ * value is returned. See {@link ModelParams} for parameter default values.
+ * {@link AlwaysOnHotwordDetector#queryParameter} should be checked first before
+ * calling this method.
+ *
+ * @param modelParam {@link ModelParams}
+ * @return value of parameter
+ */
+ public int getParameter(@ModelParams int modelParam) {
+ if (DBG) {
+ Slog.d(TAG, "getParameter(" + modelParam + ")");
+ }
+
+ synchronized (mLock) {
+ if (mAvailability == STATE_INVALID) {
+ throw new IllegalStateException("getParameter called on an invalid detector");
+ }
+
+ return getParameterLocked(modelParam);
+ }
+ }
+
+ /**
+ * Determine if parameter control is supported for the given model handle.
+ * This method should be checked prior to calling {@link AlwaysOnHotwordDetector#setParameter}
+ * or {@link AlwaysOnHotwordDetector#getParameter}.
+ *
+ * @param modelParam {@link ModelParams}
+ * @return supported range of parameter, null if not supported
+ */
+ @Nullable
+ public ModelParamRange queryParameter(@ModelParams int modelParam) {
+ if (DBG) {
+ Slog.d(TAG, "queryParameter(" + modelParam + ")");
+ }
+
+ synchronized (mLock) {
+ if (mAvailability == STATE_INVALID) {
+ throw new IllegalStateException("queryParameter called on an invalid detector");
+ }
+
+ return queryParameterLocked(modelParam);
+ }
+ }
+
+ /**
* Creates an intent to start the enrollment for the associated keyphrase.
* This intent must be invoked using {@link Context#startForegroundService(Intent)}.
* Starting re-enrollment is only valid if the keyphrase is un-enrolled,
@@ -571,12 +788,21 @@
(recognitionFlags&RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO) != 0;
boolean allowMultipleTriggers =
(recognitionFlags&RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS) != 0;
+
+ int audioCapabilities = 0;
+ if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION) != 0) {
+ audioCapabilities |= AUDIO_CAPABILITY_ECHO_CANCELLATION;
+ }
+ if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION) != 0) {
+ audioCapabilities |= AUDIO_CAPABILITY_NOISE_SUPPRESSION;
+ }
+
int code = STATUS_ERROR;
try {
code = mModelManagementService.startRecognition(mVoiceInteractionService,
mKeyphraseMetadata.id, mLocale.toLanguageTag(), mInternalCallback,
new RecognitionConfig(captureTriggerAudio, allowMultipleTriggers,
- recognitionExtra, null /* additional data */));
+ recognitionExtra, null /* additional data */, audioCapabilities));
} catch (RemoteException e) {
Slog.w(TAG, "RemoteException in startRecognition!", e);
}
@@ -601,6 +827,47 @@
return code;
}
+ private int setParameterLocked(@ModelParams int modelParam, int value) {
+ try {
+ int code = mModelManagementService.setParameter(mVoiceInteractionService,
+ mKeyphraseMetadata.id, modelParam, value);
+
+ if (code != STATUS_OK) {
+ Slog.w(TAG, "setParameter failed with error code " + code);
+ }
+
+ return code;
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ private int getParameterLocked(@ModelParams int modelParam) {
+ try {
+ return mModelManagementService.getParameter(mVoiceInteractionService,
+ mKeyphraseMetadata.id, modelParam);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Nullable
+ private ModelParamRange queryParameterLocked(@ModelParams int modelParam) {
+ try {
+ SoundTrigger.ModelParamRange modelParamRange =
+ mModelManagementService.queryParameter(mVoiceInteractionService,
+ mKeyphraseMetadata.id, modelParam);
+
+ if (modelParamRange == null) {
+ return null;
+ }
+
+ return new ModelParamRange(modelParamRange);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
private void notifyStateChangedLocked() {
Message message = Message.obtain(mHandler, MSG_AVAILABILITY_CHANGED);
message.arg1 = mAvailability;
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 0de17ca..36e057f 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -18,8 +18,8 @@
import android.annotation.NonNull;
import android.annotation.SdkConstant;
-import android.annotation.UnsupportedAppUsage;
import android.app.Service;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
diff --git a/core/java/android/service/vr/VrListenerService.java b/core/java/android/service/vr/VrListenerService.java
index 3c38495..2758ace 100644
--- a/core/java/android/service/vr/VrListenerService.java
+++ b/core/java/android/service/vr/VrListenerService.java
@@ -18,9 +18,9 @@
import android.annotation.NonNull;
import android.annotation.SdkConstant;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityManager;
import android.app.Service;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
diff --git a/core/java/android/service/wallpaper/Android.bp b/core/java/android/service/wallpaper/Android.bp
index aa6123f..ffbdb03 100644
--- a/core/java/android/service/wallpaper/Android.bp
+++ b/core/java/android/service/wallpaper/Android.bp
@@ -6,6 +6,8 @@
"I*.aidl",
],
+ libs: ["unsupportedappusage"],
+
// Enforce that the library is built against java 8 so that there are
// no compatibility issues with launcher
java_version: "1.8",
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index e50d6c2..9a76a1b 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -20,11 +20,11 @@
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.Service;
import android.app.WallpaperColors;
import android.app.WallpaperInfo;
import android.app.WallpaperManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
import android.content.res.TypedArray;
diff --git a/core/java/android/service/watchdog/ExplicitHealthCheckService.java b/core/java/android/service/watchdog/ExplicitHealthCheckService.java
index 619c507..9950143 100644
--- a/core/java/android/service/watchdog/ExplicitHealthCheckService.java
+++ b/core/java/android/service/watchdog/ExplicitHealthCheckService.java
@@ -24,6 +24,7 @@
import android.annotation.SystemApi;
import android.app.Service;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -48,7 +49,8 @@
* <p>To extend this class, you must declare the service in your manifest file with the
* {@link android.Manifest.permission#BIND_EXPLICIT_HEALTH_CHECK_SERVICE} permission,
* and include an intent filter with the {@link #SERVICE_INTERFACE} action. In adddition,
- * your implementation must live in {@link PackageManger#SYSTEM_SHARED_LIBRARY_SERVICES}.
+ * your implementation must live in
+ * {@link PackageManager#getServicesSystemSharedLibraryPackageName()}.
* For example:</p>
* <pre>
* <service android:name=".FooExplicitHealthCheckService"
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index 172495f..c861fa7 100644
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -21,7 +21,7 @@
import android.annotation.RawRes;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
diff --git a/core/java/android/speech/tts/TtsEngines.java b/core/java/android/speech/tts/TtsEngines.java
index 2fab404..a8aea7c 100644
--- a/core/java/android/speech/tts/TtsEngines.java
+++ b/core/java/android/speech/tts/TtsEngines.java
@@ -18,7 +18,7 @@
import static android.provider.Settings.Secure.getString;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
diff --git a/core/java/android/telephony/PhoneStateListener.java b/core/java/android/telephony/PhoneStateListener.java
index 7841f99..a4fe6aa 100644
--- a/core/java/android/telephony/PhoneStateListener.java
+++ b/core/java/android/telephony/PhoneStateListener.java
@@ -21,7 +21,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
@@ -369,6 +369,32 @@
@RequiresPermission(Manifest.permission.READ_ACTIVE_EMERGENCY_SESSION)
public static final int LISTEN_OUTGOING_EMERGENCY_SMS = 0x20000000;
+ /**
+ * Listen for Registration Failures.
+ *
+ * Listen for indications that a registration procedure has failed in either the CS or PS
+ * domain. This indication does not necessarily indicate a change of service state, which should
+ * be tracked via {@link #LISTEN_SERVICE_STATE}.
+ *
+ * <p>Requires permission {@link android.Manifest.permission#READ_PHONE_STATE} or the calling
+ * app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}).
+ *
+ * @see #onRegistrationFailed()
+ */
+ @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
+ public static final int LISTEN_REGISTRATION_FAILURE = 0x40000000;
+
+ /**
+ * Listen for Barring Information for the current registered / camped cell.
+ *
+ * <p>Requires permission {@link android.Manifest.permission#READ_PHONE_STATE} or the calling
+ * app has carrier privileges (see {@link TelephonyManager#hasCarrierPrivileges}).
+ *
+ * @see #onBarringInfoChanged()
+ */
+ @RequiresPermission(Manifest.permission.READ_PHONE_STATE)
+ public static final int LISTEN_BARRING_INFO = 0x80000000;
+
/*
* Subscription used to listen to the phone state changes
* @hide
@@ -932,6 +958,52 @@
}
/**
+ * Report that Registration or a Location/Routing/Tracking Area update has failed.
+ *
+ * <p>Indicate whenever a registration procedure, including a location, routing, or tracking
+ * area update fails. This includes procedures that do not necessarily result in a change of
+ * the modem's registration status. If the modem's registration status changes, that is
+ * reflected in the onNetworkStateChanged() and subsequent get{Voice/Data}RegistrationState().
+ *
+ * <p>Because registration failures are ephemeral, this callback is not sticky.
+ * Registrants will not receive the most recent past value when registering.
+ *
+ * @param cellIdentity the CellIdentity, which must include the globally unique identifier
+ * for the cell (for example, all components of the CGI or ECGI).
+ * @param chosenPlmn a 5 or 6 digit alphanumeric PLMN (MCC|MNC) among those broadcast by the
+ * cell that was chosen for the failed registration attempt.
+ * @param domain DOMAIN_CS, DOMAIN_PS or both in case of a combined procedure.
+ * @param causeCode the primary failure cause code of the procedure.
+ * For GSM/UMTS (MM), values are in TS 24.008 Sec 10.5.95
+ * For GSM/UMTS (GMM), values are in TS 24.008 Sec 10.5.147
+ * For LTE (EMM), cause codes are TS 24.301 Sec 9.9.3.9
+ * For NR (5GMM), cause codes are TS 24.501 Sec 9.11.3.2
+ * Integer.MAX_VALUE if this value is unused.
+ * @param additionalCauseCode the cause code of any secondary/combined procedure if appropriate.
+ * For UMTS, if a combined attach succeeds for PS only, then the GMM cause code shall be
+ * included as an additionalCauseCode. For LTE (ESM), cause codes are in
+ * TS 24.301 9.9.4.4. Integer.MAX_VALUE if this value is unused.
+ */
+ public void onRegistrationFailed(@NonNull CellIdentity cellIdentity, @NonNull String chosenPlmn,
+ int domain, int causeCode, int additionalCauseCode) {
+ // default implementation empty
+ }
+
+ /**
+ * Report updated barring information for the current camped/registered cell.
+ *
+ * <p>Barring info is provided for all services applicable to the current camped/registered
+ * cell, for the registered PLMN and current access class/access category.
+ *
+ * @param barringInfo for all services on the current cell.
+ *
+ * @see android.telephony.BarringInfo
+ */
+ public void onBarringInfoChanged(@NonNull BarringInfo barringInfo) {
+ // default implementation empty
+ }
+
+ /**
* The callback methods need to be called on the handler thread where
* this object was created. If the binder did that for us it'd be nice.
*
@@ -1203,6 +1275,26 @@
() -> psl.onImsCallDisconnectCauseChanged(disconnectCause)));
}
+
+ public void onRegistrationFailed(@NonNull CellIdentity cellIdentity,
+ @NonNull String chosenPlmn, int domain,
+ int causeCode, int additionalCauseCode) {
+ PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+ if (psl == null) return;
+
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(() -> psl.onRegistrationFailed(
+ cellIdentity, chosenPlmn, domain, causeCode, additionalCauseCode)));
+ // default implementation empty
+ }
+
+ public void onBarringInfoChanged(BarringInfo barringInfo) {
+ PhoneStateListener psl = mPhoneStateListenerWeakRef.get();
+ if (psl == null) return;
+
+ Binder.withCleanCallingIdentity(
+ () -> mExecutor.execute(() -> psl.onBarringInfoChanged(barringInfo)));
+ }
}
diff --git a/core/java/android/telephony/Rlog.java b/core/java/android/telephony/Rlog.java
index cdab2dc..2afdd33 100644
--- a/core/java/android/telephony/Rlog.java
+++ b/core/java/android/telephony/Rlog.java
@@ -16,12 +16,11 @@
package android.telephony;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.text.TextUtils;
-import android.util.Log;
-
-import android.annotation.UnsupportedAppUsage;
import android.util.Base64;
+import android.util.Log;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
diff --git a/core/java/android/telephony/TelephonyRegistryManager.java b/core/java/android/telephony/TelephonyRegistryManager.java
index 1b2feda..e25826c 100644
--- a/core/java/android/telephony/TelephonyRegistryManager.java
+++ b/core/java/android/telephony/TelephonyRegistryManager.java
@@ -24,6 +24,7 @@
import android.os.Binder;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.telephony.Annotation.ApnType;
import android.telephony.Annotation.CallState;
import android.telephony.Annotation.DataActivityType;
import android.telephony.Annotation.DataFailureCause;
@@ -352,7 +353,7 @@
* @param subId for which data connection state changed.
* @param slotIndex for which data connections state changed. Can be derived from subId except
* when subId is invalid.
- * @param apnType the APN type that triggered this update
+ * @param apnType the apn type bitmask, defined with {@code ApnSetting#TYPE_*} flags.
* @param preciseState the PreciseDataConnectionState
*
* @see android.telephony.PreciseDataConnection
@@ -360,7 +361,7 @@
* @hide
*/
public void notifyDataConnectionForSubscriber(int slotIndex, int subId,
- String apnType, PreciseDataConnectionState preciseState) {
+ @ApnType int apnType, PreciseDataConnectionState preciseState) {
try {
sRegistry.notifyDataConnectionForSubscriber(
slotIndex, subId, apnType, preciseState);
@@ -553,13 +554,13 @@
* @param subId for which data connection failed.
* @param slotIndex for which data conenction failed. Can be derived from subId except when
* subId is invalid.
- * @param apnType the apnType, "ims" for IMS APN, "emergency" for EMERGENCY APN.
+ * @param apnType the apn type bitmask, defined with {@code ApnSetting#TYPE_*} flags.
* @param apn the APN {@link ApnSetting#getApnName()} of this data connection.
* @param failCause data fail cause.
*
* @hide
*/
- public void notifyPreciseDataConnectionFailed(int subId, int slotIndex, String apnType,
+ public void notifyPreciseDataConnectionFailed(int subId, int slotIndex, @ApnType int apnType,
String apn, @DataFailureCause int failCause) {
try {
sRegistry.notifyPreciseDataConnectionFailed(slotIndex, subId, apnType, apn, failCause);
@@ -614,9 +615,9 @@
* Notify call disconnect causes which contains {@link DisconnectCause} and {@link
* android.telephony.PreciseDisconnectCause}.
*
- * @param subId for which call disconnected.
* @param slotIndex for which call disconnected. Can be derived from subId except when subId is
* invalid.
+ * @param subId for which call disconnected.
* @param cause {@link DisconnectCause} for the disconnected call.
* @param preciseCause {@link android.telephony.PreciseDisconnectCause} for the disconnected
* call.
@@ -675,4 +676,53 @@
}
}
+
+ /**
+ * Report that Registration or a Location/Routing/Tracking Area update has failed.
+ *
+ * @param slotIndex for which call disconnected. Can be derived from subId except when subId is
+ * invalid.
+ * @param subId for which cellinfo changed.
+ * @param cellIdentity the CellIdentity, which must include the globally unique identifier
+ * for the cell (for example, all components of the CGI or ECGI).
+ * @param chosenPlmn a 5 or 6 digit alphanumeric PLMN (MCC|MNC) among those broadcast by the
+ * cell that was chosen for the failed registration attempt.
+ * @param domain DOMAIN_CS, DOMAIN_PS or both in case of a combined procedure.
+ * @param causeCode the primary failure cause code of the procedure.
+ * For GSM/UMTS (MM), values are in TS 24.008 Sec 10.5.95
+ * For GSM/UMTS (GMM), values are in TS 24.008 Sec 10.5.147
+ * For LTE (EMM), cause codes are TS 24.301 Sec 9.9.3.9
+ * For NR (5GMM), cause codes are TS 24.501 Sec 9.11.3.2
+ * Integer.MAX_VALUE if this value is unused.
+ * @param additionalCauseCode the cause code of any secondary/combined procedure if appropriate.
+ * For UMTS, if a combined attach succeeds for PS only, then the GMM cause code shall be
+ * included as an additionalCauseCode. For LTE (ESM), cause codes are in
+ * TS 24.301 9.9.4.4. Integer.MAX_VALUE if this value is unused.
+ */
+ public void notifyRegistrationFailed(int slotIndex, int subId,
+ @NonNull CellIdentity cellIdentity, @NonNull String chosenPlmn,
+ int domain, int causeCode, int additionalCauseCode) {
+ try {
+ sRegistry.notifyRegistrationFailed(slotIndex, subId, cellIdentity,
+ chosenPlmn, domain, causeCode, additionalCauseCode);
+ } catch (RemoteException ex) {
+ }
+ }
+
+ /**
+ * Notify {@link BarringInfo} has changed for a specific subscription.
+ *
+ * @param slotIndex for the phone object that got updated barring info.
+ * @param subId for which the BarringInfo changed.
+ * @param barringInfo updated BarringInfo.
+ */
+ public void notifyBarringInfoChanged(
+ int slotIndex, int subId, @NonNull BarringInfo barringInfo) {
+ try {
+ sRegistry.notifyBarringInfoChanged(slotIndex, subId, barringInfo);
+ } catch (RemoteException ex) {
+ // system server crash
+ }
+ }
+
}
diff --git a/core/java/android/telephony/WapPushManagerConnector.java b/core/java/android/telephony/WapPushManagerConnector.java
new file mode 100644
index 0000000..a9df506
--- /dev/null
+++ b/core/java/android/telephony/WapPushManagerConnector.java
@@ -0,0 +1,178 @@
+/*
+ * 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.telephony;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.android.internal.telephony.IWapPushManager;
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * APIs for platform to connect to the WAP push manager service.
+ *
+ * <p>To start connection, {@link #bindToWapPushManagerService} should be called.
+ *
+ * <p>Upon completion {@link #unbindWapPushManagerService} should be called to unbind the service.
+ *
+ * @hide
+ */
+@SystemApi
+public final class WapPushManagerConnector {
+ private final Context mContext;
+
+ private volatile WapPushManagerConnection mConnection;
+ private volatile IWapPushManager mWapPushManager;
+ private String mWapPushManagerPackage;
+
+ /**
+ * The {@link android.content.Intent} that must be declared as handled by the
+ * WAP push manager service.
+ * @hide
+ */
+ @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION)
+ public static final String SERVICE_INTERFACE =
+ "com.android.internal.telephony.IWapPushManager";
+
+ /** @hide */
+ @IntDef(flag = true, prefix = {"RESULT_"}, value = {
+ RESULT_MESSAGE_HANDLED,
+ RESULT_APP_QUERY_FAILED,
+ RESULT_SIGNATURE_NO_MATCH,
+ RESULT_INVALID_RECEIVER_NAME,
+ RESULT_EXCEPTION_CAUGHT,
+ RESULT_FURTHER_PROCESSING,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ProcessMessageResult{}
+
+ /** {@link #processMessage} return value: Message is handled. */
+ public static final int RESULT_MESSAGE_HANDLED = 0x1;
+ /** {@link #processMessage} return value: Application ID or content type was not found. */
+ public static final int RESULT_APP_QUERY_FAILED = 0x2;
+ /** {@link #processMessage} return value: Receiver application signature check failed. */
+ public static final int RESULT_SIGNATURE_NO_MATCH = 0x4;
+ /** {@link #processMessage} return value: Receiver application was not found. */
+ public static final int RESULT_INVALID_RECEIVER_NAME = 0x8;
+ /** {@link #processMessage} return value: Unknown exception. */
+ public static final int RESULT_EXCEPTION_CAUGHT = 0x10;
+ /** {@link #processMessage} return value: further processing needed. */
+ public static final int RESULT_FURTHER_PROCESSING = 0x8000;
+
+ /** The application package name of the WAP push manager service. */
+ private static final String SERVICE_PACKAGE = "com.android.smspush";
+
+ public WapPushManagerConnector(@NonNull Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Binds to the WAP push manager service. This method should be called exactly once.
+ *
+ * @return {@code true} upon successfully binding to a service, {@code false} otherwise
+ */
+ public boolean bindToWapPushManagerService() {
+ Preconditions.checkState(mConnection == null);
+
+ Intent intent = new Intent(SERVICE_INTERFACE);
+ ComponentName component = intent.resolveSystemService(mContext.getPackageManager(), 0);
+ intent.setComponent(component);
+ mConnection = new WapPushManagerConnection();
+ if (component != null
+ && mContext.bindService(intent, mConnection, Context.BIND_AUTO_CREATE)) {
+ mWapPushManagerPackage = component.getPackageName();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Returns the package name of WAP push manager service application connected to,
+ * or {@code null} if not connected.
+ */
+ @Nullable
+ public String getConnectedWapPushManagerServicePackage() {
+ return mWapPushManagerPackage;
+ }
+
+ /**
+ * Processes WAP push message and triggers the {@code intent}.
+ *
+ * @see RESULT_MESSAGE_HANDLED
+ * @see RESULT_APP_QUERY_FAILED
+ * @see RESULT_SIGNATURE_NO_MATCH
+ * @see RESULT_INVALID_RECEIVER_NAME
+ * @see RESULT_EXCEPTION_CAUGHT
+ * @see RESULT_FURTHER_PROCESSING
+ */
+ @ProcessMessageResult
+ public int processMessage(
+ @NonNull String applicationId, @NonNull String contentType, @NonNull Intent intent) {
+ try {
+ return mWapPushManager.processMessage(applicationId, contentType, intent);
+ } catch (NullPointerException | RemoteException e) {
+ return RESULT_EXCEPTION_CAUGHT;
+ }
+ }
+
+ /**
+ * Unbinds the WAP push manager service. This method should be called exactly once.
+ */
+ public void unbindWapPushManagerService() {
+ Preconditions.checkNotNull(mConnection);
+
+ mContext.unbindService(mConnection);
+ mConnection = null;
+ }
+
+ private class WapPushManagerConnection implements ServiceConnection {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ // Because we have bound to an explicit
+ // service that is running in our own process, we can
+ // cast its IBinder to a concrete class and directly access it.
+ mWapPushManager = IWapPushManager.Stub.asInterface(service);
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ mWapPushManager = null;
+ }
+
+ @Override
+ public void onNullBinding(ComponentName name) {
+ onServiceDisconnected(name);
+ }
+
+ @Override
+ public void onBindingDied(ComponentName name) {
+ onServiceDisconnected(name);
+ }
+ }
+}
diff --git a/core/java/android/text/AndroidBidi.java b/core/java/android/text/AndroidBidi.java
index bb7fb44..f1e2181 100644
--- a/core/java/android/text/AndroidBidi.java
+++ b/core/java/android/text/AndroidBidi.java
@@ -16,7 +16,7 @@
package android.text;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.icu.lang.UCharacter;
import android.icu.lang.UCharacterDirection;
import android.icu.lang.UProperty;
diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java
index cf6987c..3ee1a90 100644
--- a/core/java/android/text/BoringLayout.java
+++ b/core/java/android/text/BoringLayout.java
@@ -16,7 +16,7 @@
package android.text;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index 32982f9..c60d446 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -20,7 +20,7 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Build;
diff --git a/core/java/android/text/FontConfig.java b/core/java/android/text/FontConfig.java
index 19c55d5..b5688a4 100644
--- a/core/java/android/text/FontConfig.java
+++ b/core/java/android/text/FontConfig.java
@@ -21,7 +21,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.fonts.FontVariationAxis;
import android.net.Uri;
diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java
index 18f8db2..55af087 100644
--- a/core/java/android/text/Html.java
+++ b/core/java/android/text/Html.java
@@ -16,9 +16,9 @@
package android.text;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityThread;
import android.app.Application;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.Typeface;
diff --git a/core/java/android/text/InputFilter.java b/core/java/android/text/InputFilter.java
index a9a7b2f..96e7bd0 100644
--- a/core/java/android/text/InputFilter.java
+++ b/core/java/android/text/InputFilter.java
@@ -17,7 +17,7 @@
package android.text;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import com.android.internal.util.Preconditions;
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 2c2c295..8a4497a 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -18,7 +18,7 @@
import android.annotation.IntDef;
import android.annotation.IntRange;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
diff --git a/core/java/android/text/Selection.java b/core/java/android/text/Selection.java
index 68199a4..57c1d23 100644
--- a/core/java/android/text/Selection.java
+++ b/core/java/android/text/Selection.java
@@ -17,7 +17,7 @@
package android.text;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import java.text.BreakIterator;
diff --git a/core/java/android/text/SpanSet.java b/core/java/android/text/SpanSet.java
index 362825a..81bdd65 100644
--- a/core/java/android/text/SpanSet.java
+++ b/core/java/android/text/SpanSet.java
@@ -16,7 +16,8 @@
package android.text;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
import java.lang.reflect.Array;
import java.util.Arrays;
diff --git a/core/java/android/text/SpannableStringBuilder.java b/core/java/android/text/SpannableStringBuilder.java
index 0d29da0..ac27e3d 100644
--- a/core/java/android/text/SpannableStringBuilder.java
+++ b/core/java/android/text/SpannableStringBuilder.java
@@ -17,7 +17,7 @@
package android.text;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.BaseCanvas;
import android.graphics.Paint;
import android.util.Log;
diff --git a/core/java/android/text/SpannableStringInternal.java b/core/java/android/text/SpannableStringInternal.java
index a986633..4c9328a 100644
--- a/core/java/android/text/SpannableStringInternal.java
+++ b/core/java/android/text/SpannableStringInternal.java
@@ -16,7 +16,7 @@
package android.text;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
@@ -40,9 +40,10 @@
if (source instanceof Spanned) {
if (source instanceof SpannableStringInternal) {
- copySpans((SpannableStringInternal) source, start, end, ignoreNoCopySpan);
+ copySpansFromInternal(
+ (SpannableStringInternal) source, start, end, ignoreNoCopySpan);
} else {
- copySpans((Spanned) source, start, end, ignoreNoCopySpan);
+ copySpansFromSpanned((Spanned) source, start, end, ignoreNoCopySpan);
}
}
}
@@ -65,7 +66,7 @@
* @param end End index in the source object.
* @param ignoreNoCopySpan whether to copy NoCopySpans in the {@code source}
*/
- private void copySpans(Spanned src, int start, int end, boolean ignoreNoCopySpan) {
+ private void copySpansFromSpanned(Spanned src, int start, int end, boolean ignoreNoCopySpan) {
Object[] spans = src.getSpans(start, end, Object.class);
for (int i = 0; i < spans.length; i++) {
@@ -94,7 +95,7 @@
* @param end End index in the source object.
* @param ignoreNoCopySpan copy NoCopySpan for backward compatible reasons.
*/
- private void copySpans(SpannableStringInternal src, int start, int end,
+ private void copySpansFromInternal(SpannableStringInternal src, int start, int end,
boolean ignoreNoCopySpan) {
int count = 0;
final int[] srcData = src.mSpanData;
@@ -555,12 +556,12 @@
*/
@UnsupportedAppUsage
private void copySpans(Spanned src, int start, int end) {
- copySpans(src, start, end, false);
+ copySpansFromSpanned(src, start, end, false);
}
@UnsupportedAppUsage
private void copySpans(SpannableStringInternal src, int start, int end) {
- copySpans(src, start, end, false);
+ copySpansFromInternal(src, start, end, false);
}
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 69cfc03..85e2d98 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -20,7 +20,7 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Paint;
import android.graphics.text.LineBreaker;
import android.os.Build;
diff --git a/core/java/android/text/TextLine.java b/core/java/android/text/TextLine.java
index 1c50d73..3c51fa7 100644
--- a/core/java/android/text/TextLine.java
+++ b/core/java/android/text/TextLine.java
@@ -19,7 +19,7 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.FontMetricsInt;
diff --git a/core/java/android/text/TextPaint.java b/core/java/android/text/TextPaint.java
index d5aad33..73825b1 100644
--- a/core/java/android/text/TextPaint.java
+++ b/core/java/android/text/TextPaint.java
@@ -18,7 +18,7 @@
import android.annotation.ColorInt;
import android.annotation.Px;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Paint;
/**
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 5bda867..df4ead1 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -24,7 +24,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.PluralsRes;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Resources;
import android.icu.lang.UCharacter;
diff --git a/core/java/android/text/format/DateFormat.java b/core/java/android/text/format/DateFormat.java
index 3c8de94..0863a81 100755
--- a/core/java/android/text/format/DateFormat.java
+++ b/core/java/android/text/format/DateFormat.java
@@ -17,9 +17,8 @@
package android.text.format;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
-import android.os.UserHandle;
import android.provider.Settings;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
diff --git a/core/java/android/text/format/DateUtils.java b/core/java/android/text/format/DateUtils.java
index 0c27923..ce676e0 100644
--- a/core/java/android/text/format/DateUtils.java
+++ b/core/java/android/text/format/DateUtils.java
@@ -16,7 +16,7 @@
package android.text.format;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
diff --git a/core/java/android/text/format/Formatter.java b/core/java/android/text/format/Formatter.java
index d7baa10..17d3ae4 100644
--- a/core/java/android/text/format/Formatter.java
+++ b/core/java/android/text/format/Formatter.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Resources;
import android.icu.text.MeasureFormat;
diff --git a/core/java/android/text/method/AllCapsTransformationMethod.java b/core/java/android/text/method/AllCapsTransformationMethod.java
index 5a7c98d..305b056 100644
--- a/core/java/android/text/method/AllCapsTransformationMethod.java
+++ b/core/java/android/text/method/AllCapsTransformationMethod.java
@@ -17,7 +17,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.graphics.Rect;
import android.text.Spanned;
diff --git a/core/java/android/text/method/HideReturnsTransformationMethod.java b/core/java/android/text/method/HideReturnsTransformationMethod.java
index 440a4b1..40ce871 100644
--- a/core/java/android/text/method/HideReturnsTransformationMethod.java
+++ b/core/java/android/text/method/HideReturnsTransformationMethod.java
@@ -16,7 +16,7 @@
package android.text.method;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
/**
diff --git a/core/java/android/text/method/LinkMovementMethod.java b/core/java/android/text/method/LinkMovementMethod.java
index a0c44a8..c544c41 100644
--- a/core/java/android/text/method/LinkMovementMethod.java
+++ b/core/java/android/text/method/LinkMovementMethod.java
@@ -16,7 +16,7 @@
package android.text.method;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.text.Layout;
import android.text.NoCopySpan;
diff --git a/core/java/android/text/method/MetaKeyKeyListener.java b/core/java/android/text/method/MetaKeyKeyListener.java
index ec7ed34b..d1d7c96 100644
--- a/core/java/android/text/method/MetaKeyKeyListener.java
+++ b/core/java/android/text/method/MetaKeyKeyListener.java
@@ -16,7 +16,7 @@
package android.text.method;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.text.Editable;
import android.text.NoCopySpan;
import android.text.Spannable;
diff --git a/core/java/android/text/method/PasswordTransformationMethod.java b/core/java/android/text/method/PasswordTransformationMethod.java
index c96fc5d..53553be 100644
--- a/core/java/android/text/method/PasswordTransformationMethod.java
+++ b/core/java/android/text/method/PasswordTransformationMethod.java
@@ -16,7 +16,7 @@
package android.text.method;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Rect;
import android.os.Build;
import android.os.Handler;
diff --git a/core/java/android/text/method/TransformationMethod2.java b/core/java/android/text/method/TransformationMethod2.java
index 0bf401a..8d5ec24 100644
--- a/core/java/android/text/method/TransformationMethod2.java
+++ b/core/java/android/text/method/TransformationMethod2.java
@@ -15,7 +15,7 @@
*/
package android.text.method;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* TransformationMethod2 extends the TransformationMethod interface
diff --git a/core/java/android/text/method/WordIterator.java b/core/java/android/text/method/WordIterator.java
index 313567a..d766186 100644
--- a/core/java/android/text/method/WordIterator.java
+++ b/core/java/android/text/method/WordIterator.java
@@ -17,7 +17,7 @@
package android.text.method;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.icu.lang.UCharacter;
import android.icu.lang.UProperty;
import android.icu.text.BreakIterator;
diff --git a/core/java/android/text/style/BulletSpan.java b/core/java/android/text/style/BulletSpan.java
index 9b1dfbf..ad61788 100644
--- a/core/java/android/text/style/BulletSpan.java
+++ b/core/java/android/text/style/BulletSpan.java
@@ -21,7 +21,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Px;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Build;
diff --git a/core/java/android/text/style/DynamicDrawableSpan.java b/core/java/android/text/style/DynamicDrawableSpan.java
index 1a508a1..f37e423 100644
--- a/core/java/android/text/style/DynamicDrawableSpan.java
+++ b/core/java/android/text/style/DynamicDrawableSpan.java
@@ -22,7 +22,7 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
diff --git a/core/java/android/text/style/EasyEditSpan.java b/core/java/android/text/style/EasyEditSpan.java
index bfb2873..b23c2b7 100644
--- a/core/java/android/text/style/EasyEditSpan.java
+++ b/core/java/android/text/style/EasyEditSpan.java
@@ -17,8 +17,8 @@
package android.text.style;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
import android.app.PendingIntent;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.text.ParcelableSpan;
import android.text.TextUtils;
diff --git a/core/java/android/text/style/ImageSpan.java b/core/java/android/text/style/ImageSpan.java
index 98f58be..ec55aeb 100644
--- a/core/java/android/text/style/ImageSpan.java
+++ b/core/java/android/text/style/ImageSpan.java
@@ -19,7 +19,7 @@
import android.annotation.DrawableRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
diff --git a/core/java/android/text/style/SpellCheckSpan.java b/core/java/android/text/style/SpellCheckSpan.java
index 6ffde38..e8ec3c6 100644
--- a/core/java/android/text/style/SpellCheckSpan.java
+++ b/core/java/android/text/style/SpellCheckSpan.java
@@ -16,7 +16,7 @@
package android.text.style;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.text.ParcelableSpan;
import android.text.TextUtils;
diff --git a/core/java/android/text/style/SuggestionRangeSpan.java b/core/java/android/text/style/SuggestionRangeSpan.java
index d958dde..2b04a7a 100644
--- a/core/java/android/text/style/SuggestionRangeSpan.java
+++ b/core/java/android/text/style/SuggestionRangeSpan.java
@@ -16,7 +16,7 @@
package android.text.style;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.text.ParcelableSpan;
import android.text.TextPaint;
diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java
index c000ae3..be01bfb 100644
--- a/core/java/android/text/style/SuggestionSpan.java
+++ b/core/java/android/text/style/SuggestionSpan.java
@@ -19,7 +19,7 @@
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
diff --git a/core/java/android/text/util/Linkify.java b/core/java/android/text/util/Linkify.java
index 993bbe8..2aca36a 100644
--- a/core/java/android/text/util/Linkify.java
+++ b/core/java/android/text/util/Linkify.java
@@ -19,7 +19,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.telephony.PhoneNumberUtils;
import android.telephony.TelephonyManager;
diff --git a/core/java/android/timezone/CountryTimeZones.java b/core/java/android/timezone/CountryTimeZones.java
new file mode 100644
index 0000000..ada59d6
--- /dev/null
+++ b/core/java/android/timezone/CountryTimeZones.java
@@ -0,0 +1,265 @@
+/*
+ * 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.timezone;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.icu.util.TimeZone;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Information about a country's time zones.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public final class CountryTimeZones {
+
+ /**
+ * A mapping to a time zone ID with some associated metadata.
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final class TimeZoneMapping {
+
+ private libcore.timezone.CountryTimeZones.TimeZoneMapping mDelegate;
+
+ TimeZoneMapping(libcore.timezone.CountryTimeZones.TimeZoneMapping delegate) {
+ this.mDelegate = Objects.requireNonNull(delegate);
+ }
+
+ /**
+ * Returns the ID for this mapping. See also {@link #getTimeZone()} which handles when the
+ * ID is unrecognized.
+ */
+ @NonNull
+ public String getTimeZoneId() {
+ return mDelegate.timeZoneId;
+ }
+
+ /**
+ * Returns a {@link TimeZone} object for this mapping, or {@code null} if the ID is
+ * unrecognized.
+ */
+ @Nullable
+ public TimeZone getTimeZone() {
+ return mDelegate.getTimeZone();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ TimeZoneMapping that = (TimeZoneMapping) o;
+ return this.mDelegate.equals(that.mDelegate);
+ }
+
+ @Override
+ public int hashCode() {
+ return this.mDelegate.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return mDelegate.toString();
+ }
+ }
+
+ /**
+ * The result of lookup up a time zone using offset information (and possibly more).
+ *
+ * @hide
+ */
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+ public static final class OffsetResult {
+
+ private final TimeZone mTimeZone;
+ private final boolean mIsOnlyMatch;
+
+ /** Creates an instance with the supplied information. */
+ public OffsetResult(@NonNull TimeZone timeZone, boolean isOnlyMatch) {
+ mTimeZone = Objects.requireNonNull(timeZone);
+ mIsOnlyMatch = isOnlyMatch;
+ }
+
+ /**
+ * Returns a time zone that matches the supplied criteria.
+ */
+ @NonNull
+ public TimeZone getTimeZone() {
+ return mTimeZone;
+ }
+
+ /**
+ * Returns {@code true} if there is only one matching time zone for the supplied criteria.
+ */
+ public boolean isOnlyMatch() {
+ return mIsOnlyMatch;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ OffsetResult that = (OffsetResult) o;
+ return mIsOnlyMatch == that.mIsOnlyMatch
+ && mTimeZone.getID().equals(that.mTimeZone.getID());
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mTimeZone, mIsOnlyMatch);
+ }
+
+ @Override
+ public String toString() {
+ return "OffsetResult{"
+ + "mTimeZone=" + mTimeZone
+ + ", mIsOnlyMatch=" + mIsOnlyMatch
+ + '}';
+ }
+ }
+
+ @NonNull
+ private final libcore.timezone.CountryTimeZones mDelegate;
+
+ CountryTimeZones(libcore.timezone.CountryTimeZones delegate) {
+ mDelegate = delegate;
+ }
+
+ /**
+ * Returns true if the ISO code for the country is a match for the one specified.
+ */
+ public boolean isForCountryCode(@NonNull String countryIso) {
+ return mDelegate.isForCountryCode(countryIso);
+ }
+
+ /**
+ * Returns the default time zone ID for the country. Can return {@code null} in cases when no
+ * data is available or the time zone ID was not recognized.
+ */
+ @Nullable
+ public String getDefaultTimeZoneId() {
+ return mDelegate.getDefaultTimeZoneId();
+ }
+
+ /**
+ * Returns the default time zone for the country. Can return {@code null} in cases when no data
+ * is available or the time zone ID was not recognized.
+ */
+ @Nullable
+ public TimeZone getDefaultTimeZone() {
+ return mDelegate.getDefaultTimeZone();
+ }
+
+ /**
+ * Qualifier for a country's default time zone. {@code true} indicates whether the default
+ * would be a good choice <em>generally</em> when there's no other information available.
+ */
+ public boolean isDefaultTimeZoneBoosted() {
+ return mDelegate.getDefaultTimeZoneBoost();
+ }
+
+ /**
+ * Returns true if the country has at least one zone that is the same as UTC at the given time.
+ */
+ public boolean hasUtcZone(long whenMillis) {
+ return mDelegate.hasUtcZone(whenMillis);
+ }
+
+ /**
+ * Returns a time zone for the country, if there is one, that matches the desired properties. If
+ * there are multiple matches and the {@code bias} is one of them then it is returned, otherwise
+ * an arbitrary match is returned based on the {@link #getEffectiveTimeZoneMappingsAt(long)}
+ * ordering.
+ *
+ * @param totalOffsetMillis the offset from UTC at {@code whenMillis}
+ * @param isDst the Daylight Savings Time state at {@code whenMillis}. {@code true} means DST,
+ * {@code false} means not DST, {@code null} means unknown
+ * @param dstOffsetMillis the part of {@code totalOffsetMillis} contributed by DST, only used if
+ * {@code isDst} is {@code true}. The value can be {@code null} if the DST offset is
+ * unknown
+ * @param whenMillis the UTC time to match against
+ * @param bias the time zone to prefer, can be {@code null}
+ */
+ @Nullable
+ public OffsetResult lookupByOffsetWithBias(int totalOffsetMillis, @Nullable Boolean isDst,
+ @SuppressLint("AutoBoxing") @Nullable Integer dstOffsetMillis, long whenMillis,
+ @Nullable TimeZone bias) {
+ libcore.timezone.CountryTimeZones.OffsetResult delegateOffsetResult =
+ mDelegate.lookupByOffsetWithBias(
+ totalOffsetMillis, isDst, dstOffsetMillis, whenMillis, bias);
+ return delegateOffsetResult == null ? null :
+ new OffsetResult(delegateOffsetResult.mTimeZone, delegateOffsetResult.mOneMatch);
+ }
+
+ /**
+ * Returns an immutable, ordered list of time zone mappings for the country in an undefined but
+ * "priority" order, filtered so that only "effective" time zone IDs are returned. An
+ * "effective" time zone is one that differs from another time zone used in the country after
+ * {@code whenMillis}. The list can be empty if there were no zones configured or the configured
+ * zone IDs were not recognized.
+ */
+ @NonNull
+ public List<TimeZoneMapping> getEffectiveTimeZoneMappingsAt(long whenMillis) {
+ List<libcore.timezone.CountryTimeZones.TimeZoneMapping> delegateList =
+ mDelegate.getEffectiveTimeZoneMappingsAt(whenMillis);
+
+ List<TimeZoneMapping> toReturn = new ArrayList<>(delegateList.size());
+ for (libcore.timezone.CountryTimeZones.TimeZoneMapping delegateMapping : delegateList) {
+ toReturn.add(new TimeZoneMapping(delegateMapping));
+ }
+ return Collections.unmodifiableList(toReturn);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ CountryTimeZones that = (CountryTimeZones) o;
+ return mDelegate.equals(that.mDelegate);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mDelegate);
+ }
+
+ @Override
+ public String toString() {
+ return mDelegate.toString();
+ }
+}
diff --git a/core/java/android/timezone/TelephonyLookup.java b/core/java/android/timezone/TelephonyLookup.java
new file mode 100644
index 0000000..39dbe85
--- /dev/null
+++ b/core/java/android/timezone/TelephonyLookup.java
@@ -0,0 +1,71 @@
+/*
+ * 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.timezone;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Objects;
+
+/**
+ * A class that can find time zone-related information about telephony networks.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public class TelephonyLookup {
+
+ private static Object sLock = new Object();
+ @GuardedBy("sLock")
+ private static TelephonyLookup sInstance;
+
+ @NonNull
+ private final libcore.timezone.TelephonyLookup mDelegate;
+
+ /**
+ * Obtains an instance for use when resolving telephony time zone information. This method never
+ * returns {@code null}.
+ */
+ @NonNull
+ public static TelephonyLookup getInstance() {
+ synchronized (sLock) {
+ if (sInstance == null) {
+ sInstance = new TelephonyLookup(libcore.timezone.TelephonyLookup.getInstance());
+ }
+ return sInstance;
+ }
+ }
+
+ private TelephonyLookup(@NonNull libcore.timezone.TelephonyLookup delegate) {
+ mDelegate = Objects.requireNonNull(delegate);
+ }
+
+ /**
+ * Returns an object capable of querying telephony network information. This method can return
+ * {@code null} in the event of an error while reading the underlying data files.
+ */
+ @Nullable
+ public TelephonyNetworkFinder getTelephonyNetworkFinder() {
+ libcore.timezone.TelephonyNetworkFinder telephonyNetworkFinderDelegate =
+ mDelegate.getTelephonyNetworkFinder();
+ return telephonyNetworkFinderDelegate != null
+ ? new TelephonyNetworkFinder(telephonyNetworkFinderDelegate) : null;
+ }
+}
diff --git a/core/java/android/timezone/TelephonyNetwork.java b/core/java/android/timezone/TelephonyNetwork.java
new file mode 100644
index 0000000..ae39fbd
--- /dev/null
+++ b/core/java/android/timezone/TelephonyNetwork.java
@@ -0,0 +1,86 @@
+/*
+ * 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.timezone;
+
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import java.util.Objects;
+
+/**
+ * Information about a telephony network.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public class TelephonyNetwork {
+
+ @NonNull
+ private final libcore.timezone.TelephonyNetwork mDelegate;
+
+ TelephonyNetwork(@NonNull libcore.timezone.TelephonyNetwork delegate) {
+ mDelegate = Objects.requireNonNull(delegate);
+ }
+
+ /**
+ * Returns the Mobile Country Code of the network.
+ */
+ @NonNull
+ public String getMcc() {
+ return mDelegate.getMcc();
+ }
+
+ /**
+ * Returns the Mobile Network Code of the network.
+ */
+ @NonNull
+ public String getMnc() {
+ return mDelegate.getMnc();
+ }
+
+ /**
+ * Returns the country in which the network operates as an ISO 3166 alpha-2 (lower case).
+ */
+ @NonNull
+ public String getCountryIsoCode() {
+ return mDelegate.getCountryIsoCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ TelephonyNetwork that = (TelephonyNetwork) o;
+ return mDelegate.equals(that.mDelegate);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mDelegate);
+ }
+
+ @Override
+ public String toString() {
+ return "TelephonyNetwork{"
+ + "mDelegate=" + mDelegate
+ + '}';
+ }
+}
diff --git a/core/java/android/timezone/TelephonyNetworkFinder.java b/core/java/android/timezone/TelephonyNetworkFinder.java
new file mode 100644
index 0000000..a81a516
--- /dev/null
+++ b/core/java/android/timezone/TelephonyNetworkFinder.java
@@ -0,0 +1,55 @@
+/*
+ * 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.timezone;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+import java.util.Objects;
+
+/**
+ * A class that can find telephony networks loaded via {@link TelephonyLookup}.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public class TelephonyNetworkFinder {
+
+ @NonNull
+ private final libcore.timezone.TelephonyNetworkFinder mDelegate;
+
+ TelephonyNetworkFinder(libcore.timezone.TelephonyNetworkFinder delegate) {
+ mDelegate = Objects.requireNonNull(delegate);
+ }
+
+ /**
+ * Returns information held about a specific MCC + MNC combination. It is expected for this
+ * method to return {@code null}. Only known, unusual networks will typically have information
+ * returned, e.g. if they operate in countries other than the one suggested by their MCC.
+ */
+ @Nullable
+ public TelephonyNetwork findNetworkByMccMnc(@NonNull String mcc, @NonNull String mnc) {
+ Objects.requireNonNull(mcc);
+ Objects.requireNonNull(mnc);
+
+ libcore.timezone.TelephonyNetwork telephonyNetworkDelegate =
+ mDelegate.findNetworkByMccMnc(mcc, mnc);
+ return telephonyNetworkDelegate != null
+ ? new TelephonyNetwork(telephonyNetworkDelegate) : null;
+ }
+}
diff --git a/core/java/android/timezone/TimeZoneFinder.java b/core/java/android/timezone/TimeZoneFinder.java
new file mode 100644
index 0000000..15dfe62
--- /dev/null
+++ b/core/java/android/timezone/TimeZoneFinder.java
@@ -0,0 +1,67 @@
+/*
+ * 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.timezone;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * A class that can be used to find time zones.
+ *
+ * @hide
+ */
+@SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+public final class TimeZoneFinder {
+
+ private static Object sLock = new Object();
+ @GuardedBy("sLock")
+ private static TimeZoneFinder sInstance;
+
+ private final libcore.timezone.TimeZoneFinder mDelegate;
+
+ private TimeZoneFinder(libcore.timezone.TimeZoneFinder delegate) {
+ mDelegate = delegate;
+ }
+
+ /**
+ * Obtains an instance for use when resolving telephony time zone information. This method never
+ * returns {@code null}.
+ */
+ @NonNull
+ public static TimeZoneFinder getInstance() {
+ synchronized (sLock) {
+ if (sInstance == null) {
+ sInstance = new TimeZoneFinder(libcore.timezone.TimeZoneFinder.getInstance());
+ }
+ }
+ return sInstance;
+ }
+
+ /**
+ * Returns a {@link CountryTimeZones} object associated with the specified country code.
+ * Caching is handled as needed. If the country code is not recognized or there is an error
+ * during lookup this method can return null.
+ */
+ @Nullable
+ public CountryTimeZones lookupCountryTimeZones(@NonNull String countryIso) {
+ libcore.timezone.CountryTimeZones delegate = mDelegate.lookupCountryTimeZones(countryIso);
+ return delegate == null ? null : new CountryTimeZones(delegate);
+ }
+}
diff --git a/core/java/android/transition/ChangeBounds.java b/core/java/android/transition/ChangeBounds.java
index de182da..59a05ac 100644
--- a/core/java/android/transition/ChangeBounds.java
+++ b/core/java/android/transition/ChangeBounds.java
@@ -22,7 +22,7 @@
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.animation.RectEvaluator;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
diff --git a/core/java/android/transition/Scene.java b/core/java/android/transition/Scene.java
index 8d4db54..52a97e3 100644
--- a/core/java/android/transition/Scene.java
+++ b/core/java/android/transition/Scene.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Build;
import android.util.SparseArray;
diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java
index 0feab4d..e511584 100644
--- a/core/java/android/transition/Transition.java
+++ b/core/java/android/transition/Transition.java
@@ -20,7 +20,7 @@
import android.animation.AnimatorListenerAdapter;
import android.animation.TimeInterpolator;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Path;
diff --git a/core/java/android/transition/TransitionManager.java b/core/java/android/transition/TransitionManager.java
index 0e5252e..1b0612e 100644
--- a/core/java/android/transition/TransitionManager.java
+++ b/core/java/android/transition/TransitionManager.java
@@ -17,7 +17,7 @@
package android.transition;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.util.ArrayMap;
import android.util.Log;
diff --git a/core/java/android/util/ArrayMap.java b/core/java/android/util/ArrayMap.java
index 3f44e48..4cf0a36 100644
--- a/core/java/android/util/ArrayMap.java
+++ b/core/java/android/util/ArrayMap.java
@@ -16,7 +16,7 @@
package android.util;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import com.android.internal.util.ArrayUtils;
diff --git a/core/java/android/util/ArraySet.java b/core/java/android/util/ArraySet.java
index 4dda709..7f652ba 100644
--- a/core/java/android/util/ArraySet.java
+++ b/core/java/android/util/ArraySet.java
@@ -18,7 +18,7 @@
import android.annotation.Nullable;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import libcore.util.EmptyArray;
diff --git a/core/java/android/util/Base64.java b/core/java/android/util/Base64.java
index ecc0c9c..92abd7c 100644
--- a/core/java/android/util/Base64.java
+++ b/core/java/android/util/Base64.java
@@ -16,7 +16,8 @@
package android.util;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
import java.io.UnsupportedEncodingException;
/**
diff --git a/core/java/android/util/Base64OutputStream.java b/core/java/android/util/Base64OutputStream.java
index 230a3a5..48fadeb 100644
--- a/core/java/android/util/Base64OutputStream.java
+++ b/core/java/android/util/Base64OutputStream.java
@@ -16,7 +16,8 @@
package android.util;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
diff --git a/core/java/android/util/DebugUtils.java b/core/java/android/util/DebugUtils.java
index bc5edf8..6d5e830 100644
--- a/core/java/android/util/DebugUtils.java
+++ b/core/java/android/util/DebugUtils.java
@@ -16,7 +16,7 @@
package android.util;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import java.io.PrintWriter;
diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java
index 451a669..9f6065e 100755
--- a/core/java/android/util/DisplayMetrics.java
+++ b/core/java/android/util/DisplayMetrics.java
@@ -16,7 +16,7 @@
package android.util;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.SystemProperties;
diff --git a/core/java/android/util/EventLog.java b/core/java/android/util/EventLog.java
index 65d825a..c9dc05b 100644
--- a/core/java/android/util/EventLog.java
+++ b/core/java/android/util/EventLog.java
@@ -17,7 +17,7 @@
package android.util;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import java.io.BufferedReader;
import java.io.FileReader;
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 1b2db36..eb4af1c 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -42,6 +42,8 @@
public static final String DYNAMIC_SYSTEM = "settings_dynamic_system";
public static final String SETTINGS_WIFITRACKER2 = "settings_wifitracker2";
public static final String SETTINGS_FUSE_FLAG = "settings_fuse";
+ public static final String NOTIF_CONVO_BYPASS_SHORTCUT_REQ =
+ "settings_notif_convo_bypass_shortcut_req";
private static final Map<String, String> DEFAULT_FLAGS;
@@ -57,9 +59,9 @@
DEFAULT_FLAGS.put("settings_wifi_details_datausage_header", "false");
DEFAULT_FLAGS.put("settings_skip_direction_mutable", "true");
DEFAULT_FLAGS.put(SETTINGS_WIFITRACKER2, "false");
- DEFAULT_FLAGS.put("settings_work_profile", "true");
DEFAULT_FLAGS.put("settings_controller_loading_enhancement", "false");
DEFAULT_FLAGS.put("settings_conditionals", "false");
+ DEFAULT_FLAGS.put(NOTIF_CONVO_BYPASS_SHORTCUT_REQ, "true");
}
/**
diff --git a/core/java/android/util/IconDrawableFactory.java b/core/java/android/util/IconDrawableFactory.java
index d86ebf3..721e6b3 100644
--- a/core/java/android/util/IconDrawableFactory.java
+++ b/core/java/android/util/IconDrawableFactory.java
@@ -15,8 +15,8 @@
*/
package android.util;
-import android.annotation.UnsupportedAppUsage;
import android.annotation.UserIdInt;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageItemInfo;
diff --git a/core/java/android/util/LocalLog.java b/core/java/android/util/LocalLog.java
index 6a6bccf..fda5e0d 100644
--- a/core/java/android/util/LocalLog.java
+++ b/core/java/android/util/LocalLog.java
@@ -16,7 +16,7 @@
package android.util;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.SystemClock;
import java.io.FileDescriptor;
diff --git a/core/java/android/util/Log.java b/core/java/android/util/Log.java
index 50779031..f324113 100644
--- a/core/java/android/util/Log.java
+++ b/core/java/android/util/Log.java
@@ -19,7 +19,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.DeadSystemException;
import com.android.internal.os.RuntimeInit;
@@ -387,6 +387,26 @@
public static native int println_native(int bufID, int priority, String tag, String msg);
/**
+ * Send a log message to the "radio" log buffer, which can be dumped with
+ * {@code adb logcat -b radio}.
+ *
+ * <p>Only the telephony mainline module should use it.
+ *
+ * <p>Note ART will protect {@code MODULE_LIBRARIES} system APIs from regular app code.
+ *
+ * @param priority Log priority.
+ * @param tag Used to identify the source of a log message. It usually identifies
+ * the class or activity where the log call occurs.
+ * @param message The message you would like logged.
+ * @hide
+ */
+ // @SystemApi(client= SystemApi.Client.MODULE_LIBRARIES) // TODO Uncomment once http://ag/9956147 is in.
+ public static int logToRadioBuffer(@Level int priority, @Nullable String tag,
+ @Nullable String message) {
+ return println_native(LOG_ID_RADIO, priority, tag, message);
+ }
+
+ /**
* Return the maximum payload the log daemon accepts without truncation.
* @return LOGGER_ENTRY_MAX_PAYLOAD.
*/
diff --git a/core/java/android/util/LogWriter.java b/core/java/android/util/LogWriter.java
index b062ace..a674ae1 100644
--- a/core/java/android/util/LogWriter.java
+++ b/core/java/android/util/LogWriter.java
@@ -16,7 +16,8 @@
package android.util;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
import java.io.Writer;
/** @hide */
diff --git a/core/java/android/util/LongArray.java b/core/java/android/util/LongArray.java
index 6f4aa52..93bcd6b 100644
--- a/core/java/android/util/LongArray.java
+++ b/core/java/android/util/LongArray.java
@@ -17,7 +17,7 @@
package android.util;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.Preconditions;
diff --git a/core/java/android/util/LongSparseLongArray.java b/core/java/android/util/LongSparseLongArray.java
index a0edd04..c05dd9d 100644
--- a/core/java/android/util/LongSparseLongArray.java
+++ b/core/java/android/util/LongSparseLongArray.java
@@ -16,7 +16,7 @@
package android.util;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import com.android.internal.util.ArrayUtils;
diff --git a/core/java/android/util/LruCache.java b/core/java/android/util/LruCache.java
index 3cbf727d..3f7fdd8 100644
--- a/core/java/android/util/LruCache.java
+++ b/core/java/android/util/LruCache.java
@@ -16,7 +16,7 @@
package android.util;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import java.util.LinkedHashMap;
import java.util.Map;
diff --git a/core/java/android/util/MathUtils.java b/core/java/android/util/MathUtils.java
index 0eeef70..971e161 100644
--- a/core/java/android/util/MathUtils.java
+++ b/core/java/android/util/MathUtils.java
@@ -16,7 +16,7 @@
package android.util;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Rect;
/**
diff --git a/core/java/android/util/NtpTrustedTime.java b/core/java/android/util/NtpTrustedTime.java
index da566c9..0892c94 100644
--- a/core/java/android/util/NtpTrustedTime.java
+++ b/core/java/android/util/NtpTrustedTime.java
@@ -16,7 +16,9 @@
package android.util;
-import android.annotation.UnsupportedAppUsage;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
@@ -28,151 +30,267 @@
import android.provider.Settings;
import android.text.TextUtils;
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Objects;
+import java.util.function.Supplier;
+
/**
- * {@link TrustedTime} that connects with a remote NTP server as its trusted
- * time source.
+ * A singleton that connects with a remote NTP server as its trusted time source. This class
+ * is thread-safe. The {@link #forceRefresh()} method is synchronous, i.e. it may occupy the
+ * current thread while performing an NTP request. All other threads calling {@link #forceRefresh()}
+ * will block during that request.
*
* @hide
*/
public class NtpTrustedTime implements TrustedTime {
+
+ /**
+ * The result of a successful NTP query.
+ *
+ * @hide
+ */
+ public static class TimeResult {
+ private final long mTimeMillis;
+ private final long mElapsedRealtimeMillis;
+ private final long mCertaintyMillis;
+
+ public TimeResult(long timeMillis, long elapsedRealtimeMillis, long certaintyMillis) {
+ mTimeMillis = timeMillis;
+ mElapsedRealtimeMillis = elapsedRealtimeMillis;
+ mCertaintyMillis = certaintyMillis;
+ }
+
+ public long getTimeMillis() {
+ return mTimeMillis;
+ }
+
+ public long getElapsedRealtimeMillis() {
+ return mElapsedRealtimeMillis;
+ }
+
+ public long getCertaintyMillis() {
+ return mCertaintyMillis;
+ }
+
+ /** Calculates and returns the current time accounting for the age of this result. */
+ public long currentTimeMillis() {
+ return mTimeMillis + getAgeMillis();
+ }
+
+ /** Calculates and returns the age of this result. */
+ public long getAgeMillis() {
+ return SystemClock.elapsedRealtime() - mElapsedRealtimeMillis;
+ }
+
+ @Override
+ public String toString() {
+ return "TimeResult{"
+ + "mTimeMillis=" + mTimeMillis
+ + ", mElapsedRealtimeMillis=" + mElapsedRealtimeMillis
+ + ", mCertaintyMillis=" + mCertaintyMillis
+ + '}';
+ }
+ }
+
private static final String TAG = "NtpTrustedTime";
private static final boolean LOGD = false;
private static NtpTrustedTime sSingleton;
- private static Context sContext;
- private final String mServer;
- private final long mTimeout;
+ @NonNull
+ private final Context mContext;
- private ConnectivityManager mCM;
+ /**
+ * A supplier that returns the ConnectivityManager. The Supplier can return null if
+ * ConnectivityService isn't running yet.
+ */
+ private final Supplier<ConnectivityManager> mConnectivityManagerSupplier =
+ new Supplier<ConnectivityManager>() {
+ private ConnectivityManager mConnectivityManager;
- private boolean mHasCache;
- private long mCachedNtpTime;
- private long mCachedNtpElapsedRealtime;
- private long mCachedNtpCertainty;
+ @Nullable
+ @Override
+ public synchronized ConnectivityManager get() {
+ // We can't do this at initialization time: ConnectivityService might not be running
+ // yet.
+ if (mConnectivityManager == null) {
+ mConnectivityManager = mContext.getSystemService(ConnectivityManager.class);
+ }
+ return mConnectivityManager;
+ }
+ };
- private NtpTrustedTime(String server, long timeout) {
- if (LOGD) Log.d(TAG, "creating NtpTrustedTime using " + server);
- mServer = server;
- mTimeout = timeout;
+ // Declared volatile and accessed outside of synchronized blocks to avoid blocking reads during
+ // forceRefresh().
+ private volatile TimeResult mTimeResult;
+
+ private NtpTrustedTime(Context context) {
+ mContext = Objects.requireNonNull(context);
}
@UnsupportedAppUsage
public static synchronized NtpTrustedTime getInstance(Context context) {
if (sSingleton == null) {
- final Resources res = context.getResources();
- final ContentResolver resolver = context.getContentResolver();
-
- final String defaultServer = res.getString(
- com.android.internal.R.string.config_ntpServer);
- final long defaultTimeout = res.getInteger(
- com.android.internal.R.integer.config_ntpTimeout);
-
- final String secureServer = Settings.Global.getString(
- resolver, Settings.Global.NTP_SERVER);
- final long timeout = Settings.Global.getLong(
- resolver, Settings.Global.NTP_TIMEOUT, defaultTimeout);
-
- final String server = secureServer != null ? secureServer : defaultServer;
- sSingleton = new NtpTrustedTime(server, timeout);
- sContext = context;
+ Context appContext = context.getApplicationContext();
+ sSingleton = new NtpTrustedTime(appContext);
}
-
return sSingleton;
}
- @Override
@UnsupportedAppUsage
public boolean forceRefresh() {
- // We can't do this at initialization time: ConnectivityService might not be running yet.
synchronized (this) {
- if (mCM == null) {
- mCM = sContext.getSystemService(ConnectivityManager.class);
+ NtpConnectionInfo connectionInfo = getNtpConnectionInfo();
+ if (connectionInfo == null) {
+ // missing server config, so no trusted time available
+ if (LOGD) Log.d(TAG, "forceRefresh: invalid server config");
+ return false;
}
- }
- final Network network = mCM == null ? null : mCM.getActiveNetwork();
- return forceRefresh(network);
- }
-
- public boolean forceRefresh(Network network) {
- if (TextUtils.isEmpty(mServer)) {
- // missing server, so no trusted time available
- return false;
- }
-
- // We can't do this at initialization time: ConnectivityService might not be running yet.
- synchronized (this) {
- if (mCM == null) {
- mCM = sContext.getSystemService(ConnectivityManager.class);
+ ConnectivityManager connectivityManager = mConnectivityManagerSupplier.get();
+ if (connectivityManager == null) {
+ if (LOGD) Log.d(TAG, "forceRefresh: no ConnectivityManager");
+ return false;
}
- }
+ final Network network = connectivityManager.getActiveNetwork();
+ final NetworkInfo ni = connectivityManager.getNetworkInfo(network);
+ if (ni == null || !ni.isConnected()) {
+ if (LOGD) Log.d(TAG, "forceRefresh: no connectivity");
+ return false;
+ }
- final NetworkInfo ni = mCM == null ? null : mCM.getNetworkInfo(network);
- if (ni == null || !ni.isConnected()) {
- if (LOGD) Log.d(TAG, "forceRefresh: no connectivity");
- return false;
- }
-
-
- if (LOGD) Log.d(TAG, "forceRefresh() from cache miss");
- final SntpClient client = new SntpClient();
- if (client.requestTime(mServer, (int) mTimeout, network)) {
- mHasCache = true;
- mCachedNtpTime = client.getNtpTime();
- mCachedNtpElapsedRealtime = client.getNtpTimeReference();
- mCachedNtpCertainty = client.getRoundTripTime() / 2;
- return true;
- } else {
- return false;
+ if (LOGD) Log.d(TAG, "forceRefresh() from cache miss");
+ final SntpClient client = new SntpClient();
+ final String serverName = connectionInfo.getServer();
+ final int timeoutMillis = connectionInfo.getTimeoutMillis();
+ if (client.requestTime(serverName, timeoutMillis, network)) {
+ long ntpCertainty = client.getRoundTripTime() / 2;
+ mTimeResult = new TimeResult(
+ client.getNtpTime(), client.getNtpTimeReference(), ntpCertainty);
+ return true;
+ } else {
+ return false;
+ }
}
}
- @Override
+ /**
+ * Only kept for UnsupportedAppUsage.
+ *
+ * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically.
+ */
+ @Deprecated
@UnsupportedAppUsage
public boolean hasCache() {
- return mHasCache;
+ return mTimeResult != null;
}
+ /**
+ * Only kept for UnsupportedAppUsage.
+ *
+ * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically.
+ */
+ @Deprecated
@Override
public long getCacheAge() {
- if (mHasCache) {
- return SystemClock.elapsedRealtime() - mCachedNtpElapsedRealtime;
+ TimeResult timeResult = mTimeResult;
+ if (timeResult != null) {
+ return SystemClock.elapsedRealtime() - timeResult.getElapsedRealtimeMillis();
} else {
return Long.MAX_VALUE;
}
}
- @Override
- public long getCacheCertainty() {
- if (mHasCache) {
- return mCachedNtpCertainty;
- } else {
- return Long.MAX_VALUE;
- }
- }
-
- @Override
+ /**
+ * Only kept for UnsupportedAppUsage.
+ *
+ * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically.
+ */
+ @Deprecated
@UnsupportedAppUsage
public long currentTimeMillis() {
- if (!mHasCache) {
+ TimeResult timeResult = mTimeResult;
+ if (timeResult == null) {
throw new IllegalStateException("Missing authoritative time source");
}
if (LOGD) Log.d(TAG, "currentTimeMillis() cache hit");
// current time is age after the last ntp cache; callers who
- // want fresh values will hit makeAuthoritative() first.
- return mCachedNtpTime + getCacheAge();
+ // want fresh values will hit forceRefresh() first.
+ return timeResult.currentTimeMillis();
}
+ /**
+ * Only kept for UnsupportedAppUsage.
+ *
+ * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically.
+ */
+ @Deprecated
@UnsupportedAppUsage
public long getCachedNtpTime() {
if (LOGD) Log.d(TAG, "getCachedNtpTime() cache hit");
- return mCachedNtpTime;
+ TimeResult timeResult = mTimeResult;
+ return timeResult == null ? 0 : timeResult.getTimeMillis();
}
+ /**
+ * Only kept for UnsupportedAppUsage.
+ *
+ * @deprecated Use {@link #getCachedTimeResult()} to obtain a {@link TimeResult} atomically.
+ */
+ @Deprecated
@UnsupportedAppUsage
public long getCachedNtpTimeReference() {
- return mCachedNtpElapsedRealtime;
+ TimeResult timeResult = mTimeResult;
+ return timeResult == null ? 0 : timeResult.getElapsedRealtimeMillis();
+ }
+
+ /**
+ * Returns an object containing the latest NTP information available. Can return {@code null} if
+ * no information is available.
+ */
+ @Nullable
+ public TimeResult getCachedTimeResult() {
+ return mTimeResult;
+ }
+
+ private static class NtpConnectionInfo {
+
+ @NonNull private final String mServer;
+ private final int mTimeoutMillis;
+
+ NtpConnectionInfo(@NonNull String server, int timeoutMillis) {
+ mServer = Objects.requireNonNull(server);
+ mTimeoutMillis = timeoutMillis;
+ }
+
+ @NonNull
+ public String getServer() {
+ return mServer;
+ }
+
+ int getTimeoutMillis() {
+ return mTimeoutMillis;
+ }
+ }
+
+ @GuardedBy("this")
+ private NtpConnectionInfo getNtpConnectionInfo() {
+ final ContentResolver resolver = mContext.getContentResolver();
+
+ final Resources res = mContext.getResources();
+ final String defaultServer = res.getString(
+ com.android.internal.R.string.config_ntpServer);
+ final int defaultTimeoutMillis = res.getInteger(
+ com.android.internal.R.integer.config_ntpTimeout);
+
+ final String secureServer = Settings.Global.getString(
+ resolver, Settings.Global.NTP_SERVER);
+ final int timeoutMillis = Settings.Global.getInt(
+ resolver, Settings.Global.NTP_TIMEOUT, defaultTimeoutMillis);
+
+ final String server = secureServer != null ? secureServer : defaultServer;
+ return TextUtils.isEmpty(server) ? null : new NtpConnectionInfo(server, timeoutMillis);
}
}
diff --git a/core/java/android/util/PathParser.java b/core/java/android/util/PathParser.java
index 5342d5d..1e5ec0b 100644
--- a/core/java/android/util/PathParser.java
+++ b/core/java/android/util/PathParser.java
@@ -14,7 +14,7 @@
package android.util;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Path;
import dalvik.annotation.optimization.FastNative;
diff --git a/core/java/android/util/Pools.java b/core/java/android/util/Pools.java
index e242fe5..7ae32441 100644
--- a/core/java/android/util/Pools.java
+++ b/core/java/android/util/Pools.java
@@ -16,7 +16,7 @@
package android.util;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* Helper class for crating pools of objects. An example use looks like this:
diff --git a/core/java/android/util/Rational.java b/core/java/android/util/Rational.java
index 39e8b14..5930000 100644
--- a/core/java/android/util/Rational.java
+++ b/core/java/android/util/Rational.java
@@ -15,9 +15,10 @@
*/
package android.util;
-import static com.android.internal.util.Preconditions.*;
+import static com.android.internal.util.Preconditions.checkNotNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
import java.io.IOException;
import java.io.InvalidObjectException;
diff --git a/core/java/android/util/RecurrenceRule.java b/core/java/android/util/RecurrenceRule.java
index 9c60228..a570e5e 100644
--- a/core/java/android/util/RecurrenceRule.java
+++ b/core/java/android/util/RecurrenceRule.java
@@ -16,7 +16,7 @@
package android.util;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/android/util/Singleton.java b/core/java/android/util/Singleton.java
index 15c6b5b..92646b4 100644
--- a/core/java/android/util/Singleton.java
+++ b/core/java/android/util/Singleton.java
@@ -16,7 +16,7 @@
package android.util;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* Singleton helper class for lazily initialization.
diff --git a/core/java/android/util/Slog.java b/core/java/android/util/Slog.java
index a85120f..2c8bbbf 100644
--- a/core/java/android/util/Slog.java
+++ b/core/java/android/util/Slog.java
@@ -16,7 +16,7 @@
package android.util;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
/**
diff --git a/core/java/android/util/SparseArray.java b/core/java/android/util/SparseArray.java
index d3367c1..dae760f 100644
--- a/core/java/android/util/SparseArray.java
+++ b/core/java/android/util/SparseArray.java
@@ -16,7 +16,7 @@
package android.util;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
diff --git a/core/java/android/util/SparseBooleanArray.java b/core/java/android/util/SparseBooleanArray.java
index d6e0e53..846df39 100644
--- a/core/java/android/util/SparseBooleanArray.java
+++ b/core/java/android/util/SparseBooleanArray.java
@@ -16,7 +16,7 @@
package android.util;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
diff --git a/core/java/android/util/SparseIntArray.java b/core/java/android/util/SparseIntArray.java
index 1ca1717..d4f6685 100644
--- a/core/java/android/util/SparseIntArray.java
+++ b/core/java/android/util/SparseIntArray.java
@@ -16,7 +16,7 @@
package android.util;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
diff --git a/core/java/android/util/StatsLog.java b/core/java/android/util/StatsLog.java
index 952d7cb..8635340 100644
--- a/core/java/android/util/StatsLog.java
+++ b/core/java/android/util/StatsLog.java
@@ -254,6 +254,7 @@
* @param statsEvent The StatsEvent object containing the encoded buffer of data to write.
* @hide
*/
+ @SystemApi
public static void write(@NonNull final StatsEvent statsEvent) {
writeImpl(statsEvent.getBytes(), statsEvent.getNumBytes(), statsEvent.getAtomId());
statsEvent.release();
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index 74cff20..8439f5a 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -19,7 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.SystemClock;
diff --git a/core/java/android/util/TrustedTime.java b/core/java/android/util/TrustedTime.java
index c78665d..f41fe85 100644
--- a/core/java/android/util/TrustedTime.java
+++ b/core/java/android/util/TrustedTime.java
@@ -16,46 +16,52 @@
package android.util;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* Interface that provides trusted time information, possibly coming from an NTP
- * server. Implementations may cache answers until {@link #forceRefresh()}.
+ * server.
*
* @hide
+ * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime}
*/
public interface TrustedTime {
/**
* Force update with an external trusted time source, returning {@code true}
* when successful.
+ *
+ * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime}
*/
+ @Deprecated
@UnsupportedAppUsage
public boolean forceRefresh();
/**
* Check if this instance has cached a response from a trusted time source.
+ *
+ * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime}
*/
+ @Deprecated
@UnsupportedAppUsage
- public boolean hasCache();
+ boolean hasCache();
/**
* Return time since last trusted time source contact, or
* {@link Long#MAX_VALUE} if never contacted.
+ *
+ * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime}
*/
+ @Deprecated
@UnsupportedAppUsage
public long getCacheAge();
/**
- * Return certainty of cached trusted time in milliseconds, or
- * {@link Long#MAX_VALUE} if never contacted. Smaller values are more
- * precise.
- */
- public long getCacheCertainty();
-
- /**
* Return current time similar to {@link System#currentTimeMillis()},
* possibly using a cached authoritative time source.
+ *
+ * @deprecated Only kept for UnsupportedAppUsage. Do not use. See {@link NtpTrustedTime}
*/
+ @Deprecated
@UnsupportedAppUsage
- public long currentTimeMillis();
+ long currentTimeMillis();
}
diff --git a/core/java/android/util/XmlPullAttributes.java b/core/java/android/util/XmlPullAttributes.java
index 32fe16f..d83b355 100644
--- a/core/java/android/util/XmlPullAttributes.java
+++ b/core/java/android/util/XmlPullAttributes.java
@@ -16,13 +16,12 @@
package android.util;
-import org.xmlpull.v1.XmlPullParser;
-
-import android.annotation.UnsupportedAppUsage;
-import android.util.AttributeSet;
+import android.compat.annotation.UnsupportedAppUsage;
import com.android.internal.util.XmlUtils;
+import org.xmlpull.v1.XmlPullParser;
+
/**
* Provides an implementation of AttributeSet on top of an XmlPullParser.
*/
diff --git a/core/java/android/view/AccessibilityIterators.java b/core/java/android/view/AccessibilityIterators.java
index 5f9bf39..bee04f4 100644
--- a/core/java/android/view/AccessibilityIterators.java
+++ b/core/java/android/view/AccessibilityIterators.java
@@ -16,7 +16,7 @@
package android.view;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.Configuration;
import java.text.BreakIterator;
diff --git a/core/java/android/view/ActionMode.java b/core/java/android/view/ActionMode.java
index 6b200e1..d9f9d03 100644
--- a/core/java/android/view/ActionMode.java
+++ b/core/java/android/view/ActionMode.java
@@ -19,10 +19,9 @@
import android.annotation.StringRes;
import android.annotation.TestApi;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Rect;
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
/**
* Represents a contextual mode of the user interface. Action modes can be used to provide
* alternative interaction modes and replace parts of the normal UI until finished.
diff --git a/core/java/android/view/ActionProvider.java b/core/java/android/view/ActionProvider.java
index cd7e67e..e1be0fe 100644
--- a/core/java/android/view/ActionProvider.java
+++ b/core/java/android/view/ActionProvider.java
@@ -16,7 +16,7 @@
package android.view;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.util.Log;
diff --git a/core/java/android/view/AppTransitionAnimationSpec.java b/core/java/android/view/AppTransitionAnimationSpec.java
index 4c0ed52..330061f 100644
--- a/core/java/android/view/AppTransitionAnimationSpec.java
+++ b/core/java/android/view/AppTransitionAnimationSpec.java
@@ -1,6 +1,6 @@
package android.view;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.GraphicBuffer;
import android.graphics.Rect;
import android.os.Parcel;
diff --git a/core/java/android/view/BatchedInputEventReceiver.java b/core/java/android/view/BatchedInputEventReceiver.java
index 61ccac9..95b2c70 100644
--- a/core/java/android/view/BatchedInputEventReceiver.java
+++ b/core/java/android/view/BatchedInputEventReceiver.java
@@ -16,7 +16,7 @@
package android.view;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Looper;
/**
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index 4368115..178b3c0 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -19,6 +19,7 @@
import static android.Manifest.permission.CONFIGURE_DISPLAY_COLOR_MODE;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
@@ -1010,6 +1011,9 @@
* @return Supported WCG color spaces.
* @hide
*/
+ @SuppressLint("VisiblySynchronized")
+ @NonNull
+ @TestApi
public @ColorMode ColorSpace[] getSupportedWideColorGamut() {
synchronized (this) {
final ColorSpace[] defaultColorSpaces = new ColorSpace[0];
diff --git a/core/java/android/view/IDisplayWindowInsetsController.aidl b/core/java/android/view/IDisplayWindowInsetsController.aidl
new file mode 100644
index 0000000..429c3ae
--- /dev/null
+++ b/core/java/android/view/IDisplayWindowInsetsController.aidl
@@ -0,0 +1,48 @@
+/**
+ * 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.view;
+
+import android.view.InsetsSourceControl;
+import android.view.InsetsState;
+
+/**
+ * Singular controller of insets to use when there isn't another obvious controller available.
+ * Specifically, this will take over insets control in multi-window.
+ * @hide
+ */
+oneway interface IDisplayWindowInsetsController {
+
+ /**
+ * @see IWindow#insetsChanged
+ */
+ void insetsChanged(in InsetsState insetsState);
+
+ /**
+ * @see IWindow#insetsControlChanged
+ */
+ void insetsControlChanged(in InsetsState insetsState, in InsetsSourceControl[] activeControls);
+
+ /**
+ * @see IWindow#showInsets
+ */
+ void showInsets(int types, boolean fromIme);
+
+ /**
+ * @see IWindow#hideInsets
+ */
+ void hideInsets(int types, boolean fromIme);
+}
diff --git a/core/java/android/view/ITaskOrganizer.aidl b/core/java/android/view/ITaskOrganizer.aidl
new file mode 100644
index 0000000..e92aafe
--- /dev/null
+++ b/core/java/android/view/ITaskOrganizer.aidl
@@ -0,0 +1,38 @@
+/* //device/java/android/android/view/ITaskOrganizer.aidl
+**
+** 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.view;
+
+import android.view.IWindowContainer;
+import android.view.SurfaceControl;
+import android.app.ActivityManager;
+
+/**
+ * Interface for ActivityTaskManager/WindowManager to delegate control of tasks.
+ * {@hide}
+ */
+oneway interface ITaskOrganizer {
+ void taskAppeared(in IWindowContainer container,
+ in ActivityManager.RunningTaskInfo taskInfo);
+ void taskVanished(in IWindowContainer container);
+
+ /**
+ * Called upon completion of
+ * ActivityTaskManagerService#applyTaskOrganizerTransaction
+ */
+ void transactionReady(int id, in SurfaceControl.Transaction t);
+}
\ No newline at end of file
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 9496827..993bdc4 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -35,6 +35,7 @@
import android.view.IApplicationToken;
import android.view.IAppTransitionAnimationSpecsFuture;
import android.view.IDockedStackListener;
+import android.view.IDisplayWindowInsetsController;
import android.view.IDisplayWindowListener;
import android.view.IDisplayFoldListener;
import android.view.IDisplayWindowRotationController;
@@ -49,6 +50,7 @@
import android.view.IWindowSessionCallback;
import android.view.KeyEvent;
import android.view.InputEvent;
+import android.view.InsetsState;
import android.view.MagnificationSpec;
import android.view.MotionEvent;
import android.view.InputChannel;
@@ -711,4 +713,16 @@
* @return true if the display was successfully mirrored.
*/
boolean mirrorDisplay(int displayId, out SurfaceControl outSurfaceControl);
+
+ /**
+ * When in multi-window mode, the provided displayWindowInsetsController will control insets
+ * animations.
+ */
+ void setDisplayWindowInsetsController(
+ int displayId, in IDisplayWindowInsetsController displayWindowInsetsController);
+
+ /**
+ * Called when a remote process modifies insets on a display window container.
+ */
+ void modifyDisplayWindowInsets(int displayId, in InsetsState state);
}
diff --git a/core/java/android/view/ImeFocusController.java b/core/java/android/view/ImeFocusController.java
new file mode 100644
index 0000000..5c494c1
--- /dev/null
+++ b/core/java/android/view/ImeFocusController.java
@@ -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.view;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.UiThread;
+import android.util.Log;
+import android.view.inputmethod.InputMethodManager;
+
+import com.android.internal.inputmethod.InputMethodDebug;
+import com.android.internal.inputmethod.StartInputFlags;
+import com.android.internal.inputmethod.StartInputReason;
+
+/**
+ * Responsible for IME focus handling inside {@link ViewRootImpl}.
+ * @hide
+ */
+public final class ImeFocusController {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "ImeFocusController";
+
+ private final ViewRootImpl mViewRootImpl;
+ private boolean mHasImeFocus = false;
+ private View mServedView;
+ private View mNextServedView;
+
+ @UiThread
+ ImeFocusController(@NonNull ViewRootImpl viewRootImpl) {
+ mViewRootImpl = viewRootImpl;
+ }
+
+ private InputMethodManagerDelegate getImmDelegate() {
+ return mViewRootImpl.mContext.getSystemService(InputMethodManager.class).getDelegate();
+ }
+
+ @UiThread
+ void onTraversal(boolean hasWindowFocus, WindowManager.LayoutParams windowAttribute) {
+ final boolean hasImeFocus = updateImeFocusable(windowAttribute, false /* force */);
+ if (!hasWindowFocus || isInLocalFocusMode(windowAttribute)) {
+ return;
+ }
+ if (hasImeFocus == mHasImeFocus) {
+ return;
+ }
+ mHasImeFocus = hasImeFocus;
+ if (mHasImeFocus) {
+ onPreWindowFocus(true /* hasWindowFocus */, windowAttribute);
+ onPostWindowFocus(mViewRootImpl.mView.findFocus(), true /* hasWindowFocus */,
+ windowAttribute);
+ }
+ }
+
+ @UiThread
+ void onPreWindowFocus(boolean hasWindowFocus, WindowManager.LayoutParams windowAttribute) {
+ if (!mHasImeFocus || isInLocalFocusMode(windowAttribute)) {
+ return;
+ }
+ if (hasWindowFocus) {
+ getImmDelegate().setCurrentRootView(mViewRootImpl);
+ }
+ }
+
+ @UiThread
+ boolean updateImeFocusable(WindowManager.LayoutParams windowAttribute, boolean force) {
+ final boolean hasImeFocus = WindowManager.LayoutParams.mayUseInputMethod(
+ windowAttribute.flags);
+ if (force) {
+ mHasImeFocus = hasImeFocus;
+ }
+ return hasImeFocus;
+ }
+
+ @UiThread
+ void onPostWindowFocus(View focusedView, boolean hasWindowFocus,
+ WindowManager.LayoutParams windowAttribute) {
+ if (!hasWindowFocus || !mHasImeFocus || isInLocalFocusMode(windowAttribute)) {
+ return;
+ }
+ if (DEBUG) {
+ Log.v(TAG, "onWindowFocus: " + focusedView
+ + " softInputMode=" + InputMethodDebug.softInputModeToString(
+ windowAttribute.softInputMode));
+ }
+
+ boolean forceFocus = false;
+ if (getImmDelegate().isRestartOnNextWindowFocus(true /* reset */)) {
+ if (DEBUG) Log.v(TAG, "Restarting due to isRestartOnNextWindowFocus as true");
+ forceFocus = true;
+ }
+ // Update mNextServedView when focusedView changed.
+ final View viewForWindowFocus = focusedView != null ? focusedView : mViewRootImpl.mView;
+ onViewFocusChanged(viewForWindowFocus, true);
+
+ getImmDelegate().startInputAsyncOnWindowFocusGain(viewForWindowFocus,
+ windowAttribute.softInputMode, windowAttribute.flags, forceFocus);
+ }
+
+ public boolean checkFocus(boolean forceNewFocus, boolean startInput) {
+ if (!getImmDelegate().isCurrentRootView(mViewRootImpl)
+ || (mServedView == mNextServedView && !forceNewFocus)) {
+ return false;
+ }
+ if (DEBUG) Log.v(TAG, "checkFocus: view=" + mServedView
+ + " next=" + mNextServedView
+ + " force=" + forceNewFocus
+ + " package="
+ + (mServedView != null ? mServedView.getContext().getPackageName() : "<none>"));
+
+ // Close the connection when no next served view coming.
+ if (mNextServedView == null) {
+ getImmDelegate().finishInput();
+ getImmDelegate().closeCurrentIme();
+ return false;
+ }
+ mServedView = mNextServedView;
+ getImmDelegate().finishComposingText();
+
+ if (startInput) {
+ getImmDelegate().startInput(StartInputReason.CHECK_FOCUS, null, 0, 0, 0);
+ }
+ return true;
+ }
+
+ @UiThread
+ void onViewFocusChanged(View view, boolean hasFocus) {
+ if (view == null || view.isTemporarilyDetached()) {
+ return;
+ }
+ if (!getImmDelegate().isCurrentRootView(view.getViewRootImpl())) {
+ return;
+ }
+ if (mServedView == view || !view.hasImeFocus() || !view.hasWindowFocus()) {
+ return;
+ }
+ mNextServedView = hasFocus ? view : null;
+ mViewRootImpl.dispatchCheckFocus();
+ }
+
+ @UiThread
+ void onViewDetachedFromWindow(View view) {
+ if (!getImmDelegate().isCurrentRootView(view.getViewRootImpl())) {
+ return;
+ }
+ if (mServedView == view) {
+ mNextServedView = null;
+ mViewRootImpl.dispatchCheckFocus();
+ }
+ }
+
+ @UiThread
+ void onWindowDismissed() {
+ if (!getImmDelegate().isCurrentRootView(mViewRootImpl)) {
+ return;
+ }
+ if (mServedView != null) {
+ getImmDelegate().finishInput();
+ }
+ getImmDelegate().setCurrentRootView(null);
+ mHasImeFocus = false;
+ }
+
+ /**
+ * @param windowAttribute {@link WindowManager.LayoutParams} to be checked.
+ * @return Whether the window is in local focus mode or not.
+ */
+ @AnyThread
+ private static boolean isInLocalFocusMode(WindowManager.LayoutParams windowAttribute) {
+ return (windowAttribute.flags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0;
+ }
+
+ int onProcessImeInputStage(Object token, InputEvent event,
+ WindowManager.LayoutParams windowAttribute,
+ InputMethodManager.FinishedInputEventCallback callback) {
+ if (!mHasImeFocus || isInLocalFocusMode(windowAttribute)) {
+ return InputMethodManager.DISPATCH_NOT_HANDLED;
+ }
+ final InputMethodManager imm =
+ mViewRootImpl.mContext.getSystemService(InputMethodManager.class);
+ if (imm == null) {
+ return InputMethodManager.DISPATCH_NOT_HANDLED;
+ }
+ return imm.dispatchInputEvent(event, token, callback, mViewRootImpl.mHandler);
+ }
+
+ /**
+ * A delegate implementing some basic {@link InputMethodManager} APIs.
+ * @hide
+ */
+ public interface InputMethodManagerDelegate {
+ boolean startInput(@StartInputReason int startInputReason, View focusedView,
+ @StartInputFlags int startInputFlags,
+ @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, int windowFlags);
+ void startInputAsyncOnWindowFocusGain(View rootView,
+ @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, int windowFlags,
+ boolean forceNewFocus);
+ void finishInput();
+ void closeCurrentIme();
+ void finishComposingText();
+ void setCurrentRootView(ViewRootImpl rootView);
+ boolean isCurrentRootView(ViewRootImpl rootView);
+ boolean isRestartOnNextWindowFocus(boolean reset);
+ }
+
+ public View getServedView() {
+ return mServedView;
+ }
+
+ public View getNextServedView() {
+ return mNextServedView;
+ }
+
+ public void setServedView(View view) {
+ mServedView = view;
+ }
+
+ public void setNextServedView(View view) {
+ mNextServedView = view;
+ }
+}
diff --git a/core/java/android/view/InputEventReceiver.java b/core/java/android/view/InputEventReceiver.java
index c67ff6e..7986ceb 100644
--- a/core/java/android/view/InputEventReceiver.java
+++ b/core/java/android/view/InputEventReceiver.java
@@ -128,6 +128,19 @@
}
/**
+ * Called when a focus event is received.
+ *
+ * @param hasFocus if true, the window associated with this input channel has just received
+ * focus
+ * if false, the window associated with this input channel has just lost focus
+ * @param inTouchMode if true, the device is in touch mode
+ * if false, the device is not in touch mode
+ */
+ // Called from native code.
+ public void onFocusEvent(boolean hasFocus, boolean inTouchMode) {
+ }
+
+ /**
* Called when a batched input event is pending.
*
* The batched input event will continue to accumulate additional movement
@@ -213,8 +226,13 @@
onBatchedInputEventPending();
}
- public static interface Factory {
- public InputEventReceiver createInputEventReceiver(
- InputChannel inputChannel, Looper looper);
+ /**
+ * Factory for InputEventReceiver
+ */
+ public interface Factory {
+ /**
+ * Create a new InputReceiver for a given inputChannel
+ */
+ InputEventReceiver createInputEventReceiver(InputChannel inputChannel, Looper looper);
}
}
diff --git a/core/java/android/view/InsetsAnimationControlCallbacks.java b/core/java/android/view/InsetsAnimationControlCallbacks.java
index 6fdadc6..27edb0b 100644
--- a/core/java/android/view/InsetsAnimationControlCallbacks.java
+++ b/core/java/android/view/InsetsAnimationControlCallbacks.java
@@ -16,17 +16,28 @@
package android.view;
+import android.view.InsetsController.LayoutInsetsDuringAnimation;
+import android.view.WindowInsetsAnimationCallback.AnimationBounds;
+import android.view.WindowInsetsAnimationCallback.InsetsAnimation;
+
/**
* Provide an interface to let InsetsAnimationControlImpl call back into its owner.
* @hide
*/
public interface InsetsAnimationControlCallbacks {
+
/**
- * Dispatch the animation started event to all listeners.
- * @param animation
+ * Executes the necessary code to start the animation in the correct order, including:
+ * <ul>
+ * <li>Dispatch {@link WindowInsetsAnimationCallback#onPrepare}</li>
+ * <li>Update insets state and run layout according to {@code layoutDuringAnimation}</li>
+ * <li>Dispatch {@link WindowInsetsAnimationCallback#onStart}</li>
+ * <li>Dispatch {@link WindowInsetsAnimationControlListener#onReady}</li>
+ * </ul>
*/
- void dispatchAnimationStarted(WindowInsetsAnimationCallback.InsetsAnimation animation,
- WindowInsetsAnimationCallback.AnimationBounds bounds);
+ void startAnimation(InsetsAnimationControlImpl controller,
+ WindowInsetsAnimationControlListener listener, int types, InsetsAnimation animation,
+ AnimationBounds bounds, @LayoutInsetsDuringAnimation int layoutDuringAnimation);
/**
* Schedule the apply by posting the animation callback.
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 771695c..69d0105 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -16,6 +16,8 @@
package android.view;
+import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_HIDDEN;
+import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
import static android.view.InsetsState.ISIDE_BOTTOM;
import static android.view.InsetsState.ISIDE_FLOATING;
import static android.view.InsetsState.ISIDE_LEFT;
@@ -30,7 +32,9 @@
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.util.SparseSetArray;
+import android.view.InsetsController.LayoutInsetsDuringAnimation;
import android.view.InsetsState.InternalInsetsSide;
+import android.view.InsetsState.InternalInsetsType;
import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsAnimationCallback.AnimationBounds;
@@ -80,7 +84,8 @@
public InsetsAnimationControlImpl(SparseArray<InsetsSourceControl> controls, Rect frame,
InsetsState state, WindowInsetsAnimationControlListener listener,
@InsetsType int types,
- InsetsAnimationControlCallbacks controller, long durationMs, boolean fade) {
+ InsetsAnimationControlCallbacks controller, long durationMs, boolean fade,
+ @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation) {
mControls = controls;
mListener = listener;
mTypes = types;
@@ -88,6 +93,7 @@
mController = controller;
mInitialInsetsState = new InsetsState(state, true /* copySources */);
mCurrentInsets = getInsetsFromState(mInitialInsetsState, frame, null /* typeSideMap */);
+ mPendingInsets = mCurrentInsets;
mHiddenInsets = calculateInsets(mInitialInsetsState, frame, controls, false /* shown */,
null /* typeSideMap */);
mShownInsets = calculateInsets(mInitialInsetsState, frame, controls, true /* shown */,
@@ -95,14 +101,11 @@
mFrame = new Rect(frame);
buildTypeSourcesMap(mTypeSideMap, mSideSourceMap, mControls);
- // TODO: Check for controllability first and wait for IME if needed.
- listener.onReady(this, types);
-
mAnimation = new WindowInsetsAnimationCallback.InsetsAnimation(mTypes,
InsetsController.INTERPOLATOR, durationMs);
mAnimation.setAlpha(getCurrentAlpha());
- mController.dispatchAnimationStarted(mAnimation,
- new AnimationBounds(mHiddenInsets, mShownInsets));
+ mController.startAnimation(this, listener, types, mAnimation,
+ new AnimationBounds(mHiddenInsets, mShownInsets), layoutInsetsDuringAnimation);
}
@Override
@@ -130,6 +133,10 @@
return mTypes;
}
+ boolean controlsInternalType(@InternalInsetsType int type) {
+ return InsetsState.toInternalType(mTypes).contains(type);
+ }
+
@Override
public void setInsetsAndAlpha(Insets insets, float alpha, float fraction) {
if (mFinished) {
@@ -257,10 +264,6 @@
for (int i = items.size() - 1; i >= 0; i--) {
final InsetsSourceControl control = items.valueAt(i);
final InsetsSource source = mInitialInsetsState.getSource(control.getType());
- if (control == null) {
- // TODO: remove this check when we ensure the elements will not be null.
- continue;
- }
final SurfaceControl leash = control.getLeash();
mTmpMatrix.setTranslate(control.getSurfacePosition().x, control.getSurfacePosition().y);
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 4d4ace27c..e2739c4 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -28,6 +28,7 @@
import android.annotation.NonNull;
import android.graphics.Insets;
import android.graphics.Rect;
+import android.net.InvalidPacketException.ErrorCode;
import android.os.RemoteException;
import android.util.ArraySet;
import android.util.Log;
@@ -37,16 +38,20 @@
import android.view.InsetsSourceConsumer.ShowResult;
import android.view.InsetsState.InternalInsetsType;
import android.view.SurfaceControl.Transaction;
+import android.view.ViewTreeObserver.OnPreDrawListener;
import android.view.WindowInsets.Type;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowInsetsAnimationCallback.AnimationBounds;
import android.view.WindowInsetsAnimationCallback.InsetsAnimation;
+import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
import android.view.animation.Interpolator;
import android.view.animation.PathInterpolator;
import com.android.internal.annotations.VisibleForTesting;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
/**
@@ -57,14 +62,61 @@
private static final int ANIMATION_DURATION_SHOW_MS = 275;
private static final int ANIMATION_DURATION_HIDE_MS = 340;
- private static final int DIRECTION_NONE = 0;
- private static final int DIRECTION_SHOW = 1;
- private static final int DIRECTION_HIDE = 2;
static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
- @IntDef ({DIRECTION_NONE, DIRECTION_SHOW, DIRECTION_HIDE})
- private @interface AnimationDirection{}
+ /**
+ * Layout mode during insets animation: The views should be laid out as if the changing inset
+ * types are fully shown. Before starting the animation, {@link View#onApplyWindowInsets} will
+ * be called as if the changing insets types are shown, which will result in the views being
+ * laid out as if the insets are fully shown.
+ */
+ static final int LAYOUT_INSETS_DURING_ANIMATION_SHOWN = 0;
+
+ /**
+ * Layout mode during insets animation: The views should be laid out as if the changing inset
+ * types are fully hidden. Before starting the animation, {@link View#onApplyWindowInsets} will
+ * be called as if the changing insets types are hidden, which will result in the views being
+ * laid out as if the insets are fully hidden.
+ */
+ static final int LAYOUT_INSETS_DURING_ANIMATION_HIDDEN = 1;
+
+ /**
+ * Determines the behavior of how the views should be laid out during an insets animation that
+ * is controlled by the application by calling {@link #controlWindowInsetsAnimation}.
+ * <p>
+ * When the animation is system-initiated, the layout mode is always chosen such that the
+ * pre-animation layout will represent the opposite of the starting state, i.e. when insets
+ * are appearing, {@link #LAYOUT_INSETS_DURING_ANIMATION_SHOWN} will be used. When insets
+ * are disappearing, {@link #LAYOUT_INSETS_DURING_ANIMATION_HIDDEN} will be used.
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {LAYOUT_INSETS_DURING_ANIMATION_SHOWN,
+ LAYOUT_INSETS_DURING_ANIMATION_HIDDEN})
+ @interface LayoutInsetsDuringAnimation {
+ }
+
+ /** Not running an animation. */
+ @VisibleForTesting
+ public static final int ANIMATION_TYPE_NONE = -1;
+
+ /** Running animation will show insets */
+ @VisibleForTesting
+ public static final int ANIMATION_TYPE_SHOW = 0;
+
+ /** Running animation will hide insets */
+ @VisibleForTesting
+ public static final int ANIMATION_TYPE_HIDE = 1;
+
+ /** Running animation is controlled by user via {@link #controlWindowInsetsAnimation} */
+ @VisibleForTesting
+ public static final int ANIMATION_TYPE_USER = 2;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(value = {ANIMATION_TYPE_NONE, ANIMATION_TYPE_SHOW, ANIMATION_TYPE_HIDE,
+ ANIMATION_TYPE_USER})
+ @interface AnimationType {
+ }
/**
* Translation animation evaluator.
@@ -109,12 +161,7 @@
@Override
public void onReady(WindowInsetsAnimationController controller, int types) {
mController = controller;
- if (mShow) {
- showDirectly(types);
- } else {
- hideDirectly(types);
- }
- mAnimationDirection = mShow ? DIRECTION_SHOW : DIRECTION_HIDE;
+
mAnimator = ObjectAnimator.ofObject(
controller,
new InsetsProperty(),
@@ -131,7 +178,9 @@
onAnimationFinish();
}
});
+ mStartingAnimation = true;
mAnimator.start();
+ mStartingAnimation = false;
}
@Override
@@ -143,7 +192,6 @@
}
private void onAnimationFinish() {
- mAnimationDirection = DIRECTION_NONE;
mController.finish(mShow);
}
@@ -160,6 +208,20 @@
}
}
+ /**
+ * Represents a running animation
+ */
+ private static class RunningAnimation {
+
+ RunningAnimation(InsetsAnimationControlImpl control, int type) {
+ this.control = control;
+ this.type = type;
+ }
+
+ final InsetsAnimationControlImpl control;
+ final @AnimationType int type;
+ }
+
private final String TAG = "InsetsControllerImpl";
private final InsetsState mState = new InsetsState();
@@ -170,7 +232,7 @@
private final ViewRootImpl mViewRoot;
private final SparseArray<InsetsSourceControl> mTmpControlArray = new SparseArray<>();
- private final ArrayList<InsetsAnimationControlImpl> mAnimationControls = new ArrayList<>();
+ private final ArrayList<RunningAnimation> mRunningAnimations = new ArrayList<>();
private final ArrayList<InsetsAnimationControlImpl> mTmpFinishedControls = new ArrayList<>();
private WindowInsets mLastInsets;
@@ -180,11 +242,11 @@
private final Rect mLastLegacyContentInsets = new Rect();
private final Rect mLastLegacyStableInsets = new Rect();
- private @AnimationDirection int mAnimationDirection;
private int mPendingTypesToShow;
private int mLastLegacySoftInputMode;
+ private boolean mStartingAnimation;
private SyncRtSurfaceTransactionApplier mApplier;
@@ -192,7 +254,7 @@
mViewRoot = viewRoot;
mAnimCallback = () -> {
mAnimCallbackScheduled = false;
- if (mAnimationControls.isEmpty()) {
+ if (mRunningAnimations.isEmpty()) {
return;
}
if (mViewRoot.mView == null) {
@@ -202,9 +264,9 @@
mTmpFinishedControls.clear();
InsetsState state = new InsetsState(mState, true /* copySources */);
- for (int i = mAnimationControls.size() - 1; i >= 0; i--) {
- InsetsAnimationControlImpl control = mAnimationControls.get(i);
- if (mAnimationControls.get(i).applyChangeInsets(state)) {
+ for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
+ InsetsAnimationControlImpl control = mRunningAnimations.get(i).control;
+ if (control.applyChangeInsets(state)) {
mTmpFinishedControls.add(control);
}
}
@@ -266,6 +328,14 @@
}
/**
+ * @see InsetsState#calculateVisibleInsets(Rect, Rect, int)
+ */
+ public Rect calculateVisibleInsets(Rect legacyVisibleInsets,
+ @SoftInputModeFlags int softInputMode) {
+ return mState.calculateVisibleInsets(mFrame, legacyVisibleInsets, softInputMode);
+ }
+
+ /**
* Called when the server has dispatched us a new set of inset controls.
*/
public void onControlsChanged(InsetsSourceControl[] activeControls) {
@@ -307,18 +377,13 @@
int typesReady = 0;
final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
for (int i = internalTypes.size() - 1; i >= 0; i--) {
- InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
- if (mAnimationDirection == DIRECTION_HIDE) {
- // Only one animator (with multiple InsetsType) can run at a time.
- // previous one should be cancelled for simplicity.
- cancelExistingAnimation();
- } else if (consumer.isVisible()
- && (mAnimationDirection == DIRECTION_NONE
- || mAnimationDirection == DIRECTION_HIDE)) {
+ @InternalInsetsType int internalType = internalTypes.valueAt(i);
+ @AnimationType int animationType = getAnimationType(internalType);
+ InsetsSourceConsumer consumer = getSourceConsumer(internalType);
+ if (mState.getSource(internalType).isVisible() && animationType == ANIMATION_TYPE_NONE
+ || animationType == ANIMATION_TYPE_SHOW) {
// no-op: already shown or animating in (because window visibility is
// applied before starting animation).
- // TODO: When we have more than one types: handle specific case when
- // show animation is going on, but the current type is not becoming visible.
continue;
}
typesReady |= InsetsState.toPublicType(consumer.getType());
@@ -335,12 +400,11 @@
int typesReady = 0;
final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
for (int i = internalTypes.size() - 1; i >= 0; i--) {
- InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
- if (mAnimationDirection == DIRECTION_SHOW) {
- cancelExistingAnimation();
- } else if (!consumer.isVisible()
- && (mAnimationDirection == DIRECTION_NONE
- || mAnimationDirection == DIRECTION_HIDE)) {
+ @InternalInsetsType int internalType = internalTypes.valueAt(i);
+ @AnimationType int animationType = getAnimationType(internalType);
+ InsetsSourceConsumer consumer = getSourceConsumer(internalType);
+ if (!mState.getSource(internalType).isVisible() && animationType == ANIMATION_TYPE_NONE
+ || animationType == ANIMATION_TYPE_HIDE) {
// no-op: already hidden or animating out.
continue;
}
@@ -352,31 +416,34 @@
@Override
public void controlWindowInsetsAnimation(@InsetsType int types, long durationMs,
WindowInsetsAnimationControlListener listener) {
- controlWindowInsetsAnimation(types, listener, false /* fromIme */, durationMs);
+ controlWindowInsetsAnimation(types, listener, false /* fromIme */, durationMs,
+ ANIMATION_TYPE_USER);
}
private void controlWindowInsetsAnimation(@InsetsType int types,
- WindowInsetsAnimationControlListener listener, boolean fromIme, long durationMs) {
+ WindowInsetsAnimationControlListener listener, boolean fromIme, long durationMs,
+ @AnimationType int animationType) {
// If the frame of our window doesn't span the entire display, the control API makes very
// little sense, as we don't deal with negative insets. So just cancel immediately.
if (!mState.getDisplayFrame().equals(mFrame)) {
listener.onCancelled();
return;
}
- controlAnimationUnchecked(types, listener, mFrame, fromIme, durationMs, false /* fade */);
+ controlAnimationUnchecked(types, listener, mFrame, fromIme, durationMs, false /* fade */,
+ animationType, getLayoutInsetsDuringAnimationMode(types));
}
private void controlAnimationUnchecked(@InsetsType int types,
WindowInsetsAnimationControlListener listener, Rect frame, boolean fromIme,
- long durationMs, boolean fade) {
+ long durationMs, boolean fade, @AnimationType int animationType,
+ @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation) {
if (types == 0) {
// nothing to animate.
return;
}
cancelExistingControllers(types);
- final ArraySet<Integer> internalTypes = mState.toInternalType(types);
- final SparseArray<InsetsSourceConsumer> consumers = new SparseArray<>();
+ final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
final SparseArray<InsetsSourceControl> controls = new SparseArray<>();
Pair<Integer, Boolean> typesReadyPair = collectSourceControls(
@@ -399,8 +466,9 @@
}
final InsetsAnimationControlImpl controller = new InsetsAnimationControlImpl(controls,
- frame, mState, listener, typesReady, this, durationMs, fade);
- mAnimationControls.add(controller);
+ frame, mState, listener, typesReady, this, durationMs, fade,
+ layoutInsetsDuringAnimation);
+ mRunningAnimations.add(new RunningAnimation(controller, animationType));
}
/**
@@ -413,7 +481,7 @@
boolean isReady = true;
for (int i = internalTypes.size() - 1; i >= 0; i--) {
InsetsSourceConsumer consumer = getSourceConsumer(internalTypes.valueAt(i));
- boolean setVisible = !consumer.isVisible();
+ boolean setVisible = !consumer.isRequestedVisible();
if (setVisible) {
// Show request
switch(consumer.requestShow(fromIme)) {
@@ -441,7 +509,10 @@
}
typesReady |= InsetsState.toPublicType(consumer.getType());
}
- controls.put(consumer.getType(), consumer.getControl());
+ final InsetsSourceControl control = consumer.getControl();
+ if (control != null) {
+ controls.put(consumer.getType(), control);
+ }
}
return new Pair<>(typesReady, isReady);
}
@@ -452,11 +523,34 @@
return typesReady;
}
+ private @LayoutInsetsDuringAnimation int getLayoutInsetsDuringAnimationMode(
+ @InsetsType int types) {
+
+ final ArraySet<Integer> internalTypes = InsetsState.toInternalType(types);
+
+ // Generally, we want to layout the opposite of the current state. This is to make animation
+ // callbacks easy to use: The can capture the layout values and then treat that as end-state
+ // during the animation.
+ //
+ // However, if controlling multiple sources, we want to treat it as shown if any of the
+ // types is currently hidden.
+ for (int i = internalTypes.size() - 1; i >= 0; i--) {
+ InsetsSourceConsumer consumer = mSourceConsumers.get(internalTypes.valueAt(i));
+ if (consumer == null) {
+ continue;
+ }
+ if (!consumer.isRequestedVisible()) {
+ return LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
+ }
+ }
+ return LAYOUT_INSETS_DURING_ANIMATION_HIDDEN;
+ }
+
private void cancelExistingControllers(@InsetsType int types) {
- for (int i = mAnimationControls.size() - 1; i >= 0; i--) {
- InsetsAnimationControlImpl control = mAnimationControls.get(i);
+ for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
+ InsetsAnimationControlImpl control = mRunningAnimations.get(i).control;
if ((control.getTypes() & types) != 0) {
- cancelAnimation(control);
+ cancelAnimation(control, true /* invokeCallback */);
}
}
}
@@ -464,7 +558,7 @@
@VisibleForTesting
@Override
public void notifyFinished(InsetsAnimationControlImpl controller, boolean shown) {
- mAnimationControls.remove(controller);
+ cancelAnimation(controller, false /* invokeCallback */);
if (shown) {
showDirectly(controller.getTypes());
} else {
@@ -484,17 +578,24 @@
}
void notifyControlRevoked(InsetsSourceConsumer consumer) {
- for (int i = mAnimationControls.size() - 1; i >= 0; i--) {
- InsetsAnimationControlImpl control = mAnimationControls.get(i);
+ for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
+ InsetsAnimationControlImpl control = mRunningAnimations.get(i).control;
if ((control.getTypes() & toPublicType(consumer.getType())) != 0) {
- cancelAnimation(control);
+ cancelAnimation(control, true /* invokeCallback */);
}
}
}
- private void cancelAnimation(InsetsAnimationControlImpl control) {
- control.onCancelled();
- mAnimationControls.remove(control);
+ private void cancelAnimation(InsetsAnimationControlImpl control, boolean invokeCallback) {
+ if (invokeCallback) {
+ control.onCancelled();
+ }
+ for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
+ if (mRunningAnimations.get(i).control == control) {
+ mRunningAnimations.remove(i);
+ break;
+ }
+ }
}
private void applyLocalVisibilityOverride() {
@@ -552,8 +653,15 @@
}
}
- boolean isAnimating() {
- return mAnimationDirection != DIRECTION_NONE;
+ @VisibleForTesting
+ public @AnimationType int getAnimationType(@InternalInsetsType int type) {
+ for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
+ InsetsAnimationControlImpl control = mRunningAnimations.get(i).control;
+ if (control.controlsInternalType(type)) {
+ return mRunningAnimations.get(i).type;
+ }
+ }
+ return ANIMATION_TYPE_NONE;
}
private InsetsSourceConsumer createConsumerOfType(int type) {
@@ -595,7 +703,9 @@
// and hidden state insets are correct.
controlAnimationUnchecked(
types, listener, mState.getDisplayFrame(), fromIme, listener.getDurationMs(),
- true /* fade */);
+ true /* fade */, show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE,
+ show ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN
+ : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN);
}
private void hideDirectly(@InsetsType int types) {
@@ -627,18 +737,40 @@
@VisibleForTesting
@Override
- public void dispatchAnimationStarted(InsetsAnimation animation, AnimationBounds bounds) {
- mViewRoot.mView.dispatchWindowInsetsAnimationStarted(animation, bounds);
+ public void startAnimation(InsetsAnimationControlImpl controller,
+ WindowInsetsAnimationControlListener listener, int types, InsetsAnimation animation,
+ AnimationBounds bounds, int layoutDuringAnimation) {
+ if (layoutDuringAnimation == LAYOUT_INSETS_DURING_ANIMATION_SHOWN) {
+ showDirectly(types);
+ } else {
+ hideDirectly(types);
+ }
+ mViewRoot.mView.dispatchWindowInsetsAnimationPrepare(animation);
+ mViewRoot.mView.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ mViewRoot.mView.getViewTreeObserver().removeOnPreDrawListener(this);
+ mViewRoot.mView.dispatchWindowInsetsAnimationStart(animation, bounds);
+ listener.onReady(controller, types);
+ return true;
+ }
+ });
+ mViewRoot.mView.invalidate();
}
@VisibleForTesting
public void dispatchAnimationFinished(InsetsAnimation animation) {
- mViewRoot.mView.dispatchWindowInsetsAnimationFinished(animation);
+ mViewRoot.mView.dispatchWindowInsetsAnimationFinish(animation);
}
@VisibleForTesting
@Override
public void scheduleApplyChangeInsets() {
+ if (mStartingAnimation) {
+ mAnimCallback.run();
+ mAnimCallbackScheduled = false;
+ return;
+ }
if (!mAnimCallbackScheduled) {
mViewRoot.mChoreographer.postCallback(Choreographer.CALLBACK_INSETS_ANIMATION,
mAnimCallback, null /* token*/);
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index 1a33ea9..67ccfd6 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -16,6 +16,7 @@
package android.view;
+import android.annotation.Nullable;
import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Parcel;
@@ -23,6 +24,7 @@
import android.view.InsetsState.InternalInsetsType;
import java.io.PrintWriter;
+import java.util.Objects;
/**
* Represents the state of a single window generating insets for clients.
@@ -34,6 +36,7 @@
/** Frame of the source in screen coordinate space */
private final Rect mFrame;
+ private @Nullable Rect mVisibleFrame;
private boolean mVisible;
private final Rect mTmpFrame = new Rect();
@@ -41,6 +44,7 @@
public InsetsSource(@InternalInsetsType int type) {
mType = type;
mFrame = new Rect();
+ mVisible = InsetsState.getDefaultVisibility(type);
}
public InsetsSource(InsetsSource other) {
@@ -53,6 +57,10 @@
mFrame.set(frame);
}
+ public void setVisibleFrame(@Nullable Rect visibleFrame) {
+ mVisibleFrame = visibleFrame != null ? new Rect(visibleFrame) : visibleFrame;
+ }
+
public void setVisible(boolean visible) {
mVisible = visible;
}
@@ -65,6 +73,10 @@
return mFrame;
}
+ public @Nullable Rect getVisibleFrame() {
+ return mVisibleFrame;
+ }
+
public boolean isVisible() {
return mVisible;
}
@@ -78,10 +90,22 @@
* source.
*/
public Insets calculateInsets(Rect relativeFrame, boolean ignoreVisibility) {
+ return calculateInsets(relativeFrame, mFrame, ignoreVisibility);
+ }
+
+ /**
+ * Like {@link #calculateInsets(Rect, boolean)}, but will return visible insets.
+ */
+ public Insets calculateVisibleInsets(Rect relativeFrame) {
+ return calculateInsets(relativeFrame, mVisibleFrame != null ? mVisibleFrame : mFrame,
+ false /* ignoreVisibility */);
+ }
+
+ private Insets calculateInsets(Rect relativeFrame, Rect frame, boolean ignoreVisibility) {
if (!ignoreVisibility && !mVisible) {
return Insets.NONE;
}
- if (!mTmpFrame.setIntersect(mFrame, relativeFrame)) {
+ if (!mTmpFrame.setIntersect(frame, relativeFrame)) {
return Insets.NONE;
}
@@ -109,6 +133,9 @@
pw.print(prefix);
pw.print("InsetsSource type="); pw.print(InsetsState.typeToString(mType));
pw.print(" frame="); pw.print(mFrame.toShortString());
+ if (mVisibleFrame != null) {
+ pw.print(" visibleFrmae="); pw.print(mVisibleFrame.toShortString());
+ }
pw.print(" visible="); pw.print(mVisible);
pw.println();
}
@@ -122,6 +149,7 @@
if (mType != that.mType) return false;
if (mVisible != that.mVisible) return false;
+ if (!Objects.equals(mVisibleFrame, that.mVisibleFrame)) return false;
return mFrame.equals(that.mFrame);
}
@@ -136,6 +164,7 @@
public InsetsSource(Parcel in) {
mType = in.readInt();
mFrame = in.readParcelable(null /* loader */);
+ mVisibleFrame = in.readParcelable(null /* loader */);
mVisible = in.readBoolean();
}
@@ -148,6 +177,7 @@
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mType);
dest.writeParcelable(mFrame, 0 /* flags*/);
+ dest.writeParcelable(mVisibleFrame, 0 /* flags */);
dest.writeBoolean(mVisible);
}
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index c6d9898..8a1b45a 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -16,6 +16,8 @@
package android.view;
+import static android.view.InsetsController.ANIMATION_TYPE_NONE;
+
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.view.InsetsState.InternalInsetsType;
@@ -53,7 +55,7 @@
}
protected final InsetsController mController;
- protected boolean mVisible;
+ protected boolean mRequestedVisible;
private final Supplier<Transaction> mTransactionSupplier;
private final @InternalInsetsType int mType;
private final InsetsState mState;
@@ -66,7 +68,7 @@
mState = state;
mTransactionSupplier = transactionSupplier;
mController = controller;
- mVisible = InsetsState.getDefaultVisibility(type);
+ mRequestedVisible = InsetsState.getDefaultVisibility(type);
}
public void setControl(@Nullable InsetsSourceControl control) {
@@ -94,12 +96,12 @@
@VisibleForTesting
public void show() {
- setVisible(true);
+ setRequestedVisible(true);
}
@VisibleForTesting
public void hide() {
- setVisible(false);
+ setRequestedVisible(false);
}
/**
@@ -126,16 +128,16 @@
if (mSourceControl == null) {
return false;
}
- if (mState.getSource(mType).isVisible() == mVisible) {
+ if (mState.getSource(mType).isVisible() == mRequestedVisible) {
return false;
}
- mState.getSource(mType).setVisible(mVisible);
+ mState.getSource(mType).setVisible(mRequestedVisible);
return true;
}
@VisibleForTesting
- public boolean isVisible() {
- return mVisible;
+ public boolean isRequestedVisible() {
+ return mRequestedVisible;
}
/**
@@ -157,23 +159,27 @@
// no-op for types that always return ShowResult#SHOW_IMMEDIATELY.
}
- private void setVisible(boolean visible) {
- if (mVisible == visible) {
+ /**
+ * Sets requested visibility from the client, regardless of whether we are able to control it at
+ * the moment.
+ */
+ private void setRequestedVisible(boolean requestedVisible) {
+ if (mRequestedVisible == requestedVisible) {
return;
}
- mVisible = visible;
+ mRequestedVisible = requestedVisible;
applyLocalVisibilityOverride();
mController.notifyVisibilityChanged();
}
private void applyHiddenToControl() {
if (mSourceControl == null || mSourceControl.getLeash() == null
- || mController.isAnimating()) {
+ || mController.getAnimationType(mType) != ANIMATION_TYPE_NONE) {
return;
}
final Transaction t = mTransactionSupplier.get();
- if (mVisible) {
+ if (mRequestedVisible) {
t.show(mSourceControl.getLeash());
} else {
t.hide(mSourceControl.getLeash());
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index e3fed3a..e33ca70 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -19,10 +19,17 @@
import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
import static android.view.ViewRootImpl.NEW_INSETS_MODE_IME;
import static android.view.ViewRootImpl.NEW_INSETS_MODE_NONE;
+import static android.view.ViewRootImpl.sNewInsetsMode;
+import static android.view.WindowInsets.Type.IME;
import static android.view.WindowInsets.Type.MANDATORY_SYSTEM_GESTURES;
import static android.view.WindowInsets.Type.SIZE;
import static android.view.WindowInsets.Type.SYSTEM_GESTURES;
+import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.indexOf;
+import static android.view.WindowInsets.Type.isVisibleInsetsType;
+import static android.view.WindowInsets.Type.systemBars;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
import android.annotation.IntDef;
import android.annotation.Nullable;
@@ -36,6 +43,7 @@
import android.view.WindowInsets.Type;
import android.view.WindowInsets.Type.InsetsType;
import android.view.WindowManager.LayoutParams;
+import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -156,11 +164,10 @@
&& source.getType() != ITYPE_IME;
boolean skipSystemBars = ViewRootImpl.sNewInsetsMode != NEW_INSETS_MODE_FULL
&& (type == ITYPE_STATUS_BAR || type == ITYPE_NAVIGATION_BAR);
- boolean skipIme = source.getType() == ITYPE_IME
- && (legacySoftInputMode & LayoutParams.SOFT_INPUT_ADJUST_RESIZE) == 0;
boolean skipLegacyTypes = ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE
- && (toPublicType(type) & Type.compatSystemInsets()) != 0;
- if (skipSystemBars || skipIme || skipLegacyTypes || skipNonImeInImeMode) {
+ && (type == ITYPE_STATUS_BAR || type == ITYPE_NAVIGATION_BAR
+ || type == ITYPE_IME);
+ if (skipSystemBars || skipLegacyTypes || skipNonImeInImeMode) {
typeVisibilityMap[indexOf(toPublicType(type))] = source.isVisible();
continue;
}
@@ -175,8 +182,37 @@
typeMaxInsetsMap, null /* typeSideMap */, null /* typeVisibilityMap */);
}
}
+ final int softInputAdjustMode = legacySoftInputMode & SOFT_INPUT_MASK_ADJUST;
return new WindowInsets(typeInsetsMap, typeMaxInsetsMap, typeVisibilityMap, isScreenRound,
- alwaysConsumeSystemBars, cutout);
+ alwaysConsumeSystemBars, cutout, softInputAdjustMode == SOFT_INPUT_ADJUST_RESIZE
+ ? systemBars() | ime()
+ : systemBars());
+ }
+
+ public Rect calculateVisibleInsets(Rect frame, Rect legacyVisibleInsets,
+ @SoftInputModeFlags int softInputMode) {
+ if (sNewInsetsMode == NEW_INSETS_MODE_NONE) {
+ return legacyVisibleInsets;
+ }
+
+ Insets insets = Insets.NONE;
+ for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
+ InsetsSource source = mSources.get(type);
+ if (source == null) {
+ continue;
+ }
+ if (sNewInsetsMode != NEW_INSETS_MODE_FULL && type != ITYPE_IME) {
+ continue;
+ }
+
+ // Ignore everything that's not a system bar or IME.
+ int publicType = InsetsState.toPublicType(type);
+ if (!isVisibleInsetsType(publicType, softInputMode)) {
+ continue;
+ }
+ insets = Insets.max(source.calculateVisibleInsets(frame), insets);
+ }
+ return insets.toRect();
}
private void processSource(InsetsSource source, Rect relativeFrame, boolean ignoreVisibility,
diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java
index c3c7b95..0068476 100644
--- a/core/java/android/view/MotionEvent.java
+++ b/core/java/android/view/MotionEvent.java
@@ -1541,6 +1541,8 @@
@FastNative
private static native float nativeGetAxisValue(long nativePtr,
int axis, int pointerIndex, int historyPos);
+ @FastNative
+ private static native void nativeTransform(long nativePtr, Matrix matrix);
// -------------- @CriticalNative ----------------------
@@ -1614,8 +1616,6 @@
@CriticalNative
private static native void nativeScale(long nativePtr, float scale);
- @CriticalNative
- private static native void nativeTransform(long nativePtr, long matrix);
private MotionEvent() {
}
@@ -3210,7 +3210,7 @@
throw new IllegalArgumentException("matrix must not be null");
}
- nativeTransform(mNativePtr, matrix.native_instance);
+ nativeTransform(mNativePtr, matrix);
}
/**
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index f6d6522..cc4278b 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -122,6 +122,8 @@
private static native void nativeSetColor(long transactionObj, long nativeObject, float[] color);
private static native void nativeSetFlags(long transactionObj, long nativeObject,
int flags, int mask);
+ private static native void nativeSetFrameRateSelectionPriority(long transactionObj,
+ long nativeObject, int priority);
private static native void nativeSetWindowCrop(long transactionObj, long nativeObject,
int l, int t, int r, int b);
private static native void nativeSetCornerRadius(long transactionObj, long nativeObject,
@@ -677,6 +679,22 @@
}
/**
+ * Set the initial visibility for the SurfaceControl.
+ *
+ * @param hidden Whether the Surface is initially HIDDEN.
+ * @hide
+ */
+ @NonNull
+ public Builder setHidden(boolean hidden) {
+ if (hidden) {
+ mFlags |= HIDDEN;
+ } else {
+ mFlags &= ~HIDDEN;
+ }
+ return this;
+ }
+
+ /**
* Set a parent surface for our new SurfaceControl.
*
* Child surfaces are constrained to the onscreen region of their parent.
@@ -789,8 +807,7 @@
* @param name The surface name, must not be null.
* @param w The surface initial width.
* @param h The surface initial height.
- * @param flags The surface creation flags. Should always include {@link #HIDDEN}
- * in the creation flags.
+ * @param flags The surface creation flags.
* @param metadata Initial metadata.
* @throws throws OutOfResourcesException If the SurfaceControl cannot be created.
*/
@@ -801,15 +818,6 @@
throw new IllegalArgumentException("name must not be null");
}
- if ((flags & SurfaceControl.HIDDEN) == 0) {
- Log.w(TAG, "Surfaces should always be created with the HIDDEN flag set "
- + "to ensure that they are not made visible prematurely before "
- + "all of the surface's properties have been configured. "
- + "Set the other properties and make the surface visible within "
- + "a transaction. New surface name: " + name,
- new Throwable());
- }
-
mName = name;
mWidth = w;
mHeight = h;
@@ -2239,6 +2247,19 @@
}
/**
+ * This information is passed to SurfaceFlinger to decide which window should have a
+ * priority when deciding about the refresh rate of the display. All windows have the
+ * lowest priority by default.
+ * @hide
+ */
+ @NonNull
+ public Transaction setFrameRateSelectionPriority(@NonNull SurfaceControl sc, int priority) {
+ sc.checkNotReleased();
+ nativeSetFrameRateSelectionPriority(mNativeObject, sc.mNativeObject, priority);
+ return this;
+ }
+
+ /**
* Request that a given surface and it's sub-tree be shown.
*
* @param sc The surface to show.
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 52ea2b2..0de1a4f 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -42,6 +42,7 @@
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceControl.Transaction;
+import android.view.accessibility.AccessibilityNodeInfo;
import com.android.internal.view.SurfaceCallbackHelper;
@@ -201,6 +202,9 @@
private SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
private int mParentSurfaceGenerationId;
+ // The token of embedded windowless view hierarchy.
+ private IBinder mEmbeddedViewHierarchy;
+
public SurfaceView(Context context) {
this(context, null);
}
@@ -379,7 +383,7 @@
* This gets called on a RenderThread worker thread, so members accessed here must
* be protected by a lock.
*/
- final boolean useBLAST = ViewRootImpl.USE_BLAST_BUFFERQUEUE;
+ final boolean useBLAST = WindowManagerGlobal.USE_BLAST_ADAPTER;
viewRoot.registerRtFrameCallback(frame -> {
try {
final SurfaceControl.Transaction t = useBLAST ?
@@ -1107,7 +1111,7 @@
private void applySurfaceTransforms(SurfaceControl surface, SurfaceControl.Transaction t,
Rect position, long frameNumber) {
- if (frameNumber > 0 && ViewRootImpl.USE_BLAST_BUFFERQUEUE == false) {
+ if (frameNumber > 0 && !WindowManagerGlobal.USE_BLAST_ADAPTER) {
final ViewRootImpl viewRoot = getViewRootImpl();
t.deferTransactionUntilSurface(surface, viewRoot.mSurface,
@@ -1125,7 +1129,7 @@
}
private void setParentSpaceRectangle(Rect position, long frameNumber) {
- final boolean useBLAST = ViewRootImpl.USE_BLAST_BUFFERQUEUE;
+ final boolean useBLAST = WindowManagerGlobal.USE_BLAST_ADAPTER;
final ViewRootImpl viewRoot = getViewRootImpl();
final SurfaceControl.Transaction t = useBLAST ? viewRoot.getBLASTSyncTransaction() :
mRtTransaction;
@@ -1186,7 +1190,7 @@
@Override
public void positionLost(long frameNumber) {
- boolean useBLAST = ViewRootImpl.USE_BLAST_BUFFERQUEUE;
+ boolean useBLAST = WindowManagerGlobal.USE_BLAST_ADAPTER;
if (DEBUG) {
Log.d(TAG, String.format("%d windowPositionLost, frameNr = %d",
System.identityHashCode(this), frameNumber));
@@ -1524,11 +1528,34 @@
@Override
public void invalidate(boolean invalidateCache) {
super.invalidate(invalidateCache);
- if (ViewRootImpl.USE_BLAST_BUFFERQUEUE == false) {
+ if (!WindowManagerGlobal.USE_BLAST_ADAPTER) {
return;
}
final ViewRootImpl viewRoot = getViewRootImpl();
if (viewRoot == null) return;
viewRoot.setUseBLASTSyncTransaction();
}
+
+ /**
+ * Add the token of embedded view hierarchy. Set {@code null} to clear the embedded view
+ * hierarchy.
+ *
+ * @param token IBinder token.
+ * @hide
+ */
+ public void setEmbeddedViewHierarchy(IBinder token) {
+ mEmbeddedViewHierarchy = token;
+ }
+
+ /** @hide */
+ @Override
+ public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfoInternal(info);
+ if (mEmbeddedViewHierarchy == null) {
+ return;
+ }
+ // Add a leashed child when this SurfaceView embeds another view hierarchy. Getting this
+ // leashed child would return the root node in the embedded hierarchy
+ info.addChild(mEmbeddedViewHierarchy);
+ }
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 0db80e2..562ed0e 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -130,7 +130,6 @@
import android.view.contentcapture.ContentCaptureSession;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
-import android.view.inputmethod.InputMethodManager;
import android.view.inspector.InspectableProperty;
import android.view.inspector.InspectableProperty.EnumEntry;
import android.view.inspector.InspectableProperty.FlagEntry;
@@ -7942,12 +7941,12 @@
if (isPressed()) {
setPressed(false);
}
- if (mAttachInfo != null && mAttachInfo.mHasWindowFocus) {
- notifyFocusChangeToInputMethodManager(false /* hasFocus */);
+ if (hasWindowFocus()) {
+ notifyFocusChangeToImeFocusController(false /* hasFocus */);
}
onFocusLost();
- } else if (mAttachInfo != null && mAttachInfo.mHasWindowFocus) {
- notifyFocusChangeToInputMethodManager(true /* hasFocus */);
+ } else if (hasWindowFocus()) {
+ notifyFocusChangeToImeFocusController(true /* hasFocus */);
}
invalidate(true);
@@ -7964,23 +7963,15 @@
}
/**
- * Notify {@link InputMethodManager} about the focus change of the {@link View}.
- *
- * <p>Does nothing when {@link InputMethodManager} is not available.</p>
+ * Notify {@link ImeFocusController} about the focus change of the {@link View}.
*
* @param hasFocus {@code true} when the {@link View} is being focused.
*/
- private void notifyFocusChangeToInputMethodManager(boolean hasFocus) {
- final InputMethodManager imm =
- getContext().getSystemService(InputMethodManager.class);
- if (imm == null) {
+ private void notifyFocusChangeToImeFocusController(boolean hasFocus) {
+ if (mAttachInfo == null) {
return;
}
- if (hasFocus) {
- imm.focusIn(this);
- } else {
- imm.focusOut(this);
- }
+ mAttachInfo.mViewRootImpl.getImeFocusController().onViewFocusChanged(this, hasFocus);
}
/** @hide */
@@ -11117,7 +11108,21 @@
}
/**
- * Dispatches {@link WindowInsetsAnimationCallback#onStarted(InsetsAnimation, AnimationBounds)}
+ * Dispatches {@link WindowInsetsAnimationCallback#onPrepare(InsetsAnimation)}
+ * when Window Insets animation is being prepared.
+ * @param animation current animation
+ *
+ * @see WindowInsetsAnimationCallback#onPrepare(InsetsAnimation)
+ */
+ public void dispatchWindowInsetsAnimationPrepare(
+ @NonNull InsetsAnimation animation) {
+ if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) {
+ mListenerInfo.mWindowInsetsAnimationCallback.onPrepare(animation);
+ }
+ }
+
+ /**
+ * Dispatches {@link WindowInsetsAnimationCallback#onStart(InsetsAnimation, AnimationBounds)}
* when Window Insets animation is started.
* @param animation current animation
* @param bounds the upper and lower {@link AnimationBounds} that provides range of
@@ -11125,10 +11130,10 @@
* @return the upper and lower {@link AnimationBounds}.
*/
@NonNull
- public AnimationBounds dispatchWindowInsetsAnimationStarted(
+ public AnimationBounds dispatchWindowInsetsAnimationStart(
@NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) {
if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) {
- return mListenerInfo.mWindowInsetsAnimationCallback.onStarted(animation, bounds);
+ return mListenerInfo.mWindowInsetsAnimationCallback.onStart(animation, bounds);
}
return bounds;
}
@@ -11149,13 +11154,13 @@
}
/**
- * Dispatches {@link WindowInsetsAnimationCallback#onFinished(InsetsAnimation)}
+ * Dispatches {@link WindowInsetsAnimationCallback#onFinish(InsetsAnimation)}
* when Window Insets animation finishes.
* @param animation The current ongoing {@link InsetsAnimation}.
*/
- public void dispatchWindowInsetsAnimationFinished(@NonNull InsetsAnimation animation) {
+ public void dispatchWindowInsetsAnimationFinish(@NonNull InsetsAnimation animation) {
if (mListenerInfo != null && mListenerInfo.mWindowInsetsAnimationCallback != null) {
- mListenerInfo.mWindowInsetsAnimationCallback.onFinished(animation);
+ mListenerInfo.mWindowInsetsAnimationCallback.onFinish(animation);
}
}
@@ -13904,7 +13909,7 @@
mPrivateFlags3 &= ~PFLAG3_TEMPORARY_DETACH;
onFinishTemporaryDetach();
if (hasWindowFocus() && hasFocus()) {
- notifyFocusChangeToInputMethodManager(true /* hasFocus */);
+ notifyFocusChangeToImeFocusController(true /* hasFocus */);
}
notifyEnterOrExitForAutoFillIfNeeded(true);
notifyAppearedOrDisappearedForContentCaptureIfNeeded(true);
@@ -14312,13 +14317,13 @@
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((mPrivateFlags & PFLAG_FOCUSED) != 0) {
- notifyFocusChangeToInputMethodManager(false /* hasFocus */);
+ notifyFocusChangeToImeFocusController(false /* hasFocus */);
}
removeLongPressCallback();
removeTapCallback();
onFocusLost();
} else if ((mPrivateFlags & PFLAG_FOCUSED) != 0) {
- notifyFocusChangeToInputMethodManager(true /* hasFocus */);
+ notifyFocusChangeToImeFocusController(true /* hasFocus */);
}
refreshDrawableState();
@@ -14335,6 +14340,14 @@
}
/**
+ * @return {@code true} if this view is in a window that currently has IME focusable state.
+ * @hide
+ */
+ public boolean hasImeFocus() {
+ return mAttachInfo != null && mAttachInfo.mHasImeFocus;
+ }
+
+ /**
* Dispatch a view visibility change down the view hierarchy.
* ViewGroups should override to route to their children.
* @param changedView The view whose visibility changed. Could be 'this' or
@@ -19630,7 +19643,7 @@
rebuildOutline();
if (isFocused()) {
- notifyFocusChangeToInputMethodManager(true /* hasFocus */);
+ notifyFocusChangeToImeFocusController(true /* hasFocus */);
}
}
@@ -20213,9 +20226,8 @@
onDetachedFromWindow();
onDetachedFromWindowInternal();
- InputMethodManager imm = getContext().getSystemService(InputMethodManager.class);
- if (imm != null) {
- imm.onViewDetachedFromWindow(this);
+ if (info != null) {
+ info.mViewRootImpl.getImeFocusController().onViewDetachedFromWindow(this);
}
ListenerInfo li = mListenerInfo;
@@ -28551,6 +28563,11 @@
boolean mHasWindowFocus;
/**
+ * Indicates whether the view's window has IME focused.
+ */
+ boolean mHasImeFocus;
+
+ /**
* The current visibility of the window.
*/
int mWindowVisibility;
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 5fb7177..047d7da 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -7199,13 +7199,23 @@
}
@Override
- @NonNull
- public AnimationBounds dispatchWindowInsetsAnimationStarted(
- @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) {
- super.dispatchWindowInsetsAnimationStarted(animation, bounds);
+ public void dispatchWindowInsetsAnimationPrepare(
+ @NonNull InsetsAnimation animation) {
+ super.dispatchWindowInsetsAnimationPrepare(animation);
final int count = getChildCount();
for (int i = 0; i < count; i++) {
- getChildAt(i).dispatchWindowInsetsAnimationStarted(animation, bounds);
+ getChildAt(i).dispatchWindowInsetsAnimationPrepare(animation);
+ }
+ }
+
+ @Override
+ @NonNull
+ public AnimationBounds dispatchWindowInsetsAnimationStart(
+ @NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) {
+ super.dispatchWindowInsetsAnimationStart(animation, bounds);
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ getChildAt(i).dispatchWindowInsetsAnimationStart(animation, bounds);
}
return bounds;
}
@@ -7222,11 +7232,11 @@
}
@Override
- public void dispatchWindowInsetsAnimationFinished(@NonNull InsetsAnimation animation) {
- super.dispatchWindowInsetsAnimationFinished(animation);
+ public void dispatchWindowInsetsAnimationFinish(@NonNull InsetsAnimation animation) {
+ super.dispatchWindowInsetsAnimationFinish(animation);
final int count = getChildCount();
for (int i = 0; i < count; i++) {
- getChildAt(i).dispatchWindowInsetsAnimationFinished(animation);
+ getChildAt(i).dispatchWindowInsetsAnimationFinish(animation);
}
}
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 522ff9a..17b945b 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -192,11 +192,6 @@
private static final boolean MT_RENDERER_AVAILABLE = true;
/**
- * @hide
- */
- public static final boolean USE_BLAST_BUFFERQUEUE = false;
-
- /**
* If set to 2, the view system will switch from using rectangles retrieved from window to
* dispatch to the view hierarchy to using {@link InsetsController}, that derives the insets
* directly from the full configuration, enabling richer information about the insets state, as
@@ -458,7 +453,6 @@
boolean mReportNextDraw;
boolean mFullRedrawNeeded;
boolean mNewSurfaceNeeded;
- boolean mLastWasImTarget;
boolean mForceNextWindowRelayout;
CountDownLatch mWindowDrawCountDown;
@@ -624,6 +618,16 @@
InputEventConsistencyVerifier.isInstrumentationEnabled() ?
new InputEventConsistencyVerifier(this, 0) : null;
+ private final ImeFocusController mImeFocusController;
+
+ /**
+ * @return {@link ImeFocusController} for this instance.
+ */
+ @NonNull
+ public ImeFocusController getImeFocusController() {
+ return mImeFocusController;
+ }
+
private final InsetsController mInsetsController = new InsetsController(this);
private final GestureExclusionTracker mGestureExclusionTracker = new GestureExclusionTracker();
@@ -709,6 +713,7 @@
}
loadSystemProperties();
+ mImeFocusController = new ImeFocusController(this);
}
public static void addFirstDrawHandler(Runnable callback) {
@@ -831,6 +836,10 @@
if (mWindowAttributes.packageName == null) {
mWindowAttributes.packageName = mBasePackageName;
}
+ if (WindowManagerGlobal.USE_BLAST_ADAPTER) {
+ mWindowAttributes.privateFlags |=
+ WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
+ }
attrs = mWindowAttributes;
setTag();
@@ -1055,11 +1064,6 @@
}
}
- /** Whether the window is in local focus mode or not */
- private boolean isInLocalFocusMode() {
- return (mWindowAttributes.flags & WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE) != 0;
- }
-
@UnsupportedAppUsage
public int getWindowFlags() {
return mWindowAttributes.flags;
@@ -1312,8 +1316,8 @@
}
mWindowAttributes.privateFlags |= compatibleWindowFlag;
- if (USE_BLAST_BUFFERQUEUE) {
- mWindowAttributes.privateFlags =
+ if (WindowManagerGlobal.USE_BLAST_ADAPTER) {
+ mWindowAttributes.privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
}
@@ -1469,6 +1473,7 @@
return;
}
mApplyInsetsRequested = true;
+ requestLayout();
// If this changes during traversal, no need to schedule another one as it will dispatch it
// during the current traversal.
@@ -2107,6 +2112,12 @@
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
+ private void updateVisibleInsets() {
+ Rect visibleInsets = mInsetsController.calculateVisibleInsets(mPendingVisibleInsets,
+ mWindowAttributes.softInputMode);
+ mAttachInfo.mVisibleInsets.set(visibleInsets);
+ }
+
InsetsController getInsetsController() {
return mInsetsController;
}
@@ -2255,7 +2266,7 @@
insetsChanged = true;
}
if (!mPendingVisibleInsets.equals(mAttachInfo.mVisibleInsets)) {
- mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
+ updateVisibleInsets();
if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: "
+ mAttachInfo.mVisibleInsets);
}
@@ -2321,6 +2332,7 @@
if (mApplyInsetsRequested) {
mApplyInsetsRequested = false;
+ updateVisibleInsets();
dispatchApplyInsets(host);
if (mLayoutRequested) {
// Short-circuit catching a new layout request here, so
@@ -2505,7 +2517,7 @@
contentInsetsChanged = true;
}
if (visibleInsetsChanged) {
- mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
+ updateVisibleInsets();
if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: "
+ mAttachInfo.mVisibleInsets);
}
@@ -2885,19 +2897,7 @@
mViewVisibility = viewVisibility;
mHadWindowFocus = hasWindowFocus;
- if (hasWindowFocus && !isInLocalFocusMode()) {
- final boolean imTarget = WindowManager.LayoutParams
- .mayUseInputMethod(mWindowAttributes.flags);
- if (imTarget != mLastWasImTarget) {
- mLastWasImTarget = imTarget;
- InputMethodManager imm = mContext.getSystemService(InputMethodManager.class);
- if (imm != null && imTarget) {
- imm.onPreWindowFocus(mView, hasWindowFocus);
- imm.onPostWindowFocus(mView, mView.findFocus(),
- mWindowAttributes.softInputMode, mWindowAttributes.flags);
- }
- }
- }
+ mImeFocusController.onTraversal(hasWindowFocus, mWindowAttributes);
// Remember if we must report the next draw.
if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
@@ -3065,14 +3065,10 @@
}
mAttachInfo.mHasWindowFocus = hasWindowFocus;
+ mAttachInfo.mHasImeFocus = mImeFocusController.updateImeFocusable(
+ mWindowAttributes, true /* force */);
+ mImeFocusController.onPreWindowFocus(hasWindowFocus, mWindowAttributes);
- mLastWasImTarget = WindowManager.LayoutParams
- .mayUseInputMethod(mWindowAttributes.flags);
-
- InputMethodManager imm = mContext.getSystemService(InputMethodManager.class);
- if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
- imm.onPreWindowFocus(mView, hasWindowFocus);
- }
if (mView != null) {
mAttachInfo.mKeyDispatchState.reset();
mView.dispatchWindowFocusChanged(hasWindowFocus);
@@ -3084,11 +3080,10 @@
// Note: must be done after the focus change callbacks,
// so all of the view state is set up correctly.
+ mImeFocusController.onPostWindowFocus(mView.findFocus(), hasWindowFocus,
+ mWindowAttributes);
+
if (hasWindowFocus) {
- if (imm != null && mLastWasImTarget && !isInLocalFocusMode()) {
- imm.onPostWindowFocus(mView, mView.findFocus(),
- mWindowAttributes.softInputMode, mWindowAttributes.flags);
- }
// Clear the forward bit. We can just do this directly, since
// the window manager doesn't care about it.
mWindowAttributes.softInputMode &=
@@ -4884,10 +4879,7 @@
enqueueInputEvent(event, null, 0, true);
} break;
case MSG_CHECK_FOCUS: {
- InputMethodManager imm = mContext.getSystemService(InputMethodManager.class);
- if (imm != null) {
- imm.checkFocus();
- }
+ getImeFocusController().checkFocus(false, true);
} break;
case MSG_CLOSE_SYSTEM_DIALOGS: {
if (mView != null) {
@@ -5451,23 +5443,20 @@
@Override
protected int onProcess(QueuedInputEvent q) {
- if (mLastWasImTarget && !isInLocalFocusMode()) {
- InputMethodManager imm = mContext.getSystemService(InputMethodManager.class);
- if (imm != null) {
- final InputEvent event = q.mEvent;
- if (DEBUG_IMF) Log.v(mTag, "Sending input event to IME: " + event);
- int result = imm.dispatchInputEvent(event, q, this, mHandler);
- if (result == InputMethodManager.DISPATCH_HANDLED) {
- return FINISH_HANDLED;
- } else if (result == InputMethodManager.DISPATCH_NOT_HANDLED) {
- // The IME could not handle it, so skip along to the next InputStage
- return FORWARD;
- } else {
- return DEFER; // callback will be invoked later
- }
- }
+ final int result = mImeFocusController.onProcessImeInputStage(
+ q, q.mEvent, mWindowAttributes, this);
+ switch (result) {
+ case InputMethodManager.DISPATCH_IN_PROGRESS:
+ // callback will be invoked later
+ return DEFER;
+ case InputMethodManager.DISPATCH_NOT_HANDLED:
+ // The IME could not handle it, so skip along to the next InputStage
+ return FORWARD;
+ case InputMethodManager.DISPATCH_HANDLED:
+ return FINISH_HANDLED;
+ default:
+ throw new IllegalStateException("Unexpected result=" + result);
}
- return FORWARD;
}
@Override
@@ -7273,7 +7262,7 @@
mPendingStableInsets, mPendingBackDropFrame, mPendingDisplayCutout,
mPendingMergedConfiguration, mSurfaceControl, mTempInsets);
if (mSurfaceControl.isValid()) {
- if (USE_BLAST_BUFFERQUEUE == false) {
+ if (!WindowManagerGlobal.USE_BLAST_ADAPTER) {
mSurface.copyFrom(mSurfaceControl);
} else {
mSurface.transferFrom(getOrCreateBLASTSurface(
@@ -8037,6 +8026,11 @@
}
@Override
+ public void onFocusEvent(boolean hasFocus, boolean inTouchMode) {
+ windowFocusChanged(hasFocus, inTouchMode);
+ }
+
+ @Override
public void dispose() {
unscheduleConsumeBatchedInput();
super.dispose();
diff --git a/core/java/android/view/WindowContainerTransaction.java b/core/java/android/view/WindowContainerTransaction.java
index 607a870..c55caa3 100644
--- a/core/java/android/view/WindowContainerTransaction.java
+++ b/core/java/android/view/WindowContainerTransaction.java
@@ -62,6 +62,30 @@
return this;
}
+ /**
+ * Notify activies within the hiearchy of a container that they have entered picture-in-picture
+ * mode with the given bounds.
+ */
+ public WindowContainerTransaction scheduleFinishEnterPip(IWindowContainer container,
+ Rect bounds) {
+ Change chg = getOrCreateChange(container.asBinder());
+ chg.mSchedulePipCallback = true;
+ chg.mPinnedBounds = new Rect(bounds);
+ return this;
+ }
+
+ /**
+ * Sets whether a container or any of its children can be focusable. When {@code false}, no
+ * child can be focused; however, when {@code true}, it is still possible for children to be
+ * non-focusable due to WM policy.
+ */
+ public WindowContainerTransaction setFocusable(IWindowContainer container, boolean focusable) {
+ Change chg = getOrCreateChange(container.asBinder());
+ chg.mFocusable = focusable;
+ chg.mChangeMask |= Change.CHANGE_FOCUSABLE;
+ return this;
+ }
+
public Map<IBinder, Change> getChanges() {
return mChanges;
}
@@ -100,22 +124,48 @@
* @hide
*/
public static class Change implements Parcelable {
+ public static final int CHANGE_FOCUSABLE = 1;
+
private final Configuration mConfiguration = new Configuration();
+ private boolean mFocusable = true;
+ private int mChangeMask = 0;
private @ActivityInfo.Config int mConfigSetMask = 0;
private @WindowConfiguration.WindowConfig int mWindowSetMask = 0;
+ private boolean mSchedulePipCallback = false;
+ private Rect mPinnedBounds = null;
+
public Change() {}
protected Change(Parcel in) {
mConfiguration.readFromParcel(in);
+ mFocusable = in.readBoolean();
+ mChangeMask = in.readInt();
mConfigSetMask = in.readInt();
mWindowSetMask = in.readInt();
+ mSchedulePipCallback = (in.readInt() != 0);
+ if (mSchedulePipCallback ) {
+ mPinnedBounds = new Rect();
+ mPinnedBounds.readFromParcel(in);
+ }
}
public Configuration getConfiguration() {
return mConfiguration;
}
+ /** Gets the requested focusable value */
+ public boolean getFocusable() {
+ if ((mChangeMask & CHANGE_FOCUSABLE) == 0) {
+ throw new RuntimeException("Focusable not set. check CHANGE_FOCUSABLE first");
+ }
+ return mFocusable;
+ }
+
+ public int getChangeMask() {
+ return mChangeMask;
+ }
+
@ActivityInfo.Config
public int getConfigSetMask() {
return mConfigSetMask;
@@ -126,6 +176,14 @@
return mWindowSetMask;
}
+ /**
+ * Returns the bounds to be used for scheduling the enter pip callback
+ * or null if no callback is to be scheduled.
+ */
+ public Rect getEnterPipBounds() {
+ return mPinnedBounds;
+ }
+
@Override
public String toString() {
final boolean changesBounds =
@@ -142,6 +200,9 @@
if (changesSss) {
sb.append("ssw:" + mConfiguration.smallestScreenWidthDp + ",");
}
+ if ((mChangeMask & CHANGE_FOCUSABLE) != 0) {
+ sb.append("focusable:" + mFocusable + ",");
+ }
sb.append("}");
return sb.toString();
}
@@ -149,8 +210,15 @@
@Override
public void writeToParcel(Parcel dest, int flags) {
mConfiguration.writeToParcel(dest, flags);
+ dest.writeBoolean(mFocusable);
+ dest.writeInt(mChangeMask);
dest.writeInt(mConfigSetMask);
dest.writeInt(mWindowSetMask);
+
+ dest.writeInt(mSchedulePipCallback ? 1 : 0);
+ if (mSchedulePipCallback ) {
+ mPinnedBounds.writeToParcel(dest, flags);
+ }
}
@Override
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index a9cc50f..9291b56 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -27,8 +27,10 @@
import static android.view.WindowInsets.Type.SYSTEM_GESTURES;
import static android.view.WindowInsets.Type.TAPPABLE_ELEMENT;
import static android.view.WindowInsets.Type.all;
-import static android.view.WindowInsets.Type.compatSystemInsets;
import static android.view.WindowInsets.Type.indexOf;
+import static android.view.WindowInsets.Type.systemBars;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
import android.annotation.IntDef;
import android.annotation.IntRange;
@@ -40,6 +42,7 @@
import android.graphics.Rect;
import android.util.SparseArray;
import android.view.WindowInsets.Type.InsetsType;
+import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethod;
@@ -87,6 +90,8 @@
private final boolean mStableInsetsConsumed;
private final boolean mDisplayCutoutConsumed;
+ private final int mCompatInsetTypes;
+
/**
* Since new insets may be added in the future that existing apps couldn't
* know about, this fully empty constant shouldn't be made available to apps
@@ -112,7 +117,7 @@
boolean isRound, boolean alwaysConsumeSystemBars, DisplayCutout displayCutout) {
this(createCompatTypeMap(systemWindowInsetsRect), createCompatTypeMap(stableInsetsRect),
createCompatVisibilityMap(createCompatTypeMap(systemWindowInsetsRect)),
- isRound, alwaysConsumeSystemBars, displayCutout);
+ isRound, alwaysConsumeSystemBars, displayCutout, systemBars());
}
/**
@@ -131,7 +136,7 @@
@Nullable Insets[] typeMaxInsetsMap,
boolean[] typeVisibilityMap,
boolean isRound,
- boolean alwaysConsumeSystemBars, DisplayCutout displayCutout) {
+ boolean alwaysConsumeSystemBars, DisplayCutout displayCutout, int compatInsetTypes) {
mSystemWindowInsetsConsumed = typeInsetsMap == null;
mTypeInsetsMap = mSystemWindowInsetsConsumed
? new Insets[SIZE]
@@ -145,6 +150,7 @@
mTypeVisibilityMap = typeVisibilityMap;
mIsRound = isRound;
mAlwaysConsumeSystemBars = alwaysConsumeSystemBars;
+ mCompatInsetTypes = compatInsetTypes;
mDisplayCutoutConsumed = displayCutout == null;
mDisplayCutout = (mDisplayCutoutConsumed || displayCutout.isEmpty())
@@ -160,7 +166,8 @@
this(src.mSystemWindowInsetsConsumed ? null : src.mTypeInsetsMap,
src.mStableInsetsConsumed ? null : src.mTypeMaxInsetsMap,
src.mTypeVisibilityMap, src.mIsRound,
- src.mAlwaysConsumeSystemBars, displayCutoutCopyConstructorArgument(src));
+ src.mAlwaysConsumeSystemBars, displayCutoutCopyConstructorArgument(src),
+ src.mCompatInsetTypes);
}
private static DisplayCutout displayCutoutCopyConstructorArgument(WindowInsets w) {
@@ -211,7 +218,8 @@
/** @hide */
@UnsupportedAppUsage
public WindowInsets(Rect systemWindowInsets) {
- this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, false, null);
+ this(createCompatTypeMap(systemWindowInsets), null, new boolean[SIZE], false, false, null,
+ systemBars());
}
/**
@@ -280,7 +288,7 @@
*/
@NonNull
public Insets getSystemWindowInsets() {
- return getInsets(mTypeInsetsMap, compatSystemInsets());
+ return getInsets(mTypeInsetsMap, mCompatInsetTypes);
}
/**
@@ -439,7 +447,8 @@
mStableInsetsConsumed ? null : mTypeMaxInsetsMap,
mTypeVisibilityMap,
mIsRound, mAlwaysConsumeSystemBars,
- null /* displayCutout */);
+ null /* displayCutout */,
+ mCompatInsetTypes);
}
@@ -485,7 +494,8 @@
return new WindowInsets(null, mStableInsetsConsumed ? null : mTypeMaxInsetsMap,
mTypeVisibilityMap,
mIsRound, mAlwaysConsumeSystemBars,
- displayCutoutCopyConstructorArgument(this));
+ displayCutoutCopyConstructorArgument(this),
+ mCompatInsetTypes);
}
// TODO(b/119190588): replace @code with @link below
@@ -555,7 +565,7 @@
*/
@NonNull
public Insets getStableInsets() {
- return getInsets(mTypeMaxInsetsMap, compatSystemInsets());
+ return getInsets(mTypeMaxInsetsMap, mCompatInsetTypes);
}
/**
@@ -733,7 +743,8 @@
public WindowInsets consumeStableInsets() {
return new WindowInsets(mSystemWindowInsetsConsumed ? null : mTypeInsetsMap, null,
mTypeVisibilityMap, mIsRound, mAlwaysConsumeSystemBars,
- displayCutoutCopyConstructorArgument(this));
+ displayCutoutCopyConstructorArgument(this),
+ mCompatInsetTypes);
}
/**
@@ -817,7 +828,8 @@
? null
: mDisplayCutout == null
? DisplayCutout.NO_CUTOUT
- : mDisplayCutout.inset(left, top, right, bottom));
+ : mDisplayCutout.inset(left, top, right, bottom),
+ mCompatInsetTypes);
}
@Override
@@ -1134,7 +1146,8 @@
public WindowInsets build() {
return new WindowInsets(mSystemInsetsConsumed ? null : mTypeInsetsMap,
mStableInsetsConsumed ? null : mTypeMaxInsetsMap, mTypeVisibilityMap,
- mIsRound, mAlwaysConsumeSystemBars, mDisplayCutout);
+ mIsRound, mAlwaysConsumeSystemBars, mDisplayCutout,
+ systemBars());
}
}
@@ -1271,15 +1284,6 @@
}
/**
- * @return Inset types representing the list of bars that traditionally were denoted as
- * system insets.
- * @hide
- */
- static @InsetsType int compatSystemInsets() {
- return STATUS_BARS | NAVIGATION_BARS | IME;
- }
-
- /**
* @return All inset types combined.
*
* TODO: Figure out if this makes sense at all, mixing e.g {@link #systemGestures()} and
@@ -1288,6 +1292,17 @@
public static @InsetsType int all() {
return 0xFFFFFFFF;
}
+
+ /**
+ * Checks whether the specified type is considered to be part of visible insets.
+ * @hide
+ */
+ public static boolean isVisibleInsetsType(int type,
+ @SoftInputModeFlags int softInputModeFlags) {
+ int softInputMode = softInputModeFlags & SOFT_INPUT_MASK_ADJUST;
+ return (type & Type.systemBars()) != 0
+ || (softInputMode != SOFT_INPUT_ADJUST_NOTHING && (type & Type.ime()) != 0);
+ }
}
/**
diff --git a/core/java/android/view/WindowInsetsAnimationCallback.java b/core/java/android/view/WindowInsetsAnimationCallback.java
index 5e71f27..e84c3e3 100644
--- a/core/java/android/view/WindowInsetsAnimationCallback.java
+++ b/core/java/android/view/WindowInsetsAnimationCallback.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Insets;
+import android.view.WindowInsets.Type;
import android.view.WindowInsets.Type.InsetsType;
import android.view.animation.Interpolator;
@@ -30,7 +31,47 @@
public interface WindowInsetsAnimationCallback {
/**
- * Called when an inset animation gets started.
+ * Called when an insets animation is about to start and before the views have been laid out in
+ * the end state of the animation. The ordering of events during an insets animation is the
+ * following:
+ * <p>
+ * <ul>
+ * <li>Application calls {@link WindowInsetsController#hideInputMethod()},
+ * {@link WindowInsetsController#showInputMethod()},
+ * {@link WindowInsetsController#controlInputMethodAnimation(long, WindowInsetsAnimationControlListener)}</li>
+ * <li>onPrepare is called on the view hierarchy listeners</li>
+ * <li>{@link View#onApplyWindowInsets} will be called with the end state of the
+ * animation</li>
+ * <li>View hierarchy gets laid out according to the changes the application has requested
+ * due to the new insets being dispatched</li>
+ * <li>{@link #onStart} is called <em>before</em> the view
+ * hierarchy gets drawn in the new laid out state</li>
+ * <li>{@link #onProgress} is called immediately after with the animation start state</li>
+ * <li>The frame gets drawn.</li>
+ * </ul>
+ * <p>
+ * This ordering allows the application to inspect the end state after the animation has
+ * finished, and then revert to the starting state of the animation in the first
+ * {@link #onProgress} callback by using post-layout view properties like {@link View#setX} and
+ * related methods.
+ * <p>
+ * Note: If the animation is application controlled by using
+ * {@link WindowInsetsController#controlInputMethodAnimation}, the end state of the animation
+ * is undefined as the application may decide on the end state only by passing in the
+ * {@code shown} parameter when calling {@link WindowInsetsAnimationController#finish}. In this
+ * situation, the system will dispatch the insets in the opposite visibility state before the
+ * animation starts. Example: When controlling the input method with
+ * {@link WindowInsetsController#controlInputMethodAnimation} and the input method is currently
+ * showing, {@link View#onApplyWindowInsets} will receive a {@link WindowInsets} instance for
+ * which {@link WindowInsets#isVisible} will return {@code false} for {@link Type#ime}.
+ *
+ * @param animation The animation that is about to start.
+ */
+ default void onPrepare(@NonNull InsetsAnimation animation) {
+ }
+
+ /**
+ * Called when an insets animation gets started.
* <p>
* Note that, like {@link #onProgress}, dispatch of the animation start event is hierarchical:
* It will starts at the root of the view hierarchy and then traverse it and invoke the callback
@@ -45,7 +86,7 @@
* subtree of the hierarchy.
*/
@NonNull
- default AnimationBounds onStarted(
+ default AnimationBounds onStart(
@NonNull InsetsAnimation animation, @NonNull AnimationBounds bounds) {
return bounds;
}
@@ -72,12 +113,12 @@
WindowInsets onProgress(@NonNull WindowInsets insets);
/**
- * Called when an inset animation has finished.
+ * Called when an insets animation has finished.
*
* @param animation The animation that has finished running. This will be the same instance as
- * passed into {@link #onStarted}
+ * passed into {@link #onStart}
*/
- default void onFinished(@NonNull InsetsAnimation animation) {
+ default void onFinish(@NonNull InsetsAnimation animation) {
}
/**
@@ -253,14 +294,14 @@
/**
* Insets both the lower and upper bound by the specified insets. This is to be used in
- * {@link WindowInsetsAnimationCallback#onStarted} to indicate that a part of the insets has
+ * {@link WindowInsetsAnimationCallback#onStart} to indicate that a part of the insets has
* been used to offset or clip its children, and the children shouldn't worry about that
* part anymore.
*
* @param insets The amount to inset.
* @return A copy of this instance inset in the given directions.
* @see WindowInsets#inset
- * @see WindowInsetsAnimationCallback#onStarted
+ * @see WindowInsetsAnimationCallback#onStart
*/
@NonNull
public AnimationBounds inset(@NonNull Insets insets) {
diff --git a/core/java/android/view/WindowInsetsAnimationControlListener.java b/core/java/android/view/WindowInsetsAnimationControlListener.java
index 8a226c1..f91254d 100644
--- a/core/java/android/view/WindowInsetsAnimationControlListener.java
+++ b/core/java/android/view/WindowInsetsAnimationControlListener.java
@@ -16,6 +16,7 @@
package android.view;
+import android.annotation.Hide;
import android.annotation.NonNull;
import android.view.WindowInsets.Type.InsetsType;
import android.view.inputmethod.EditorInfo;
@@ -26,6 +27,12 @@
public interface WindowInsetsAnimationControlListener {
/**
+ * @hide
+ */
+ default void onPrepare(int types) {
+ }
+
+ /**
* Called when the animation is ready to be controlled. This may be delayed when the IME needs
* to redraw because of an {@link EditorInfo} change, or when the window is starting up.
*
diff --git a/core/java/android/view/WindowInsetsController.java b/core/java/android/view/WindowInsetsController.java
index 6de56be..9d7f292 100644
--- a/core/java/android/view/WindowInsetsController.java
+++ b/core/java/android/view/WindowInsetsController.java
@@ -149,7 +149,8 @@
*
* @param types The {@link InsetsType}s the application has requested to control.
* @param durationMillis duration of animation in
- * {@link java.util.concurrent.TimeUnit#MILLISECONDS}
+ * {@link java.util.concurrent.TimeUnit#MILLISECONDS}, or -1 if the
+ * animation doesn't have a predetermined duration.
* @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the
* windows are ready to be controlled, among other callbacks.
* @hide
@@ -162,7 +163,8 @@
* modifying the position of the IME when it's causing insets.
*
* @param durationMillis duration of the animation in
- * {@link java.util.concurrent.TimeUnit#MILLISECONDS}
+ * {@link java.util.concurrent.TimeUnit#MILLISECONDS}, or -1 if the
+ * animation doesn't have a predetermined duration.
* @param listener The {@link WindowInsetsAnimationControlListener} that gets called when the
* IME are ready to be controlled, among other callbacks.
*/
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index 9578002..ccfbd7e 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -57,6 +57,12 @@
private static final String TAG = "WindowManager";
/**
+ * This flag controls whether ViewRootImpl will utilize the Blast Adapter
+ * to send buffer updates to SurfaceFlinger
+ */
+ public static final boolean USE_BLAST_ADAPTER = false;
+
+ /**
* The user is navigating with keys (not the touch screen), so
* navigational focus should be shown.
*/
@@ -481,11 +487,8 @@
ViewRootImpl root = mRoots.get(index);
View view = root.getView();
- if (view != null) {
- InputMethodManager imm = view.getContext().getSystemService(InputMethodManager.class);
- if (imm != null) {
- imm.windowDismissed(mViews.get(index).getWindowToken());
- }
+ if (root != null) {
+ root.getImeFocusController().onWindowDismissed();
}
boolean deferred = root.die(immediate);
if (view != null) {
diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java
index f8bcb00..a22f5a5 100644
--- a/core/java/android/view/WindowManagerPolicyConstants.java
+++ b/core/java/android/view/WindowManagerPolicyConstants.java
@@ -72,12 +72,6 @@
"android.intent.action.USER_ACTIVITY_NOTIFICATION";
/**
- * Broadcast sent when a (custom) bugreport is requested.
- */
- String ACTION_CUSTOM_BUGREPORT_REQUESTED =
- "android.intent.action.CUSTOM_BUGREPORT_REQUESTED";
-
- /**
* Sticky broadcast of the current HDMI plugged state.
*/
String ACTION_HDMI_PLUGGED = "android.intent.action.HDMI_PLUGGED";
diff --git a/core/java/android/view/accessibility/AccessibilityInteractionClient.java b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
index 914ff18..b9f08ad 100644
--- a/core/java/android/view/accessibility/AccessibilityInteractionClient.java
+++ b/core/java/android/view/accessibility/AccessibilityInteractionClient.java
@@ -17,10 +17,14 @@
package android.view.accessibility;
import android.accessibilityservice.IAccessibilityServiceConnection;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
+import android.graphics.Bitmap;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.Message;
import android.os.Process;
import android.os.RemoteException;
@@ -337,6 +341,48 @@
return emptyWindows;
}
+
+ /**
+ * Finds an {@link AccessibilityNodeInfo} by accessibility id and given leash token instead of
+ * window id. This method is used to find the leashed node on the embedded view hierarchy.
+ *
+ * @param connectionId The id of a connection for interacting with the system.
+ * @param leashToken The token of the embedded hierarchy.
+ * @param accessibilityNodeId A unique view id or virtual descendant id from
+ * where to start the search. Use
+ * {@link android.view.accessibility.AccessibilityNodeInfo#ROOT_NODE_ID}
+ * to start from the root.
+ * @param bypassCache Whether to bypass the cache while looking for the node.
+ * @param prefetchFlags flags to guide prefetching.
+ * @param arguments Optional action arguments.
+ * @return An {@link AccessibilityNodeInfo} if found, null otherwise.
+ */
+ public @Nullable AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(
+ int connectionId, @NonNull IBinder leashToken, long accessibilityNodeId,
+ boolean bypassCache, int prefetchFlags, Bundle arguments) {
+ if (leashToken == null) {
+ return null;
+ }
+ int windowId = -1;
+ try {
+ IAccessibilityServiceConnection connection = getConnection(connectionId);
+ if (connection != null) {
+ windowId = connection.getWindowIdForLeashToken(leashToken);
+ } else {
+ if (DEBUG) {
+ Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
+ }
+ }
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error while calling remote getWindowIdForLeashToken", re);
+ }
+ if (windowId == -1) {
+ return null;
+ }
+ return findAccessibilityNodeInfoByAccessibilityId(connectionId, windowId,
+ accessibilityNodeId, bypassCache, prefetchFlags, arguments);
+ }
+
/**
* Finds an {@link AccessibilityNodeInfo} by accessibility id.
*
@@ -783,6 +829,31 @@
}
/**
+ * Takes the screenshot of the specified display and returns it by bitmap format.
+ *
+ * @param connectionId The id of a connection for interacting with the system.
+ * @param displayId The logic display id, use {@link Display#DEFAULT_DISPLAY} for
+ * default display.
+ * @return The screenshot bitmap on success, null otherwise.
+ */
+ public Bitmap takeScreenshot(int connectionId, int displayId) {
+ Bitmap screenShot = null;
+ try {
+ IAccessibilityServiceConnection connection = getConnection(connectionId);
+ if (connection != null) {
+ screenShot = connection.takeScreenshot(displayId);
+ } else {
+ if (DEBUG) {
+ Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
+ }
+ }
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Error while calling remote takeScreenshot", re);
+ }
+ return screenShot;
+ }
+
+ /**
* Clears the result state.
*/
private void clearResultLocked() {
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index ff31bcc..3dfeffb 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -31,6 +31,7 @@
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.annotation.UserIdInt;
+import android.app.RemoteAction;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
@@ -1194,6 +1195,19 @@
@TestApi
@RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
public void performAccessibilityShortcut() {
+ performAccessibilityShortcut(null);
+ }
+
+ /**
+ * Perform the accessibility shortcut for the given target which is assigned to the shortcut.
+ *
+ * @param targetName The flattened {@link ComponentName} string or the class name of a system
+ * class implementing a supported accessibility feature, or {@code null} if there's no
+ * specified target.
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
+ public void performAccessibilityShortcut(@Nullable String targetName) {
final IAccessibilityManager service;
synchronized (mLock) {
service = getServiceLocked();
@@ -1202,19 +1216,89 @@
}
}
try {
- service.performAccessibilityShortcut();
+ service.performAccessibilityShortcut(targetName);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error performing accessibility shortcut. ", re);
}
}
/**
+ * Register the provided {@link RemoteAction} with the given actionId
+ *
+ * @param action The remote action to be registered with the given actionId as system action.
+ * @param actionId The id uniquely identify the system action.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
+ public void registerSystemAction(@NonNull RemoteAction action, int actionId) {
+ final IAccessibilityManager service;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return;
+ }
+ }
+ try {
+ service.registerSystemAction(action, actionId);
+
+ if (DEBUG) {
+ Log.i(LOG_TAG, "System action " + action.getTitle() + " is registered.");
+ }
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error registering system action " + action.getTitle() + " ", re);
+ }
+ }
+
+ /**
+ * Unregister a system action with the given actionId
+ *
+ * @param actionId The id uniquely identify the system action to be unregistered.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
+ public void unregisterSystemAction(int actionId) {
+ final IAccessibilityManager service;
+ synchronized (mLock) {
+ service = getServiceLocked();
+ if (service == null) {
+ return;
+ }
+ }
+ try {
+ service.unregisterSystemAction(actionId);
+
+ if (DEBUG) {
+ Log.i(LOG_TAG, "System action with actionId " + actionId + " is unregistered.");
+ }
+ } catch (RemoteException re) {
+ Log.e(LOG_TAG, "Error unregistering system action with actionId " + actionId + " ", re);
+ }
+ }
+
+ /**
* Notifies that the accessibility button in the system's navigation area has been clicked
*
* @param displayId The logical display id.
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
public void notifyAccessibilityButtonClicked(int displayId) {
+ notifyAccessibilityButtonClicked(displayId, null);
+ }
+
+ /**
+ * Perform the accessibility button for the given target which is assigned to the button.
+ *
+ * @param displayId displayId The logical display id.
+ * @param targetName The flattened {@link ComponentName} string or the class name of a system
+ * class implementing a supported accessibility feature, or {@code null} if there's no
+ * specified target.
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
+ public void notifyAccessibilityButtonClicked(int displayId, @Nullable String targetName) {
final IAccessibilityManager service;
synchronized (mLock) {
service = getServiceLocked();
@@ -1223,7 +1307,7 @@
}
}
try {
- service.notifyAccessibilityButtonClicked(displayId);
+ service.notifyAccessibilityButtonClicked(displayId, targetName);
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error while dispatching accessibility button click", re);
}
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 92aa7d5..184f3302 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -31,6 +31,7 @@
import android.graphics.Region;
import android.os.Build;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.InputType;
@@ -110,6 +111,9 @@
public static final int ROOT_ITEM_ID = Integer.MAX_VALUE - 1;
/** @hide */
+ public static final int LEASHED_ITEM_ID = Integer.MAX_VALUE - 2;
+
+ /** @hide */
public static final long UNDEFINED_NODE_ID = makeNodeId(UNDEFINED_ITEM_ID, UNDEFINED_ITEM_ID);
/** @hide */
@@ -117,6 +121,10 @@
AccessibilityNodeProvider.HOST_VIEW_ID);
/** @hide */
+ public static final long LEASHED_NODE_ID = makeNodeId(LEASHED_ITEM_ID,
+ AccessibilityNodeProvider.HOST_VIEW_ID);
+
+ /** @hide */
public static final int FLAG_PREFETCH_PREDECESSORS = 0x00000001;
/** @hide */
@@ -788,6 +796,10 @@
private TouchDelegateInfo mTouchDelegateInfo;
+ private IBinder mLeashedChild;
+ private IBinder mLeashedParent;
+ private long mLeashedParentNodeId = UNDEFINED_NODE_ID;
+
/**
* Creates a new {@link AccessibilityNodeInfo}.
*/
@@ -1039,7 +1051,12 @@
return null;
}
final long childId = mChildNodeIds.get(index);
- AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ final AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ if (mLeashedChild != null && childId == LEASHED_NODE_ID) {
+ return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mLeashedChild,
+ ROOT_NODE_ID, false, FLAG_PREFETCH_DESCENDANTS, null);
+ }
+
return client.findAccessibilityNodeInfoByAccessibilityId(mConnectionId, mWindowId,
childId, false, FLAG_PREFETCH_DESCENDANTS, null);
}
@@ -1062,6 +1079,43 @@
}
/**
+ * Adds a view root from leashed content as a child. This method is used to embedded another
+ * view hierarchy.
+ * <p>
+ * <strong>Note:</strong> Only one leashed child is permitted.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * Note that a view cannot be made its own child.
+ * </p>
+ *
+ * @param token The token to which a view root is added.
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ * @hide
+ */
+ @TestApi
+ public void addChild(@NonNull IBinder token) {
+ enforceNotSealed();
+ if (token == null) {
+ return;
+ }
+ if (mChildNodeIds == null) {
+ mChildNodeIds = new LongArray();
+ }
+
+ mLeashedChild = token;
+ // Checking uniqueness.
+ // Since only one leashed child is permitted, skip adding ID if the ID already exists.
+ if (mChildNodeIds.indexOf(LEASHED_NODE_ID) >= 0) {
+ return;
+ }
+ mChildNodeIds.add(LEASHED_NODE_ID);
+ }
+
+ /**
* Unchecked version of {@link #addChild(View)} that does not verify
* uniqueness. For framework use only.
*
@@ -1090,6 +1144,38 @@
}
/**
+ * Removes a leashed child. If the child was not previously added to the node,
+ * calling this method has no effect.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param token The token of the leashed child
+ * @return true if the child was present
+ *
+ * @throws IllegalStateException If called from an AccessibilityService.
+ * @hide
+ */
+ public boolean removeChild(IBinder token) {
+ enforceNotSealed();
+ if (mChildNodeIds == null || mLeashedChild == null) {
+ return false;
+ }
+ if (!mLeashedChild.equals(token)) {
+ return false;
+ }
+ final int index = mChildNodeIds.indexOf(LEASHED_NODE_ID);
+ mLeashedChild = null;
+ if (index < 0) {
+ return false;
+ }
+ mChildNodeIds.remove(index);
+ return true;
+ }
+
+ /**
* Adds a virtual child which is a descendant of the given <code>root</code>.
* If <code>virtualDescendantId</code> is {@link View#NO_ID} the root
* is added as a child.
@@ -1668,6 +1754,9 @@
*/
public AccessibilityNodeInfo getParent() {
enforceSealed();
+ if (mLeashedParent != null && mLeashedParentNodeId != UNDEFINED_NODE_ID) {
+ return getNodeForAccessibilityId(mConnectionId, mLeashedParent, mLeashedParentNodeId);
+ }
return getNodeForAccessibilityId(mConnectionId, mWindowId, mParentNodeId);
}
@@ -3257,6 +3346,40 @@
}
/**
+ * Sets the token and node id of the leashed parent.
+ *
+ * @param token The token.
+ * @param viewId The accessibility view id.
+ * @hide
+ */
+ @TestApi
+ public void setLeashedParent(@Nullable IBinder token, int viewId) {
+ enforceNotSealed();
+ mLeashedParent = token;
+ mLeashedParentNodeId = makeNodeId(viewId, AccessibilityNodeProvider.HOST_VIEW_ID);
+ }
+
+ /**
+ * Gets the token of the leashed parent.
+ *
+ * @return The token.
+ * @hide
+ */
+ public @Nullable IBinder getLeashedParent() {
+ return mLeashedParent;
+ }
+
+ /**
+ * Gets the node id of the leashed parent.
+ *
+ * @return The accessibility node id.
+ * @hide
+ */
+ public long getLeashedParentNodeId() {
+ return mLeashedParentNodeId;
+ }
+
+ /**
* Sets if this instance is sealed.
*
* @param sealed Whether is sealed.
@@ -3559,6 +3682,18 @@
if (!Objects.equals(mTouchDelegateInfo, DEFAULT.mTouchDelegateInfo)) {
nonDefaultFields |= bitAt(fieldIndex);
}
+ fieldIndex++;
+ if (mLeashedChild != DEFAULT.mLeashedChild) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (mLeashedParent != DEFAULT.mLeashedParent) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
+ fieldIndex++;
+ if (mLeashedParentNodeId != DEFAULT.mLeashedParentNodeId) {
+ nonDefaultFields |= bitAt(fieldIndex);
+ }
int totalFields = fieldIndex;
parcel.writeLong(nonDefaultFields);
@@ -3685,6 +3820,16 @@
mTouchDelegateInfo.writeToParcel(parcel, flags);
}
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ parcel.writeStrongBinder(mLeashedChild);
+ }
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ parcel.writeStrongBinder(mLeashedParent);
+ }
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ parcel.writeLong(mLeashedParentNodeId);
+ }
+
if (DEBUG) {
fieldIndex--;
if (totalFields != fieldIndex) {
@@ -3768,6 +3913,10 @@
final TouchDelegateInfo otherInfo = other.mTouchDelegateInfo;
mTouchDelegateInfo = (otherInfo != null)
? new TouchDelegateInfo(otherInfo.mTargetMap, true) : null;
+
+ mLeashedChild = other.mLeashedChild;
+ mLeashedParent = other.mLeashedParent;
+ mLeashedParentNodeId = other.mLeashedParentNodeId;
}
private void initPoolingInfos(AccessibilityNodeInfo other) {
@@ -3921,6 +4070,16 @@
mTouchDelegateInfo = TouchDelegateInfo.CREATOR.createFromParcel(parcel);
}
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ mLeashedChild = parcel.readStrongBinder();
+ }
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ mLeashedParent = parcel.readStrongBinder();
+ }
+ if (isBitSet(nonDefaultFields, fieldIndex++)) {
+ mLeashedParentNodeId = parcel.readLong();
+ }
+
mSealed = sealed;
}
@@ -4200,6 +4359,19 @@
| FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS, null);
}
+ private static AccessibilityNodeInfo getNodeForAccessibilityId(int connectionId,
+ IBinder leashToken, long accessibilityId) {
+ if (!((leashToken != null)
+ && (getAccessibilityViewId(accessibilityId) != UNDEFINED_ITEM_ID)
+ && (connectionId != UNDEFINED_CONNECTION_ID))) {
+ return null;
+ }
+ AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
+ return client.findAccessibilityNodeInfoByAccessibilityId(connectionId,
+ leashToken, accessibilityId, false, FLAG_PREFETCH_PREDECESSORS
+ | FLAG_PREFETCH_DESCENDANTS | FLAG_PREFETCH_SIBLINGS, null);
+ }
+
/** @hide */
public static String idToString(long accessibilityId) {
int accessibilityViewId = getAccessibilityViewId(accessibilityId);
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 36515b3..fcaaa2e 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -16,6 +16,7 @@
package android.view.accessibility;
+import android.app.RemoteAction;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceConnection;
import android.accessibilityservice.IAccessibilityServiceClient;
@@ -65,12 +66,12 @@
// Used by UiAutomation
IBinder getWindowToken(int windowId, int userId);
- void notifyAccessibilityButtonClicked(int displayId);
+ void notifyAccessibilityButtonClicked(int displayId, String targetName);
void notifyAccessibilityButtonVisibilityChanged(boolean available);
// Requires Manifest.permission.MANAGE_ACCESSIBILITY
- void performAccessibilityShortcut();
+ void performAccessibilityShortcut(String targetName);
// Requires Manifest.permission.MANAGE_ACCESSIBILITY
List<String> getAccessibilityShortcutTargets(int shortcutType);
@@ -82,4 +83,7 @@
int getAccessibilityWindowId(IBinder windowToken);
long getRecommendedTimeoutMillis();
+
+ oneway void registerSystemAction(in RemoteAction action, int actionId);
+ oneway void unregisterSystemAction(int actionId);
}
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 9c04b39..c159f89 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -230,6 +230,7 @@
/** @hide */ public static final int ACTION_VIEW_ENTERED = 2;
/** @hide */ public static final int ACTION_VIEW_EXITED = 3;
/** @hide */ public static final int ACTION_VALUE_CHANGED = 4;
+ /** @hide */ public static final int ACTION_RESPONSE_EXPIRED = 5;
/** @hide */ public static final int NO_LOGGING = 0;
/** @hide */ public static final int FLAG_ADD_CLIENT_ENABLED = 0x1;
@@ -776,11 +777,19 @@
*
* @see AutofillClient#autofillClientIsVisibleForAutofill()
*
+ * @param isExpiredResponse The response has expired or not
+ *
* {@hide}
*/
- public void onInvisibleForAutofill() {
+ public void onInvisibleForAutofill(boolean isExpiredResponse) {
synchronized (mLock) {
mOnInvisibleCalled = true;
+
+ if (isExpiredResponse) {
+ // Notify service the response has expired.
+ updateSessionLocked(/* id= */ null, /* bounds= */ null, /* value= */ null,
+ ACTION_RESPONSE_EXPIRED, /* flags= */ 0);
+ }
}
}
diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java
index ae2fb8e..d5d631a 100644
--- a/core/java/android/view/inputmethod/BaseInputConnection.java
+++ b/core/java/android/view/inputmethod/BaseInputConnection.java
@@ -758,8 +758,9 @@
Context context;
if (mTargetView != null) {
context = mTargetView.getContext();
- } else if (mIMM.mServedView != null) {
- context = mIMM.mServedView.getContext();
+ } else if (mIMM.mCurRootView != null) {
+ final View servedView = mIMM.mCurRootView.getImeFocusController().getServedView();
+ context = servedView != null ? servedView.getContext() : null;
} else {
context = null;
}
diff --git a/core/java/android/view/inputmethod/InlineSuggestion.java b/core/java/android/view/inputmethod/InlineSuggestion.java
index c10144e..a32ea4b 100644
--- a/core/java/android/view/inputmethod/InlineSuggestion.java
+++ b/core/java/android/view/inputmethod/InlineSuggestion.java
@@ -19,6 +19,7 @@
import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Parcelable;
@@ -61,6 +62,20 @@
private final @Nullable IInlineContentProvider mContentProvider;
/**
+ * Creates a new {@link InlineSuggestion}, for testing purpose.
+ *
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ public static InlineSuggestion newInlineSuggestion(@NonNull InlineSuggestionInfo info) {
+ return new InlineSuggestion(info, null);
+ }
+
+
+
+
+ /**
* Inflates a view with the content of this suggestion at a specific size.
* The size must be between the {@link InlinePresentationSpec#getMinSize() min size}
* and the {@link InlinePresentationSpec#getMaxSize() max size} of the presentation
@@ -271,10 +286,10 @@
};
@DataClass.Generated(
- time = 1575933636929L,
+ time = 1578972138081L,
codegenVersion = "1.0.14",
sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestion.java",
- inputSignatures = "private static final java.lang.String TAG\nprivate final @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo mInfo\nprivate final @android.annotation.Nullable com.android.internal.view.inline.IInlineContentProvider mContentProvider\npublic void inflate(android.content.Context,android.util.Size,java.util.concurrent.Executor,java.util.function.Consumer<android.view.View>)\nclass InlineSuggestion extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)")
+ inputSignatures = "private static final java.lang.String TAG\nprivate final @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo mInfo\nprivate final @android.annotation.Nullable com.android.internal.view.inline.IInlineContentProvider mContentProvider\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestion newInlineSuggestion(android.view.inputmethod.InlineSuggestionInfo)\npublic void inflate(android.content.Context,android.util.Size,java.util.concurrent.Executor,java.util.function.Consumer<android.view.View>)\nclass InlineSuggestion extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/view/inputmethod/InlineSuggestionInfo.java b/core/java/android/view/inputmethod/InlineSuggestionInfo.java
index 07fce31..195b63a 100644
--- a/core/java/android/view/inputmethod/InlineSuggestionInfo.java
+++ b/core/java/android/view/inputmethod/InlineSuggestionInfo.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.os.Parcelable;
import android.view.inline.InlinePresentationSpec;
@@ -53,6 +54,19 @@
/** Hints for the type of data being suggested. */
private final @Nullable String[] mAutofillHints;
+ /**
+ * Creates a new {@link InlineSuggestionInfo}, for testing purpose.
+ *
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ public static InlineSuggestionInfo newInlineSuggestionInfo(
+ @NonNull InlinePresentationSpec presentationSpec,
+ @NonNull @Source String source,
+ @Nullable String[] autofillHints) {
+ return new InlineSuggestionInfo(presentationSpec, source, autofillHints);
+ }
@@ -247,10 +261,10 @@
};
@DataClass.Generated(
- time = 1574406074120L,
+ time = 1578972121865L,
codegenVersion = "1.0.14",
sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionInfo.java",
- inputSignatures = "public static final @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String SOURCE_AUTOFILL\npublic static final @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String SOURCE_PLATFORM\nprivate final @android.annotation.NonNull android.view.inline.InlinePresentationSpec mPresentationSpec\nprivate final @android.annotation.NonNull @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String mSource\nprivate final @android.annotation.Nullable java.lang.String[] mAutofillHints\nclass InlineSuggestionInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)")
+ inputSignatures = "public static final @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String SOURCE_AUTOFILL\npublic static final @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String SOURCE_PLATFORM\nprivate final @android.annotation.NonNull android.view.inline.InlinePresentationSpec mPresentationSpec\nprivate final @android.annotation.NonNull @android.view.inputmethod.InlineSuggestionInfo.Source java.lang.String mSource\nprivate final @android.annotation.Nullable java.lang.String[] mAutofillHints\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestionInfo newInlineSuggestionInfo(android.view.inline.InlinePresentationSpec,java.lang.String,java.lang.String[])\nclass InlineSuggestionInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstDefs=true, genHiddenConstructor=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
index 386c9cb..860ce90 100644
--- a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
+++ b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
@@ -17,6 +17,8 @@
package android.view.inputmethod;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityThread;
import android.os.Parcelable;
import android.view.inline.InlinePresentationSpec;
@@ -49,6 +51,21 @@
*/
private final @NonNull List<InlinePresentationSpec> mPresentationSpecs;
+ /**
+ * The package name of the app that requests for the inline suggestions and will host the
+ * embedded suggestion views. The app does not have to set the value for the field because
+ * it'll be set by the system for safety reasons.
+ */
+ private @NonNull String mHostPackageName;
+
+ /**
+ * @hide
+ * @see {@link #mHostPackageName}.
+ */
+ public void setHostPackageName(@NonNull String hostPackageName) {
+ mHostPackageName = hostPackageName;
+ }
+
private void onConstructed() {
Preconditions.checkState(mMaxSuggestionCount >= mPresentationSpecs.size());
}
@@ -57,9 +74,15 @@
return SUGGESTION_COUNT_UNLIMITED;
}
+ private static String defaultHostPackageName() {
+ return ActivityThread.currentPackageName();
+ }
+
/** @hide */
abstract static class BaseBuilder {
abstract Builder setPresentationSpecs(@NonNull List<InlinePresentationSpec> value);
+
+ abstract Builder setHostPackageName(@Nullable String value);
}
@@ -80,11 +103,15 @@
@DataClass.Generated.Member
/* package-private */ InlineSuggestionsRequest(
int maxSuggestionCount,
- @NonNull List<InlinePresentationSpec> presentationSpecs) {
+ @NonNull List<InlinePresentationSpec> presentationSpecs,
+ @NonNull String hostPackageName) {
this.mMaxSuggestionCount = maxSuggestionCount;
this.mPresentationSpecs = presentationSpecs;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mPresentationSpecs);
+ this.mHostPackageName = hostPackageName;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mHostPackageName);
onConstructed();
}
@@ -108,6 +135,16 @@
return mPresentationSpecs;
}
+ /**
+ * The package name of the app that requests for the inline suggestions and will host the
+ * embedded suggestion views. The app does not have to set the value for the field because
+ * it'll be set by the system for safety reasons.
+ */
+ @DataClass.Generated.Member
+ public @NonNull String getHostPackageName() {
+ return mHostPackageName;
+ }
+
@Override
@DataClass.Generated.Member
public String toString() {
@@ -116,13 +153,14 @@
return "InlineSuggestionsRequest { " +
"maxSuggestionCount = " + mMaxSuggestionCount + ", " +
- "presentationSpecs = " + mPresentationSpecs +
+ "presentationSpecs = " + mPresentationSpecs + ", " +
+ "hostPackageName = " + mHostPackageName +
" }";
}
@Override
@DataClass.Generated.Member
- public boolean equals(@android.annotation.Nullable Object o) {
+ public boolean equals(@Nullable Object o) {
// You can override field equality logic by defining either of the methods like:
// boolean fieldNameEquals(InlineSuggestionsRequest other) { ... }
// boolean fieldNameEquals(FieldType otherValue) { ... }
@@ -134,7 +172,8 @@
//noinspection PointlessBooleanExpression
return true
&& mMaxSuggestionCount == that.mMaxSuggestionCount
- && java.util.Objects.equals(mPresentationSpecs, that.mPresentationSpecs);
+ && java.util.Objects.equals(mPresentationSpecs, that.mPresentationSpecs)
+ && java.util.Objects.equals(mHostPackageName, that.mHostPackageName);
}
@Override
@@ -146,6 +185,7 @@
int _hash = 1;
_hash = 31 * _hash + mMaxSuggestionCount;
_hash = 31 * _hash + java.util.Objects.hashCode(mPresentationSpecs);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mHostPackageName);
return _hash;
}
@@ -157,6 +197,7 @@
dest.writeInt(mMaxSuggestionCount);
dest.writeParcelableList(mPresentationSpecs, flags);
+ dest.writeString(mHostPackageName);
}
@Override
@@ -173,11 +214,15 @@
int maxSuggestionCount = in.readInt();
List<InlinePresentationSpec> presentationSpecs = new ArrayList<>();
in.readParcelableList(presentationSpecs, InlinePresentationSpec.class.getClassLoader());
+ String hostPackageName = in.readString();
this.mMaxSuggestionCount = maxSuggestionCount;
this.mPresentationSpecs = presentationSpecs;
com.android.internal.util.AnnotationValidations.validate(
NonNull.class, null, mPresentationSpecs);
+ this.mHostPackageName = hostPackageName;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mHostPackageName);
onConstructed();
}
@@ -205,6 +250,7 @@
private int mMaxSuggestionCount;
private @NonNull List<InlinePresentationSpec> mPresentationSpecs;
+ private @NonNull String mHostPackageName;
private long mBuilderFieldsSet = 0L;
@@ -260,22 +306,40 @@
return this;
}
+ /**
+ * The package name of the app that requests for the inline suggestions and will host the
+ * embedded suggestion views. The app does not have to set the value for the field because
+ * it'll be set by the system for safety reasons.
+ */
+ @DataClass.Generated.Member
+ @Override
+ @NonNull Builder setHostPackageName(@NonNull String value) {
+ checkNotUsed();
+ mBuilderFieldsSet |= 0x4;
+ mHostPackageName = value;
+ return this;
+ }
+
/** Builds the instance. This builder should not be touched after calling this! */
public @NonNull InlineSuggestionsRequest build() {
checkNotUsed();
- mBuilderFieldsSet |= 0x4; // Mark builder used
+ mBuilderFieldsSet |= 0x8; // Mark builder used
if ((mBuilderFieldsSet & 0x1) == 0) {
mMaxSuggestionCount = defaultMaxSuggestionCount();
}
+ if ((mBuilderFieldsSet & 0x4) == 0) {
+ mHostPackageName = defaultHostPackageName();
+ }
InlineSuggestionsRequest o = new InlineSuggestionsRequest(
mMaxSuggestionCount,
- mPresentationSpecs);
+ mPresentationSpecs,
+ mHostPackageName);
return o;
}
private void checkNotUsed() {
- if ((mBuilderFieldsSet & 0x4) != 0) {
+ if ((mBuilderFieldsSet & 0x8) != 0) {
throw new IllegalStateException(
"This Builder should not be reused. Use a new Builder instance instead");
}
@@ -283,10 +347,10 @@
}
@DataClass.Generated(
- time = 1576637222199L,
+ time = 1578948035951L,
codegenVersion = "1.0.14",
sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java",
- inputSignatures = "public static final int SUGGESTION_COUNT_UNLIMITED\nprivate final int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.view.inline.InlinePresentationSpec> mPresentationSpecs\nprivate void onConstructed()\nprivate static int defaultMaxSuggestionCount()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setPresentationSpecs(java.util.List<android.view.inline.InlinePresentationSpec>)\nclass BaseBuilder extends java.lang.Object implements []")
+ inputSignatures = "public static final int SUGGESTION_COUNT_UNLIMITED\nprivate final int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.view.inline.InlinePresentationSpec> mPresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\npublic void setHostPackageName(java.lang.String)\nprivate void onConstructed()\nprivate static int defaultMaxSuggestionCount()\nprivate static java.lang.String defaultHostPackageName()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setPresentationSpecs(java.util.List<android.view.inline.InlinePresentationSpec>)\nabstract android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nclass BaseBuilder extends java.lang.Object implements []")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/view/inputmethod/InlineSuggestionsResponse.java b/core/java/android/view/inputmethod/InlineSuggestionsResponse.java
index 924a5ee..be833df 100644
--- a/core/java/android/view/inputmethod/InlineSuggestionsResponse.java
+++ b/core/java/android/view/inputmethod/InlineSuggestionsResponse.java
@@ -18,6 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.TestApi;
import android.os.Parcelable;
import com.android.internal.util.DataClass;
@@ -33,6 +34,18 @@
public final class InlineSuggestionsResponse implements Parcelable {
private final @NonNull List<InlineSuggestion> mInlineSuggestions;
+ /**
+ * Creates a new {@link InlineSuggestionsResponse}, for testing purpose.
+ *
+ * @hide
+ */
+ @TestApi
+ @NonNull
+ public static InlineSuggestionsResponse newInlineSuggestionsResponse(
+ @NonNull List<InlineSuggestion> inlineSuggestions) {
+ return new InlineSuggestionsResponse(inlineSuggestions);
+ }
+
// Code below generated by codegen v1.0.14.
@@ -151,10 +164,10 @@
};
@DataClass.Generated(
- time = 1574406147911L,
+ time = 1578972149519L,
codegenVersion = "1.0.14",
sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsResponse.java",
- inputSignatures = "private final @android.annotation.NonNull java.util.List<android.view.inputmethod.InlineSuggestion> mInlineSuggestions\nclass InlineSuggestionsResponse extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstructor=true)")
+ inputSignatures = "private final @android.annotation.NonNull java.util.List<android.view.inputmethod.InlineSuggestion> mInlineSuggestions\npublic static @android.annotation.TestApi @android.annotation.NonNull android.view.inputmethod.InlineSuggestionsResponse newInlineSuggestionsResponse(java.util.List<android.view.inputmethod.InlineSuggestion>)\nclass InlineSuggestionsResponse extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genHiddenConstructor=true)")
@Deprecated
private void __metadata() {}
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 67ce8d2..904e736 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -64,6 +64,7 @@
import android.view.InputEvent;
import android.view.InputEventSender;
import android.view.KeyEvent;
+import android.view.ImeFocusController;
import android.view.View;
import android.view.ViewRootImpl;
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
@@ -93,9 +94,12 @@
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CancellationException;
-import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
/**
@@ -361,10 +365,10 @@
boolean mActive = false;
/**
- * {@code true} if next {@link #onPostWindowFocus(View, View, int, int)} needs to
+ * {@code true} if next {@link ImeFocusController#onPostWindowFocus} needs to
* restart input.
*/
- boolean mRestartOnNextWindowFocus = true;
+ private boolean mRestartOnNextWindowFocus = true;
/**
* As reported by IME through InputConnection.
@@ -377,22 +381,8 @@
* This is the root view of the overall window that currently has input
* method focus.
*/
- @UnsupportedAppUsage
- View mCurRootView;
- /**
- * This is the view that should currently be served by an input method,
- * regardless of the state of setting that up.
- */
- // See comment to mH field in regard to @UnsupportedAppUsage
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
- View mServedView;
- /**
- * This is then next view that will be served by the input method, when
- * we get around to updating things.
- */
- // See comment to mH field in regard to @UnsupportedAppUsage
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
- View mNextServedView;
+ @GuardedBy("mH")
+ ViewRootImpl mCurRootView;
/**
* This is set when we are in the process of connecting, to determine
* when we have actually finished.
@@ -429,7 +419,10 @@
* in a background thread. Later, if there is an actual startInput it will wait on
* main thread till the background thread completes.
*/
- private CompletableFuture<Void> mWindowFocusGainFuture;
+ private Future<?> mWindowFocusGainFuture;
+
+ private ExecutorService mStartInputWorker = Executors.newSingleThreadExecutor(
+ new ImeThreadFactory("StartInputWorker"));
/**
* The instance that has previously been sent to the input method.
@@ -483,6 +476,8 @@
final Pool<PendingEvent> mPendingEventPool = new SimplePool<>(20);
final SparseArray<PendingEvent> mPendingEvents = new SparseArray<>(20);
+ final DelegateImpl mDelegate = new DelegateImpl();
+
// -----------------------------------------------------------
static final int MSG_DUMP = 1;
@@ -558,6 +553,178 @@
return servedView.hasWindowFocus() || isAutofillUIShowing(servedView);
}
+ private final class DelegateImpl implements
+ ImeFocusController.InputMethodManagerDelegate {
+ /**
+ * Used by {@link ImeFocusController} to start input connection.
+ */
+ @Override
+ public boolean startInput(@StartInputReason int startInputReason, View focusedView,
+ @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode,
+ int windowFlags) {
+ synchronized (mH) {
+ mCurrentTextBoxAttribute = null;
+ mCompletions = null;
+ mServedConnecting = true;
+ if (getServedViewLocked() != null && !getServedViewLocked().onCheckIsTextEditor()) {
+ // servedView has changed and it's not editable.
+ maybeCallServedViewChangedLocked(null);
+ }
+ }
+ return startInputInner(startInputReason,
+ focusedView != null ? focusedView.getWindowToken() : null, startInputFlags,
+ softInputMode, windowFlags);
+ }
+
+ /**
+ * Used by {@link ImeFocusController} to finish input connection.
+ */
+ @Override
+ public void finishInput() {
+ synchronized (mH) {
+ finishInputLocked();
+ }
+ }
+
+ /**
+ * Used by {@link ImeFocusController} to hide current input method editor.
+ */
+ @Override
+ public void closeCurrentIme() {
+ closeCurrentInput();
+ }
+
+ /**
+ * For {@link ImeFocusController} to start input asynchronously when focus gain.
+ */
+ @Override
+ public void startInputAsyncOnWindowFocusGain(View focusedView,
+ @SoftInputModeFlags int softInputMode, int windowFlags, boolean forceNewFocus) {
+ final boolean forceNewFocus1 = forceNewFocus;
+ final int startInputFlags = getStartInputFlags(focusedView, 0);
+
+ if (mWindowFocusGainFuture != null) {
+ mWindowFocusGainFuture.cancel(false /* mayInterruptIfRunning */);
+ }
+ mWindowFocusGainFuture = mStartInputWorker.submit(() -> {
+ synchronized (mH) {
+ if (mCurRootView == null) {
+ return;
+ }
+ if (mCurRootView.getImeFocusController().checkFocus(forceNewFocus1, false)) {
+ // We need to restart input on the current focus view. This
+ // should be done in conjunction with telling the system service
+ // about the window gaining focus, to help make the transition
+ // smooth.
+ if (startInput(StartInputReason.WINDOW_FOCUS_GAIN,
+ focusedView, startInputFlags, softInputMode, windowFlags)) {
+ return;
+ }
+ }
+
+ // For some reason we didn't do a startInput + windowFocusGain, so
+ // we'll just do a window focus gain and call it a day.
+ try {
+ if (DEBUG) Log.v(TAG, "Reporting focus gain, without startInput");
+ mService.startInputOrWindowGainedFocus(
+ StartInputReason.WINDOW_FOCUS_GAIN_REPORT_ONLY, mClient,
+ focusedView.getWindowToken(), startInputFlags, softInputMode,
+ windowFlags,
+ null, null, 0 /* missingMethodFlags */,
+ mCurRootView.mContext.getApplicationInfo().targetSdkVersion);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+ });
+ }
+
+ /**
+ * Used by {@link ImeFocusController} to finish current composing text.
+ */
+ @Override
+ public void finishComposingText() {
+ if (mServedInputConnectionWrapper != null) {
+ mServedInputConnectionWrapper.finishComposingText();
+ }
+ }
+
+ /**
+ * Used for {@link ImeFocusController} to set the current focused root view.
+ */
+ @Override
+ public void setCurrentRootView(ViewRootImpl rootView) {
+ // If the mCurRootView is losing window focus, release the strong reference to it
+ // so as not to prevent it from being garbage-collected.
+ if (mWindowFocusGainFuture != null) {
+ mWindowFocusGainFuture.cancel(false /* mayInterruptIfRunning */);
+ mWindowFocusGainFuture = null;
+ }
+ synchronized (mH) {
+ mCurRootView = rootView;
+ }
+ }
+
+ /**
+ * Used for {@link ImeFocusController} to return if the root view from the
+ * controller is this {@link InputMethodManager} currently focused.
+ * TODO: Address event-order problem when get current root view in multi-threads.
+ */
+ @Override
+ public boolean isCurrentRootView(ViewRootImpl rootView) {
+ synchronized (mH) {
+ return mCurRootView == rootView;
+ }
+ }
+
+ /**
+ * For {@link ImeFocusController#checkFocus} if needed to force check new focus.
+ */
+ @Override
+ public boolean isRestartOnNextWindowFocus(boolean reset) {
+ final boolean result = mRestartOnNextWindowFocus;
+ if (reset) {
+ mRestartOnNextWindowFocus = false;
+ }
+ return result;
+ }
+ }
+
+ /** @hide */
+ public DelegateImpl getDelegate() {
+ return mDelegate;
+ }
+
+ private View getServedViewLocked() {
+ return mCurRootView != null ? mCurRootView.getImeFocusController().getServedView() : null;
+ }
+
+ private View getNextServedViewLocked() {
+ return mCurRootView != null ? mCurRootView.getImeFocusController().getNextServedView()
+ : null;
+ }
+
+ private void setServedViewLocked(View view) {
+ if (mCurRootView != null) {
+ mCurRootView.getImeFocusController().setServedView(view);
+ }
+ }
+
+ private void setNextServedViewLocked(View view) {
+ if (mCurRootView != null) {
+ mCurRootView.getImeFocusController().setNextServedView(view);
+ }
+ }
+
+ /**
+ * Returns {@code true} when the given view has been served by Input Method.
+ */
+ private boolean hasServedByInputMethodLocked(View view) {
+ final View servedView = getServedViewLocked();
+ return (servedView == view
+ || (servedView != null && servedView.checkInputConnectionProxy(view)));
+ }
+
class H extends Handler {
H(Looper looper) {
super(looper, null, true);
@@ -623,7 +790,8 @@
clearBindingLocked();
// If we were actively using the last input method, then
// we would like to re-connect to the next input method.
- if (mServedView != null && mServedView.isFocused()) {
+ final View servedView = getServedViewLocked();
+ if (servedView != null && servedView.isFocused()) {
mServedConnecting = true;
}
startInput = mActive;
@@ -658,11 +826,16 @@
}
// Check focus again in case that "onWindowFocus" is called before
// handling this message.
- if (mServedView != null && canStartInput(mServedView)) {
- if (checkFocusNoStartInput(mRestartOnNextWindowFocus)) {
+ final View servedView;
+ synchronized (mH) {
+ servedView = getServedViewLocked();
+ }
+ if (servedView != null && canStartInput(servedView)) {
+ if (mCurRootView != null && mCurRootView.getImeFocusController()
+ .checkFocus(mRestartOnNextWindowFocus, false)) {
final int reason = active ? StartInputReason.ACTIVATED_BY_IMMS
: StartInputReason.DEACTIVATED_BY_IMMS;
- startInputInner(reason, null, 0, 0, 0);
+ mDelegate.startInput(reason, null, 0, 0, 0);
}
}
return;
@@ -790,6 +963,19 @@
}
}
+ private static class ImeThreadFactory implements ThreadFactory {
+ private final String mThreadName;
+
+ ImeThreadFactory(String name) {
+ mThreadName = name;
+ }
+
+ @Override
+ public Thread newThread(Runnable r) {
+ return new Thread(r, mThreadName);
+ }
+ }
+
final IInputMethodClient.Stub mClient = new IInputMethodClient.Stub() {
@Override
protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
@@ -1193,10 +1379,7 @@
checkFocus();
synchronized (mH) {
- return (mServedView == view
- || (mServedView != null
- && mServedView.checkInputConnectionProxy(view)))
- && mCurrentTextBoxAttribute != null;
+ return hasServedByInputMethodLocked(view) && mCurrentTextBoxAttribute != null;
}
}
@@ -1206,7 +1389,7 @@
public boolean isActive() {
checkFocus();
synchronized (mH) {
- return mServedView != null && mCurrentTextBoxAttribute != null;
+ return getServedViewLocked() != null && mCurrentTextBoxAttribute != null;
}
}
@@ -1267,11 +1450,14 @@
*/
@UnsupportedAppUsage
void finishInputLocked() {
- mNextServedView = null;
mActivityViewToScreenMatrix = null;
- if (mServedView != null) {
- if (DEBUG) Log.v(TAG, "FINISH INPUT: mServedView=" + dumpViewInfo(mServedView));
- mServedView = null;
+ setNextServedViewLocked(null);
+ if (getServedViewLocked() != null) {
+ if (DEBUG) {
+ Log.v(TAG, "FINISH INPUT: mServedView="
+ + dumpViewInfo(getServedViewLocked()));
+ }
+ setServedViewLocked(null);
mCompletions = null;
mServedConnecting = false;
clearConnectionLocked();
@@ -1288,8 +1474,7 @@
checkFocus();
synchronized (mH) {
- if (mServedView != view && (mServedView == null
- || !mServedView.checkInputConnectionProxy(view))) {
+ if (!hasServedByInputMethodLocked(view)) {
return;
}
@@ -1313,8 +1498,7 @@
checkFocus();
synchronized (mH) {
- if (mServedView != view && (mServedView == null
- || !mServedView.checkInputConnectionProxy(view))) {
+ if (!hasServedByInputMethodLocked(view)) {
return;
}
@@ -1428,8 +1612,7 @@
checkFocus();
synchronized (mH) {
- if (mServedView != view && (mServedView == null
- || !mServedView.checkInputConnectionProxy(view))) {
+ if (!hasServedByInputMethodLocked(view)) {
return false;
}
@@ -1520,7 +1703,8 @@
ResultReceiver resultReceiver) {
checkFocus();
synchronized (mH) {
- if (mServedView == null || mServedView.getWindowToken() != windowToken) {
+ final View servedView = getServedViewLocked();
+ if (servedView == null || servedView.getWindowToken() != windowToken) {
return false;
}
@@ -1547,7 +1731,8 @@
**/
public void toggleSoftInputFromWindow(IBinder windowToken, int showFlags, int hideFlags) {
synchronized (mH) {
- if (mServedView == null || mServedView.getWindowToken() != windowToken) {
+ final View servedView = getServedViewLocked();
+ if (servedView == null || servedView.getWindowToken() != windowToken) {
return;
}
if (mCurMethod != null) {
@@ -1598,8 +1783,7 @@
checkFocus();
synchronized (mH) {
- if (mServedView != view && (mServedView == null
- || !mServedView.checkInputConnectionProxy(view))) {
+ if (!hasServedByInputMethodLocked(view)) {
return;
}
@@ -1626,7 +1810,7 @@
final View view;
synchronized (mH) {
- view = mServedView;
+ view = getServedViewLocked();
// Make sure we have a window token for the served view.
if (DEBUG) {
@@ -1645,10 +1829,7 @@
Log.e(TAG, "ABORT input: ServedView must be attached to a Window");
return false;
}
- startInputFlags |= StartInputFlags.VIEW_HAS_FOCUS;
- if (view.onCheckIsTextEditor()) {
- startInputFlags |= StartInputFlags.IS_TEXT_EDITOR;
- }
+ startInputFlags = getStartInputFlags(view, startInputFlags);
softInputMode = view.getViewRootImpl().mWindowAttributes.softInputMode;
windowFlags = view.getViewRootImpl().mWindowAttributes.flags;
}
@@ -1671,7 +1852,7 @@
// The view is running on a different thread than our own, so
// we need to reschedule our work for over there.
if (DEBUG) Log.v(TAG, "Starting input: reschedule to view thread");
- vh.post(() -> startInputInner(startInputReason, null, 0, 0, 0));
+ vh.post(() -> mDelegate.startInput(startInputReason, null, 0, 0, 0));
return false;
}
@@ -1690,11 +1871,12 @@
synchronized (mH) {
// Now that we are locked again, validate that our state hasn't
// changed.
- if (mServedView != view || !mServedConnecting) {
+ final View servedView = getServedViewLocked();
+ if (servedView != view || !mServedConnecting) {
// Something else happened, so abort.
if (DEBUG) Log.v(TAG,
"Starting input: finished by someone else. view=" + dumpViewInfo(view)
- + " mServedView=" + dumpViewInfo(mServedView)
+ + " servedView=" + dumpViewInfo(servedView)
+ " mServedConnecting=" + mServedConnecting);
return false;
}
@@ -1785,101 +1967,12 @@
return true;
}
- /**
- * When the focused window is dismissed, this method is called to finish the
- * input method started before.
- * @hide
- */
- @UnsupportedAppUsage
- public void windowDismissed(IBinder appWindowToken) {
- checkFocus();
- synchronized (mH) {
- if (mServedView != null &&
- mServedView.getWindowToken() == appWindowToken) {
- finishInputLocked();
- }
- if (mCurRootView != null &&
- mCurRootView.getWindowToken() == appWindowToken) {
- mCurRootView = null;
- }
+ private int getStartInputFlags(View focusedView, int startInputFlags) {
+ startInputFlags |= StartInputFlags.VIEW_HAS_FOCUS;
+ if (focusedView.onCheckIsTextEditor()) {
+ startInputFlags |= StartInputFlags.IS_TEXT_EDITOR;
}
- }
-
- /**
- * Call this when a view receives focus.
- * @hide
- */
- @UnsupportedAppUsage
- public void focusIn(View view) {
- synchronized (mH) {
- focusInLocked(view);
- }
- }
-
- void focusInLocked(View view) {
- if (DEBUG) Log.v(TAG, "focusIn: " + dumpViewInfo(view));
-
- if (view != null && view.isTemporarilyDetached()) {
- // This is a request from a view that is temporarily detached from a window.
- if (DEBUG) Log.v(TAG, "Temporarily detached view, ignoring");
- return;
- }
-
- if (mCurRootView != view.getRootView()) {
- // This is a request from a window that isn't in the window with
- // IME focus, so ignore it.
- if (DEBUG) Log.v(TAG, "Not IME target window, ignoring");
- return;
- }
-
- mNextServedView = view;
- scheduleCheckFocusLocked(view);
- }
-
- /**
- * Call this when a view loses focus.
- * @hide
- */
- @UnsupportedAppUsage
- public void focusOut(View view) {
- synchronized (mH) {
- if (DEBUG) Log.v(TAG, "focusOut: view=" + dumpViewInfo(view)
- + " mServedView=" + dumpViewInfo(mServedView));
- if (mServedView != view) {
- // The following code would auto-hide the IME if we end up
- // with no more views with focus. This can happen, however,
- // whenever we go into touch mode, so it ends up hiding
- // at times when we don't really want it to. For now it
- // seems better to just turn it all off.
- // TODO: Check view.isTemporarilyDetached() when re-enable the following code.
- if (false && canStartInput(view)) {
- mNextServedView = null;
- scheduleCheckFocusLocked(view);
- }
- }
- }
- }
-
- /**
- * Call this when a view is being detached from a {@link android.view.Window}.
- * @hide
- */
- public void onViewDetachedFromWindow(View view) {
- synchronized (mH) {
- if (DEBUG) Log.v(TAG, "onViewDetachedFromWindow: view=" + dumpViewInfo(view)
- + " mServedView=" + dumpViewInfo(mServedView));
- if (mServedView == view) {
- mNextServedView = null;
- scheduleCheckFocusLocked(view);
- }
- }
- }
-
- static void scheduleCheckFocusLocked(View view) {
- ViewRootImpl viewRootImpl = view.getViewRootImpl();
- if (viewRootImpl != null) {
- viewRootImpl.dispatchCheckFocus();
- }
+ return startInputFlags;
}
/**
@@ -1887,54 +1980,12 @@
*/
@UnsupportedAppUsage
public void checkFocus() {
- if (checkFocusNoStartInput(false)) {
- startInputInner(StartInputReason.CHECK_FOCUS, null, 0, 0, 0);
- }
- }
-
- private boolean checkFocusNoStartInput(boolean forceNewFocus) {
- // This is called a lot, so short-circuit before locking.
- if (mServedView == mNextServedView && !forceNewFocus) {
- return false;
- }
-
- final ControlledInputConnectionWrapper ic;
synchronized (mH) {
- if (mServedView == mNextServedView && !forceNewFocus) {
- return false;
- }
- if (DEBUG) Log.v(TAG, "checkFocus: view=" + mServedView
- + " next=" + mNextServedView
- + " forceNewFocus=" + forceNewFocus
- + " package="
- + (mServedView != null ? mServedView.getContext().getPackageName() : "<none>"));
-
- if (mNextServedView == null) {
- finishInputLocked();
- // In this case, we used to have a focused view on the window,
- // but no longer do. We should make sure the input method is
- // no longer shown, since it serves no purpose.
- closeCurrentInput();
- return false;
- }
-
- ic = mServedInputConnectionWrapper;
-
- mServedView = mNextServedView;
- mCurrentTextBoxAttribute = null;
- mCompletions = null;
- mServedConnecting = true;
- // servedView has changed and it's not editable.
- if (!mServedView.onCheckIsTextEditor()) {
- maybeCallServedViewChangedLocked(null);
+ if (mCurRootView != null) {
+ mCurRootView.getImeFocusController().checkFocus(false /* forceNewFocus */,
+ true /* startInput */);
}
}
-
- if (ic != null) {
- ic.finishComposingText();
- }
-
- return true;
}
@UnsupportedAppUsage
@@ -1947,92 +1998,6 @@
}
/**
- * Called by ViewAncestor when its window gets input focus.
- * @hide
- */
- public void onPostWindowFocus(View rootView, View focusedView,
- @SoftInputModeFlags int softInputMode, int windowFlags) {
- boolean forceNewFocus = false;
- synchronized (mH) {
- if (DEBUG) Log.v(TAG, "onWindowFocus: " + focusedView
- + " softInputMode=" + InputMethodDebug.softInputModeToString(softInputMode)
- + " flags=#" + Integer.toHexString(windowFlags));
- if (mRestartOnNextWindowFocus) {
- if (DEBUG) Log.v(TAG, "Restarting due to mRestartOnNextWindowFocus");
- mRestartOnNextWindowFocus = false;
- forceNewFocus = true;
- }
- focusInLocked(focusedView != null ? focusedView : rootView);
- }
-
- int startInputFlags = 0;
- if (focusedView != null) {
- startInputFlags |= StartInputFlags.VIEW_HAS_FOCUS;
- if (focusedView.onCheckIsTextEditor()) {
- startInputFlags |= StartInputFlags.IS_TEXT_EDITOR;
- }
- }
-
- final boolean forceNewFocus1 = forceNewFocus;
- final int startInputFlags1 = startInputFlags;
- if (mWindowFocusGainFuture != null) {
- mWindowFocusGainFuture.cancel(false/* mayInterruptIfRunning */);
- }
- mWindowFocusGainFuture = CompletableFuture.runAsync(() -> {
- if (checkFocusNoStartInput(forceNewFocus1)) {
- // We need to restart input on the current focus view. This
- // should be done in conjunction with telling the system service
- // about the window gaining focus, to help make the transition
- // smooth.
- if (startInputInner(StartInputReason.WINDOW_FOCUS_GAIN, rootView.getWindowToken(),
- startInputFlags1, softInputMode, windowFlags)) {
- return;
- }
- }
-
- // For some reason we didn't do a startInput + windowFocusGain, so
- // we'll just do a window focus gain and call it a day.
- synchronized (mH) {
- try {
- if (DEBUG) Log.v(TAG, "Reporting focus gain, without startInput");
- mService.startInputOrWindowGainedFocus(
- StartInputReason.WINDOW_FOCUS_GAIN_REPORT_ONLY, mClient,
- rootView.getWindowToken(), startInputFlags1, softInputMode, windowFlags,
- null, null, 0 /* missingMethodFlags */,
- rootView.getContext().getApplicationInfo().targetSdkVersion);
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
- });
- }
-
- /** @hide */
- @UnsupportedAppUsage
- public void onPreWindowFocus(View rootView, boolean hasWindowFocus) {
- synchronized (mH) {
- if (rootView == null) {
- mCurRootView = null;
- } if (hasWindowFocus) {
- mCurRootView = rootView;
- } else if (rootView == mCurRootView) {
- // If the mCurRootView is losing window focus, release the strong reference to it
- // so as not to prevent it from being garbage-collected.
- mCurRootView = null;
- if (mWindowFocusGainFuture != null) {
- mWindowFocusGainFuture.cancel(false /* mayInterruptIfRunning */);
- mWindowFocusGainFuture = null;
- }
- } else {
- if (DEBUG) {
- Log.v(TAG, "Ignoring onPreWindowFocus()."
- + " mCurRootView=" + mCurRootView + " rootView=" + rootView);
- }
- }
- }
- }
-
- /**
* Register for IME state callbacks and applying visibility in
* {@link android.view.ImeInsetsSourceConsumer}.
* @hide
@@ -2071,10 +2036,11 @@
*/
public boolean requestImeShow(ResultReceiver resultReceiver) {
synchronized (mH) {
- if (mServedView == null) {
+ final View servedView = getServedViewLocked();
+ if (servedView == null) {
return false;
}
- showSoftInput(mServedView, 0 /* flags */, resultReceiver);
+ showSoftInput(servedView, 0 /* flags */, resultReceiver);
return true;
}
}
@@ -2116,9 +2082,8 @@
checkFocus();
synchronized (mH) {
- if ((mServedView != view && (mServedView == null
- || !mServedView.checkInputConnectionProxy(view)))
- || mCurrentTextBoxAttribute == null || mCurMethod == null) {
+ if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null
+ || mCurMethod == null) {
return;
}
@@ -2166,12 +2131,17 @@
return;
}
- final boolean focusChanged = mServedView != mNextServedView;
+ final View servedView;
+ final View nextServedView;
+ synchronized (mH) {
+ servedView = getServedViewLocked();
+ nextServedView = getNextServedViewLocked();
+ }
+ final boolean focusChanged = servedView != nextServedView;
checkFocus();
synchronized (mH) {
- if ((mServedView != view && (mServedView == null
- || !mServedView.checkInputConnectionProxy(view)))
- || mCurrentTextBoxAttribute == null || mCurMethod == null) {
+ if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null
+ || mCurMethod == null) {
return;
}
try {
@@ -2239,9 +2209,8 @@
checkFocus();
synchronized (mH) {
- if ((mServedView != view && (mServedView == null
- || !mServedView.checkInputConnectionProxy(view)))
- || mCurrentTextBoxAttribute == null || mCurMethod == null) {
+ if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null
+ || mCurMethod == null) {
return;
}
@@ -2277,9 +2246,8 @@
checkFocus();
synchronized (mH) {
- if ((mServedView != view &&
- (mServedView == null || !mServedView.checkInputConnectionProxy(view)))
- || mCurrentTextBoxAttribute == null || mCurMethod == null) {
+ if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null
+ || mCurMethod == null) {
return;
}
// If immediate bit is set, we will call updateCursorAnchorInfo() even when the data has
@@ -2335,9 +2303,8 @@
checkFocus();
synchronized (mH) {
- if ((mServedView != view && (mServedView == null
- || !mServedView.checkInputConnectionProxy(view)))
- || mCurrentTextBoxAttribute == null || mCurMethod == null) {
+ if (!hasServedByInputMethodLocked(view) || mCurrentTextBoxAttribute == null
+ || mCurMethod == null) {
return;
}
try {
@@ -2558,8 +2525,9 @@
synchronized (mH) {
ViewRootImpl viewRootImpl = targetView != null ? targetView.getViewRootImpl() : null;
if (viewRootImpl == null) {
- if (mServedView != null) {
- viewRootImpl = mServedView.getViewRootImpl();
+ final View servedView = getServedViewLocked();
+ if (servedView != null) {
+ viewRootImpl = servedView.getViewRootImpl();
}
}
if (viewRootImpl != null) {
@@ -2740,6 +2708,7 @@
/**
* Show the settings for enabling subtypes of the specified input method.
+ *
* @param imiId An input method, whose subtypes settings will be shown. If imiId is null,
* subtypes of all input methods will be shown.
*/
@@ -3038,8 +3007,8 @@
p.println(" mFullscreenMode=" + mFullscreenMode);
p.println(" mCurMethod=" + mCurMethod);
p.println(" mCurRootView=" + mCurRootView);
- p.println(" mServedView=" + mServedView);
- p.println(" mNextServedView=" + mNextServedView);
+ p.println(" mServedView=" + getServedViewLocked());
+ p.println(" mNextServedView=" + getNextServedViewLocked());
p.println(" mServedConnecting=" + mServedConnecting);
if (mCurrentTextBoxAttribute != null) {
p.println(" mCurrentTextBoxAttribute:");
@@ -3115,6 +3084,8 @@
sb.append(",window=" + view.getWindowToken());
sb.append(",displayId=" + view.getContext().getDisplayId());
sb.append(",temporaryDetach=" + view.isTemporarilyDetached());
+ sb.append(",hasImeFocus=" + view.hasImeFocus());
+
return sb.toString();
}
}
diff --git a/core/java/android/webkit/PacProcessor.java b/core/java/android/webkit/PacProcessor.java
new file mode 100644
index 0000000..fe8bbb8
--- /dev/null
+++ b/core/java/android/webkit/PacProcessor.java
@@ -0,0 +1,58 @@
+/*
+ * 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.webkit;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+
+
+/**
+ * Class to evaluate PAC scripts.
+ * @hide
+ */
+
+@SystemApi
+public interface PacProcessor {
+
+ /**
+ * Returns the default PacProcessor instance.
+ *
+ * @return the default PacProcessor instance.
+ */
+ @NonNull
+ static PacProcessor getInstance() {
+ return WebViewFactory.getProvider().getPacProcessor();
+ }
+
+ /**
+ * Set PAC script to use.
+ *
+ * @param script PAC script.
+ * @return true if PAC script is successfully set.
+ */
+ boolean setProxyScript(@NonNull String script);
+
+ /**
+ * Gets a list of proxy servers to use.
+ * @param url The URL being accessed.
+ * @return a PAC-style semicolon-separated list of valid proxy servers.
+ * For example: "PROXY xxx.xxx.xxx.xxx:xx; SOCKS yyy.yyy.yyy:yy".
+ */
+ @Nullable
+ String makeProxyRequest(@NonNull String url);
+}
diff --git a/core/java/android/webkit/WebViewFactoryProvider.java b/core/java/android/webkit/WebViewFactoryProvider.java
index 6a1ed39..f7c3ec0 100644
--- a/core/java/android/webkit/WebViewFactoryProvider.java
+++ b/core/java/android/webkit/WebViewFactoryProvider.java
@@ -175,6 +175,15 @@
WebViewDatabase getWebViewDatabase(Context context);
/**
+ * Gets the singleton PacProcessor instance.
+ * @return the PacProcessor instance
+ */
+ @NonNull
+ default PacProcessor getPacProcessor() {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ /**
* Gets the classloader used to load internal WebView implementation classes. This interface
* should only be used by the WebView Support Library.
*/
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 0340cb3..b891af5 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -152,7 +152,7 @@
// Specifies whether to allow starting a cursor drag by dragging anywhere over the text.
@VisibleForTesting
- public static boolean FLAG_ENABLE_CURSOR_DRAG = false;
+ public static boolean FLAG_ENABLE_CURSOR_DRAG = true;
// Specifies whether to use the magnifier when pressing the insertion or selection handles.
private static final boolean FLAG_USE_MAGNIFIER = true;
@@ -1462,7 +1462,11 @@
return false;
}
- void onTouchEvent(MotionEvent event) {
+ /**
+ * Handles touch events on an editable text view, implementing cursor movement, selection, etc.
+ */
+ @VisibleForTesting
+ public void onTouchEvent(MotionEvent event) {
final boolean filterOutEvent = shouldFilterOutTouchEvent(event);
mLastButtonState = event.getButtonState();
if (filterOutEvent) {
@@ -2424,7 +2428,9 @@
return mSelectionControllerEnabled;
}
- private InsertionPointCursorController getInsertionController() {
+ /** Returns the controller for the insertion cursor. */
+ @VisibleForTesting
+ public @Nullable InsertionPointCursorController getInsertionController() {
if (!mInsertionControllerEnabled) {
return null;
}
@@ -2439,8 +2445,9 @@
return mInsertionPointCursorController;
}
- @Nullable
- SelectionModifierCursorController getSelectionController() {
+ /** Returns the controller for selection. */
+ @VisibleForTesting
+ public @Nullable SelectionModifierCursorController getSelectionController() {
if (!mSelectionControllerEnabled) {
return null;
}
@@ -5723,22 +5730,28 @@
}
}
- class InsertionPointCursorController implements CursorController {
+ /** Controller for the insertion cursor. */
+ @VisibleForTesting
+ public class InsertionPointCursorController implements CursorController {
private InsertionHandleView mHandle;
private boolean mIsDraggingCursor;
public void onTouchEvent(MotionEvent event) {
+ if (hasSelectionController() && getSelectionController().isCursorBeingModified()) {
+ return;
+ }
switch (event.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- mIsDraggingCursor = false;
- break;
case MotionEvent.ACTION_MOVE:
+ if (event.isFromSource(InputDevice.SOURCE_MOUSE)) {
+ break;
+ }
if (mIsDraggingCursor) {
performCursorDrag(event);
} else if (FLAG_ENABLE_CURSOR_DRAG
&& mTextView.getLayout() != null
&& mTextView.isFocused()
- && mTouchState.isMovedEnoughForDrag()) {
+ && mTouchState.isMovedEnoughForDrag()
+ && !mTouchState.isDragCloseToVertical()) {
startCursorDrag(event);
}
break;
@@ -5899,15 +5912,15 @@
}
}
- class SelectionModifierCursorController implements CursorController {
+ /** Controller for selection. */
+ @VisibleForTesting
+ public class SelectionModifierCursorController implements CursorController {
// The cursor controller handles, lazily created when shown.
private SelectionHandleView mStartHandle;
private SelectionHandleView mEndHandle;
// The offsets of that last touch down event. Remembered to start selection there.
private int mMinTouchOffset, mMaxTouchOffset;
- private boolean mGestureStayedInTapRegion;
-
// Where the user first starts the drag motion.
private int mStartOffset = -1;
@@ -6014,8 +6027,7 @@
eventX, eventY);
// Double tap detection
- if (mGestureStayedInTapRegion
- && mTouchState.isMultiTapInSameArea()
+ if (mTouchState.isMultiTapInSameArea()
&& (isMouse || isPositionOnText(eventX, eventY))) {
if (TextView.DEBUG_CURSOR) {
logCursor("SelectionModifierCursorController: onTouchEvent",
@@ -6028,7 +6040,6 @@
}
mDiscardNextActionUp = true;
}
- mGestureStayedInTapRegion = true;
mHaventMovedEnoughToStartDrag = true;
}
break;
@@ -6044,25 +6055,8 @@
break;
case MotionEvent.ACTION_MOVE:
- final ViewConfiguration viewConfig = ViewConfiguration.get(
- mTextView.getContext());
-
- if (mGestureStayedInTapRegion || mHaventMovedEnoughToStartDrag) {
- final float deltaX = eventX - mTouchState.getLastDownX();
- final float deltaY = eventY - mTouchState.getLastDownY();
- final float distanceSquared = deltaX * deltaX + deltaY * deltaY;
-
- if (mGestureStayedInTapRegion) {
- int doubleTapTouchSlop = viewConfig.getScaledDoubleTapTouchSlop();
- mGestureStayedInTapRegion =
- distanceSquared <= doubleTapTouchSlop * doubleTapTouchSlop;
- }
- if (mHaventMovedEnoughToStartDrag) {
- // We don't start dragging until the user has moved enough.
- int touchSlop = viewConfig.getScaledTouchSlop();
- mHaventMovedEnoughToStartDrag =
- distanceSquared <= touchSlop * touchSlop;
- }
+ if (mHaventMovedEnoughToStartDrag) {
+ mHaventMovedEnoughToStartDrag = !mTouchState.isMovedEnoughForDrag();
}
if (isMouse && !isDragAcceleratorActive()) {
diff --git a/core/java/android/widget/EditorTouchState.java b/core/java/android/widget/EditorTouchState.java
index 3798d00..b13ca42 100644
--- a/core/java/android/widget/EditorTouchState.java
+++ b/core/java/android/widget/EditorTouchState.java
@@ -31,13 +31,15 @@
import java.lang.annotation.RetentionPolicy;
/**
- * Helper class used by {@link Editor} to track state for touch events.
+ * Helper class used by {@link Editor} to track state for touch events. Ideally the logic here
+ * should be replaced with {@link android.view.GestureDetector}.
*
* @hide
*/
@VisibleForTesting(visibility = PACKAGE)
public class EditorTouchState {
private float mLastDownX, mLastDownY;
+ private long mLastDownMillis;
private float mLastUpX, mLastUpY;
private long mLastUpMillis;
@@ -56,6 +58,7 @@
private boolean mMultiTapInSameArea;
private boolean mMovedEnoughForDrag;
+ private boolean mIsDragCloseToVertical;
public float getLastDownX() {
return mLastDownX;
@@ -94,6 +97,10 @@
return mMovedEnoughForDrag;
}
+ public boolean isDragCloseToVertical() {
+ return mIsDragCloseToVertical;
+ }
+
/**
* Updates the state based on the new event.
*/
@@ -101,9 +108,18 @@
final int action = event.getActionMasked();
if (action == MotionEvent.ACTION_DOWN) {
final boolean isMouse = event.isFromSource(InputDevice.SOURCE_MOUSE);
+
+ // We check both the time between the last up and current down event, as well as the
+ // time between the first down and up events. The latter check is necessary to handle
+ // the case when the user taps, drags/holds for some time, and then lifts up and
+ // quickly taps in the same area. This scenario should not be treated as a double-tap.
+ // This follows the behavior in GestureDetector.
final long millisSinceLastUp = event.getEventTime() - mLastUpMillis;
+ final long millisBetweenLastDownAndLastUp = mLastUpMillis - mLastDownMillis;
+
// Detect double tap and triple click.
if (millisSinceLastUp <= ViewConfiguration.getDoubleTapTimeout()
+ && millisBetweenLastDownAndLastUp <= ViewConfiguration.getDoubleTapTimeout()
&& (mMultiTapStatus == MultiTapStatus.FIRST_TAP
|| (mMultiTapStatus == MultiTapStatus.DOUBLE_TAP && isMouse))) {
if (mMultiTapStatus == MultiTapStatus.FIRST_TAP) {
@@ -128,7 +144,9 @@
}
mLastDownX = event.getX();
mLastDownY = event.getY();
+ mLastDownMillis = event.getEventTime();
mMovedEnoughForDrag = false;
+ mIsDragCloseToVertical = false;
} else if (action == MotionEvent.ACTION_UP) {
if (TextView.DEBUG_CURSOR) {
logCursor("EditorTouchState", "ACTION_UP");
@@ -137,9 +155,31 @@
mLastUpY = event.getY();
mLastUpMillis = event.getEventTime();
mMovedEnoughForDrag = false;
+ mIsDragCloseToVertical = false;
} else if (action == MotionEvent.ACTION_MOVE) {
- mMovedEnoughForDrag = !isDistanceWithin(mLastDownX, mLastDownY,
- event.getX(), event.getY(), config.getScaledTouchSlop());
+ if (!mMovedEnoughForDrag) {
+ float deltaX = event.getX() - mLastDownX;
+ float deltaY = event.getY() - mLastDownY;
+ float deltaXSquared = deltaX * deltaX;
+ float distanceSquared = (deltaXSquared) + (deltaY * deltaY);
+ int touchSlop = config.getScaledTouchSlop();
+ mMovedEnoughForDrag = distanceSquared > touchSlop * touchSlop;
+ if (mMovedEnoughForDrag) {
+ // If the direction of the swipe motion is within 30 degrees of vertical, it is
+ // considered a vertical drag. We don't actually have to compute the angle to
+ // implement the check though. When the angle is exactly 30 degrees from
+ // vertical, 2*deltaX = distance. When the angle is less than 30 degrees from
+ // vertical, 2*deltaX < distance.
+ mIsDragCloseToVertical = (4 * deltaXSquared) <= distanceSquared;
+ }
+ }
+ } else if (action == MotionEvent.ACTION_CANCEL) {
+ mLastDownMillis = 0;
+ mLastUpMillis = 0;
+ mMultiTapStatus = MultiTapStatus.NONE;
+ mMultiTapInSameArea = false;
+ mMovedEnoughForDrag = false;
+ mIsDragCloseToVertical = false;
}
}
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index d86766e..01a0e9b 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -565,7 +565,8 @@
}
private static void visitIconUri(Icon icon, @NonNull Consumer<Uri> visitor) {
- if (icon != null && icon.getType() == Icon.TYPE_URI) {
+ if (icon != null && (icon.getType() == Icon.TYPE_URI
+ || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) {
visitor.accept(icon.getUri());
}
}
diff --git a/core/java/android/widget/TextClock.java b/core/java/android/widget/TextClock.java
index 5731e50..8565493 100644
--- a/core/java/android/widget/TextClock.java
+++ b/core/java/android/widget/TextClock.java
@@ -422,7 +422,7 @@
/**
* Update the displayed time if necessary and invalidate the view.
*/
- public void refresh() {
+ public void refreshTime() {
onTimeChanged();
invalidate();
}
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index 9bdb4c1..d119b2e 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -16,6 +16,8 @@
package android.widget;
+import static com.android.internal.util.Preconditions.checkNotNull;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -42,8 +44,12 @@
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
+import com.android.internal.annotations.GuardedBy;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
/**
* A toast is a view containing a quick little message for the user. The toast class
@@ -262,6 +268,29 @@
}
/**
+ * Adds a callback to be notified when the toast is shown or hidden.
+ *
+ * Note that if the toast is blocked for some reason you won't get a call back.
+ *
+ * @see #removeCallback(Callback)
+ */
+ public void addCallback(@NonNull Callback callback) {
+ checkNotNull(callback);
+ synchronized (mTN.mCallbacks) {
+ mTN.mCallbacks.add(callback);
+ }
+ }
+
+ /**
+ * Removes a callback previously added with {@link #addCallback(Callback)}.
+ */
+ public void removeCallback(@NonNull Callback callback) {
+ synchronized (mTN.mCallbacks) {
+ mTN.mCallbacks.remove(callback);
+ }
+ }
+
+ /**
* Gets the LayoutParams for the Toast window.
* @hide
*/
@@ -389,6 +418,9 @@
String mPackageName;
+ @GuardedBy("mCallbacks")
+ private final List<Callback> mCallbacks = new ArrayList<>();
+
static final long SHORT_DURATION_TIMEOUT = 4000;
static final long LONG_DURATION_TIMEOUT = 7000;
@@ -449,6 +481,12 @@
};
}
+ private List<Callback> getCallbacks() {
+ synchronized (mCallbacks) {
+ return new ArrayList<>(mCallbacks);
+ }
+ }
+
/**
* schedule handleShow into the right thread
*/
@@ -522,6 +560,9 @@
try {
mWM.addView(mView, mParams);
trySendAccessibilityEvent();
+ for (Callback callback : getCallbacks()) {
+ callback.onToastShown();
+ }
} catch (WindowManager.BadTokenException e) {
/* ignore */
}
@@ -564,8 +605,30 @@
} catch (RemoteException e) {
}
+ for (Callback callback : getCallbacks()) {
+ callback.onToastHidden();
+ }
mView = null;
}
}
}
+
+ /**
+ * Callback object to be called when the toast is shown or hidden.
+ *
+ * Callback methods will be called on the looper thread provided on construction.
+ *
+ * @see #addCallback(Callback)
+ */
+ public abstract static class Callback {
+ /**
+ * Called when the toast is displayed on the screen.
+ */
+ public void onToastShown() {}
+
+ /**
+ * Called when the toast is hidden.
+ */
+ public void onToastHidden() {}
+ }
}
diff --git a/core/java/com/android/ims/internal/uce/common/CapInfo.java b/core/java/com/android/ims/internal/uce/common/CapInfo.java
index a9847ba..2bb3f1f 100644
--- a/core/java/com/android/ims/internal/uce/common/CapInfo.java
+++ b/core/java/com/android/ims/internal/uce/common/CapInfo.java
@@ -16,10 +16,9 @@
package com.android.ims.internal.uce.common;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
-import android.util.Log;
/** Class for capability discovery information.
* @hide */
@@ -65,6 +64,20 @@
private boolean mRcsIpVideoCallSupported = false;
/** RCS IP Video call support . */
private boolean mRcsIpVideoOnlyCallSupported = false;
+ /** IP Geo location Push using SMS. */
+ private boolean mGeoSmsSupported = false;
+ /** RCS call composer support. */
+ private boolean mCallComposerSupported = false;
+ /** RCS post-call support. */
+ private boolean mPostCallSupported = false;
+ /** Shared map support. */
+ private boolean mSharedMapSupported = false;
+ /** Shared Sketch supported. */
+ private boolean mSharedSketchSupported = false;
+ /** Chatbot communication support. */
+ private boolean mChatbotSupported = false;
+ /** Chatbot role support. */
+ private boolean mChatbotRoleSupported = false;
/** List of supported extensions. */
private String[] mExts = new String[10];
/** Time used to compute when to query again. */
@@ -387,6 +400,104 @@
this.mRcsIpVideoOnlyCallSupported = rcsIpVideoOnlyCallSupported;
}
+ /**
+ * Checks whether Geo Push via SMS is supported.
+ */
+ public boolean isGeoSmsSupported() {
+ return mGeoSmsSupported;
+ }
+
+ /**
+ * Sets Geolocation Push via SMS as supported or not supported.
+ */
+ public void setGeoSmsSupported(boolean geoSmsSupported) {
+ this.mGeoSmsSupported = geoSmsSupported;
+ }
+
+ /**
+ * Checks whether RCS call composer is supported.
+ */
+ public boolean isCallComposerSupported() {
+ return mCallComposerSupported;
+ }
+
+ /**
+ * Sets call composer as supported or not supported.
+ */
+ public void setCallComposerSupported(boolean callComposerSupported) {
+ this.mCallComposerSupported = callComposerSupported;
+ }
+
+ /**
+ * Checks whether post call is supported.
+ */
+ public boolean isPostCallSupported(){
+ return mPostCallSupported;
+ }
+
+ /**
+ * Sets post call as supported or not supported.
+ */
+ public void setPostCallSupported(boolean postCallSupported) {
+ this.mPostCallSupported = postCallSupported;
+ }
+
+ /**
+ * Checks whether shared map is supported.
+ */
+ public boolean isSharedMapSupported() {
+ return mSharedMapSupported;
+ }
+
+ /**
+ * Sets shared map as supported or not supported.
+ */
+ public void setSharedMapSupported(boolean sharedMapSupported) {
+ this.mSharedMapSupported = sharedMapSupported;
+ }
+
+ /**
+ * Checks whether shared sketch is supported.
+ */
+ public boolean isSharedSketchSupported() {
+ return mSharedSketchSupported;
+ }
+
+ /**
+ * Sets shared sketch as supported or not supported.
+ */
+ public void setSharedSketchSupported(boolean sharedSketchSupported) {
+ this.mSharedSketchSupported = sharedSketchSupported;
+ }
+
+ /**
+ * Checks whether chatbot communication is supported.
+ */
+ public boolean isChatbotSupported() {
+ return mChatbotSupported;
+ }
+
+ /**
+ * Sets chatbot communication as supported or not supported.
+ */
+ public void setChatbotSupported(boolean chatbotSupported) {
+ this.mChatbotSupported = chatbotSupported;
+ }
+
+ /**
+ * Checks whether chatbot role is supported.
+ */
+ public boolean isChatbotRoleSupported() {
+ return mChatbotRoleSupported;
+ }
+
+ /**
+ * Sets chatbot role as supported or not supported.
+ */
+ public void setChatbotRoleSupported(boolean chatbotRoleSupported) {
+ this.mChatbotRoleSupported = chatbotRoleSupported;
+ }
+
/** Gets the list of supported extensions. */
public String[] getExts() {
return mExts;
@@ -435,6 +546,13 @@
dest.writeInt(mGeoPushSupported ? 1 : 0);
dest.writeInt(mSmSupported ? 1 : 0);
dest.writeInt(mFullSnFGroupChatSupported ? 1 : 0);
+ dest.writeInt(mGeoSmsSupported ? 1 : 0);
+ dest.writeInt(mCallComposerSupported ? 1 : 0);
+ dest.writeInt(mPostCallSupported ? 1 : 0);
+ dest.writeInt(mSharedMapSupported ? 1 : 0);
+ dest.writeInt(mSharedSketchSupported ? 1 : 0);
+ dest.writeInt(mChatbotSupported ? 1 : 0);
+ dest.writeInt(mChatbotRoleSupported ? 1 : 0);
dest.writeInt(mRcsIpVoiceCallSupported ? 1 : 0);
dest.writeInt(mRcsIpVideoCallSupported ? 1 : 0);
@@ -477,6 +595,13 @@
mGeoPushSupported = (source.readInt() == 0) ? false : true;
mSmSupported = (source.readInt() == 0) ? false : true;
mFullSnFGroupChatSupported = (source.readInt() == 0) ? false : true;
+ mGeoSmsSupported = (source.readInt() == 0) ? false : true;
+ mCallComposerSupported = (source.readInt() == 0) ? false : true;
+ mPostCallSupported = (source.readInt() == 0) ? false : true;
+ mSharedMapSupported = (source.readInt() == 0) ? false : true;
+ mSharedSketchSupported = (source.readInt() == 0) ? false : true;
+ mChatbotSupported = (source.readInt() == 0) ? false : true;
+ mChatbotRoleSupported = (source.readInt() == 0) ? false : true;
mRcsIpVoiceCallSupported = (source.readInt() == 0) ? false : true;
mRcsIpVideoCallSupported = (source.readInt() == 0) ? false : true;
diff --git a/core/java/com/android/ims/internal/uce/common/StatusCode.java b/core/java/com/android/ims/internal/uce/common/StatusCode.java
index 7250eee..7f69493 100644
--- a/core/java/com/android/ims/internal/uce/common/StatusCode.java
+++ b/core/java/com/android/ims/internal/uce/common/StatusCode.java
@@ -16,10 +16,9 @@
package com.android.ims.internal.uce.common;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
-import android.util.Log;
/** Class for UCE status codes.
diff --git a/core/java/com/android/ims/internal/uce/common/UceLong.java b/core/java/com/android/ims/internal/uce/common/UceLong.java
index 7207899..bf51447 100644
--- a/core/java/com/android/ims/internal/uce/common/UceLong.java
+++ b/core/java/com/android/ims/internal/uce/common/UceLong.java
@@ -16,10 +16,9 @@
package com.android.ims.internal.uce.common;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
-import android.util.Log;
/** Simple object wrapper for a long type.
diff --git a/core/java/com/android/ims/internal/uce/options/OptionsCapInfo.java b/core/java/com/android/ims/internal/uce/options/OptionsCapInfo.java
index bcb9f2d..1da5a24 100644
--- a/core/java/com/android/ims/internal/uce/options/OptionsCapInfo.java
+++ b/core/java/com/android/ims/internal/uce/options/OptionsCapInfo.java
@@ -15,11 +15,11 @@
*/
package com.android.ims.internal.uce.options;
-import android.annotation.UnsupportedAppUsage;
-import com.android.ims.internal.uce.common.CapInfo;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
-import android.util.Log;
+
+import com.android.ims.internal.uce.common.CapInfo;
/** @hide */
public class OptionsCapInfo implements Parcelable {
diff --git a/core/java/com/android/ims/internal/uce/options/OptionsCmdId.java b/core/java/com/android/ims/internal/uce/options/OptionsCmdId.java
index 14c64ac..401ca2f 100644
--- a/core/java/com/android/ims/internal/uce/options/OptionsCmdId.java
+++ b/core/java/com/android/ims/internal/uce/options/OptionsCmdId.java
@@ -17,7 +17,7 @@
package com.android.ims.internal.uce.options;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/com/android/ims/internal/uce/options/OptionsCmdStatus.java b/core/java/com/android/ims/internal/uce/options/OptionsCmdStatus.java
index 4af3e6e..70a7a84 100644
--- a/core/java/com/android/ims/internal/uce/options/OptionsCmdStatus.java
+++ b/core/java/com/android/ims/internal/uce/options/OptionsCmdStatus.java
@@ -16,13 +16,13 @@
package com.android.ims.internal.uce.options;
-import com.android.ims.internal.uce.common.StatusCode;
-import com.android.ims.internal.uce.common.CapInfo;
-
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.ims.internal.uce.common.CapInfo;
+import com.android.ims.internal.uce.common.StatusCode;
+
/** @hide */
public class OptionsCmdStatus implements Parcelable {
diff --git a/core/java/com/android/ims/internal/uce/options/OptionsSipResponse.java b/core/java/com/android/ims/internal/uce/options/OptionsSipResponse.java
index c5f333d..5afddf0 100644
--- a/core/java/com/android/ims/internal/uce/options/OptionsSipResponse.java
+++ b/core/java/com/android/ims/internal/uce/options/OptionsSipResponse.java
@@ -16,7 +16,7 @@
package com.android.ims.internal.uce.options;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/com/android/ims/internal/uce/presence/PresCapInfo.java b/core/java/com/android/ims/internal/uce/presence/PresCapInfo.java
index 745df5b..1a3a028 100644
--- a/core/java/com/android/ims/internal/uce/presence/PresCapInfo.java
+++ b/core/java/com/android/ims/internal/uce/presence/PresCapInfo.java
@@ -16,12 +16,12 @@
package com.android.ims.internal.uce.presence;
-import com.android.ims.internal.uce.common.CapInfo;
-
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.ims.internal.uce.common.CapInfo;
+
/** @hide */
public class PresCapInfo implements Parcelable {
diff --git a/core/java/com/android/ims/internal/uce/presence/PresCmdId.java b/core/java/com/android/ims/internal/uce/presence/PresCmdId.java
index 41020ec..fba0c77 100644
--- a/core/java/com/android/ims/internal/uce/presence/PresCmdId.java
+++ b/core/java/com/android/ims/internal/uce/presence/PresCmdId.java
@@ -16,7 +16,7 @@
package com.android.ims.internal.uce.presence;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/com/android/ims/internal/uce/presence/PresCmdStatus.java b/core/java/com/android/ims/internal/uce/presence/PresCmdStatus.java
index ff8069c..fbc64b8 100644
--- a/core/java/com/android/ims/internal/uce/presence/PresCmdStatus.java
+++ b/core/java/com/android/ims/internal/uce/presence/PresCmdStatus.java
@@ -16,12 +16,12 @@
package com.android.ims.internal.uce.presence;
-import com.android.ims.internal.uce.common.StatusCode;
-
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.ims.internal.uce.common.StatusCode;
+
/** @hide */
public class PresCmdStatus implements Parcelable{
diff --git a/core/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.java b/core/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.java
index 87193e3..fdff86f 100644
--- a/core/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.java
+++ b/core/java/com/android/ims/internal/uce/presence/PresPublishTriggerType.java
@@ -16,7 +16,7 @@
package com.android.ims.internal.uce.presence;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
@@ -47,6 +47,10 @@
public static final int UCE_PRES_PUBLISH_TRIGGER_MOVE_TO_IWLAN = 8;
/** Trigger is unknown. */
public static final int UCE_PRES_PUBLISH_TRIGGER_UNKNOWN = 9;
+ /** Move to 5G NR with VoPS disabled. */
+ public static final int UCE_PRES_PUBLISH_TRIGGER_MOVE_TO_NR5G_VOPS_DISABLED = 10;
+ /** Move to 5G NR with VoPS enabled. */
+ public static final int UCE_PRES_PUBLISH_TRIGGER_MOVE_TO_NR5G_VOPS_ENABLED = 11;
@@ -113,4 +117,4 @@
public void readFromParcel(Parcel source) {
mPublishTriggerType = source.readInt();
}
-}
\ No newline at end of file
+}
diff --git a/core/java/com/android/ims/internal/uce/presence/PresResInfo.java b/core/java/com/android/ims/internal/uce/presence/PresResInfo.java
index 237c999..af9b056 100644
--- a/core/java/com/android/ims/internal/uce/presence/PresResInfo.java
+++ b/core/java/com/android/ims/internal/uce/presence/PresResInfo.java
@@ -16,7 +16,7 @@
package com.android.ims.internal.uce.presence;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/com/android/ims/internal/uce/presence/PresResInstanceInfo.java b/core/java/com/android/ims/internal/uce/presence/PresResInstanceInfo.java
index 29699ea..9f37251 100644
--- a/core/java/com/android/ims/internal/uce/presence/PresResInstanceInfo.java
+++ b/core/java/com/android/ims/internal/uce/presence/PresResInstanceInfo.java
@@ -16,9 +16,10 @@
package com.android.ims.internal.uce.presence;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
+
import java.util.Arrays;
/** @hide */
diff --git a/core/java/com/android/ims/internal/uce/presence/PresRlmiInfo.java b/core/java/com/android/ims/internal/uce/presence/PresRlmiInfo.java
index ab46e4b..65b9fdb 100644
--- a/core/java/com/android/ims/internal/uce/presence/PresRlmiInfo.java
+++ b/core/java/com/android/ims/internal/uce/presence/PresRlmiInfo.java
@@ -16,7 +16,7 @@
package com.android.ims.internal.uce.presence;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/com/android/ims/internal/uce/presence/PresServiceInfo.java b/core/java/com/android/ims/internal/uce/presence/PresServiceInfo.java
index 83ba722..5eafa0f 100644
--- a/core/java/com/android/ims/internal/uce/presence/PresServiceInfo.java
+++ b/core/java/com/android/ims/internal/uce/presence/PresServiceInfo.java
@@ -16,7 +16,7 @@
package com.android.ims.internal.uce.presence;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/com/android/ims/internal/uce/presence/PresSipResponse.java b/core/java/com/android/ims/internal/uce/presence/PresSipResponse.java
index 5e42592..45b02f3 100644
--- a/core/java/com/android/ims/internal/uce/presence/PresSipResponse.java
+++ b/core/java/com/android/ims/internal/uce/presence/PresSipResponse.java
@@ -16,7 +16,7 @@
package com.android.ims.internal.uce.presence;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/com/android/ims/internal/uce/presence/PresSubscriptionState.java b/core/java/com/android/ims/internal/uce/presence/PresSubscriptionState.java
index bee928c..ab1e17c 100644
--- a/core/java/com/android/ims/internal/uce/presence/PresSubscriptionState.java
+++ b/core/java/com/android/ims/internal/uce/presence/PresSubscriptionState.java
@@ -16,7 +16,7 @@
package com.android.ims.internal.uce.presence;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/com/android/ims/internal/uce/presence/PresTupleInfo.java b/core/java/com/android/ims/internal/uce/presence/PresTupleInfo.java
index 7a47786..3608eb6a 100644
--- a/core/java/com/android/ims/internal/uce/presence/PresTupleInfo.java
+++ b/core/java/com/android/ims/internal/uce/presence/PresTupleInfo.java
@@ -16,7 +16,7 @@
package com.android.ims.internal.uce.presence;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/core/java/com/android/ims/internal/uce/uceservice/ImsUceManager.java b/core/java/com/android/ims/internal/uce/uceservice/ImsUceManager.java
index 4b0b098..9aee879f 100644
--- a/core/java/com/android/ims/internal/uce/uceservice/ImsUceManager.java
+++ b/core/java/com/android/ims/internal/uce/uceservice/ImsUceManager.java
@@ -18,16 +18,9 @@
import android.content.Context;
import android.content.Intent;
-
-import android.os.Handler;
-import android.os.HandlerThread;
import android.os.IBinder;
-import android.os.Message;
-import android.os.ServiceManager;
import android.os.RemoteException;
-
-import java.util.HashMap;
-import android.util.Log;
+import android.os.ServiceManager;
/**
* ImsUceManager Declaration
@@ -49,55 +42,25 @@
private IUceService mUceService = null;
private UceServiceDeathRecipient mDeathReceipient = new UceServiceDeathRecipient();
private Context mContext;
- private int mPhoneId;
- /**
- * Stores the UceManager instaces of Clients identified by
- * phoneId
- * @hide
- */
- private static HashMap<Integer, ImsUceManager> sUceManagerInstances =
- new HashMap<Integer, ImsUceManager>();
+ private static final Object sLock = new Object();
+ private static ImsUceManager sUceManager;
public static final String ACTION_UCE_SERVICE_UP =
"com.android.ims.internal.uce.UCE_SERVICE_UP";
public static final String ACTION_UCE_SERVICE_DOWN =
"com.android.ims.internal.uce.UCE_SERVICE_DOWN";
- /** Uce Service status received in IUceListener.setStatus()
- * callback
- * @hide
- */
- public static final int UCE_SERVICE_STATUS_FAILURE = 0;
- /** indicate UI to call Presence/Options API. */
- public static final int UCE_SERVICE_STATUS_ON = 1;
- /** Indicate UI destroy Presence/Options */
- public static final int UCE_SERVICE_STATUS_CLOSED = 2;
- /** Service up and trying to register for network events */
- public static final int UCE_SERVICE_STATUS_READY = 3;
-
- /**
- * Part of the ACTION_UCE_SERVICE_UP or _DOWN intents. A long
- * value; the phone ID corresponding to the IMS service coming up or down.
- * Internal use only.
- * @hide
- */
- public static final String EXTRA_PHONE_ID = "android:phone_id";
-
/**
* Gets the instance of UCE Manager
* @hide
*/
- public static ImsUceManager getInstance(Context context, int phoneId) {
- //if (DBG) Log.d (LOG_TAG, "GetInstance Called");
- synchronized (sUceManagerInstances) {
- if (sUceManagerInstances.containsKey(phoneId)) {
- return sUceManagerInstances.get(phoneId);
- } else {
- ImsUceManager uceMgr = new ImsUceManager(context, phoneId);
- sUceManagerInstances.put(phoneId, uceMgr);
- return uceMgr;
+ public static ImsUceManager getInstance(Context context) {
+ synchronized (sLock) {
+ if (sUceManager == null && context != null) {
+ sUceManager = new ImsUceManager(context);
}
+ return sUceManager;
}
}
@@ -105,10 +68,9 @@
* Constructor
* @hide
*/
- private ImsUceManager(Context context, int phoneId) {
+ private ImsUceManager(Context context) {
//if (DBG) Log.d (LOG_TAG, "Constructor");
mContext = context;
- mPhoneId = phoneId;
createUceService(true);
}
@@ -129,7 +91,7 @@
* Gets the UCE service name
* @hide
*/
- private String getUceServiceName(int phoneId) {
+ private String getUceServiceName() {
return UCE_SERVICE;
}
@@ -143,14 +105,14 @@
public void createUceService(boolean checkService) {
//if (DBG) Log.d (LOG_TAG, "CreateUceService Called");
if (checkService) {
- IBinder binder = ServiceManager.checkService(getUceServiceName(mPhoneId));
+ IBinder binder = ServiceManager.checkService(getUceServiceName());
if (binder == null) {
//if (DBG)Log.d (LOG_TAG, "Unable to find IBinder");
return;
}
}
- IBinder b = ServiceManager.getService(getUceServiceName(mPhoneId));
+ IBinder b = ServiceManager.getService(getUceServiceName());
if (b != null) {
try {
@@ -174,12 +136,10 @@
private class UceServiceDeathRecipient implements IBinder.DeathRecipient {
@Override
public void binderDied() {
- //if (DBG) Log.d (LOG_TAG, "found IBinder/IUceService Service Died");
mUceService = null;
if (mContext != null) {
Intent intent = new Intent(ACTION_UCE_SERVICE_DOWN);
- intent.putExtra(EXTRA_PHONE_ID, mPhoneId);
mContext.sendBroadcast(new Intent(intent));
}
}
diff --git a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
index f9ba34e..457c033 100644
--- a/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
+++ b/core/java/com/android/internal/app/AccessibilityButtonChooserActivity.java
@@ -16,21 +16,35 @@
package com.android.internal.app;
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;
+import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY;
import static android.view.accessibility.AccessibilityManager.ShortcutType;
+import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME;
+import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME;
+import static com.android.internal.app.AccessibilityButtonChooserActivity.WhiteListingFeatureElementIndex.COMPONENT_ID;
+import static com.android.internal.app.AccessibilityButtonChooserActivity.WhiteListingFeatureElementIndex.FRAGMENT_TYPE;
+import static com.android.internal.app.AccessibilityButtonChooserActivity.WhiteListingFeatureElementIndex.ICON_ID;
+import static com.android.internal.app.AccessibilityButtonChooserActivity.WhiteListingFeatureElementIndex.LABEL_ID;
+import static com.android.internal.app.AccessibilityButtonChooserActivity.WhiteListingFeatureElementIndex.SETTINGS_KEY;
+
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
import android.app.AlertDialog;
+import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
+import android.os.UserHandle;
import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.ArraySet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -49,23 +63,58 @@
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
+import java.util.StringJoiner;
/**
* Activity used to display and persist a service or feature target for the Accessibility button.
*/
public class AccessibilityButtonChooserActivity extends Activity {
+ private static final char SERVICES_SEPARATOR = ':';
+ private static final TextUtils.SimpleStringSplitter sStringColonSplitter =
+ new TextUtils.SimpleStringSplitter(SERVICES_SEPARATOR);
+ @UserShortcutType
+ private static final int ACCESSIBILITY_BUTTON_USER_TYPE = convertToUserType(
+ ACCESSIBILITY_BUTTON);
+ @UserShortcutType
+ private static final int ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE = convertToUserType(
+ ACCESSIBILITY_SHORTCUT_KEY);
- private static final String MAGNIFICATION_COMPONENT_ID =
- "com.android.server.accessibility.MagnificationController";
-
+ @ShortcutType
private int mShortcutType;
- private List<AccessibilityButtonTarget> mTargets = new ArrayList<>();
- private List<AccessibilityButtonTarget> mReadyToBeDisabledTargets = new ArrayList<>();
+ private final List<AccessibilityButtonTarget> mTargets = new ArrayList<>();
private AlertDialog mAlertDialog;
private TargetAdapter mTargetAdapter;
/**
+ * Annotation for different user shortcut type UI type.
+ *
+ * {@code DEFAULT} for displaying default value.
+ * {@code SOFTWARE} for displaying specifying the accessibility services or features which
+ * choose accessibility button in the navigation bar as preferred shortcut.
+ * {@code HARDWARE} for displaying specifying the accessibility services or features which
+ * choose accessibility shortcut as preferred shortcut.
+ * {@code TRIPLETAP} for displaying specifying magnification to be toggled via quickly
+ * tapping screen 3 times as preferred shortcut.
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ UserShortcutType.DEFAULT,
+ UserShortcutType.SOFTWARE,
+ UserShortcutType.HARDWARE,
+ UserShortcutType.TRIPLETAP,
+ })
+ /** Denotes the user shortcut type. */
+ private @interface UserShortcutType {
+ int DEFAULT = 0;
+ int SOFTWARE = 1; // 1 << 0
+ int HARDWARE = 2; // 1 << 1
+ int TRIPLETAP = 4; // 1 << 2
+ }
+
+ /**
* Annotation for different accessibilityService fragment UI type.
*
* {@code LEGACY} for displaying appearance aligned with sdk version Q accessibility service
@@ -81,7 +130,7 @@
AccessibilityServiceFragmentType.INTUITIVE,
AccessibilityServiceFragmentType.BOUNCE,
})
- public @interface AccessibilityServiceFragmentType {
+ private @interface AccessibilityServiceFragmentType {
int LEGACY = 0;
int INVISIBLE = 1;
int INTUITIVE = 2;
@@ -99,11 +148,61 @@
ShortcutMenuMode.LAUNCH,
ShortcutMenuMode.EDIT,
})
- public @interface ShortcutMenuMode {
+ private @interface ShortcutMenuMode {
int LAUNCH = 0;
int EDIT = 1;
}
+ /**
+ * Annotation for align the element index of white listing feature
+ * {@code WHITE_LISTING_FEATURES}.
+ *
+ * {@code COMPONENT_ID} is to get the service component name.
+ * {@code LABEL_ID} is to get the service label text.
+ * {@code ICON_ID} is to get the service icon.
+ * {@code FRAGMENT_TYPE} is to get the service fragment type.
+ * {@code SETTINGS_KEY} is to get the service settings key.
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ WhiteListingFeatureElementIndex.COMPONENT_ID,
+ WhiteListingFeatureElementIndex.LABEL_ID,
+ WhiteListingFeatureElementIndex.ICON_ID,
+ WhiteListingFeatureElementIndex.FRAGMENT_TYPE,
+ WhiteListingFeatureElementIndex.SETTINGS_KEY,
+ })
+ @interface WhiteListingFeatureElementIndex {
+ int COMPONENT_ID = 0;
+ int LABEL_ID = 1;
+ int ICON_ID = 2;
+ int FRAGMENT_TYPE = 3;
+ int SETTINGS_KEY = 4;
+ }
+
+ private static final String[][] WHITE_LISTING_FEATURES = {
+ {
+ COLOR_INVERSION_COMPONENT_NAME.flattenToString(),
+ String.valueOf(R.string.color_inversion_feature_name),
+ String.valueOf(R.drawable.ic_accessibility_color_inversion),
+ String.valueOf(AccessibilityServiceFragmentType.INTUITIVE),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED,
+ },
+ {
+ DALTONIZER_COMPONENT_NAME.flattenToString(),
+ String.valueOf(R.string.color_correction_feature_name),
+ String.valueOf(R.drawable.ic_accessibility_color_correction),
+ String.valueOf(AccessibilityServiceFragmentType.INTUITIVE),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
+ },
+ {
+ MAGNIFICATION_CONTROLLER_NAME,
+ String.valueOf(R.string.accessibility_magnification_chooser_text),
+ String.valueOf(R.drawable.ic_accessibility_magnification),
+ String.valueOf(AccessibilityServiceFragmentType.INVISIBLE),
+ Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
+ },
+ };
+
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -115,10 +214,14 @@
mShortcutType = getIntent().getIntExtra(AccessibilityManager.EXTRA_SHORTCUT_TYPE,
ACCESSIBILITY_BUTTON);
+ if ((mShortcutType != ACCESSIBILITY_BUTTON)
+ && (mShortcutType != ACCESSIBILITY_SHORTCUT_KEY)) {
+ throw new IllegalStateException("Unexpected shortcut type: " + mShortcutType);
+ }
+
mTargets.addAll(getServiceTargets(this, mShortcutType));
- // TODO(b/146815548): Will add title to separate which one type
- mTargetAdapter = new TargetAdapter(mTargets);
+ mTargetAdapter = new TargetAdapter(mTargets, mShortcutType);
mAlertDialog = new AlertDialog.Builder(this)
.setAdapter(mTargetAdapter, /* listener= */ null)
.setPositiveButton(
@@ -159,6 +262,20 @@
private static List<AccessibilityButtonTarget> getServiceTargets(@NonNull Context context,
@ShortcutType int shortcutType) {
+ final List<AccessibilityButtonTarget> targets = new ArrayList<>();
+ targets.addAll(getAccessibilityServiceTargets(context));
+ targets.addAll(getWhiteListingServiceTargets(context));
+
+ final AccessibilityManager ams = (AccessibilityManager) context.getSystemService(
+ Context.ACCESSIBILITY_SERVICE);
+ final List<String> requiredTargets = ams.getAccessibilityShortcutTargets(shortcutType);
+ targets.removeIf(target -> !requiredTargets.contains(target.getId()));
+
+ return targets;
+ }
+
+ private static List<AccessibilityButtonTarget> getAccessibilityServiceTargets(
+ @NonNull Context context) {
final AccessibilityManager ams = (AccessibilityManager) context.getSystemService(
Context.ACCESSIBILITY_SERVICE);
final List<AccessibilityServiceInfo> installedServices =
@@ -169,29 +286,74 @@
final List<AccessibilityButtonTarget> targets = new ArrayList<>(installedServices.size());
for (AccessibilityServiceInfo info : installedServices) {
- if ((info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0) {
- targets.add(new AccessibilityButtonTarget(context, info));
- }
- }
-
- final List<String> requiredTargets = ams.getAccessibilityShortcutTargets(shortcutType);
- targets.removeIf(target -> !requiredTargets.contains(target.getId()));
-
- // TODO(b/146815874): Will replace it with white list services.
- if (Settings.Secure.getInt(context.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, 0) == 1) {
- final AccessibilityButtonTarget magnificationTarget = new AccessibilityButtonTarget(
- context,
- MAGNIFICATION_COMPONENT_ID,
- R.string.accessibility_magnification_chooser_text,
- R.drawable.ic_accessibility_magnification,
- AccessibilityServiceFragmentType.INTUITIVE);
- targets.add(magnificationTarget);
+ targets.add(new AccessibilityButtonTarget(context, info));
}
return targets;
}
+ private static List<AccessibilityButtonTarget> getWhiteListingServiceTargets(
+ @NonNull Context context) {
+ final List<AccessibilityButtonTarget> targets = new ArrayList<>();
+
+ for (int i = 0; i < WHITE_LISTING_FEATURES.length; i++) {
+ final AccessibilityButtonTarget target = new AccessibilityButtonTarget(
+ context,
+ WHITE_LISTING_FEATURES[i][COMPONENT_ID],
+ Integer.parseInt(WHITE_LISTING_FEATURES[i][LABEL_ID]),
+ Integer.parseInt(WHITE_LISTING_FEATURES[i][ICON_ID]),
+ Integer.parseInt(WHITE_LISTING_FEATURES[i][FRAGMENT_TYPE]));
+ targets.add(target);
+ }
+
+ return targets;
+ }
+
+ private static boolean isWhiteListingServiceEnabled(@NonNull Context context,
+ AccessibilityButtonTarget target) {
+
+ for (int i = 0; i < WHITE_LISTING_FEATURES.length; i++) {
+ if (WHITE_LISTING_FEATURES[i][COMPONENT_ID].equals(target.getId())) {
+ return Settings.Secure.getInt(context.getContentResolver(),
+ WHITE_LISTING_FEATURES[i][SETTINGS_KEY],
+ /* settingsValueOff */ 0) == /* settingsValueOn */ 1;
+ }
+ }
+
+ return false;
+ }
+
+ private static boolean isWhiteListingService(String componentId) {
+ for (int i = 0; i < WHITE_LISTING_FEATURES.length; i++) {
+ if (WHITE_LISTING_FEATURES[i][COMPONENT_ID].equals(componentId)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private void setWhiteListingServiceEnabled(String componentId, int settingsValue) {
+ for (int i = 0; i < WHITE_LISTING_FEATURES.length; i++) {
+ if (WHITE_LISTING_FEATURES[i][COMPONENT_ID].equals(componentId)) {
+ Settings.Secure.putInt(getContentResolver(),
+ WHITE_LISTING_FEATURES[i][SETTINGS_KEY], settingsValue);
+ return;
+ }
+ }
+ }
+
+ private void disableService(ComponentName componentName) {
+ final String componentId = componentName.flattenToString();
+
+ if (isWhiteListingService(componentId)) {
+ setWhiteListingServiceEnabled(componentName.flattenToString(),
+ /* settingsValueOff */ 0);
+ } else {
+ setAccessibilityServiceState(this, componentName, /* enabled= */ false);
+ }
+ }
+
private static class ViewHolder {
ImageView mIconView;
TextView mLabelView;
@@ -203,16 +365,21 @@
private static class TargetAdapter extends BaseAdapter {
@ShortcutMenuMode
private int mShortcutMenuMode = ShortcutMenuMode.LAUNCH;
+ @ShortcutType
+ private int mShortcutButtonType;
private List<AccessibilityButtonTarget> mButtonTargets;
- TargetAdapter(List<AccessibilityButtonTarget> targets) {
+ TargetAdapter(List<AccessibilityButtonTarget> targets,
+ @ShortcutType int shortcutButtonType) {
this.mButtonTargets = targets;
+ this.mShortcutButtonType = shortcutButtonType;
}
- void setShortcutMenuMode(int shortcutMenuMode) {
+ void setShortcutMenuMode(@ShortcutMenuMode int shortcutMenuMode) {
mShortcutMenuMode = shortcutMenuMode;
}
+ @ShortcutMenuMode
int getShortcutMenuMode() {
return mShortcutMenuMode;
}
@@ -269,8 +436,10 @@
switch (target.getFragmentType()) {
case AccessibilityServiceFragmentType.LEGACY:
+ updateLegacyActionItemVisibility(context, holder);
+ break;
case AccessibilityServiceFragmentType.INVISIBLE:
- updateLegacyOrInvisibleActionItemVisibility(context, holder);
+ updateInvisibleActionItemVisibility(context, holder);
break;
case AccessibilityServiceFragmentType.INTUITIVE:
updateIntuitiveActionItemVisibility(context, holder, target);
@@ -283,9 +452,23 @@
}
}
- private void updateLegacyOrInvisibleActionItemVisibility(@NonNull Context context,
+ private void updateLegacyActionItemVisibility(@NonNull Context context,
@NonNull ViewHolder holder) {
- final boolean isEditMenuMode = mShortcutMenuMode == ShortcutMenuMode.EDIT;
+ final boolean isLaunchMenuMode = (mShortcutMenuMode == ShortcutMenuMode.LAUNCH);
+ final boolean isHardwareButtonTriggered =
+ (mShortcutButtonType == ACCESSIBILITY_SHORTCUT_KEY);
+
+ holder.mLabelView.setEnabled(isLaunchMenuMode || isHardwareButtonTriggered);
+ holder.mViewItem.setEnabled(isLaunchMenuMode || isHardwareButtonTriggered);
+ holder.mViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item));
+ holder.mViewItem.setVisibility(View.VISIBLE);
+ holder.mSwitchItem.setVisibility(View.GONE);
+ holder.mItemContainer.setVisibility(isLaunchMenuMode ? View.GONE : View.VISIBLE);
+ }
+
+ private void updateInvisibleActionItemVisibility(@NonNull Context context,
+ @NonNull ViewHolder holder) {
+ final boolean isEditMenuMode = (mShortcutMenuMode == ShortcutMenuMode.EDIT);
holder.mViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item));
holder.mViewItem.setVisibility(View.VISIBLE);
@@ -295,18 +478,21 @@
private void updateIntuitiveActionItemVisibility(@NonNull Context context,
@NonNull ViewHolder holder, AccessibilityButtonTarget target) {
- final boolean isEditMenuMode = mShortcutMenuMode == ShortcutMenuMode.EDIT;
+ final boolean isEditMenuMode = (mShortcutMenuMode == ShortcutMenuMode.EDIT);
+ final boolean isServiceEnabled = isWhiteListingService(target.getId())
+ ? isWhiteListingServiceEnabled(context, target)
+ : isAccessibilityServiceEnabled(context, target);
holder.mViewItem.setImageDrawable(context.getDrawable(R.drawable.ic_delete_item));
holder.mViewItem.setVisibility(isEditMenuMode ? View.VISIBLE : View.GONE);
holder.mSwitchItem.setVisibility(isEditMenuMode ? View.GONE : View.VISIBLE);
- holder.mSwitchItem.setChecked(!isEditMenuMode && isServiceEnabled(context, target));
+ holder.mSwitchItem.setChecked(!isEditMenuMode && isServiceEnabled);
holder.mItemContainer.setVisibility(View.VISIBLE);
}
private void updateBounceActionItemVisibility(@NonNull Context context,
@NonNull ViewHolder holder) {
- final boolean isEditMenuMode = mShortcutMenuMode == ShortcutMenuMode.EDIT;
+ final boolean isEditMenuMode = (mShortcutMenuMode == ShortcutMenuMode.EDIT);
holder.mViewItem.setImageDrawable(
isEditMenuMode ? context.getDrawable(R.drawable.ic_delete_item)
@@ -357,7 +543,7 @@
}
}
- private static boolean isServiceEnabled(@NonNull Context context,
+ private static boolean isAccessibilityServiceEnabled(@NonNull Context context,
AccessibilityButtonTarget target) {
final AccessibilityManager ams = (AccessibilityManager) context.getSystemService(
Context.ACCESSIBILITY_SERVICE);
@@ -375,28 +561,129 @@
}
private void onTargetSelected(AdapterView<?> parent, View view, int position, long id) {
- Settings.Secure.putString(getContentResolver(),
- Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
- mTargets.get(position).getId());
- // TODO(b/146969684): notify accessibility button clicked.
+ final AccessibilityButtonTarget target = mTargets.get(position);
+ switch (target.getFragmentType()) {
+ case AccessibilityServiceFragmentType.LEGACY:
+ onLegacyTargetSelected(target);
+ break;
+ case AccessibilityServiceFragmentType.INVISIBLE:
+ onInvisibleTargetSelected(target);
+ break;
+ case AccessibilityServiceFragmentType.INTUITIVE:
+ onIntuitiveTargetSelected(target);
+ break;
+ case AccessibilityServiceFragmentType.BOUNCE:
+ // Do nothing
+ break;
+ default:
+ throw new IllegalStateException("Unexpected fragment type");
+ }
+
mAlertDialog.dismiss();
}
+ private void onLegacyTargetSelected(AccessibilityButtonTarget target) {
+ if (mShortcutType == ACCESSIBILITY_BUTTON) {
+ final AccessibilityManager ams = (AccessibilityManager) getSystemService(
+ Context.ACCESSIBILITY_SERVICE);
+ ams.notifyAccessibilityButtonClicked(getDisplayId(), target.getId());
+ } else if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
+ switchServiceState(target);
+ }
+ }
+
+ private void onInvisibleTargetSelected(AccessibilityButtonTarget target) {
+ final AccessibilityManager ams = (AccessibilityManager) getSystemService(
+ Context.ACCESSIBILITY_SERVICE);
+ ams.notifyAccessibilityButtonClicked(getDisplayId(), target.getId());
+ }
+
+ private void onIntuitiveTargetSelected(AccessibilityButtonTarget target) {
+ switchServiceState(target);
+ }
+
+ private void switchServiceState(AccessibilityButtonTarget target) {
+ final ComponentName componentName =
+ ComponentName.unflattenFromString(target.getId());
+ final String componentId = componentName.flattenToString();
+
+ if (isWhiteListingService(componentId)) {
+ setWhiteListingServiceEnabled(componentId,
+ isWhiteListingServiceEnabled(this, target)
+ ? /* settingsValueOff */ 0
+ : /* settingsValueOn */ 1);
+ } else {
+ setAccessibilityServiceState(this, componentName,
+ /* enabled= */!isAccessibilityServiceEnabled(this, target));
+ }
+ }
+
private void onTargetDeleted(AdapterView<?> parent, View view, int position, long id) {
- // TODO(b/147027236): Will discuss with UX designer what UX behavior about deleting item
- // is good for user.
- mReadyToBeDisabledTargets.add(mTargets.get(position));
+ final AccessibilityButtonTarget target = mTargets.get(position);
+ final ComponentName targetComponentName =
+ ComponentName.unflattenFromString(target.getId());
+
+ switch (target.getFragmentType()) {
+ case AccessibilityServiceFragmentType.LEGACY:
+ onLegacyTargetDeleted(targetComponentName);
+ break;
+ case AccessibilityServiceFragmentType.INVISIBLE:
+ onInvisibleTargetDeleted(targetComponentName);
+ break;
+ case AccessibilityServiceFragmentType.INTUITIVE:
+ onIntuitiveTargetDeleted(targetComponentName);
+ break;
+ case AccessibilityServiceFragmentType.BOUNCE:
+ // Do nothing
+ break;
+ default:
+ throw new IllegalStateException("Unexpected fragment type");
+ }
+
mTargets.remove(position);
mTargetAdapter.notifyDataSetChanged();
+
+ if (mTargets.isEmpty()) {
+ mAlertDialog.dismiss();
+ }
+ }
+
+ private void onLegacyTargetDeleted(ComponentName componentName) {
+ if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
+ optOutValueFromSettings(this, ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE, componentName);
+ }
+ }
+
+ private void onInvisibleTargetDeleted(ComponentName componentName) {
+ if (mShortcutType == ACCESSIBILITY_BUTTON) {
+ optOutValueFromSettings(this, ACCESSIBILITY_BUTTON_USER_TYPE, componentName);
+
+ if (!hasValueInSettings(this,
+ ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE, componentName)) {
+ disableService(componentName);
+ }
+ } else if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
+ optOutValueFromSettings(this, ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE, componentName);
+
+ if (!hasValueInSettings(this,
+ ACCESSIBILITY_BUTTON_USER_TYPE, componentName)) {
+ disableService(componentName);
+ }
+ }
+ }
+
+ private void onIntuitiveTargetDeleted(ComponentName componentName) {
+ if (mShortcutType == ACCESSIBILITY_BUTTON) {
+ optOutValueFromSettings(this, ACCESSIBILITY_BUTTON_USER_TYPE, componentName);
+ } else if (mShortcutType == ACCESSIBILITY_SHORTCUT_KEY) {
+ optOutValueFromSettings(this, ACCESSIBILITY_SHORTCUT_KEY_USER_TYPE, componentName);
+ }
}
private void onCancelButtonClicked() {
- resetAndUpdateTargets();
-
mTargetAdapter.setShortcutMenuMode(ShortcutMenuMode.LAUNCH);
mTargetAdapter.notifyDataSetChanged();
- mAlertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.GONE);
mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText(
getString(R.string.edit_accessibility_shortcut_menu_button));
@@ -407,49 +694,181 @@
mTargetAdapter.setShortcutMenuMode(ShortcutMenuMode.EDIT);
mTargetAdapter.notifyDataSetChanged();
- mAlertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setText(
+ mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText(
getString(R.string.cancel_accessibility_shortcut_menu_button));
- mAlertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.VISIBLE);
- mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText(
- getString(R.string.save_accessibility_shortcut_menu_button));
-
- updateDialogListeners();
- }
-
- private void onSaveButtonClicked() {
- disableTargets();
- resetAndUpdateTargets();
-
- mTargetAdapter.setShortcutMenuMode(ShortcutMenuMode.LAUNCH);
- mTargetAdapter.notifyDataSetChanged();
-
- mAlertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setVisibility(View.GONE);
- mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setText(
- getString(R.string.edit_accessibility_shortcut_menu_button));
updateDialogListeners();
}
private void updateDialogListeners() {
final boolean isEditMenuMode =
- mTargetAdapter.getShortcutMenuMode() == ShortcutMenuMode.EDIT;
+ (mTargetAdapter.getShortcutMenuMode() == ShortcutMenuMode.EDIT);
- mAlertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).setOnClickListener(
- view -> onCancelButtonClicked());
mAlertDialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(
- isEditMenuMode ? view -> onSaveButtonClicked() : view -> onEditButtonClicked());
+ isEditMenuMode ? view -> onCancelButtonClicked() : view -> onEditButtonClicked());
mAlertDialog.getListView().setOnItemClickListener(
isEditMenuMode ? this::onTargetDeleted : this::onTargetSelected);
}
- private void disableTargets() {
- for (AccessibilityButtonTarget service : mReadyToBeDisabledTargets) {
- // TODO(b/146967898): disable services.
+ /**
+ * @return the set of enabled accessibility services for {@param userId}. If there are no
+ * services, it returns the unmodifiable {@link Collections#emptySet()}.
+ */
+ private Set<ComponentName> getEnabledServicesFromSettings(Context context, int userId) {
+ final String enabledServicesSetting = Settings.Secure.getStringForUser(
+ context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ userId);
+ if (TextUtils.isEmpty(enabledServicesSetting)) {
+ return Collections.emptySet();
+ }
+
+ final Set<ComponentName> enabledServices = new HashSet<>();
+ final TextUtils.StringSplitter colonSplitter =
+ new TextUtils.SimpleStringSplitter(SERVICES_SEPARATOR);
+ colonSplitter.setString(enabledServicesSetting);
+
+ for (String componentNameString : colonSplitter) {
+ final ComponentName enabledService = ComponentName.unflattenFromString(
+ componentNameString);
+ if (enabledService != null) {
+ enabledServices.add(enabledService);
+ }
+ }
+
+ return enabledServices;
+ }
+
+ /**
+ * Changes an accessibility component's state.
+ */
+ private void setAccessibilityServiceState(Context context, ComponentName componentName,
+ boolean enabled) {
+ setAccessibilityServiceState(context, componentName, enabled, UserHandle.myUserId());
+ }
+
+ /**
+ * Changes an accessibility component's state for {@param userId}.
+ */
+ private void setAccessibilityServiceState(Context context, ComponentName componentName,
+ boolean enabled, int userId) {
+ Set<ComponentName> enabledServices = getEnabledServicesFromSettings(
+ context, userId);
+
+ if (enabledServices.isEmpty()) {
+ enabledServices = new ArraySet<>(/* capacity= */ 1);
+ }
+
+ if (enabled) {
+ enabledServices.add(componentName);
+ } else {
+ enabledServices.remove(componentName);
+ }
+
+ final StringBuilder enabledServicesBuilder = new StringBuilder();
+ for (ComponentName enabledService : enabledServices) {
+ enabledServicesBuilder.append(enabledService.flattenToString());
+ enabledServicesBuilder.append(
+ SERVICES_SEPARATOR);
+ }
+
+ final int enabledServicesBuilderLength = enabledServicesBuilder.length();
+ if (enabledServicesBuilderLength > 0) {
+ enabledServicesBuilder.deleteCharAt(enabledServicesBuilderLength - 1);
+ }
+
+ Settings.Secure.putStringForUser(context.getContentResolver(),
+ Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+ enabledServicesBuilder.toString(), userId);
+ }
+
+ /**
+ * Opts out component name into colon-separated {@code shortcutType} key's string in Settings.
+ *
+ * @param context The current context.
+ * @param shortcutType The preferred shortcut type user selected.
+ * @param componentName The component name that need to be opted out from Settings.
+ */
+ private void optOutValueFromSettings(
+ Context context, @UserShortcutType int shortcutType, ComponentName componentName) {
+ final StringJoiner joiner = new StringJoiner(String.valueOf(SERVICES_SEPARATOR));
+ final String targetsKey = convertToKey(shortcutType);
+ final String targetsValue = Settings.Secure.getString(context.getContentResolver(),
+ targetsKey);
+
+ if (TextUtils.isEmpty(targetsValue)) {
+ return;
+ }
+
+ sStringColonSplitter.setString(targetsValue);
+ while (sStringColonSplitter.hasNext()) {
+ final String name = sStringColonSplitter.next();
+ if (TextUtils.isEmpty(name) || (componentName.flattenToString()).equals(name)) {
+ continue;
+ }
+ joiner.add(name);
+ }
+
+ Settings.Secure.putString(context.getContentResolver(), targetsKey, joiner.toString());
+ }
+
+ /**
+ * Returns if component name existed in Settings.
+ *
+ * @param context The current context.
+ * @param shortcutType The preferred shortcut type user selected.
+ * @param componentName The component name that need to be checked existed in Settings.
+ * @return {@code true} if componentName existed in Settings.
+ */
+ private boolean hasValueInSettings(Context context, @UserShortcutType int shortcutType,
+ @NonNull ComponentName componentName) {
+ final String targetKey = convertToKey(shortcutType);
+ final String targetString = Settings.Secure.getString(context.getContentResolver(),
+ targetKey);
+
+ if (TextUtils.isEmpty(targetString)) {
+ return false;
+ }
+
+ sStringColonSplitter.setString(targetString);
+ while (sStringColonSplitter.hasNext()) {
+ final String name = sStringColonSplitter.next();
+ if ((componentName.flattenToString()).equals(name)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Converts {@link UserShortcutType} to key in Settings.
+ *
+ * @param type The shortcut type.
+ * @return Mapping key in Settings.
+ */
+ private String convertToKey(@UserShortcutType int type) {
+ switch (type) {
+ case UserShortcutType.SOFTWARE:
+ return Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT;
+ case UserShortcutType.HARDWARE:
+ return Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE;
+ case UserShortcutType.TRIPLETAP:
+ return Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED;
+ default:
+ throw new IllegalArgumentException(
+ "Unsupported user shortcut type: " + type);
}
}
- private void resetAndUpdateTargets() {
- mTargets.clear();
- mTargets.addAll(getServiceTargets(this, mShortcutType));
+ private static @UserShortcutType int convertToUserType(@ShortcutType int type) {
+ switch (type) {
+ case ACCESSIBILITY_BUTTON:
+ return UserShortcutType.SOFTWARE;
+ case ACCESSIBILITY_SHORTCUT_KEY:
+ return UserShortcutType.HARDWARE;
+ default:
+ throw new IllegalArgumentException(
+ "Unsupported shortcut type:" + type);
+ }
}
}
diff --git a/core/java/com/android/internal/app/BlockedAppActivity.java b/core/java/com/android/internal/app/BlockedAppActivity.java
new file mode 100644
index 0000000..fbdbbfb
--- /dev/null
+++ b/core/java/com/android/internal/app/BlockedAppActivity.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import com.android.internal.R;
+
+/**
+ * A dialog shown to the user when they try to launch an app that is not allowed in lock task
+ * mode. The intent to start this activity must be created with the static factory method provided
+ * below.
+ */
+public class BlockedAppActivity extends AlertActivity {
+
+ private static final String TAG = "BlockedAppActivity";
+ private static final String PACKAGE_NAME = "com.android.internal.app";
+ private static final String EXTRA_BLOCKED_PACKAGE = PACKAGE_NAME + ".extra.BLOCKED_PACKAGE";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Intent intent = getIntent();
+ int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, /* defaultValue= */ -1);
+ if (userId < 0) {
+ Slog.wtf(TAG, "Invalid user: " + userId);
+ finish();
+ return;
+ }
+
+ String packageName = intent.getStringExtra(EXTRA_BLOCKED_PACKAGE);
+ if (TextUtils.isEmpty(packageName)) {
+ Slog.wtf(TAG, "Invalid package: " + packageName);
+ finish();
+ return;
+ }
+
+ CharSequence appLabel = getAppLabel(userId, packageName);
+
+ mAlertParams.mTitle = getString(R.string.app_blocked_title);
+ mAlertParams.mMessage = getString(R.string.app_blocked_message, appLabel);
+ mAlertParams.mPositiveButtonText = getString(android.R.string.ok);
+ setupAlert();
+ }
+
+ private CharSequence getAppLabel(int userId, String packageName) {
+ PackageManager pm = getPackageManager();
+ try {
+ ApplicationInfo aInfo =
+ pm.getApplicationInfoAsUser(packageName, /* flags= */ 0, userId);
+ return aInfo.loadLabel(pm);
+ } catch (PackageManager.NameNotFoundException ne) {
+ Slog.e(TAG, "Package " + packageName + " not found", ne);
+ }
+ return packageName;
+ }
+
+
+ /** Creates an intent that launches {@link BlockedAppActivity}. */
+ public static Intent createIntent(int userId, String packageName) {
+ return new Intent()
+ .setClassName("android", BlockedAppActivity.class.getName())
+ .putExtra(Intent.EXTRA_USER_ID, userId)
+ .putExtra(EXTRA_BLOCKED_PACKAGE, packageName);
+ }
+}
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 1cc6d78..b6b548c 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -2434,11 +2434,6 @@
FooterViewHolder(View itemView) {
super(itemView);
}
-
- public void setHeight(int height) {
- itemView.setLayoutParams(
- new RecyclerView.LayoutParams(LayoutParams.MATCH_PARENT, height));
- }
}
/**
@@ -2471,7 +2466,7 @@
private boolean mLayoutRequested = false;
- private FooterViewHolder mFooterViewHolder;
+ private int mFooterHeight = 0;
private static final int VIEW_TYPE_DIRECT_SHARE = 0;
private static final int VIEW_TYPE_NORMAL = 1;
@@ -2490,9 +2485,6 @@
mChooserListAdapter = wrappedAdapter;
mLayoutInflater = LayoutInflater.from(ChooserActivity.this);
- mFooterViewHolder = new FooterViewHolder(
- new Space(ChooserActivity.this.getApplicationContext()));
-
mShowAzLabelIfPoss = getNumSheetExpansions() < NUM_EXPANSIONS_TO_HIDE_AZ_LABEL;
wrappedAdapter.registerDataSetObserver(new DataSetObserver() {
@@ -2511,7 +2503,7 @@
}
public void setFooterHeight(int height) {
- mFooterViewHolder.setHeight(height);
+ mFooterHeight = height;
}
/**
@@ -2614,7 +2606,10 @@
case VIEW_TYPE_CALLER_AND_RANK:
return createItemGroupViewHolder(viewType, parent);
case VIEW_TYPE_FOOTER:
- return mFooterViewHolder;
+ Space sp = new Space(parent.getContext());
+ sp.setLayoutParams(new RecyclerView.LayoutParams(
+ LayoutParams.MATCH_PARENT, mFooterHeight));
+ return new FooterViewHolder(sp);
default:
// Since we catch all possible viewTypes above, no chance this is being called.
return null;
diff --git a/core/java/com/android/internal/app/IAppOpsService.aidl b/core/java/com/android/internal/app/IAppOpsService.aidl
index 4e764fc..dabaf5a 100644
--- a/core/java/com/android/internal/app/IAppOpsService.aidl
+++ b/core/java/com/android/internal/app/IAppOpsService.aidl
@@ -58,10 +58,12 @@
List<AppOpsManager.PackageOps> getPackagesForOps(in int[] ops);
@UnsupportedAppUsage
List<AppOpsManager.PackageOps> getOpsForPackage(int uid, String packageName, in int[] ops);
- void getHistoricalOps(int uid, String packageName, in List<String> ops, long beginTimeMillis,
- long endTimeMillis, int flags, in RemoteCallback callback);
- void getHistoricalOpsFromDiskRaw(int uid, String packageName, in List<String> ops,
- long beginTimeMillis, long endTimeMillis, int flags, in RemoteCallback callback);
+ void getHistoricalOps(int uid, String packageName, String featureId, in List<String> ops,
+ int filter, long beginTimeMillis, long endTimeMillis, int flags,
+ in RemoteCallback callback);
+ void getHistoricalOpsFromDiskRaw(int uid, String packageName, String featureId,
+ in List<String> ops, int filter, long beginTimeMillis, long endTimeMillis, int flags,
+ in RemoteCallback callback);
void offsetHistory(long duration);
void setHistoryParameters(int mode, long baseSnapshotInterval, int compressionStep);
void addHistoricalOps(in AppOpsManager.HistoricalOps ops);
diff --git a/core/java/com/android/internal/app/ISoundTriggerService.aidl b/core/java/com/android/internal/app/ISoundTriggerService.aidl
index d94294f..74bfb96 100644
--- a/core/java/com/android/internal/app/ISoundTriggerService.aidl
+++ b/core/java/com/android/internal/app/ISoundTriggerService.aidl
@@ -61,10 +61,6 @@
int setParameter(in ParcelUuid soundModelId, in ModelParams modelParam,
int value);
- /**
- * @throws UnsupportedOperationException if hal or model do not support this API.
- * @throws IllegalArgumentException if invalid model handle or parameter is passed.
- */
int getParameter(in ParcelUuid soundModelId, in ModelParams modelParam);
@nullable SoundTrigger.ModelParamRange queryParameter(in ParcelUuid soundModelId,
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index f462f5d..be2d1d6 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -26,6 +26,7 @@
import com.android.internal.app.IVoiceInteractor;
import com.android.internal.app.IVoiceInteractionSessionListener;
import android.hardware.soundtrigger.IRecognitionStatusCallback;
+import android.hardware.soundtrigger.ModelParams;
import android.hardware.soundtrigger.SoundTrigger;
import android.service.voice.IVoiceInteractionService;
import android.service.voice.IVoiceInteractionSession;
@@ -91,6 +92,49 @@
*/
int stopRecognition(in IVoiceInteractionService service, int keyphraseId,
in IRecognitionStatusCallback callback);
+ /**
+ * Set a model specific ModelParams with the given value. This
+ * parameter will keep its value for the duration the model is loaded regardless of starting and
+ * stopping recognition. Once the model is unloaded, the value will be lost.
+ * queryParameter should be checked first before calling this method.
+ *
+ * @param service The current VoiceInteractionService.
+ * @param keyphraseId The unique identifier for the keyphrase.
+ * @param modelParam ModelParams
+ * @param value Value to set
+ * @return - {@link SoundTrigger#STATUS_OK} in case of success
+ * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
+ * - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter
+ * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or
+ * if API is not supported by HAL
+ */
+ int setParameter(in IVoiceInteractionService service, int keyphraseId,
+ in ModelParams modelParam, int value);
+ /**
+ * Get a model specific ModelParams. This parameter will keep its value
+ * for the duration the model is loaded regardless of starting and stopping recognition.
+ * Once the model is unloaded, the value will be lost. If the value is not set, a default
+ * value is returned. See ModelParams for parameter default values.
+ * queryParameter should be checked first before calling this method.
+ *
+ * @param service The current VoiceInteractionService.
+ * @param keyphraseId The unique identifier for the keyphrase.
+ * @param modelParam ModelParams
+ * @return value of parameter
+ */
+ int getParameter(in IVoiceInteractionService service, int keyphraseId,
+ in ModelParams modelParam);
+ /**
+ * Determine if parameter control is supported for the given model handle.
+ * This method should be checked prior to calling setParameter or getParameter.
+ *
+ * @param service The current VoiceInteractionService.
+ * @param keyphraseId The unique identifier for the keyphrase.
+ * @param modelParam ModelParams
+ * @return supported range of parameter, null if not supported
+ */
+ @nullable SoundTrigger.ModelParamRange queryParameter(in IVoiceInteractionService service,
+ int keyphraseId, in ModelParams modelParam);
/**
* @return the component name for the currently active voice interaction service
diff --git a/core/java/com/android/internal/compat/AndroidBuildClassifier.java b/core/java/com/android/internal/compat/AndroidBuildClassifier.java
new file mode 100644
index 0000000..0b937fa
--- /dev/null
+++ b/core/java/com/android/internal/compat/AndroidBuildClassifier.java
@@ -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.
+ */
+
+package com.android.internal.compat;
+
+import android.os.Build;
+
+/**
+ * Platform private class for determining the type of Android build installed.
+ *
+ */
+public class AndroidBuildClassifier {
+
+ public boolean isDebuggableBuild() {
+ return Build.IS_DEBUGGABLE;
+ }
+
+ public boolean isFinalBuild() {
+ return "REL".equals(Build.VERSION.CODENAME);
+ }
+}
diff --git a/core/java/com/android/internal/compat/IOverrideValidator.aidl b/core/java/com/android/internal/compat/IOverrideValidator.aidl
new file mode 100644
index 0000000..add4708
--- /dev/null
+++ b/core/java/com/android/internal/compat/IOverrideValidator.aidl
@@ -0,0 +1,38 @@
+/*
+ * 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 com.android.internal.compat;
+
+import android.content.pm.ApplicationInfo;
+
+import com.android.internal.compat.OverrideAllowedState;
+
+/**
+ * Platform private API for determining whether a changeId can be overridden.
+ *
+ * {@hide}
+ */
+interface IOverrideValidator
+{
+ /**
+ * Validation function.
+ * @param changeId id of the change to be toggled on or off.
+ * @param packageName package of the app for which the change should be overridden.
+ * @return {@link OverrideAllowedState} specifying whether the change can be overridden for
+ * the given package or a reason why not.
+ */
+ OverrideAllowedState getOverrideAllowedState(long changeId, String packageName);
+}
diff --git a/core/java/com/android/internal/compat/IPlatformCompat.aidl b/core/java/com/android/internal/compat/IPlatformCompat.aidl
index 7dcb12c..4c203d3 100644
--- a/core/java/com/android/internal/compat/IPlatformCompat.aidl
+++ b/core/java/com/android/internal/compat/IPlatformCompat.aidl
@@ -17,6 +17,7 @@
package com.android.internal.compat;
import android.content.pm.ApplicationInfo;
+import com.android.internal.compat.IOverrideValidator;
import java.util.Map;
parcelable CompatibilityChangeConfig;
@@ -195,4 +196,9 @@
* @return An array of {@link CompatChangeInfo} known to the service.
*/
CompatibilityChangeInfo[] listAllChanges();
+
+ /**
+ * Get an instance that can determine whether a changeid can be overridden for a package name.
+ */
+ IOverrideValidator getOverrideValidator();
}
diff --git a/media/java/android/media/RouteSessionInfo.aidl b/core/java/com/android/internal/compat/OverrideAllowedState.aidl
similarity index 81%
copy from media/java/android/media/RouteSessionInfo.aidl
copy to core/java/com/android/internal/compat/OverrideAllowedState.aidl
index fb5d836..10ceac7 100644
--- a/media/java/android/media/RouteSessionInfo.aidl
+++ b/core/java/com/android/internal/compat/OverrideAllowedState.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 The Android Open Source Project
+ * 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.
@@ -14,6 +14,6 @@
* limitations under the License.
*/
-package android.media;
+package com.android.internal.compat;
-parcelable RouteSessionInfo;
+parcelable OverrideAllowedState;
\ No newline at end of file
diff --git a/core/java/com/android/internal/compat/OverrideAllowedState.java b/core/java/com/android/internal/compat/OverrideAllowedState.java
new file mode 100644
index 0000000..56216c2
--- /dev/null
+++ b/core/java/com/android/internal/compat/OverrideAllowedState.java
@@ -0,0 +1,153 @@
+/*
+ * 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 com.android.internal.compat;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This class contains all the possible override allowed states.
+ */
+public final class OverrideAllowedState implements Parcelable {
+ @IntDef({
+ ALLOWED,
+ DISABLED_NOT_DEBUGGABLE,
+ DISABLED_NON_TARGET_SDK,
+ DISABLED_TARGET_SDK_TOO_HIGH,
+ PACKAGE_DOES_NOT_EXIST
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface State {
+ }
+
+ /**
+ * Change can be overridden.
+ */
+ public static final int ALLOWED = 0;
+ /**
+ * Change cannot be overridden, due to the app not being debuggable.
+ */
+ public static final int DISABLED_NOT_DEBUGGABLE = 1;
+ /**
+ * Change cannot be overridden, due to the build being non-debuggable and the change being
+ * non-targetSdk.
+ */
+ public static final int DISABLED_NON_TARGET_SDK = 2;
+ /**
+ * Change cannot be overridden, due to the app's targetSdk being above the change's targetSdk.
+ */
+ public static final int DISABLED_TARGET_SDK_TOO_HIGH = 3;
+ /**
+ * Package does not exist.
+ */
+ public static final int PACKAGE_DOES_NOT_EXIST = 4;
+
+ @State
+ public final int state;
+ public final int appTargetSdk;
+ public final int changeIdTargetSdk;
+
+ private OverrideAllowedState(Parcel parcel) {
+ state = parcel.readInt();
+ appTargetSdk = parcel.readInt();
+ changeIdTargetSdk = parcel.readInt();
+ }
+
+ public OverrideAllowedState(@State int state, int appTargetSdk, int changeIdTargetSdk) {
+ this.state = state;
+ this.appTargetSdk = appTargetSdk;
+ this.changeIdTargetSdk = changeIdTargetSdk;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(state);
+ out.writeInt(appTargetSdk);
+ out.writeInt(changeIdTargetSdk);
+ }
+
+ /**
+ * Enforces the policy for overriding compat changes.
+ *
+ * @param changeId the change id that was attempted to be overridden.
+ * @param packageName the package for which the attempt was made.
+ * @throws SecurityException if the policy forbids this operation.
+ */
+ public void enforce(long changeId, String packageName)
+ throws SecurityException {
+ switch (state) {
+ case ALLOWED:
+ return;
+ case DISABLED_NOT_DEBUGGABLE:
+ throw new SecurityException(
+ "Cannot override a change on a non-debuggable app and user build.");
+ case DISABLED_NON_TARGET_SDK:
+ throw new SecurityException(
+ "Cannot override a default enabled/disabled change on a user build.");
+ case DISABLED_TARGET_SDK_TOO_HIGH:
+ throw new SecurityException(String.format(
+ "Cannot override %1$d for %2$s because the app's targetSdk (%3$d) is "
+ + "above the change's targetSdk threshold (%4$d)",
+ changeId, packageName, appTargetSdk, changeIdTargetSdk));
+ case PACKAGE_DOES_NOT_EXIST:
+ throw new SecurityException(String.format(
+ "Cannot override %1$d for %2$s because the package does not exist, and "
+ + "the change is targetSdk gated.",
+ changeId, packageName));
+ }
+ }
+
+ public static final @NonNull
+ Parcelable.Creator<OverrideAllowedState> CREATOR =
+ new Parcelable.Creator<OverrideAllowedState>() {
+ public OverrideAllowedState createFromParcel(Parcel parcel) {
+ OverrideAllowedState info = new OverrideAllowedState(parcel);
+ return info;
+ }
+
+ public OverrideAllowedState[] newArray(int size) {
+ return new OverrideAllowedState[size];
+ }
+ };
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (!(obj instanceof OverrideAllowedState)) {
+ return false;
+ }
+ OverrideAllowedState otherState = (OverrideAllowedState) obj;
+ return state == otherState.state
+ && appTargetSdk == otherState.appTargetSdk
+ && changeIdTargetSdk == otherState.changeIdTargetSdk;
+ }
+}
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 9e23c28..de64573 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -350,7 +350,19 @@
* (boolean) Whether screenshot flow going to the corner (instead of shown in a notification)
* is enabled.
*/
- public static final String SCREENSHOT_CORNER_FLOW = "screenshot_corner_flow";
+ public static final String SCREENSHOT_CORNER_FLOW = "enable_screenshot_corner_flow";
+
+ /**
+ * (boolean) Whether scrolling screenshots are enabled.
+ */
+ public static final String SCREENSHOT_SCROLLING_ENABLED = "enable_screenshot_scrolling";
+
+ // Flags related to Nav Bar
+
+ /**
+ * (boolean) Whether to force the Nav Bar handle to remain opaque.
+ */
+ public static final String NAV_BAR_HANDLE_FORCE_OPAQUE = "nav_bar_handle_force_opaque";
private SystemUiDeviceConfigFlags() {
}
diff --git a/core/java/com/android/internal/content/FileSystemProvider.java b/core/java/com/android/internal/content/FileSystemProvider.java
index 221cd6d..ef9b3d10 100644
--- a/core/java/com/android/internal/content/FileSystemProvider.java
+++ b/core/java/com/android/internal/content/FileSystemProvider.java
@@ -561,7 +561,7 @@
flags |= Document.FLAG_SUPPORTS_MOVE;
if (shouldBlockFromTree(docId)) {
- flags |= Document.FLAG_DIR_BLOCKS_TREE;
+ flags |= Document.FLAG_DIR_BLOCKS_OPEN_DOCUMENT_TREE;
}
} else {
diff --git a/core/java/com/android/internal/infra/AndroidFuture.java b/core/java/com/android/internal/infra/AndroidFuture.java
index b250578..9f15d89 100644
--- a/core/java/com/android/internal/infra/AndroidFuture.java
+++ b/core/java/com/android/internal/infra/AndroidFuture.java
@@ -75,6 +75,7 @@
private static final boolean DEBUG = false;
private static final String LOG_TAG = AndroidFuture.class.getSimpleName();
+ private static final StackTraceElement[] EMPTY_STACK_TRACE = new StackTraceElement[0];
private final @NonNull Object mLock = new Object();
@GuardedBy("mLock")
@@ -95,15 +96,7 @@
// Done
if (in.readBoolean()) {
// Failed
- try {
- in.readException();
- } catch (Throwable e) {
- completeExceptionally(e);
- }
- if (!isCompletedExceptionally()) {
- throw new IllegalStateException(
- "Error unparceling AndroidFuture: exception expected");
- }
+ completeExceptionally(unparcelException(in));
} else {
// Success
complete((T) in.readValue(null));
@@ -512,14 +505,9 @@
T result;
try {
result = get();
- } catch (Exception t) {
- // Exceptions coming out of get() are wrapped in ExecutionException, which is not
- // handled by Parcel.
- if (t instanceof ExecutionException && t.getCause() instanceof Exception) {
- t = (Exception) t.getCause();
- }
+ } catch (Throwable t) {
dest.writeBoolean(true);
- dest.writeException(t);
+ parcelException(dest, unwrapExecutionException(t));
return;
}
dest.writeBoolean(false);
@@ -528,22 +516,76 @@
dest.writeStrongBinder(new IAndroidFuture.Stub() {
@Override
public void complete(AndroidFuture resultContainer) {
+ boolean changed;
try {
- AndroidFuture.this.complete((T) resultContainer.get());
+ changed = AndroidFuture.this.complete((T) resultContainer.get());
} catch (Throwable t) {
- // If resultContainer was completed exceptionally, get() wraps the
- // underlying exception in an ExecutionException. Unwrap it now to avoid
- // double-layering ExecutionExceptions.
- if (t instanceof ExecutionException && t.getCause() != null) {
- t = t.getCause();
- }
- completeExceptionally(t);
+ changed = completeExceptionally(unwrapExecutionException(t));
+ }
+ if (!changed) {
+ Log.w(LOG_TAG, "Remote result " + resultContainer
+ + " ignored, as local future is already completed: "
+ + AndroidFuture.this);
}
}
}.asBinder());
}
}
+ /**
+ * Exceptions coming out of {@link #get} are wrapped in {@link ExecutionException}
+ */
+ Throwable unwrapExecutionException(Throwable t) {
+ return t instanceof ExecutionException
+ ? t.getCause()
+ : t;
+ }
+
+ /**
+ * Alternative to {@link Parcel#writeException} that stores the stack trace, in a
+ * way consistent with the binder IPC exception propagation behavior.
+ */
+ private static void parcelException(Parcel p, @Nullable Throwable t) {
+ p.writeBoolean(t == null);
+ if (t == null) {
+ return;
+ }
+
+ p.writeInt(Parcel.getExceptionCode(t));
+ p.writeString(t.getClass().getName());
+ p.writeString(t.getMessage());
+ p.writeStackTrace(t);
+ parcelException(p, t.getCause());
+ }
+
+ /**
+ * @see #parcelException
+ */
+ private static @Nullable Throwable unparcelException(Parcel p) {
+ if (p.readBoolean()) {
+ return null;
+ }
+
+ int exCode = p.readInt();
+ String cls = p.readString();
+ String msg = p.readString();
+ String stackTrace = p.readInt() > 0 ? p.readString() : "\t<stack trace unavailable>";
+ msg += "\n" + stackTrace;
+
+ Exception ex = p.createExceptionOrNull(exCode, msg);
+ if (ex == null) {
+ ex = new RuntimeException(cls + ": " + msg);
+ }
+ ex.setStackTrace(EMPTY_STACK_TRACE);
+
+ Throwable cause = unparcelException(p);
+ if (cause != null) {
+ ex.initCause(ex);
+ }
+
+ return ex;
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/core/java/com/android/internal/logging/UiEventLogger.java b/core/java/com/android/internal/logging/UiEventLogger.java
index cca97f6..3a450de 100644
--- a/core/java/com/android/internal/logging/UiEventLogger.java
+++ b/core/java/com/android/internal/logging/UiEventLogger.java
@@ -16,6 +16,9 @@
package com.android.internal.logging;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
/**
* Logging interface for UI events. Normal implementation is UiEventLoggerImpl.
* For testing, use fake implementation UiEventLoggerFake.
@@ -26,13 +29,24 @@
/** Put your Event IDs in enums that implement this interface, and document them using the
* UiEventEnum annotation.
* Event IDs must be globally unique. This will be enforced by tooling (forthcoming).
- * OEMs should use event IDs above 100000.
+ * OEMs should use event IDs above 100000 and below 1000000 (1 million).
*/
interface UiEventEnum {
int getId();
}
+
/**
- * Log a simple event, with no package or instance ID.
+ * Log a simple event, with no package information. Does nothing if event.getId() <= 0.
+ * @param event an enum implementing UiEventEnum interface.
*/
- void log(UiEventEnum eventID);
+ void log(@NonNull UiEventEnum event);
+
+ /**
+ * Log an event with package information. Does nothing if event.getId() <= 0.
+ * Give both uid and packageName if both are known, but one may be omitted if unknown.
+ * @param event an enum implementing UiEventEnum interface.
+ * @param uid the uid of the relevant app, if known (0 otherwise).
+ * @param packageName the package name of the relevant app, if known (null otherwise).
+ */
+ void log(@NonNull UiEventEnum event, int uid, @Nullable String packageName);
}
diff --git a/core/java/com/android/internal/logging/UiEventLoggerImpl.java b/core/java/com/android/internal/logging/UiEventLoggerImpl.java
index e64fba2..bdf460c 100644
--- a/core/java/com/android/internal/logging/UiEventLoggerImpl.java
+++ b/core/java/com/android/internal/logging/UiEventLoggerImpl.java
@@ -24,14 +24,16 @@
* See UiEventReported atom in atoms.proto for more context.
*/
public class UiEventLoggerImpl implements UiEventLogger {
- /**
- * Log a simple event, with no package or instance ID.
- */
@Override
public void log(UiEventEnum event) {
+ log(event, 0, null);
+ }
+
+ @Override
+ public void log(UiEventEnum event, int uid, String packageName) {
final int eventID = event.getId();
if (eventID > 0) {
- StatsLog.write(StatsLog.UI_EVENT_REPORTED, eventID, 0, null);
+ StatsLog.write(StatsLog.UI_EVENT_REPORTED, eventID, uid, packageName);
}
}
}
diff --git a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
index 92e9bbb..6be5b81 100644
--- a/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
+++ b/core/java/com/android/internal/logging/testing/UiEventLoggerFake.java
@@ -30,7 +30,7 @@
/**
* Immutable data class used to record fake log events.
*/
- public class FakeUiEvent {
+ public static class FakeUiEvent {
public final int eventId;
public final int uid;
public final String packageName;
@@ -44,15 +44,20 @@
private Queue<FakeUiEvent> mLogs = new LinkedList<FakeUiEvent>();
- @Override
- public void log(UiEventEnum event) {
- final int eventId = event.getId();
- if (eventId > 0) {
- mLogs.offer(new FakeUiEvent(eventId, 0, null));
- }
- }
-
public Queue<FakeUiEvent> getLogs() {
return mLogs;
}
+
+ @Override
+ public void log(UiEventEnum event) {
+ log(event, 0, null);
+ }
+
+ @Override
+ public void log(UiEventEnum event, int uid, String packageName) {
+ final int eventId = event.getId();
+ if (eventId > 0) {
+ mLogs.offer(new FakeUiEvent(eventId, uid, packageName));
+ }
+ }
}
diff --git a/core/java/com/android/internal/os/ProcessCpuTracker.java b/core/java/com/android/internal/os/ProcessCpuTracker.java
index f7b7742..5e3c5df 100644
--- a/core/java/com/android/internal/os/ProcessCpuTracker.java
+++ b/core/java/com/android/internal/os/ProcessCpuTracker.java
@@ -24,6 +24,7 @@
import static android.os.Process.PROC_SPACE_TERM;
import android.compat.annotation.UnsupportedAppUsage;
+import android.os.CpuUsageProto;
import android.os.Process;
import android.os.StrictMode;
import android.os.SystemClock;
@@ -31,10 +32,12 @@
import android.system.Os;
import android.system.OsConstants;
import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
import com.android.internal.util.FastPrintWriter;
import java.io.File;
+import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.SimpleDateFormat;
@@ -740,6 +743,63 @@
return mWorkingProcs.get(index);
}
+ /** Dump cpuinfo in protobuf format. */
+ public final void dumpProto(FileDescriptor fd) {
+ final long now = SystemClock.uptimeMillis();
+ final ProtoOutputStream proto = new ProtoOutputStream(fd);
+ final long currentLoadToken = proto.start(CpuUsageProto.CURRENT_LOAD);
+ proto.write(CpuUsageProto.Load.LOAD1, mLoad1);
+ proto.write(CpuUsageProto.Load.LOAD5, mLoad5);
+ proto.write(CpuUsageProto.Load.LOAD15, mLoad15);
+ proto.end(currentLoadToken);
+
+ proto.write(CpuUsageProto.NOW, now);
+ proto.write(CpuUsageProto.LAST_SAMPLE_TIME, mLastSampleTime);
+ proto.write(CpuUsageProto.CURRENT_SAMPLE_TIME, mCurrentSampleTime);
+ proto.write(CpuUsageProto.LAST_SAMPLE_REAL_TIME, mLastSampleRealTime);
+ proto.write(CpuUsageProto.CURRENT_SAMPLE_REAL_TIME, mCurrentSampleRealTime);
+ proto.write(CpuUsageProto.LAST_SAMPLE_WALL_TIME, mLastSampleWallTime);
+ proto.write(CpuUsageProto.CURRENT_SAMPLE_WALL_TIME, mCurrentSampleWallTime);
+
+ proto.write(CpuUsageProto.TOTAL_USER_TIME, mRelUserTime);
+ proto.write(CpuUsageProto.TOTAL_SYSTEM_TIME, mRelSystemTime);
+ proto.write(CpuUsageProto.TOTAL_IOWAIT_TIME, mRelIoWaitTime);
+ proto.write(CpuUsageProto.TOTAL_IRQ_TIME, mRelIrqTime);
+ proto.write(CpuUsageProto.TOTAL_SOFT_IRQ_TIME, mRelSoftIrqTime);
+ proto.write(CpuUsageProto.TOTAL_IDLE_TIME, mRelIdleTime);
+ final int totalTime = mRelUserTime + mRelSystemTime + mRelIoWaitTime
+ + mRelIrqTime + mRelSoftIrqTime + mRelIdleTime;
+ proto.write(CpuUsageProto.TOTAL_TIME, totalTime);
+
+ for (Stats st : mWorkingProcs) {
+ dumpProcessCpuProto(proto, st, null);
+ if (!st.removed && st.workingThreads != null) {
+ for (Stats tst : st.workingThreads) {
+ dumpProcessCpuProto(proto, tst, st);
+ }
+ }
+ }
+ proto.flush();
+ }
+
+ private static void dumpProcessCpuProto(ProtoOutputStream proto, Stats st, Stats proc) {
+ long statToken = proto.start(CpuUsageProto.PROCESSES);
+ proto.write(CpuUsageProto.Stat.UID, st.uid);
+ proto.write(CpuUsageProto.Stat.PID, st.pid);
+ proto.write(CpuUsageProto.Stat.NAME, st.name);
+ proto.write(CpuUsageProto.Stat.ADDED, st.added);
+ proto.write(CpuUsageProto.Stat.REMOVED, st.removed);
+ proto.write(CpuUsageProto.Stat.UPTIME, st.rel_uptime);
+ proto.write(CpuUsageProto.Stat.USER_TIME, st.rel_utime);
+ proto.write(CpuUsageProto.Stat.SYSTEM_TIME, st.rel_stime);
+ proto.write(CpuUsageProto.Stat.MINOR_FAULTS, st.rel_minfaults);
+ proto.write(CpuUsageProto.Stat.MAJOR_FAULTS, st.rel_majfaults);
+ if (proc != null) {
+ proto.write(CpuUsageProto.Stat.PARENT_PID, proc.pid);
+ }
+ proto.end(statToken);
+ }
+
final public String printCurrentLoad() {
StringWriter sw = new StringWriter();
PrintWriter pw = new FastPrintWriter(sw, false, 128);
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 2248b88..f0a346a 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -145,6 +145,11 @@
/** The lower file system should be bind mounted directly on external storage */
public static final int MOUNT_EXTERNAL_PASS_THROUGH = IVold.REMOUNT_MODE_PASS_THROUGH;
+ /** Use the regular scoped storage filesystem, but Android/ should be writable.
+ * Used to support the applications hosting DownloadManager and the MTP server.
+ */
+ public static final int MOUNT_EXTERNAL_ANDROID_WRITABLE = IVold.REMOUNT_MODE_ANDROID_WRITABLE;
+
/** Number of bytes sent to the Zygote over USAP pipes or the pool event FD */
static final int USAP_MANAGEMENT_MESSAGE_BYTES = 8;
diff --git a/core/java/com/android/internal/os/ZygoteArguments.java b/core/java/com/android/internal/os/ZygoteArguments.java
index d349954..37f570b 100644
--- a/core/java/com/android/internal/os/ZygoteArguments.java
+++ b/core/java/com/android/internal/os/ZygoteArguments.java
@@ -376,6 +376,8 @@
mMountExternal = Zygote.MOUNT_EXTERNAL_LEGACY;
} else if (arg.equals("--mount-external-pass-through")) {
mMountExternal = Zygote.MOUNT_EXTERNAL_PASS_THROUGH;
+ } else if (arg.equals("--mount-external-android-writable")) {
+ mMountExternal = Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE;
} else if (arg.equals("--query-abi-list")) {
mAbiListQuery = true;
} else if (arg.equals("--get-pid")) {
diff --git a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
index 6c7e3dc..0f50596 100644
--- a/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
+++ b/core/java/com/android/internal/telephony/IPhoneStateListener.aidl
@@ -16,6 +16,7 @@
package com.android.internal.telephony;
+import android.telephony.BarringInfo;
import android.telephony.CallAttributes;
import android.telephony.CellIdentity;
import android.telephony.CellInfo;
@@ -62,4 +63,7 @@
void onOutgoingEmergencySms(in EmergencyNumber sentEmergencyNumber);
void onCallDisconnectCauseChanged(in int disconnectCause, in int preciseDisconnectCause);
void onImsCallDisconnectCauseChanged(in ImsReasonInfo imsReasonInfo);
+ void onRegistrationFailed(in CellIdentity cellIdentity,
+ String chosenPlmn, int domain, int causeCode, int additionalCauseCode);
+ void onBarringInfoChanged(in BarringInfo barringInfo);
}
diff --git a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
index 4e40503..47752c5 100644
--- a/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
+++ b/core/java/com/android/internal/telephony/ITelephonyRegistry.aidl
@@ -19,6 +19,7 @@
import android.content.Intent;
import android.net.LinkProperties;
import android.net.NetworkCapabilities;
+import android.telephony.BarringInfo;
import android.telephony.CallQuality;
import android.telephony.CellIdentity;
import android.telephony.CellInfo;
@@ -63,7 +64,7 @@
void notifyDataActivity(int state);
void notifyDataActivityForSubscriber(in int subId, int state);
void notifyDataConnectionForSubscriber(
- int phoneId, int subId, String apnType, in PreciseDataConnectionState preciseState);
+ int phoneId, int subId, int apnType, in PreciseDataConnectionState preciseState);
@UnsupportedAppUsage
void notifyDataConnectionFailed(String apnType);
// Uses CellIdentity which is Parcelable here; will convert to CellLocation in client.
@@ -75,7 +76,7 @@
int foregroundCallState, int backgroundCallState);
void notifyDisconnectCause(int phoneId, int subId, int disconnectCause,
int preciseDisconnectCause);
- void notifyPreciseDataConnectionFailed(int phoneId, int subId, String apnType, String apn,
+ void notifyPreciseDataConnectionFailed(int phoneId, int subId, int apnType, String apn,
int failCause);
void notifyCellInfoForSubscriber(in int subId, in List<CellInfo> cellInfo);
void notifySrvccStateChanged(in int subId, in int lteState);
@@ -97,4 +98,7 @@
void notifyCallQualityChanged(in CallQuality callQuality, int phoneId, int subId,
int callNetworkType);
void notifyImsDisconnectCause(int subId, in ImsReasonInfo imsReasonInfo);
+ void notifyRegistrationFailed(int slotIndex, int subId, in CellIdentity cellIdentity,
+ String chosenPlmn, int domain, int causeCode, int additionalCauseCode);
+ void notifyBarringInfoChanged(int slotIndex, int subId, in BarringInfo barringInfo);
}
diff --git a/telephony/java/com/android/internal/telephony/IWapPushManager.aidl b/core/java/com/android/internal/telephony/IWapPushManager.aidl
similarity index 82%
rename from telephony/java/com/android/internal/telephony/IWapPushManager.aidl
rename to core/java/com/android/internal/telephony/IWapPushManager.aidl
index 1c3df65..9f6851b 100644
--- a/telephony/java/com/android/internal/telephony/IWapPushManager.aidl
+++ b/core/java/com/android/internal/telephony/IWapPushManager.aidl
@@ -18,6 +18,7 @@
import android.content.Intent;
+/** @hide */
interface IWapPushManager {
/**
* Processes WAP push message and triggers the receiver application registered
@@ -26,11 +27,10 @@
int processMessage(String app_id, String content_type, in Intent intent);
/**
- * Add receiver application into the application ID table.
- * Returns true if inserting the information is successfull. Inserting the duplicated
+ * Adds receiver application into the application ID table.
+ * Returns true if inserting the information is successful. Inserting duplicated
* record in the application ID table is not allowed. Use update/delete method.
*/
- @UnsupportedAppUsage
boolean addPackage(String x_app_id, String content_type,
String package_name, String class_name,
int app_type, boolean need_signature, boolean further_processing);
@@ -39,17 +39,14 @@
* Updates receiver application that is last added.
* Returns true if updating the information is successfull.
*/
- @UnsupportedAppUsage
boolean updatePackage(String x_app_id, String content_type,
String package_name, String class_name,
int app_type, boolean need_signature, boolean further_processing);
/**
- * Delites receiver application information.
+ * Deletes receiver application information.
* Returns true if deleting is successfull.
*/
- @UnsupportedAppUsage
boolean deletePackage(String x_app_id, String content_type,
- String package_name, String class_name);
+ String package_name, String class_name);
}
-
diff --git a/core/java/com/android/internal/telephony/WapPushManagerParams.java b/core/java/com/android/internal/telephony/WapPushManagerParams.java
new file mode 100644
index 0000000..eafb8f1
--- /dev/null
+++ b/core/java/com/android/internal/telephony/WapPushManagerParams.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+import android.telephony.WapPushManagerConnector;
+
+/**
+ * WapPushManager constant value definitions.
+ * @hide
+ */
+public class WapPushManagerParams {
+ /**
+ * Application type activity
+ */
+ public static final int APP_TYPE_ACTIVITY = 0;
+
+ /**
+ * Application type service
+ */
+ public static final int APP_TYPE_SERVICE = 1;
+
+ /**
+ * Process Message return value
+ * Message is handled
+ */
+ public static final int MESSAGE_HANDLED = WapPushManagerConnector.RESULT_MESSAGE_HANDLED;
+
+ /**
+ * Process Message return value
+ * Application ID or content type was not found in the application ID table
+ */
+ public static final int APP_QUERY_FAILED = WapPushManagerConnector.RESULT_APP_QUERY_FAILED;
+
+ /**
+ * Process Message return value
+ * Receiver application signature check failed
+ */
+ public static final int SIGNATURE_NO_MATCH = WapPushManagerConnector.RESULT_SIGNATURE_NO_MATCH;
+
+ /**
+ * Process Message return value
+ * Receiver application was not found
+ */
+ public static final int INVALID_RECEIVER_NAME =
+ WapPushManagerConnector.RESULT_INVALID_RECEIVER_NAME;
+
+ /**
+ * Process Message return value
+ * Unknown exception
+ */
+ public static final int EXCEPTION_CAUGHT = WapPushManagerConnector.RESULT_EXCEPTION_CAUGHT;
+
+ /**
+ * Process Message return value
+ * Need further processing after WapPushManager message processing
+ */
+ public static final int FURTHER_PROCESSING = WapPushManagerConnector.RESULT_FURTHER_PROCESSING;
+}
diff --git a/core/java/com/android/internal/util/ConnectivityUtil.java b/core/java/com/android/internal/util/ConnectivityUtil.java
new file mode 100644
index 0000000..799352b
--- /dev/null
+++ b/core/java/com/android/internal/util/ConnectivityUtil.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import android.Manifest;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.location.LocationManager;
+import android.os.Binder;
+import android.os.Build;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+
+/**
+ * Utility methods for common functionality using by different networks.
+ *
+ * @hide
+ */
+public class ConnectivityUtil {
+
+ private static final String TAG = "ConnectivityUtil";
+
+ private final Context mContext;
+ private final AppOpsManager mAppOps;
+ private final UserManager mUserManager;
+
+ public ConnectivityUtil(Context context) {
+ mContext = context;
+ mAppOps = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE);
+ mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ }
+
+ /**
+ * API to determine if the caller has fine/coarse location permission (depending on
+ * config/targetSDK level) and the location mode is enabled for the user. SecurityException is
+ * thrown if the caller has no permission or the location mode is disabled.
+ * @param pkgName package name of the application requesting access
+ * @param featureId The feature in the package
+ * @param uid The uid of the package
+ * @param message A message describing why the permission was checked. Only needed if this is
+ * not inside of a two-way binder call from the data receiver
+ */
+ public void enforceLocationPermission(String pkgName, @Nullable String featureId, int uid,
+ @Nullable String message)
+ throws SecurityException {
+ checkPackage(uid, pkgName);
+
+ // Location mode must be enabled
+ if (!isLocationModeEnabled()) {
+ // Location mode is disabled, scan results cannot be returned
+ throw new SecurityException("Location mode is disabled for the device");
+ }
+
+ // LocationAccess by App: caller must have Coarse/Fine Location permission to have access to
+ // location information.
+ boolean canAppPackageUseLocation = checkCallersLocationPermission(pkgName, featureId,
+ uid, /* coarseForTargetSdkLessThanQ */ true, message);
+
+ // If neither caller or app has location access, there is no need to check
+ // any other permissions. Deny access to scan results.
+ if (!canAppPackageUseLocation) {
+ throw new SecurityException("UID " + uid + " has no location permission");
+ }
+ // If the User or profile is current, permission is granted
+ // Otherwise, uid must have INTERACT_ACROSS_USERS_FULL permission.
+ if (!isCurrentProfile(uid) && !checkInteractAcrossUsersFull(uid)) {
+ throw new SecurityException("UID " + uid + " profile not permitted");
+ }
+ }
+
+ /**
+ * Checks that calling process has android.Manifest.permission.ACCESS_FINE_LOCATION or
+ * android.Manifest.permission.ACCESS_COARSE_LOCATION (depending on config/targetSDK level)
+ * and a corresponding app op is allowed for this package and uid.
+ *
+ * @param pkgName PackageName of the application requesting access
+ * @param featureId The feature in the package
+ * @param uid The uid of the package
+ * @param coarseForTargetSdkLessThanQ If true and the targetSDK < Q then will check for COARSE
+ * else (false or targetSDK >= Q) then will check for FINE
+ * @param message A message describing why the permission was checked. Only needed if this is
+ * not inside of a two-way binder call from the data receiver
+ */
+ public boolean checkCallersLocationPermission(String pkgName, @Nullable String featureId,
+ int uid, boolean coarseForTargetSdkLessThanQ, @Nullable String message) {
+ boolean isTargetSdkLessThanQ = isTargetSdkLessThan(pkgName, Build.VERSION_CODES.Q, uid);
+
+ String permissionType = Manifest.permission.ACCESS_FINE_LOCATION;
+ if (coarseForTargetSdkLessThanQ && isTargetSdkLessThanQ) {
+ // Having FINE permission implies having COARSE permission (but not the reverse)
+ permissionType = Manifest.permission.ACCESS_COARSE_LOCATION;
+ }
+ if (getUidPermission(permissionType, uid)
+ == PackageManager.PERMISSION_DENIED) {
+ return false;
+ }
+
+ // Always checking FINE - even if will not enforce. This will record the request for FINE
+ // so that a location request by the app is surfaced to the user.
+ boolean isFineLocationAllowed = noteAppOpAllowed(
+ AppOpsManager.OPSTR_FINE_LOCATION, pkgName, featureId, uid, message);
+ if (isFineLocationAllowed) {
+ return true;
+ }
+ if (coarseForTargetSdkLessThanQ && isTargetSdkLessThanQ) {
+ return noteAppOpAllowed(AppOpsManager.OPSTR_COARSE_LOCATION, pkgName, featureId, uid,
+ message);
+ }
+ return false;
+ }
+
+ /**
+ * Retrieves a handle to LocationManager (if not already done) and check if location is enabled.
+ */
+ public boolean isLocationModeEnabled() {
+ LocationManager locationManager =
+ (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
+ try {
+ return locationManager.isLocationEnabledForUser(UserHandle.of(
+ getCurrentUser()));
+ } catch (Exception e) {
+ Log.e(TAG, "Failure to get location mode via API, falling back to settings", e);
+ return false;
+ }
+ }
+
+ private boolean isTargetSdkLessThan(String packageName, int versionCode, int callingUid) {
+ long ident = Binder.clearCallingIdentity();
+ try {
+ if (mContext.getPackageManager().getApplicationInfoAsUser(
+ packageName, 0,
+ UserHandle.getUserHandleForUid(callingUid)).targetSdkVersion
+ < versionCode) {
+ return true;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ // In case of exception, assume unknown app (more strict checking)
+ // Note: This case will never happen since checkPackage is
+ // called to verify validity before checking App's version.
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ return false;
+ }
+
+ private boolean noteAppOpAllowed(String op, String pkgName, @Nullable String featureId,
+ int uid, @Nullable String message) {
+ return mAppOps.noteOp(op, uid, pkgName, featureId, message) == AppOpsManager.MODE_ALLOWED;
+ }
+
+ private void checkPackage(int uid, String pkgName) throws SecurityException {
+ if (pkgName == null) {
+ throw new SecurityException("Checking UID " + uid + " but Package Name is Null");
+ }
+ mAppOps.checkPackage(uid, pkgName);
+ }
+
+ private boolean isCurrentProfile(int uid) {
+ UserHandle currentUser = UserHandle.of(getCurrentUser());
+ UserHandle callingUser = UserHandle.getUserHandleForUid(uid);
+ return currentUser.equals(callingUser)
+ || mUserManager.isSameProfileGroup(currentUser, callingUser);
+ }
+
+ private boolean checkInteractAcrossUsersFull(int uid) {
+ return getUidPermission(
+ android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, uid)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ @VisibleForTesting
+ protected int getCurrentUser() {
+ return ActivityManager.getCurrentUser();
+ }
+
+ private int getUidPermission(String permissionType, int uid) {
+ // We don't care about pid, pass in -1
+ return mContext.checkPermission(permissionType, -1, uid);
+ }
+}
diff --git a/core/java/com/android/internal/util/GrowingArrayUtils.java b/core/java/com/android/internal/util/GrowingArrayUtils.java
index 9f56366..597fe6b 100644
--- a/core/java/com/android/internal/util/GrowingArrayUtils.java
+++ b/core/java/com/android/internal/util/GrowingArrayUtils.java
@@ -16,7 +16,7 @@
package com.android.internal.util;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* A helper class that aims to provide comparable growth performance to ArrayList, but on primitive
diff --git a/core/java/com/android/internal/util/HexDump.java b/core/java/com/android/internal/util/HexDump.java
index 6ffc928..ad88dd6 100644
--- a/core/java/com/android/internal/util/HexDump.java
+++ b/core/java/com/android/internal/util/HexDump.java
@@ -17,7 +17,7 @@
package com.android.internal.util;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
public class HexDump
{
diff --git a/core/java/com/android/internal/util/IState.java b/core/java/com/android/internal/util/IState.java
index eb66e2c..07837bf 100644
--- a/core/java/com/android/internal/util/IState.java
+++ b/core/java/com/android/internal/util/IState.java
@@ -16,7 +16,7 @@
package com.android.internal.util;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Message;
/**
diff --git a/core/java/com/android/internal/util/IndentingPrintWriter.java b/core/java/com/android/internal/util/IndentingPrintWriter.java
index 03a555e..34c6a05 100644
--- a/core/java/com/android/internal/util/IndentingPrintWriter.java
+++ b/core/java/com/android/internal/util/IndentingPrintWriter.java
@@ -16,7 +16,8 @@
package com.android.internal.util;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
import java.io.PrintWriter;
import java.io.Writer;
import java.util.Arrays;
diff --git a/core/java/com/android/internal/util/JournaledFile.java b/core/java/com/android/internal/util/JournaledFile.java
index 065cc5b2..a9d8f72 100644
--- a/core/java/com/android/internal/util/JournaledFile.java
+++ b/core/java/com/android/internal/util/JournaledFile.java
@@ -16,7 +16,7 @@
package com.android.internal.util;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import java.io.File;
diff --git a/core/java/com/android/internal/util/LatencyTracker.java b/core/java/com/android/internal/util/LatencyTracker.java
index 27c2478..67cfc3a 100644
--- a/core/java/com/android/internal/util/LatencyTracker.java
+++ b/core/java/com/android/internal/util/LatencyTracker.java
@@ -121,7 +121,11 @@
}
public static boolean isEnabled(Context ctx) {
- return Build.IS_DEBUGGABLE && getInstance(ctx).mEnabled;
+ return getInstance(ctx).isEnabled();
+ }
+
+ public boolean isEnabled() {
+ return Build.IS_DEBUGGABLE && mEnabled;
}
/**
diff --git a/core/java/com/android/internal/util/MemInfoReader.java b/core/java/com/android/internal/util/MemInfoReader.java
index 580c2fa..5de77d9 100644
--- a/core/java/com/android/internal/util/MemInfoReader.java
+++ b/core/java/com/android/internal/util/MemInfoReader.java
@@ -16,7 +16,7 @@
package com.android.internal.util;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Debug;
import android.os.StrictMode;
diff --git a/core/java/com/android/internal/util/Preconditions.java b/core/java/com/android/internal/util/Preconditions.java
index 3fff5c2..408a7a8 100644
--- a/core/java/com/android/internal/util/Preconditions.java
+++ b/core/java/com/android/internal/util/Preconditions.java
@@ -18,7 +18,7 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.text.TextUtils;
import java.util.Collection;
@@ -126,7 +126,9 @@
* @param reference an object reference
* @return the non-null reference that was validated
* @throws NullPointerException if {@code reference} is null
+ * @deprecated - use {@link java.util.Objects.requireNonNull} instead.
*/
+ @Deprecated
@UnsupportedAppUsage
public static @NonNull <T> T checkNotNull(final T reference) {
if (reference == null) {
@@ -144,7 +146,9 @@
* be converted to a string using {@link String#valueOf(Object)}
* @return the non-null reference that was validated
* @throws NullPointerException if {@code reference} is null
+ * @deprecated - use {@link java.util.Objects.requireNonNull} instead.
*/
+ @Deprecated
@UnsupportedAppUsage
public static @NonNull <T> T checkNotNull(final T reference, final Object errorMessage) {
if (reference == null) {
@@ -154,26 +158,6 @@
}
/**
- * Ensures that an object reference passed as a parameter to the calling
- * method is not null.
- *
- * @param reference an object reference
- * @param messageTemplate a printf-style message template to use if the check fails; will
- * be converted to a string using {@link String#format(String, Object...)}
- * @param messageArgs arguments for {@code messageTemplate}
- * @return the non-null reference that was validated
- * @throws NullPointerException if {@code reference} is null
- */
- public static @NonNull <T> T checkNotNull(final T reference,
- final String messageTemplate,
- final Object... messageArgs) {
- if (reference == null) {
- throw new NullPointerException(String.format(messageTemplate, messageArgs));
- }
- return reference;
- }
-
- /**
* Ensures the truth of an expression involving the state of the calling
* instance, but not involving any parameters to the calling method.
*
diff --git a/core/java/com/android/internal/util/State.java b/core/java/com/android/internal/util/State.java
index 3c61e03..636378e 100644
--- a/core/java/com/android/internal/util/State.java
+++ b/core/java/com/android/internal/util/State.java
@@ -16,7 +16,7 @@
package com.android.internal.util;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Message;
/**
diff --git a/core/java/com/android/internal/util/StateMachine.java b/core/java/com/android/internal/util/StateMachine.java
index 6c217e5..0c24065 100644
--- a/core/java/com/android/internal/util/StateMachine.java
+++ b/core/java/com/android/internal/util/StateMachine.java
@@ -16,7 +16,7 @@
package com.android.internal.util;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
diff --git a/core/java/com/android/internal/util/XmlUtils.java b/core/java/com/android/internal/util/XmlUtils.java
index 8799e3d..c1be33a 100644
--- a/core/java/com/android/internal/util/XmlUtils.java
+++ b/core/java/com/android/internal/util/XmlUtils.java
@@ -16,10 +16,10 @@
package com.android.internal.util;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
import android.graphics.Bitmap.CompressFormat;
+import android.graphics.BitmapFactory;
import android.net.Uri;
import android.text.TextUtils;
import android.util.ArrayMap;
diff --git a/core/java/com/android/internal/view/ActionBarPolicy.java b/core/java/com/android/internal/view/ActionBarPolicy.java
index d18c35e..d16cb43 100644
--- a/core/java/com/android/internal/view/ActionBarPolicy.java
+++ b/core/java/com/android/internal/view/ActionBarPolicy.java
@@ -16,15 +16,15 @@
package com.android.internal.view;
-import com.android.internal.R;
-
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.os.Build;
+import com.android.internal.R;
+
/**
* Allows components to query for various configuration policy decisions
* about how the action bar should lay out and behave on the current device.
diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java
index 2ac2975..5dd3389b 100644
--- a/core/java/com/android/internal/view/BaseIWindow.java
+++ b/core/java/com/android/internal/view/BaseIWindow.java
@@ -16,6 +16,7 @@
package com.android.internal.view;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.input.InputManager;
@@ -34,8 +35,6 @@
import com.android.internal.os.IResultReceiver;
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
import java.io.IOException;
public class BaseIWindow extends IWindow.Stub {
diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java
index ececba1..6278d4a3 100644
--- a/core/java/com/android/internal/view/IInputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
diff --git a/core/java/com/android/internal/view/InputBindResult.java b/core/java/com/android/internal/view/InputBindResult.java
index 1b133d2..a5964b5 100644
--- a/core/java/com/android/internal/view/InputBindResult.java
+++ b/core/java/com/android/internal/view/InputBindResult.java
@@ -20,7 +20,7 @@
import android.annotation.IntDef;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java
index 0c057ea..a41048c 100644
--- a/core/java/com/android/internal/view/InputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/InputConnectionWrapper.java
@@ -19,7 +19,7 @@
import android.annotation.AnyThread;
import android.annotation.BinderThread;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.inputmethodservice.AbstractInputMethodService;
import android.os.Bundle;
import android.os.Handler;
diff --git a/core/java/com/android/internal/view/WindowManagerPolicyThread.java b/core/java/com/android/internal/view/WindowManagerPolicyThread.java
index b009a2d..6d691fc 100644
--- a/core/java/com/android/internal/view/WindowManagerPolicyThread.java
+++ b/core/java/com/android/internal/view/WindowManagerPolicyThread.java
@@ -16,7 +16,7 @@
package com.android.internal.view;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Looper;
/**
diff --git a/core/java/com/android/internal/view/menu/ActionMenu.java b/core/java/com/android/internal/view/menu/ActionMenu.java
index 977c1f6..6482629 100644
--- a/core/java/com/android/internal/view/menu/ActionMenu.java
+++ b/core/java/com/android/internal/view/menu/ActionMenu.java
@@ -16,10 +16,7 @@
package com.android.internal.view.menu;
-import java.util.ArrayList;
-import java.util.List;
-
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -30,6 +27,9 @@
import android.view.MenuItem;
import android.view.SubMenu;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* @hide
*/
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItem.java b/core/java/com/android/internal/view/menu/ActionMenuItem.java
index ed253d5..bd8bcb9 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuItem.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuItem.java
@@ -17,7 +17,7 @@
package com.android.internal.view.menu;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
import android.content.res.ColorStateList;
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItemView.java b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
index eb94db3..7622b93 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
@@ -16,7 +16,7 @@
package com.android.internal.view.menu;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
diff --git a/core/java/com/android/internal/view/menu/ContextMenuBuilder.java b/core/java/com/android/internal/view/menu/ContextMenuBuilder.java
index 3d3aceb..a9f5e47 100644
--- a/core/java/com/android/internal/view/menu/ContextMenuBuilder.java
+++ b/core/java/com/android/internal/view/menu/ContextMenuBuilder.java
@@ -16,7 +16,7 @@
package com.android.internal.view.menu;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.IBinder;
diff --git a/core/java/com/android/internal/view/menu/IconMenuItemView.java b/core/java/com/android/internal/view/menu/IconMenuItemView.java
index 3d888d3..539c71e 100644
--- a/core/java/com/android/internal/view/menu/IconMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/IconMenuItemView.java
@@ -16,13 +16,12 @@
package com.android.internal.view.menu;
-import com.android.internal.view.menu.MenuBuilder.ItemInvoker;
-
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.text.Layout;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.Gravity;
@@ -30,7 +29,8 @@
import android.view.View;
import android.view.ViewDebug;
import android.widget.TextView;
-import android.text.Layout;
+
+import com.android.internal.view.menu.MenuBuilder.ItemInvoker;
/**
* The item view for each item in the {@link IconMenuView}.
diff --git a/core/java/com/android/internal/view/menu/IconMenuView.java b/core/java/com/android/internal/view/menu/IconMenuView.java
index 6f26434..9e240db 100644
--- a/core/java/com/android/internal/view/menu/IconMenuView.java
+++ b/core/java/com/android/internal/view/menu/IconMenuView.java
@@ -16,9 +16,7 @@
package com.android.internal.view.menu;
-import com.android.internal.view.menu.MenuBuilder.ItemInvoker;
-
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -29,10 +27,12 @@
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.KeyEvent;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
-import android.view.LayoutInflater;
+
+import com.android.internal.view.menu.MenuBuilder.ItemInvoker;
import java.util.ArrayList;
diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java
index 0e07ca7..b31ae38 100644
--- a/core/java/com/android/internal/view/menu/MenuBuilder.java
+++ b/core/java/com/android/internal/view/menu/MenuBuilder.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
diff --git a/core/java/com/android/internal/view/menu/MenuDialogHelper.java b/core/java/com/android/internal/view/menu/MenuDialogHelper.java
index 88d0a03..d02b8f6 100644
--- a/core/java/com/android/internal/view/menu/MenuDialogHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuDialogHelper.java
@@ -16,9 +16,9 @@
package com.android.internal.view.menu;
-import android.annotation.UnsupportedAppUsage;
import android.app.AlertDialog;
import android.app.Dialog;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.DialogInterface;
import android.os.IBinder;
import android.view.KeyEvent;
diff --git a/core/java/com/android/internal/view/menu/MenuItemImpl.java b/core/java/com/android/internal/view/menu/MenuItemImpl.java
index 994a9c1..218f518 100644
--- a/core/java/com/android/internal/view/menu/MenuItemImpl.java
+++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java
@@ -16,10 +16,8 @@
package com.android.internal.view.menu;
-import com.android.internal.view.menu.MenuView.ItemView;
-
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
@@ -39,6 +37,8 @@
import android.view.ViewDebug;
import android.widget.LinearLayout;
+import com.android.internal.view.menu.MenuView.ItemView;
+
/**
* @hide
*/
diff --git a/core/java/com/android/internal/view/menu/MenuPopupHelper.java b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
index d00108e..bac6025 100644
--- a/core/java/com/android/internal/view/menu/MenuPopupHelper.java
+++ b/core/java/com/android/internal/view/menu/MenuPopupHelper.java
@@ -20,7 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StyleRes;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
diff --git a/core/java/com/android/internal/view/menu/MenuPresenter.java b/core/java/com/android/internal/view/menu/MenuPresenter.java
index c5df8ad..35b8fef 100644
--- a/core/java/com/android/internal/view/menu/MenuPresenter.java
+++ b/core/java/com/android/internal/view/menu/MenuPresenter.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Parcelable;
import android.view.ViewGroup;
diff --git a/core/java/com/android/internal/view/menu/MenuView.java b/core/java/com/android/internal/view/menu/MenuView.java
index 67a5530..a31c820 100644
--- a/core/java/com/android/internal/view/menu/MenuView.java
+++ b/core/java/com/android/internal/view/menu/MenuView.java
@@ -16,10 +16,7 @@
package com.android.internal.view.menu;
-import com.android.internal.view.menu.MenuBuilder;
-import com.android.internal.view.menu.MenuItemImpl;
-
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.drawable.Drawable;
/**
diff --git a/core/java/com/android/internal/view/menu/SubMenuBuilder.java b/core/java/com/android/internal/view/menu/SubMenuBuilder.java
index cf6d974..6eb215e 100644
--- a/core/java/com/android/internal/view/menu/SubMenuBuilder.java
+++ b/core/java/com/android/internal/view/menu/SubMenuBuilder.java
@@ -16,7 +16,7 @@
package com.android.internal.view.menu;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.Menu;
diff --git a/core/java/com/android/internal/widget/AbsActionBarView.java b/core/java/com/android/internal/widget/AbsActionBarView.java
index 9ccee7f..0f0c1a3 100644
--- a/core/java/com/android/internal/widget/AbsActionBarView.java
+++ b/core/java/com/android/internal/widget/AbsActionBarView.java
@@ -15,26 +15,25 @@
*/
package com.android.internal.widget;
-import com.android.internal.R;
-
-import android.util.TypedValue;
-import android.view.ContextThemeWrapper;
-import android.view.MotionEvent;
-import android.widget.ActionMenuPresenter;
-import android.widget.ActionMenuView;
-
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.ContextThemeWrapper;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.DecelerateInterpolator;
+import android.widget.ActionMenuPresenter;
+import android.widget.ActionMenuView;
+
+import com.android.internal.R;
public abstract class AbsActionBarView extends ViewGroup {
private static final TimeInterpolator sAlphaInterpolator = new DecelerateInterpolator();
diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java
index 78ed53f..051526e 100644
--- a/core/java/com/android/internal/widget/ActionBarContextView.java
+++ b/core/java/com/android/internal/widget/ActionBarContextView.java
@@ -15,13 +15,7 @@
*/
package com.android.internal.widget;
-import com.android.internal.R;
-
-import android.widget.ActionMenuPresenter;
-import android.widget.ActionMenuView;
-import com.android.internal.view.menu.MenuBuilder;
-
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
@@ -32,9 +26,14 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
+import android.widget.ActionMenuPresenter;
+import android.widget.ActionMenuView;
import android.widget.LinearLayout;
import android.widget.TextView;
+import com.android.internal.R;
+import com.android.internal.view.menu.MenuBuilder;
+
/**
* @hide
*/
diff --git a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
index e9e3cda..aca0b71 100644
--- a/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
+++ b/core/java/com/android/internal/widget/ActionBarOverlayLayout.java
@@ -18,7 +18,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
@@ -41,6 +41,7 @@
import android.view.WindowInsets;
import android.widget.OverScroller;
import android.widget.Toolbar;
+
import com.android.internal.view.menu.MenuPresenter;
/**
diff --git a/core/java/com/android/internal/widget/AlertDialogLayout.java b/core/java/com/android/internal/widget/AlertDialogLayout.java
index 7a01749..d879b6d 100644
--- a/core/java/com/android/internal/widget/AlertDialogLayout.java
+++ b/core/java/com/android/internal/widget/AlertDialogLayout.java
@@ -18,8 +18,8 @@
import android.annotation.AttrRes;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
import android.annotation.StyleRes;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
diff --git a/core/java/com/android/internal/widget/ButtonBarLayout.java b/core/java/com/android/internal/widget/ButtonBarLayout.java
index 0ca6743..ff13107 100644
--- a/core/java/com/android/internal/widget/ButtonBarLayout.java
+++ b/core/java/com/android/internal/widget/ButtonBarLayout.java
@@ -16,7 +16,7 @@
package com.android.internal.widget;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
diff --git a/core/java/com/android/internal/widget/CachingIconView.java b/core/java/com/android/internal/widget/CachingIconView.java
index 35bff6d..74ad815 100644
--- a/core/java/com/android/internal/widget/CachingIconView.java
+++ b/core/java/com/android/internal/widget/CachingIconView.java
@@ -18,7 +18,7 @@
import android.annotation.DrawableRes;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Bitmap;
diff --git a/core/java/com/android/internal/widget/DialogTitle.java b/core/java/com/android/internal/widget/DialogTitle.java
index 405436c..0bfd684 100644
--- a/core/java/com/android/internal/widget/DialogTitle.java
+++ b/core/java/com/android/internal/widget/DialogTitle.java
@@ -16,7 +16,7 @@
package com.android.internal.widget;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.TypedArray;
import android.text.Layout;
diff --git a/core/java/com/android/internal/widget/EditableInputConnection.java b/core/java/com/android/internal/widget/EditableInputConnection.java
index 2b648e9..ff3543c8 100644
--- a/core/java/com/android/internal/widget/EditableInputConnection.java
+++ b/core/java/com/android/internal/widget/EditableInputConnection.java
@@ -16,7 +16,7 @@
package com.android.internal.widget;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Bundle;
import android.text.Editable;
import android.text.method.KeyListener;
diff --git a/core/java/com/android/internal/widget/LinearLayoutWithDefaultTouchRecepient.java b/core/java/com/android/internal/widget/LinearLayoutWithDefaultTouchRecepient.java
index cc7911d..9ef9f69 100644
--- a/core/java/com/android/internal/widget/LinearLayoutWithDefaultTouchRecepient.java
+++ b/core/java/com/android/internal/widget/LinearLayoutWithDefaultTouchRecepient.java
@@ -16,12 +16,12 @@
package com.android.internal.widget;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
-import android.view.View;
import android.view.MotionEvent;
+import android.view.View;
import android.widget.LinearLayout;
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 4f4c8c3..f37a468 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -25,11 +25,11 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
import android.app.admin.DevicePolicyManager;
import android.app.admin.PasswordMetrics;
import android.app.trust.IStrongAuthTracker;
import android.app.trust.TrustManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -55,10 +55,10 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
-import com.google.android.collect.Lists;
-
import libcore.util.HexEncoding;
+import com.google.android.collect.Lists;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.security.MessageDigest;
@@ -1599,6 +1599,11 @@
public static final int STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN = 0x20;
/**
+ * Strong authentication is required to prepare for unattended upgrade.
+ */
+ public static final int STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE = 0x40;
+
+ /**
* Strong auth flags that do not prevent biometric methods from being accepted as auth.
* If any other flags are set, biometric authentication is disabled.
*/
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index 74a0aa3..4ddc782 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -19,7 +19,7 @@
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
diff --git a/core/java/com/android/internal/widget/LockSettingsInternal.java b/core/java/com/android/internal/widget/LockSettingsInternal.java
index dd05576..90a18ef 100644
--- a/core/java/com/android/internal/widget/LockSettingsInternal.java
+++ b/core/java/com/android/internal/widget/LockSettingsInternal.java
@@ -77,4 +77,34 @@
* @return the user password metrics.
*/
public abstract @Nullable PasswordMetrics getUserPasswordMetrics(int userHandle);
+
+ /**
+ * Prepare for reboot escrow. This triggers the strong auth to be required. After the escrow
+ * is complete as indicated by calling to the listener registered with {@link
+ * #setRebootEscrowListener}, then {@link #armRebootEscrow()} should be called before
+ * rebooting to apply the update.
+ */
+ public abstract void prepareRebootEscrow();
+
+ /**
+ * Registers a listener for when the RebootEscrow HAL has stored its data needed for rebooting
+ * for an OTA.
+ *
+ * @see RebootEscrowListener
+ * @param listener
+ */
+ public abstract void setRebootEscrowListener(RebootEscrowListener listener);
+
+ /**
+ * Requests that any data needed for rebooting is cleared from the RebootEscrow HAL.
+ */
+ public abstract void clearRebootEscrow();
+
+ /**
+ * Should be called immediately before rebooting for an update. This depends on {@link
+ * #prepareRebootEscrow()} having been called and the escrow completing.
+ *
+ * @return true if the arming worked
+ */
+ public abstract boolean armRebootEscrow();
}
diff --git a/core/java/com/android/internal/widget/NumericTextView.java b/core/java/com/android/internal/widget/NumericTextView.java
index d215670..c8f9011 100644
--- a/core/java/com/android/internal/widget/NumericTextView.java
+++ b/core/java/com/android/internal/widget/NumericTextView.java
@@ -16,7 +16,7 @@
package com.android.internal.widget;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java
index 37046af..dc8d57a 100644
--- a/core/java/com/android/internal/widget/PointerLocationView.java
+++ b/core/java/com/android/internal/widget/PointerLocationView.java
@@ -16,7 +16,7 @@
package com.android.internal.widget;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
diff --git a/core/java/com/android/internal/widget/PreferenceImageView.java b/core/java/com/android/internal/widget/PreferenceImageView.java
index 02a0b8d..43b6b5a 100644
--- a/core/java/com/android/internal/widget/PreferenceImageView.java
+++ b/core/java/com/android/internal/widget/PreferenceImageView.java
@@ -16,7 +16,7 @@
package com.android.internal.widget;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ImageView;
diff --git a/core/java/com/android/internal/widget/RebootEscrowListener.java b/core/java/com/android/internal/widget/RebootEscrowListener.java
new file mode 100644
index 0000000..1654532
--- /dev/null
+++ b/core/java/com/android/internal/widget/RebootEscrowListener.java
@@ -0,0 +1,32 @@
+/*
+ * 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 com.android.internal.widget;
+
+/**
+ * Private API to be notified about reboot escrow events.
+ *
+ * {@hide}
+ */
+public interface RebootEscrowListener {
+ /**
+ * Called when the preparation status has changed. When {@code prepared} is {@code true} the
+ * user has entered their lock screen knowledge factor (LSKF) and the HAL has confirmed that
+ * it is ready to retrieve the secret after a reboot. When {@code prepared} is {@code false}
+ * then those conditions are not true.
+ */
+ void onPreparedForReboot(boolean prepared);
+}
diff --git a/core/java/com/android/internal/widget/RecyclerView.java b/core/java/com/android/internal/widget/RecyclerView.java
index b66a7b4..d7a01c4 100644
--- a/core/java/com/android/internal/widget/RecyclerView.java
+++ b/core/java/com/android/internal/widget/RecyclerView.java
@@ -20,7 +20,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.TypedArray;
import android.database.Observable;
@@ -2011,13 +2011,27 @@
}
if (!dispatchNestedPreFling(velocityX, velocityY)) {
- final boolean canScroll = canScrollHorizontal || canScrollVertical;
- dispatchNestedFling(velocityX, velocityY, canScroll);
+ final View firstChild = mLayout.getChildAt(0);
+ final View lastChild = mLayout.getChildAt(mLayout.getChildCount() - 1);
+ boolean consumed = false;
+ if (velocityY < 0) {
+ consumed = getChildAdapterPosition(firstChild) > 0
+ || firstChild.getTop() < getPaddingTop();
+ }
+
+ if (velocityY > 0) {
+ consumed = getChildAdapterPosition(lastChild) < mAdapter.getItemCount() - 1
+ || lastChild.getBottom() > getHeight() - getPaddingBottom();
+ }
+
+ dispatchNestedFling(velocityX, velocityY, consumed);
if (mOnFlingListener != null && mOnFlingListener.onFling(velocityX, velocityY)) {
return true;
}
+ final boolean canScroll = canScrollHorizontal || canScrollVertical;
+
if (canScroll) {
velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity));
velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity));
diff --git a/core/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
index 1bb2ba2..e0c3823 100644
--- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java
+++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
@@ -187,7 +187,7 @@
public void setCollapsed(boolean collapsed) {
if (!isLaidOut()) {
- mOpenOnLayout = collapsed;
+ mOpenOnLayout = !collapsed;
} else {
smoothScrollTo(collapsed ? mCollapsibleHeight : 0, 0);
}
diff --git a/core/java/com/android/internal/widget/ScrollBarUtils.java b/core/java/com/android/internal/widget/ScrollBarUtils.java
index 982e315..3e9d697 100644
--- a/core/java/com/android/internal/widget/ScrollBarUtils.java
+++ b/core/java/com/android/internal/widget/ScrollBarUtils.java
@@ -16,7 +16,7 @@
package com.android.internal.widget;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
public class ScrollBarUtils {
diff --git a/core/java/com/android/internal/widget/ScrollingTabContainerView.java b/core/java/com/android/internal/widget/ScrollingTabContainerView.java
index 5d48ab9..aa0b0bb 100644
--- a/core/java/com/android/internal/widget/ScrollingTabContainerView.java
+++ b/core/java/com/android/internal/widget/ScrollingTabContainerView.java
@@ -15,13 +15,11 @@
*/
package com.android.internal.widget;
-import com.android.internal.view.ActionBarPolicy;
-
import android.animation.Animator;
import android.animation.ObjectAnimator;
import android.animation.TimeInterpolator;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActionBar;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.drawable.Drawable;
@@ -42,6 +40,8 @@
import android.widget.Spinner;
import android.widget.TextView;
+import com.android.internal.view.ActionBarPolicy;
+
/**
* This widget implements the dynamic action bar tab behavior that can change
* across different configurations or circumstances.
diff --git a/core/java/com/android/internal/widget/SlidingTab.java b/core/java/com/android/internal/widget/SlidingTab.java
index 4b5d624..5e6f3a4 100644
--- a/core/java/com/android/internal/widget/SlidingTab.java
+++ b/core/java/com/android/internal/widget/SlidingTab.java
@@ -16,7 +16,7 @@
package com.android.internal.widget;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
@@ -34,12 +34,12 @@
import android.view.ViewGroup;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
+import android.view.animation.Animation.AnimationListener;
import android.view.animation.LinearInterpolator;
import android.view.animation.TranslateAnimation;
-import android.view.animation.Animation.AnimationListener;
import android.widget.ImageView;
-import android.widget.TextView;
import android.widget.ImageView.ScaleType;
+import android.widget.TextView;
import com.android.internal.R;
diff --git a/core/java/com/android/internal/widget/TextViewInputDisabler.java b/core/java/com/android/internal/widget/TextViewInputDisabler.java
index 8d8f0fe..57806eb 100644
--- a/core/java/com/android/internal/widget/TextViewInputDisabler.java
+++ b/core/java/com/android/internal/widget/TextViewInputDisabler.java
@@ -16,7 +16,7 @@
package com.android.internal.widget;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.text.InputFilter;
import android.text.Spanned;
import android.widget.TextView;
diff --git a/core/java/com/android/internal/widget/ViewPager.java b/core/java/com/android/internal/widget/ViewPager.java
index 7d36b02..c8a86d1 100644
--- a/core/java/com/android/internal/widget/ViewPager.java
+++ b/core/java/com/android/internal/widget/ViewPager.java
@@ -18,7 +18,7 @@
import android.annotation.DrawableRes;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
diff --git a/core/java/com/android/server/BootReceiver.java b/core/java/com/android/server/BootReceiver.java
index 13bfc1b..31b5e49 100644
--- a/core/java/com/android/server/BootReceiver.java
+++ b/core/java/com/android/server/BootReceiver.java
@@ -44,21 +44,21 @@
import com.android.internal.util.XmlUtils;
import com.android.server.DropboxLogTags;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
import java.io.File;
import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
-import java.io.FileNotFoundException;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Iterator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
/**
* Performs a number of miscellaneous, non-system-critical actions
* after the system has finished booting.
@@ -424,7 +424,23 @@
for (String propPostfix : MOUNT_DURATION_PROPS_POSTFIX) {
int duration = SystemProperties.getInt("ro.boottime.init.mount_all." + propPostfix, 0);
if (duration != 0) {
- MetricsLogger.histogram(null, "boot_mount_all_duration_" + propPostfix, duration);
+ int eventType;
+ switch (propPostfix) {
+ case "early":
+ eventType = StatsLog.BOOT_TIME_EVENT_DURATION__EVENT__MOUNT_EARLY_DURATION;
+ break;
+ case "default":
+ eventType =
+ StatsLog.BOOT_TIME_EVENT_DURATION__EVENT__MOUNT_DEFAULT_DURATION;
+ break;
+ case "late":
+ eventType = StatsLog.BOOT_TIME_EVENT_DURATION__EVENT__MOUNT_LATE_DURATION;
+ break;
+ default:
+ continue;
+ }
+ StatsLog.write(StatsLog.BOOT_TIME_EVENT_DURATION_REPORTED, eventType,
+ duration);
}
}
}
@@ -555,16 +571,19 @@
Pattern pattern = Pattern.compile(LAST_SHUTDOWN_TIME_PATTERN, Pattern.MULTILINE);
Matcher matcher = pattern.matcher(lines);
if (matcher.find()) {
- MetricsLogger.histogram(null, "boot_fs_shutdown_duration",
+ StatsLog.write(StatsLog.BOOT_TIME_EVENT_DURATION_REPORTED,
+ StatsLog.BOOT_TIME_EVENT_DURATION__EVENT__SHUTDOWN_DURATION,
Integer.parseInt(matcher.group(1)));
- MetricsLogger.histogram(null, "boot_fs_shutdown_umount_stat",
+ StatsLog.write(StatsLog.BOOT_TIME_EVENT_ERROR_CODE_REPORTED,
+ StatsLog.BOOT_TIME_EVENT_ERROR_CODE__EVENT__SHUTDOWN_UMOUNT_STAT,
Integer.parseInt(matcher.group(2)));
Slog.i(TAG, "boot_fs_shutdown," + matcher.group(1) + "," + matcher.group(2));
} else { // not found
// This can happen when a device has too much kernel log after file system unmount
// ,exceeding maxReadSize. And having that much kernel logging can affect overall
// performance as well. So it is better to fix the kernel to reduce the amount of log.
- MetricsLogger.histogram(null, "boot_fs_shutdown_umount_stat",
+ StatsLog.write(StatsLog.BOOT_TIME_EVENT_ERROR_CODE_REPORTED,
+ StatsLog.BOOT_TIME_EVENT_ERROR_CODE__EVENT__SHUTDOWN_UMOUNT_STAT,
UMOUNT_STATUS_NOT_AVAILABLE);
Slog.w(TAG, "boot_fs_shutdown, string not found");
}
@@ -674,7 +693,11 @@
return;
}
stat = fixFsckFsStat(partition, stat, lines, startLineNumber, endLineNumber);
- MetricsLogger.histogram(null, "boot_fs_stat_" + partition, stat);
+ if ("userdata".equals(partition) || "data".equals(partition)) {
+ StatsLog.write(StatsLog.BOOT_TIME_EVENT_ERROR_CODE_REPORTED,
+ StatsLog.BOOT_TIME_EVENT_ERROR_CODE__EVENT__FS_MGR_FS_STAT_DATA_PARTITION,
+ stat);
+ }
Slog.i(TAG, "fs_stat, partition:" + partition + " stat:0x" + Integer.toHexString(stat));
}
diff --git a/core/java/com/android/server/ResettableTimeout.java b/core/java/com/android/server/ResettableTimeout.java
index 64083f7..511af94 100644
--- a/core/java/com/android/server/ResettableTimeout.java
+++ b/core/java/com/android/server/ResettableTimeout.java
@@ -16,10 +16,9 @@
package com.android.server;
-import android.os.SystemClock;
-
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.ConditionVariable;
+import android.os.SystemClock;
/**
* Utility class that you can call on with a timeout, and get called back
diff --git a/core/java/com/android/server/SystemConfig.java b/core/java/com/android/server/SystemConfig.java
index 8a59c99..74b481c 100644
--- a/core/java/com/android/server/SystemConfig.java
+++ b/core/java/com/android/server/SystemConfig.java
@@ -218,6 +218,7 @@
final ArrayMap<String, ArraySet<String>> mAllowedAssociations = new ArrayMap<>();
private final ArraySet<String> mBugreportWhitelistedPackages = new ArraySet<>();
+ private final ArraySet<String> mAppDataIsolationWhitelistedApps = new ArraySet<>();
// Map of packagesNames to userTypes. Stored temporarily until cleared by UserManagerService().
private ArrayMap<String, Set<String>> mPackageToUserTypeWhitelist = new ArrayMap<>();
@@ -389,6 +390,10 @@
return mRollbackWhitelistedPackages;
}
+ public ArraySet<String> getAppDataIsolationWhitelistedApps() {
+ return mAppDataIsolationWhitelistedApps;
+ }
+
/**
* Gets map of packagesNames to userTypes, dictating on which user types each package should be
* initially installed, and then removes this map from SystemConfig.
@@ -1045,6 +1050,16 @@
}
XmlUtils.skipCurrentTag(parser);
} break;
+ case "app-data-isolation-whitelisted-app": {
+ String pkgname = parser.getAttributeValue(null, "package");
+ if (pkgname == null) {
+ Slog.w(TAG, "<" + name + "> without package in " + permFile
+ + " at " + parser.getPositionDescription());
+ } else {
+ mAppDataIsolationWhitelistedApps.add(pkgname);
+ }
+ XmlUtils.skipCurrentTag(parser);
+ } break;
case "bugreport-whitelisted": {
String pkgname = parser.getAttributeValue(null, "package");
if (pkgname == null) {
diff --git a/core/java/com/android/server/net/BaseNetworkObserver.java b/core/java/com/android/server/net/BaseNetworkObserver.java
index e1a10a5..2a9c0b4 100644
--- a/core/java/com/android/server/net/BaseNetworkObserver.java
+++ b/core/java/com/android/server/net/BaseNetworkObserver.java
@@ -16,7 +16,7 @@
package com.android.server.net;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.net.INetworkManagementEventObserver;
import android.net.LinkAddress;
import android.net.RouteInfo;
diff --git a/core/java/com/android/server/net/NetlinkTracker.java b/core/java/com/android/server/net/NetlinkTracker.java
index 647fb5b..b57397f 100644
--- a/core/java/com/android/server/net/NetlinkTracker.java
+++ b/core/java/com/android/server/net/NetlinkTracker.java
@@ -16,7 +16,7 @@
package com.android.server.net;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.RouteInfo;
diff --git a/core/java/com/google/android/collect/Lists.java b/core/java/com/google/android/collect/Lists.java
index 8f6594a..585847d 100644
--- a/core/java/com/google/android/collect/Lists.java
+++ b/core/java/com/google/android/collect/Lists.java
@@ -16,7 +16,8 @@
package com.google.android.collect;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
import java.util.ArrayList;
import java.util.Collections;
diff --git a/core/java/com/google/android/collect/Maps.java b/core/java/com/google/android/collect/Maps.java
index 6ba3320..cd4c128 100644
--- a/core/java/com/google/android/collect/Maps.java
+++ b/core/java/com/google/android/collect/Maps.java
@@ -16,7 +16,7 @@
package com.google.android.collect;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.util.ArrayMap;
import java.util.HashMap;
diff --git a/core/java/com/google/android/collect/Sets.java b/core/java/com/google/android/collect/Sets.java
index 09b5e51..c67a88a 100644
--- a/core/java/com/google/android/collect/Sets.java
+++ b/core/java/com/google/android/collect/Sets.java
@@ -16,7 +16,7 @@
package com.google.android.collect;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.util.ArraySet;
import java.util.Collections;
diff --git a/core/java/com/google/android/util/AbstractMessageParser.java b/core/java/com/google/android/util/AbstractMessageParser.java
index f11e6b2..0da7607 100644
--- a/core/java/com/google/android/util/AbstractMessageParser.java
+++ b/core/java/com/google/android/util/AbstractMessageParser.java
@@ -16,7 +16,7 @@
package com.google.android.util;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import java.util.ArrayList;
import java.util.HashMap;
diff --git a/core/java/org/apache/http/conn/ssl/AbstractVerifier.java b/core/java/org/apache/http/conn/ssl/AbstractVerifier.java
index 36d6e22..2848ad7 100644
--- a/core/java/org/apache/http/conn/ssl/AbstractVerifier.java
+++ b/core/java/org/apache/http/conn/ssl/AbstractVerifier.java
@@ -31,7 +31,7 @@
package org.apache.http.conn.ssl;
-import java.util.regex.Pattern;
+import android.compat.annotation.UnsupportedAppUsage;
import java.io.IOException;
import java.security.cert.Certificate;
@@ -43,10 +43,10 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
-import java.util.logging.Logger;
import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.regex.Pattern;
-import android.annotation.UnsupportedAppUsage;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
diff --git a/core/java/org/apache/http/conn/ssl/SSLSocketFactory.java b/core/java/org/apache/http/conn/ssl/SSLSocketFactory.java
index b2e8b5e..ffae757 100644
--- a/core/java/org/apache/http/conn/ssl/SSLSocketFactory.java
+++ b/core/java/org/apache/http/conn/ssl/SSLSocketFactory.java
@@ -31,20 +31,14 @@
package org.apache.http.conn.ssl;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+
import org.apache.http.conn.scheme.HostNameResolver;
import org.apache.http.conn.scheme.LayeredSocketFactory;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
-import android.annotation.UnsupportedAppUsage;
-import android.os.Build;
-import javax.net.ssl.HttpsURLConnection;
-import javax.net.ssl.KeyManager;
-import javax.net.ssl.KeyManagerFactory;
-import javax.net.ssl.SSLContext;
-import javax.net.ssl.SSLSocket;
-import javax.net.ssl.TrustManager;
-import javax.net.ssl.TrustManagerFactory;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
@@ -57,6 +51,14 @@
import java.security.SecureRandom;
import java.security.UnrecoverableKeyException;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
/**
* Layered socket factory for TLS/SSL connections, based on JSSE.
*.
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index 98ce8b0..a2f514a 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -344,9 +344,9 @@
cppflags: ["-Wno-conversion-null"],
srcs: [
- "android/graphics/apex/android_bitmap.cpp",
- "android/graphics/apex/android_region.cpp",
+ "android/graphics/apex/android_matrix.cpp",
"android/graphics/apex/android_paint.cpp",
+ "android/graphics/apex/android_region.cpp",
"android_graphics_Canvas.cpp",
"android_graphics_ColorSpace.cpp",
@@ -429,6 +429,7 @@
android: {
srcs: [ // sources that depend on android only libraries
"android/graphics/apex/android_canvas.cpp",
+ "android/graphics/apex/android_bitmap.cpp",
"android/graphics/apex/renderthread.cpp",
"android/graphics/apex/jni_runtime.cpp",
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index fa64fd1..adedffd 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -52,42 +52,34 @@
using namespace android;
-jstring encodedFormatToString(JNIEnv* env, SkEncodedImageFormat format) {
- const char* mimeType;
+const char* getMimeType(SkEncodedImageFormat format) {
switch (format) {
case SkEncodedImageFormat::kBMP:
- mimeType = "image/bmp";
- break;
+ return "image/bmp";
case SkEncodedImageFormat::kGIF:
- mimeType = "image/gif";
- break;
+ return "image/gif";
case SkEncodedImageFormat::kICO:
- mimeType = "image/x-ico";
- break;
+ return "image/x-ico";
case SkEncodedImageFormat::kJPEG:
- mimeType = "image/jpeg";
- break;
+ return "image/jpeg";
case SkEncodedImageFormat::kPNG:
- mimeType = "image/png";
- break;
+ return "image/png";
case SkEncodedImageFormat::kWEBP:
- mimeType = "image/webp";
- break;
+ return "image/webp";
case SkEncodedImageFormat::kHEIF:
- mimeType = "image/heif";
- break;
+ return "image/heif";
case SkEncodedImageFormat::kWBMP:
- mimeType = "image/vnd.wap.wbmp";
- break;
+ return "image/vnd.wap.wbmp";
case SkEncodedImageFormat::kDNG:
- mimeType = "image/x-adobe-dng";
- break;
+ return "image/x-adobe-dng";
default:
- mimeType = nullptr;
- break;
+ return nullptr;
}
+}
+jstring getMimeTypeAsJavaString(JNIEnv* env, SkEncodedImageFormat format) {
jstring jstr = nullptr;
+ const char* mimeType = getMimeType(format);
if (mimeType) {
// NOTE: Caller should env->ExceptionCheck() for OOM
// (can't check for nullptr as it's a valid return value)
@@ -289,10 +281,9 @@
// Set the options and return if the client only wants the size.
if (options != NULL) {
- jstring mimeType = encodedFormatToString(
- env, (SkEncodedImageFormat)codec->getEncodedFormat());
+ jstring mimeType = getMimeTypeAsJavaString(env, codec->getEncodedFormat());
if (env->ExceptionCheck()) {
- return nullObjectReturn("OOM in encodedFormatToString()");
+ return nullObjectReturn("OOM in getMimeTypeAsJavaString()");
}
env->SetIntField(options, gOptions_widthFieldID, scaledWidth);
env->SetIntField(options, gOptions_heightFieldID, scaledHeight);
diff --git a/core/jni/android/graphics/BitmapFactory.h b/core/jni/android/graphics/BitmapFactory.h
index e37c98d..45bffc4 100644
--- a/core/jni/android/graphics/BitmapFactory.h
+++ b/core/jni/android/graphics/BitmapFactory.h
@@ -26,6 +26,6 @@
extern jclass gBitmapConfig_class;
extern jmethodID gBitmapConfig_nativeToConfigMethodID;
-jstring encodedFormatToString(JNIEnv* env, SkEncodedImageFormat format);
+jstring getMimeTypeAsJavaString(JNIEnv*, SkEncodedImageFormat);
#endif // _ANDROID_GRAPHICS_BITMAP_FACTORY_H_
diff --git a/core/jni/android/graphics/BitmapRegionDecoder.cpp b/core/jni/android/graphics/BitmapRegionDecoder.cpp
index f18632d..06b4ff8 100644
--- a/core/jni/android/graphics/BitmapRegionDecoder.cpp
+++ b/core/jni/android/graphics/BitmapRegionDecoder.cpp
@@ -197,7 +197,7 @@
env->SetIntField(options, gOptions_heightFieldID, bitmap.height());
env->SetObjectField(options, gOptions_mimeFieldID,
- encodedFormatToString(env, (SkEncodedImageFormat)brd->getEncodedFormat()));
+ getMimeTypeAsJavaString(env, brd->getEncodedFormat()));
if (env->ExceptionCheck()) {
return nullObjectReturn("OOM in encodedFormatToString()");
}
diff --git a/core/jni/android/graphics/ImageDecoder.cpp b/core/jni/android/graphics/ImageDecoder.cpp
index 627f8f5..a900286 100644
--- a/core/jni/android/graphics/ImageDecoder.cpp
+++ b/core/jni/android/graphics/ImageDecoder.cpp
@@ -475,7 +475,7 @@
static jstring ImageDecoder_nGetMimeType(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr);
- return encodedFormatToString(env, decoder->mCodec->getEncodedFormat());
+ return getMimeTypeAsJavaString(env, decoder->mCodec->getEncodedFormat());
}
static jobject ImageDecoder_nGetColorSpace(JNIEnv* env, jobject /*clazz*/, jlong nativePtr) {
diff --git a/core/jni/android/graphics/Matrix.h b/core/jni/android/graphics/Matrix.h
index 11c9e72..fe90d2e 100644
--- a/core/jni/android/graphics/Matrix.h
+++ b/core/jni/android/graphics/Matrix.h
@@ -23,7 +23,7 @@
namespace android {
/* Gets the underlying SkMatrix from a Matrix object. */
-extern SkMatrix* android_graphics_Matrix_getSkMatrix(JNIEnv* env, jobject matrixObj);
+SkMatrix* android_graphics_Matrix_getSkMatrix(JNIEnv* env, jobject matrixObj);
} // namespace android
diff --git a/media/java/android/media/RouteSessionInfo.aidl b/core/jni/android/graphics/MimeType.h
similarity index 85%
copy from media/java/android/media/RouteSessionInfo.aidl
copy to core/jni/android/graphics/MimeType.h
index fb5d836..38a579c 100644
--- a/media/java/android/media/RouteSessionInfo.aidl
+++ b/core/jni/android/graphics/MimeType.h
@@ -14,6 +14,8 @@
* limitations under the License.
*/
-package android.media;
+#pragma once
-parcelable RouteSessionInfo;
+#include "SkEncodedImageFormat.h"
+
+const char* getMimeType(SkEncodedImageFormat);
diff --git a/core/jni/android/graphics/apex/android_bitmap.cpp b/core/jni/android/graphics/apex/android_bitmap.cpp
index 90cc986..6a3c01e 100644
--- a/core/jni/android/graphics/apex/android_bitmap.cpp
+++ b/core/jni/android/graphics/apex/android_bitmap.cpp
@@ -122,6 +122,98 @@
return getInfo(bitmap->info(), bitmap->rowBytes());
}
+static bool nearlyEqual(float a, float b) {
+ // By trial and error, this is close enough to match for the ADataSpaces we
+ // compare for.
+ return ::fabs(a - b) < .002f;
+}
+
+static bool nearlyEqual(const skcms_TransferFunction& x, const skcms_TransferFunction& y) {
+ return nearlyEqual(x.g, y.g)
+ && nearlyEqual(x.a, y.a)
+ && nearlyEqual(x.b, y.b)
+ && nearlyEqual(x.c, y.c)
+ && nearlyEqual(x.d, y.d)
+ && nearlyEqual(x.e, y.e)
+ && nearlyEqual(x.f, y.f);
+}
+
+static bool nearlyEqual(const skcms_Matrix3x3& x, const skcms_Matrix3x3& y) {
+ for (int i = 0; i < 3; i++) {
+ for (int j = 0; j < 3; j++) {
+ if (!nearlyEqual(x.vals[i][j], y.vals[i][j])) return false;
+ }
+ }
+ return true;
+}
+
+static constexpr skcms_TransferFunction k2Dot6 = {2.6f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f};
+
+// Skia's SkNamedGamut::kDCIP3 is based on a white point of D65. This gamut
+// matches the white point used by ColorSpace.Named.DCIP3.
+static constexpr skcms_Matrix3x3 kDCIP3 = {{
+ {0.486143, 0.323835, 0.154234},
+ {0.226676, 0.710327, 0.0629966},
+ {0.000800549, 0.0432385, 0.78275},
+}};
+
+ADataSpace ABitmap_getDataSpace(ABitmap* bitmapHandle) {
+ Bitmap* bitmap = TypeCast::toBitmap(bitmapHandle);
+ const SkImageInfo& info = bitmap->info();
+ SkColorSpace* colorSpace = info.colorSpace();
+ if (!colorSpace) {
+ return ADATASPACE_UNKNOWN;
+ }
+
+ if (colorSpace->isSRGB()) {
+ if (info.colorType() == kRGBA_F16_SkColorType) {
+ return ADATASPACE_SCRGB;
+ }
+ return ADATASPACE_SRGB;
+ }
+
+ skcms_TransferFunction fn;
+ LOG_ALWAYS_FATAL_IF(!colorSpace->isNumericalTransferFn(&fn));
+
+ skcms_Matrix3x3 gamut;
+ LOG_ALWAYS_FATAL_IF(!colorSpace->toXYZD50(&gamut));
+
+ if (nearlyEqual(gamut, SkNamedGamut::kSRGB)) {
+ if (nearlyEqual(fn, SkNamedTransferFn::kLinear)) {
+ // Skia doesn't differentiate amongst the RANGES. In Java, we associate
+ // LINEAR_EXTENDED_SRGB with F16, and LINEAR_SRGB with other Configs.
+ // Make the same association here.
+ if (info.colorType() == kRGBA_F16_SkColorType) {
+ return ADATASPACE_SCRGB_LINEAR;
+ }
+ return ADATASPACE_SRGB_LINEAR;
+ }
+
+ if (nearlyEqual(fn, SkNamedTransferFn::kRec2020)) {
+ return ADATASPACE_BT709;
+ }
+ }
+
+ if (nearlyEqual(fn, SkNamedTransferFn::kSRGB) && nearlyEqual(gamut, SkNamedGamut::kDCIP3)) {
+ return ADATASPACE_DISPLAY_P3;
+ }
+
+ if (nearlyEqual(fn, SkNamedTransferFn::k2Dot2) && nearlyEqual(gamut, SkNamedGamut::kAdobeRGB)) {
+ return ADATASPACE_ADOBE_RGB;
+ }
+
+ if (nearlyEqual(fn, SkNamedTransferFn::kRec2020) &&
+ nearlyEqual(gamut, SkNamedGamut::kRec2020)) {
+ return ADATASPACE_BT2020;
+ }
+
+ if (nearlyEqual(fn, k2Dot6) && nearlyEqual(gamut, kDCIP3)) {
+ return ADATASPACE_DCI_P3;
+ }
+
+ return ADATASPACE_UNKNOWN;
+}
+
AndroidBitmapInfo ABitmap_getInfoFromJava(JNIEnv* env, jobject bitmapObj) {
uint32_t rowBytes = 0;
SkImageInfo imageInfo = GraphicsJNI::getBitmapInfo(env, bitmapObj, &rowBytes);
diff --git a/core/jni/android/graphics/apex/android_matrix.cpp b/core/jni/android/graphics/apex/android_matrix.cpp
new file mode 100644
index 0000000..309360d
--- /dev/null
+++ b/core/jni/android/graphics/apex/android_matrix.cpp
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+#include "android/graphics/matrix.h"
+#include "Matrix.h"
+
+bool AMatrix_getContents(JNIEnv* env, jobject matrixObj, float values[9]) {
+ static_assert(SkMatrix::kMScaleX == 0, "SkMatrix unexpected index");
+ static_assert(SkMatrix::kMSkewX == 1, "SkMatrix unexpected index");
+ static_assert(SkMatrix::kMTransX == 2, "SkMatrix unexpected index");
+ static_assert(SkMatrix::kMSkewY == 3, "SkMatrix unexpected index");
+ static_assert(SkMatrix::kMScaleY == 4, "SkMatrix unexpected index");
+ static_assert(SkMatrix::kMTransY == 5, "SkMatrix unexpected index");
+ static_assert(SkMatrix::kMPersp0 == 6, "SkMatrix unexpected index");
+ static_assert(SkMatrix::kMPersp1 == 7, "SkMatrix unexpected index");
+ static_assert(SkMatrix::kMPersp2 == 8, "SkMatrix unexpected index");
+
+ SkMatrix* m = android::android_graphics_Matrix_getSkMatrix(env, matrixObj);
+ if (m != nullptr) {
+ m->get9(values);
+ return true;
+ }
+ return false;
+}
diff --git a/core/jni/android/graphics/apex/include/android/graphics/bitmap.h b/core/jni/android/graphics/apex/include/android/graphics/bitmap.h
index f231eed..32b8a45 100644
--- a/core/jni/android/graphics/apex/include/android/graphics/bitmap.h
+++ b/core/jni/android/graphics/apex/include/android/graphics/bitmap.h
@@ -17,6 +17,7 @@
#define ANDROID_GRAPHICS_BITMAP_H
#include <android/bitmap.h>
+#include <android/data_space.h>
#include <jni.h>
#include <sys/cdefs.h>
@@ -49,6 +50,7 @@
void ABitmap_releaseRef(ABitmap* bitmap);
AndroidBitmapInfo ABitmap_getInfo(ABitmap* bitmap);
+ADataSpace ABitmap_getDataSpace(ABitmap* bitmap);
void* ABitmap_getPixels(ABitmap* bitmap);
void ABitmap_notifyPixelsChanged(ABitmap* bitmap);
@@ -106,6 +108,7 @@
ABitmap* get() const { return mBitmap; }
AndroidBitmapInfo getInfo() const { return ABitmap_getInfo(mBitmap); }
+ ADataSpace getDataSpace() const { return ABitmap_getDataSpace(mBitmap); }
void* getPixels() const { return ABitmap_getPixels(mBitmap); }
void notifyPixelsChanged() const { ABitmap_notifyPixelsChanged(mBitmap); }
@@ -119,4 +122,4 @@
}; // namespace android
#endif // __cplusplus
-#endif // ANDROID_GRAPHICS_BITMAP_H
\ No newline at end of file
+#endif // ANDROID_GRAPHICS_BITMAP_H
diff --git a/core/jni/android/graphics/apex/include/android/graphics/matrix.h b/core/jni/android/graphics/apex/include/android/graphics/matrix.h
new file mode 100644
index 0000000..4039cd1
--- /dev/null
+++ b/core/jni/android/graphics/apex/include/android/graphics/matrix.h
@@ -0,0 +1,38 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_GRAPHICS_MATRIX_H
+#define ANDROID_GRAPHICS_MATRIX_H
+
+#include <jni.h>
+#include <sys/cdefs.h>
+
+__BEGIN_DECLS
+
+/**
+ * Returns an array of floats that represents the 3x3 matrix of the java object.
+ * @param values The 9 values of the 3x3 matrix in the following order.
+ * values[0] = scaleX values[1] = skewX values[2] = transX
+ * values[3] = skewY values[4] = scaleY values[5] = transY
+ * values[6] = persp0 values[7] = persp1 values[8] = persp2
+ * @return true if the values param was populated and false otherwise.
+
+ */
+bool AMatrix_getContents(JNIEnv* env, jobject matrixObj, float values[9]);
+
+__END_DECLS
+
+#endif // ANDROID_GRAPHICS_MATRIX_H
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index 79cf019..c4ee195 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -2312,6 +2312,48 @@
return jStatus;
}
+static jint
+android_media_AudioSystem_getDevicesForAttributes(JNIEnv *env, jobject thiz,
+ jobject jaa, jobjectArray jDeviceArray)
+{
+ const jsize maxResultSize = env->GetArrayLength(jDeviceArray);
+ // the JNI is always expected to provide us with an array capable of holding enough
+ // devices i.e. the most we ever route a track to. This is preferred over receiving an ArrayList
+ // with reverse JNI to make the array grow as need as this would be less efficient, and some
+ // components call this method often
+ if (jDeviceArray == nullptr || maxResultSize == 0) {
+ ALOGE("%s invalid array to store AudioDeviceAddress", __FUNCTION__);
+ return (jint)AUDIO_JAVA_BAD_VALUE;
+ }
+
+ JNIAudioAttributeHelper::UniqueAaPtr paa = JNIAudioAttributeHelper::makeUnique();
+ jint jStatus = JNIAudioAttributeHelper::nativeFromJava(env, jaa, paa.get());
+ if (jStatus != (jint) AUDIO_JAVA_SUCCESS) {
+ return jStatus;
+ }
+
+ AudioDeviceTypeAddrVector devices;
+ jStatus = check_AudioSystem_Command(
+ AudioSystem::getDevicesForAttributes(*(paa.get()), &devices));
+ if (jStatus != NO_ERROR) {
+ return jStatus;
+ }
+
+ if (devices.size() > maxResultSize) {
+ return AUDIO_JAVA_INVALID_OPERATION;
+ }
+ size_t index = 0;
+ jobject jAudioDeviceAddress = NULL;
+ for (const auto& device : devices) {
+ jStatus = createAudioDeviceAddressFromNative(env, &jAudioDeviceAddress, &device);
+ if (jStatus != AUDIO_JAVA_SUCCESS) {
+ return jStatus;
+ }
+ env->SetObjectArrayElement(jDeviceArray, index++, jAudioDeviceAddress);
+ }
+ return jStatus;
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod gMethods[] = {
@@ -2395,6 +2437,7 @@
{"setPreferredDeviceForStrategy", "(IILjava/lang/String;)I", (void *)android_media_AudioSystem_setPreferredDeviceForStrategy},
{"removePreferredDeviceForStrategy", "(I)I", (void *)android_media_AudioSystem_removePreferredDeviceForStrategy},
{"getPreferredDeviceForStrategy", "(I[Landroid/media/AudioDeviceAddress;)I", (void *)android_media_AudioSystem_getPreferredDeviceForStrategy},
+ {"getDevicesForAttributes", "(Landroid/media/AudioAttributes;[Landroid/media/AudioDeviceAddress;)I", (void *)android_media_AudioSystem_getDevicesForAttributes}
};
static const JNINativeMethod gEventHandlerMethods[] = {
@@ -2584,7 +2627,7 @@
gMidAudioRecordRoutingProxy_release =
android::GetMethodIDOrDie(env, gClsAudioRecordRoutingProxy, "native_release", "()V");
- AudioSystem::setErrorCallback(android_media_AudioSystem_error_callback);
+ AudioSystem::addErrorCallback(android_media_AudioSystem_error_callback);
RegisterMethodsOrDie(env, kClassPathName, gMethods, NELEM(gMethods));
return RegisterMethodsOrDie(env, kEventHandlerClassPathName, gEventHandlerMethods,
diff --git a/core/jni/android_net_NetUtils.cpp b/core/jni/android_net_NetUtils.cpp
index 08aa1d9..ba7fe7f 100644
--- a/core/jni/android_net_NetUtils.cpp
+++ b/core/jni/android_net_NetUtils.cpp
@@ -24,9 +24,7 @@
#include <linux/tcp.h>
#include <net/if.h>
#include <netinet/ether.h>
-#include <netinet/icmp6.h>
#include <netinet/ip.h>
-#include <netinet/ip6.h>
#include <netinet/udp.h>
#include <android_runtime/AndroidRuntime.h>
@@ -102,98 +100,6 @@
}
}
-static void android_net_utils_setupRaSocket(JNIEnv *env, jobject clazz, jobject javaFd,
- jint ifIndex)
-{
- static const int kLinkLocalHopLimit = 255;
-
- int fd = jniGetFDFromFileDescriptor(env, javaFd);
-
- // Set an ICMPv6 filter that only passes Router Solicitations.
- struct icmp6_filter rs_only;
- ICMP6_FILTER_SETBLOCKALL(&rs_only);
- ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &rs_only);
- socklen_t len = sizeof(rs_only);
- if (setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, &rs_only, len) != 0) {
- jniThrowExceptionFmt(env, "java/net/SocketException",
- "setsockopt(ICMP6_FILTER): %s", strerror(errno));
- return;
- }
-
- // Most/all of the rest of these options can be set via Java code, but
- // because we're here on account of setting an icmp6_filter go ahead
- // and do it all natively for now.
- //
- // TODO: Consider moving these out to Java.
-
- // Set the multicast hoplimit to 255 (link-local only).
- int hops = kLinkLocalHopLimit;
- len = sizeof(hops);
- if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &hops, len) != 0) {
- jniThrowExceptionFmt(env, "java/net/SocketException",
- "setsockopt(IPV6_MULTICAST_HOPS): %s", strerror(errno));
- return;
- }
-
- // Set the unicast hoplimit to 255 (link-local only).
- hops = kLinkLocalHopLimit;
- len = sizeof(hops);
- if (setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &hops, len) != 0) {
- jniThrowExceptionFmt(env, "java/net/SocketException",
- "setsockopt(IPV6_UNICAST_HOPS): %s", strerror(errno));
- return;
- }
-
- // Explicitly disable multicast loopback.
- int off = 0;
- len = sizeof(off);
- if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &off, len) != 0) {
- jniThrowExceptionFmt(env, "java/net/SocketException",
- "setsockopt(IPV6_MULTICAST_LOOP): %s", strerror(errno));
- return;
- }
-
- // Specify the IPv6 interface to use for outbound multicast.
- len = sizeof(ifIndex);
- if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &ifIndex, len) != 0) {
- jniThrowExceptionFmt(env, "java/net/SocketException",
- "setsockopt(IPV6_MULTICAST_IF): %s", strerror(errno));
- return;
- }
-
- // Additional options to be considered:
- // - IPV6_TCLASS
- // - IPV6_RECVPKTINFO
- // - IPV6_RECVHOPLIMIT
-
- // Bind to [::].
- const struct sockaddr_in6 sin6 = {
- .sin6_family = AF_INET6,
- .sin6_port = 0,
- .sin6_flowinfo = 0,
- .sin6_addr = IN6ADDR_ANY_INIT,
- .sin6_scope_id = 0,
- };
- auto sa = reinterpret_cast<const struct sockaddr *>(&sin6);
- len = sizeof(sin6);
- if (bind(fd, sa, len) != 0) {
- jniThrowExceptionFmt(env, "java/net/SocketException",
- "bind(IN6ADDR_ANY): %s", strerror(errno));
- return;
- }
-
- // Join the all-routers multicast group, ff02::2%index.
- struct ipv6_mreq all_rtrs = {
- .ipv6mr_multiaddr = {{{0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2}}},
- .ipv6mr_interface = ifIndex,
- };
- len = sizeof(all_rtrs);
- if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &all_rtrs, len) != 0) {
- jniThrowExceptionFmt(env, "java/net/SocketException",
- "setsockopt(IPV6_JOIN_GROUP): %s", strerror(errno));
- return;
- }
-}
static jboolean android_net_utils_bindProcessToNetwork(JNIEnv *env, jobject thiz, jint netId)
{
@@ -370,7 +276,6 @@
{ "attachDropAllBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_attachDropAllBPFFilter },
{ "detachBPFFilter", "(Ljava/io/FileDescriptor;)V", (void*) android_net_utils_detachBPFFilter },
{ "getTcpRepairWindow", "(Ljava/io/FileDescriptor;)Landroid/net/TcpRepairWindow;", (void*) android_net_utils_getTcpRepairWindow },
- { "setupRaSocket", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_utils_setupRaSocket },
{ "resNetworkSend", "(I[BII)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkSend },
{ "resNetworkQuery", "(ILjava/lang/String;III)Ljava/io/FileDescriptor;", (void*) android_net_utils_resNetworkQuery },
{ "resNetworkResult", "(Ljava/io/FileDescriptor;)Landroid/net/DnsResolver$DnsResponse;", (void*) android_net_utils_resNetworkResult },
diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp
index 4314eb6..566c385 100644
--- a/core/jni/android_os_Debug.cpp
+++ b/core/jni/android_os_Debug.cpp
@@ -237,7 +237,7 @@
return err;
}
-static void load_maps(int pid, stats_t* stats, bool* foundSwapPss)
+static bool load_maps(int pid, stats_t* stats, bool* foundSwapPss)
{
*foundSwapPss = false;
uint64_t prev_end = 0;
@@ -407,17 +407,19 @@
}
};
- meminfo::ForEachVmaFromFile(smaps_path, vma_scan);
+ return meminfo::ForEachVmaFromFile(smaps_path, vma_scan);
}
-static void android_os_Debug_getDirtyPagesPid(JNIEnv *env, jobject clazz,
+static jboolean android_os_Debug_getDirtyPagesPid(JNIEnv *env, jobject clazz,
jint pid, jobject object)
{
bool foundSwapPss;
stats_t stats[_NUM_HEAP];
memset(&stats, 0, sizeof(stats));
- load_maps(pid, stats, &foundSwapPss);
+ if (!load_maps(pid, stats, &foundSwapPss)) {
+ return JNI_FALSE;
+ }
struct graphics_memory_pss graphics_mem;
if (read_memtrack_memory(pid, &graphics_mem) == 0) {
@@ -462,7 +464,7 @@
jint* otherArray = (jint*)env->GetPrimitiveArrayCritical(otherIntArray, 0);
if (otherArray == NULL) {
- return;
+ return JNI_FALSE;
}
int j=0;
@@ -479,6 +481,7 @@
}
env->ReleasePrimitiveArrayCritical(otherIntArray, otherArray, 0);
+ return JNI_TRUE;
}
static void android_os_Debug_getDirtyPages(JNIEnv *env, jobject clazz, jobject object)
@@ -508,6 +511,8 @@
rss += stats.rss;
swapPss = stats.swap_pss;
pss += swapPss; // Also in swap, those pages would be accounted as Pss without SWAP
+ } else {
+ return 0;
}
if (outUssSwapPssRss != NULL) {
@@ -866,7 +871,7 @@
(void*) android_os_Debug_getNativeHeapFreeSize },
{ "getMemoryInfo", "(Landroid/os/Debug$MemoryInfo;)V",
(void*) android_os_Debug_getDirtyPages },
- { "getMemoryInfo", "(ILandroid/os/Debug$MemoryInfo;)V",
+ { "getMemoryInfo", "(ILandroid/os/Debug$MemoryInfo;)Z",
(void*) android_os_Debug_getDirtyPagesPid },
{ "getPss", "()J",
(void*) android_os_Debug_getPss },
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index ff596b4..062b886 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -220,8 +220,12 @@
// ----------------------------------------------------------------------------
+static std::unique_ptr<DynamicLibManager> sDynamicLibManager =
+ std::make_unique<DynamicLibManager>();
+
// Let the opaque type AAssetManager refer to a guarded AssetManager2 instance.
struct GuardedAssetManager : public ::AAssetManager {
+ GuardedAssetManager() : guarded_assetmanager(sDynamicLibManager.get()) {}
Guarded<AssetManager2> guarded_assetmanager;
};
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 0992beb..fb8e633 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -537,9 +537,9 @@
LOGDEATH("Receiving binderDied() on JavaDeathRecipient %p\n", this);
if (mObject != NULL) {
JNIEnv* env = javavm_to_jnienv(mVM);
-
+ jobject jBinderProxy = javaObjectForIBinder(env, who.promote());
env->CallStaticVoidMethod(gBinderProxyOffsets.mClass,
- gBinderProxyOffsets.mSendDeathNotice, mObject);
+ gBinderProxyOffsets.mSendDeathNotice, mObject, jBinderProxy);
if (env->ExceptionCheck()) {
jthrowable excep = env->ExceptionOccurred();
report_exception(env, excep,
@@ -1532,8 +1532,9 @@
gBinderProxyOffsets.mClass = MakeGlobalRefOrDie(env, clazz);
gBinderProxyOffsets.mGetInstance = GetStaticMethodIDOrDie(env, clazz, "getInstance",
"(JJ)Landroid/os/BinderProxy;");
- gBinderProxyOffsets.mSendDeathNotice = GetStaticMethodIDOrDie(env, clazz, "sendDeathNotice",
- "(Landroid/os/IBinder$DeathRecipient;)V");
+ gBinderProxyOffsets.mSendDeathNotice =
+ GetStaticMethodIDOrDie(env, clazz, "sendDeathNotice",
+ "(Landroid/os/IBinder$DeathRecipient;Landroid/os/IBinder;)V");
gBinderProxyOffsets.mNativeData = GetFieldIDOrDie(env, clazz, "mNativeData", "J");
clazz = FindClassOrDie(env, "java/lang/Class");
diff --git a/core/jni/android_view_DisplayEventReceiver.cpp b/core/jni/android_view_DisplayEventReceiver.cpp
index 3531cf2..7daefd3 100644
--- a/core/jni/android_view_DisplayEventReceiver.cpp
+++ b/core/jni/android_view_DisplayEventReceiver.cpp
@@ -62,7 +62,7 @@
void dispatchVsync(nsecs_t timestamp, PhysicalDisplayId displayId, uint32_t count) override;
void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId, bool connected) override;
void dispatchConfigChanged(nsecs_t timestamp, PhysicalDisplayId displayId,
- int32_t configId) override;
+ int32_t configId, nsecs_t vsyncPeriod) override;
};
@@ -118,24 +118,23 @@
mMessageQueue->raiseAndClearException(env, "dispatchHotplug");
}
-void NativeDisplayEventReceiver::dispatchConfigChanged(nsecs_t timestamp,
- PhysicalDisplayId displayId,
- int32_t configId) {
- JNIEnv* env = AndroidRuntime::getJNIEnv();
+void NativeDisplayEventReceiver::dispatchConfigChanged(
+ nsecs_t timestamp, PhysicalDisplayId displayId, int32_t configId, nsecs_t) {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
- ScopedLocalRef<jobject> receiverObj(env, jniGetReferent(env, mReceiverWeakGlobal));
- if (receiverObj.get()) {
- ALOGV("receiver %p ~ Invoking config changed handler.", this);
- env->CallVoidMethod(receiverObj.get(),
- gDisplayEventReceiverClassInfo.dispatchConfigChanged,
- timestamp, displayId, configId);
- ALOGV("receiver %p ~ Returned from config changed handler.", this);
- }
+ ScopedLocalRef<jobject> receiverObj(env,
+ jniGetReferent(env, mReceiverWeakGlobal));
+ if (receiverObj.get()) {
+ ALOGV("receiver %p ~ Invoking config changed handler.", this);
+ env->CallVoidMethod(receiverObj.get(),
+ gDisplayEventReceiverClassInfo.dispatchConfigChanged,
+ timestamp, displayId, configId);
+ ALOGV("receiver %p ~ Returned from config changed handler.", this);
+ }
- mMessageQueue->raiseAndClearException(env, "dispatchConfigChanged");
+ mMessageQueue->raiseAndClearException(env, "dispatchConfigChanged");
}
-
static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,
jobject messageQueueObj, jint vsyncSource, jint configChanged) {
sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index 59dab0c8..649e5d2 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -40,10 +40,15 @@
static const bool kDebugDispatchCycle = false;
+static const char* toString(bool value) {
+ return value ? "true" : "false";
+}
+
static struct {
jclass clazz;
jmethodID dispatchInputEvent;
+ jmethodID onFocusEvent;
jmethodID dispatchBatchedInputEventPending;
} gInputEventReceiverClassInfo;
@@ -219,8 +224,7 @@
bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {
if (kDebugDispatchCycle) {
ALOGD("channel '%s' ~ Consuming input events, consumeBatches=%s, frameTime=%" PRId64,
- getInputChannelName().c_str(),
- consumeBatches ? "true" : "false", frameTime);
+ getInputChannelName().c_str(), toString(consumeBatches), frameTime);
}
if (consumeBatches) {
@@ -235,6 +239,7 @@
for (;;) {
uint32_t seq;
InputEvent* inputEvent;
+
status_t status = mInputConsumer.consume(&mInputEventFactory,
consumeBatches, frameTime, &seq, &inputEvent);
if (status) {
@@ -302,6 +307,19 @@
inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);
break;
}
+ case AINPUT_EVENT_TYPE_FOCUS: {
+ FocusEvent* focusEvent = static_cast<FocusEvent*>(inputEvent);
+ if (kDebugDispatchCycle) {
+ ALOGD("channel '%s' ~ Received focus event: hasFocus=%s, inTouchMode=%s.",
+ getInputChannelName().c_str(), toString(focusEvent->getHasFocus()),
+ toString(focusEvent->getInTouchMode()));
+ }
+ env->CallVoidMethod(receiverObj.get(), gInputEventReceiverClassInfo.onFocusEvent,
+ jboolean(focusEvent->getHasFocus()),
+ jboolean(focusEvent->getInTouchMode()));
+ finishInputEvent(seq, true /* handled */);
+ return OK;
+ }
default:
assert(false); // InputConsumer should prevent this from ever happening
@@ -421,6 +439,8 @@
gInputEventReceiverClassInfo.dispatchInputEvent = GetMethodIDOrDie(env,
gInputEventReceiverClassInfo.clazz,
"dispatchInputEvent", "(ILandroid/view/InputEvent;)V");
+ gInputEventReceiverClassInfo.onFocusEvent =
+ GetMethodIDOrDie(env, gInputEventReceiverClassInfo.clazz, "onFocusEvent", "(ZZ)V");
gInputEventReceiverClassInfo.dispatchBatchedInputEventPending = GetMethodIDOrDie(env,
gInputEventReceiverClassInfo.clazz, "dispatchBatchedInputEventPending", "()V");
diff --git a/core/jni/android_view_MotionEvent.cpp b/core/jni/android_view_MotionEvent.cpp
index c75c54b..feb9fe3 100644
--- a/core/jni/android_view_MotionEvent.cpp
+++ b/core/jni/android_view_MotionEvent.cpp
@@ -18,7 +18,7 @@
#include <nativehelper/JNIHelp.h>
-#include <SkMatrix.h>
+#include <android/graphics/matrix.h>
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/Log.h>
#include <utils/Log.h>
@@ -571,6 +571,15 @@
}
}
+static void android_view_MotionEvent_nativeTransform(JNIEnv* env, jclass clazz,
+ jlong nativePtr, jobject matrixObj) {
+ MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
+
+ float m[9];
+ AMatrix_getContents(env, matrixObj, m);
+ event->transform(m);
+}
+
// ----------------- @CriticalNative ------------------------------
static jlong android_view_MotionEvent_nativeCopy(jlong destNativePtr, jlong sourceNativePtr,
@@ -745,24 +754,6 @@
event->scale(scale);
}
-static void android_view_MotionEvent_nativeTransform(jlong nativePtr, jlong matrixPtr) {
- MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
- SkMatrix* matrix = reinterpret_cast<SkMatrix*>(matrixPtr);
-
- static_assert(SkMatrix::kMScaleX == 0, "SkMatrix unexpected index");
- static_assert(SkMatrix::kMSkewX == 1, "SkMatrix unexpected index");
- static_assert(SkMatrix::kMTransX == 2, "SkMatrix unexpected index");
- static_assert(SkMatrix::kMSkewY == 3, "SkMatrix unexpected index");
- static_assert(SkMatrix::kMScaleY == 4, "SkMatrix unexpected index");
- static_assert(SkMatrix::kMTransY == 5, "SkMatrix unexpected index");
- static_assert(SkMatrix::kMPersp0 == 6, "SkMatrix unexpected index");
- static_assert(SkMatrix::kMPersp1 == 7, "SkMatrix unexpected index");
- static_assert(SkMatrix::kMPersp2 == 8, "SkMatrix unexpected index");
- float m[9];
- matrix->get9(m);
- event->transform(m);
-}
-
// ----------------------------------------------------------------------------
static const JNINativeMethod gMotionEventMethods[] = {
@@ -810,6 +801,9 @@
{ "nativeGetAxisValue",
"(JIII)F",
(void*)android_view_MotionEvent_nativeGetAxisValue },
+ { "nativeTransform",
+ "(JLandroid/graphics/Matrix;)V",
+ (void*)android_view_MotionEvent_nativeTransform },
// --------------- @CriticalNative ------------------
@@ -912,9 +906,6 @@
{ "nativeScale",
"(JF)V",
(void*)android_view_MotionEvent_nativeScale },
- { "nativeTransform",
- "(JJ)V",
- (void*)android_view_MotionEvent_nativeTransform },
};
int register_android_view_MotionEvent(JNIEnv* env) {
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 4a7276c..573f378 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -263,7 +263,8 @@
status_t res = ScreenshotClient::capture(displayToken, dataspace,
ui::PixelFormat::RGBA_8888,
sourceCrop, width, height,
- useIdentityTransform, rotation, captureSecureLayers, &buffer, capturedSecureLayers);
+ useIdentityTransform, ui::toRotation(rotation),
+ captureSecureLayers, &buffer, capturedSecureLayers);
if (res != NO_ERROR) {
return NULL;
}
@@ -422,6 +423,14 @@
transaction->setFlags(ctrl, flags, mask);
}
+static void nativeSetFrameRateSelectionPriority(JNIEnv* env, jclass clazz, jlong transactionObj,
+ jlong nativeObject, jint priority) {
+ auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+
+ SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
+ transaction->setFrameRateSelectionPriority(ctrl, priority);
+}
+
static void nativeSetTransparentRegionHint(JNIEnv* env, jclass clazz, jlong transactionObj,
jlong nativeObject, jobject regionObj) {
SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
@@ -724,7 +733,8 @@
{
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
- transaction->setDisplayProjection(token, orientation, layerStackRect, displayRect);
+ transaction->setDisplayProjection(token, static_cast<ui::Rotation>(orientation),
+ layerStackRect, displayRect);
}
}
@@ -1360,6 +1370,8 @@
(void*)nativeSetColorSpaceAgnostic },
{"nativeSetFlags", "(JJII)V",
(void*)nativeSetFlags },
+ {"nativeSetFrameRateSelectionPriority", "(JJI)V",
+ (void*)nativeSetFrameRateSelectionPriority },
{"nativeSetWindowCrop", "(JJIIII)V",
(void*)nativeSetWindowCrop },
{"nativeSetCornerRadius", "(JJF)V",
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index df5b02c..05b573a 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -174,6 +174,8 @@
static constexpr const uint64_t UPPER_HALF_WORD_MASK = 0xFFFF'FFFF'0000'0000;
static constexpr const uint64_t LOWER_HALF_WORD_MASK = 0x0000'0000'FFFF'FFFF;
+static constexpr const char* kCurProfileDirPath = "/data/misc/profiles/cur";
+
/**
* The maximum value that the gUSAPPoolSizeMax variable may take. This value
* is a mirror of ZygoteServer.USAP_POOL_SIZE_MAX_LIMIT
@@ -319,7 +321,8 @@
MOUNT_EXTERNAL_INSTALLER = 5,
MOUNT_EXTERNAL_FULL = 6,
MOUNT_EXTERNAL_PASS_THROUGH = 7,
- MOUNT_EXTERNAL_COUNT = 8
+ MOUNT_EXTERNAL_ANDROID_WRITABLE = 8,
+ MOUNT_EXTERNAL_COUNT = 9
};
// The order of entries here must be kept in sync with MountExternalKind enum values.
@@ -331,6 +334,8 @@
"/mnt/runtime/write", // MOUNT_EXTERNAL_LEGACY
"/mnt/runtime/write", // MOUNT_EXTERNAL_INSTALLER
"/mnt/runtime/full", // MOUNT_EXTERNAL_FULL
+ "/mnt/runtime/full", // MOUNT_EXTERNAL_PASS_THROUGH (only used w/ FUSE)
+ "/mnt/runtime/full", // MOUNT_EXTERNAL_ANDROID_WRITABLE (only used w/ FUSE)
};
// Must match values in com.android.internal.os.Zygote.
@@ -743,19 +748,23 @@
const userid_t user_id = multiuser_get_user_id(uid);
const std::string user_source = StringPrintf("/mnt/user/%d", user_id);
- const std::string pass_through_source = StringPrintf("/mnt/pass_through/%d", user_id);
+ // Shell is neither AID_ROOT nor AID_EVERYBODY. Since it equally needs 'execute' access to
+ // /mnt/user/0 to 'adb shell ls /sdcard' for instance, we set the uid bit of /mnt/user/0 to
+ // AID_SHELL. This gives shell access along with apps running as group everybody (user 0 apps)
+ // These bits should be consistent with what is set in vold in
+ // Utils#MountUserFuse on FUSE volume mount
+ PrepareDir(user_source, 0710, user_id ? AID_ROOT : AID_SHELL,
+ multiuser_get_uid(user_id, AID_EVERYBODY), fail_fn);
+
bool isFuse = GetBoolProperty(kPropFuse, false);
- PrepareDir(user_source, DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT, fail_fn);
-
if (isFuse) {
- if (mount_mode == MOUNT_EXTERNAL_PASS_THROUGH || mount_mode ==
- MOUNT_EXTERNAL_INSTALLER || mount_mode == MOUNT_EXTERNAL_FULL) {
- // For now, MediaProvider, installers and "full" get the pass_through mount
- // view, which is currently identical to the sdcardfs write view.
- //
- // TODO(b/146189163): scope down MOUNT_EXTERNAL_INSTALLER
+ if (mount_mode == MOUNT_EXTERNAL_PASS_THROUGH) {
+ const std::string pass_through_source = StringPrintf("/mnt/pass_through/%d", user_id);
BindMount(pass_through_source, "/storage", fail_fn);
+ } else if (mount_mode == MOUNT_EXTERNAL_INSTALLER) {
+ const std::string installer_source = StringPrintf("/mnt/installer/%d", user_id);
+ BindMount(installer_source, "/storage", fail_fn);
} else {
BindMount(user_source, "/storage", fail_fn);
}
@@ -1149,6 +1158,36 @@
createAndMountAppData(package_name, ce_data_path, mirrorCePath, actualCePath, fail_fn);
}
+// Relabel directory
+static void relabelDir(const char* path, security_context_t context, fail_fn_t fail_fn) {
+ if (setfilecon(path, context) != 0) {
+ fail_fn(CREATE_ERROR("Failed to setfilecon %s %s", path, strerror(errno)));
+ }
+}
+
+// Relabel all directories under a path non-recursively.
+static void relabelAllDirs(const char* path, security_context_t context, fail_fn_t fail_fn) {
+ DIR* dir = opendir(path);
+ if (dir == nullptr) {
+ fail_fn(CREATE_ERROR("Failed to opendir %s", path));
+ }
+ struct dirent* ent;
+ while ((ent = readdir(dir))) {
+ if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) continue;
+ auto filePath = StringPrintf("%s/%s", path, ent->d_name);
+ if (ent->d_type == DT_DIR) {
+ relabelDir(filePath.c_str(), context, fail_fn);
+ } else if (ent->d_type == DT_LNK) {
+ if (lsetfilecon(filePath.c_str(), context) != 0) {
+ fail_fn(CREATE_ERROR("Failed to lsetfilecon %s %s", filePath.c_str(), strerror(errno)));
+ }
+ } else {
+ fail_fn(CREATE_ERROR("Unexpected type: %d %s", ent->d_type, filePath.c_str()));
+ }
+ }
+ closedir(dir);
+}
+
/**
* Make other apps data directory not visible in CE, DE storage.
*
@@ -1208,10 +1247,36 @@
snprintf(internalDePath, PATH_MAX, "/data/user_de");
snprintf(externalPrivateMountPath, PATH_MAX, "/mnt/expand");
+ security_context_t dataDataContext = nullptr;
+ if (getfilecon(internalDePath, &dataDataContext) < 0) {
+ fail_fn(CREATE_ERROR("Unable to getfilecon on %s %s", internalDePath,
+ strerror(errno)));
+ }
+
MountAppDataTmpFs(internalLegacyCePath, fail_fn);
MountAppDataTmpFs(internalCePath, fail_fn);
MountAppDataTmpFs(internalDePath, fail_fn);
- MountAppDataTmpFs(externalPrivateMountPath, fail_fn);
+
+ // Mount tmpfs on all external vols DE and CE storage
+ DIR* dir = opendir(externalPrivateMountPath);
+ if (dir == nullptr) {
+ fail_fn(CREATE_ERROR("Failed to opendir %s", externalPrivateMountPath));
+ }
+ struct dirent* ent;
+ while ((ent = readdir(dir))) {
+ if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) continue;
+ if (ent->d_type != DT_DIR) {
+ fail_fn(CREATE_ERROR("Unexpected type: %d %s", ent->d_type, ent->d_name));
+ }
+ auto volPath = StringPrintf("%s/%s", externalPrivateMountPath, ent->d_name);
+ auto cePath = StringPrintf("%s/user", volPath.c_str());
+ auto dePath = StringPrintf("%s/user_de", volPath.c_str());
+ MountAppDataTmpFs(cePath.c_str(), fail_fn);
+ MountAppDataTmpFs(dePath.c_str(), fail_fn);
+ }
+ closedir(dir);
+
+ bool legacySymlinkCreated = false;
for (int i = 0; i < size; i += 3) {
jstring package_str = (jstring) (env->GetObjectArrayElement(pkg_data_info_list, i));
@@ -1259,7 +1324,14 @@
// If it's user 0, create a symlink /data/user/0 -> /data/data,
// otherwise create /data/user/$USER
if (userId == 0) {
- symlink(internalLegacyCePath, internalCeUserPath);
+ if (!legacySymlinkCreated) {
+ legacySymlinkCreated = true;
+ int result = symlink(internalLegacyCePath, internalCeUserPath);
+ if (result != 0) {
+ fail_fn(CREATE_ERROR("Failed to create symlink %s %s", internalCeUserPath,
+ strerror(errno)));
+ }
+ }
actualCePath = internalLegacyCePath;
} else {
PrepareDirIfNotPresent(internalCeUserPath, DEFAULT_DATA_DIR_PERMISSION,
@@ -1273,6 +1345,86 @@
isolateAppDataPerPackage(userId, packageName, volUuid, ceDataInode,
actualCePath, actualDePath, fail_fn);
}
+ // We set the label AFTER everything is done, as we are applying
+ // the file operations on tmpfs. If we set the label when we mount
+ // tmpfs, SELinux will not happy as we are changing system_data_files.
+ // Relabel dir under /data/user, including /data/user/0
+ relabelAllDirs(internalCePath, dataDataContext, fail_fn);
+
+ // Relabel /data/user
+ relabelDir(internalCePath, dataDataContext, fail_fn);
+
+ // Relabel /data/data
+ relabelDir(internalLegacyCePath, dataDataContext, fail_fn);
+
+ // Relabel dir under /data/user_de
+ relabelAllDirs(internalDePath, dataDataContext, fail_fn);
+
+ // Relabel /data/user_de
+ relabelDir(internalDePath, dataDataContext, fail_fn);
+
+ // Relabel CE and DE dirs under /mnt/expand
+ dir = opendir(externalPrivateMountPath);
+ if (dir == nullptr) {
+ fail_fn(CREATE_ERROR("Failed to opendir %s", externalPrivateMountPath));
+ }
+ while ((ent = readdir(dir))) {
+ if (strcmp(ent->d_name, ".") == 0 || strcmp(ent->d_name, "..") == 0) continue;
+ auto volPath = StringPrintf("%s/%s", externalPrivateMountPath, ent->d_name);
+ auto cePath = StringPrintf("%s/user", volPath.c_str());
+ auto dePath = StringPrintf("%s/user_de", volPath.c_str());
+
+ relabelAllDirs(cePath.c_str(), dataDataContext, fail_fn);
+ relabelDir(cePath.c_str(), dataDataContext, fail_fn);
+ relabelAllDirs(dePath.c_str(), dataDataContext, fail_fn);
+ relabelDir(dePath.c_str(), dataDataContext, fail_fn);
+ }
+ closedir(dir);
+
+ freecon(dataDataContext);
+}
+
+/**
+ * Like isolateAppData(), isolate jit profile directories, so apps don't see what
+ * other apps are installed by reading content inside /data/misc/profiles/cur.
+ *
+ * The implementation is similar to isolateAppData(), it creates a tmpfs
+ * on /data/misc/profiles/cur, and bind mounts related package profiles to it.
+ */
+static void isolateJitProfile(JNIEnv* env, jobjectArray pkg_data_info_list,
+ uid_t uid, const char* process_name, jstring managed_nice_name,
+ fail_fn_t fail_fn) {
+
+ auto extract_fn = std::bind(ExtractJString, env, process_name, managed_nice_name, _1);
+ const userid_t user_id = multiuser_get_user_id(uid);
+
+ int size = (pkg_data_info_list != nullptr) ? env->GetArrayLength(pkg_data_info_list) : 0;
+ // Size should be a multiple of 3, as it contains list of <package_name, volume_uuid, inode>
+ if ((size % 3) != 0) {
+ fail_fn(CREATE_ERROR("Wrong pkg_inode_list size %d", size));
+ }
+
+ // Mount (namespace) tmpfs on profile directory, so apps no longer access
+ // the original profile directory anymore.
+ MountAppDataTmpFs(kCurProfileDirPath, fail_fn);
+
+ // Create profile directory for this user.
+ std::string actualCurUserProfile = StringPrintf("%s/%d", kCurProfileDirPath, user_id);
+ PrepareDir(actualCurUserProfile.c_str(), DEFAULT_DATA_DIR_PERMISSION, AID_ROOT, AID_ROOT,
+ fail_fn);
+
+ for (int i = 0; i < size; i += 3) {
+ jstring package_str = (jstring) (env->GetObjectArrayElement(pkg_data_info_list, i));
+ std::string packageName = extract_fn(package_str).value();
+
+ std::string actualCurPackageProfile = StringPrintf("%s/%s", actualCurUserProfile.c_str(),
+ packageName.c_str());
+ std::string mirrorCurPackageProfile = StringPrintf("/data_mirror/cur_profiles/%d/%s",
+ user_id, packageName.c_str());
+
+ PrepareDir(actualCurPackageProfile, DEFAULT_DATA_DIR_PERMISSION, uid, uid, fail_fn);
+ BindMount(mirrorCurPackageProfile, actualCurPackageProfile, fail_fn);
+ }
}
// Utility routine to specialize a zygote child process.
@@ -1324,9 +1476,9 @@
// Isolated process / webview / app zygote should be gated by SELinux and file permission
// so they can't even traverse CE / DE directories.
if (pkg_data_info_list != nullptr
- && GetBoolProperty(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, false)) {
- isolateAppData(env, pkg_data_info_list, uid, process_name, managed_nice_name,
- fail_fn);
+ && GetBoolProperty(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, true)) {
+ isolateAppData(env, pkg_data_info_list, uid, process_name, managed_nice_name, fail_fn);
+ isolateJitProfile(env, pkg_data_info_list, uid, process_name, managed_nice_name, fail_fn);
}
// If this zygote isn't root, it won't be able to create a process group,
diff --git a/core/jni/fd_utils.cpp b/core/jni/fd_utils.cpp
index 5624f45..1fcc8ac 100644
--- a/core/jni/fd_utils.cpp
+++ b/core/jni/fd_utils.cpp
@@ -33,27 +33,28 @@
// Static whitelist of open paths that the zygote is allowed to keep open.
static const char* kPathWhitelist[] = {
- "/apex/com.android.appsearch/javalib/framework-appsearch.jar",
- "/apex/com.android.conscrypt/javalib/conscrypt.jar",
- "/apex/com.android.ipsec/javalib/ike.jar",
- "/apex/com.android.media/javalib/updatable-media.jar",
- "/apex/com.android.mediaprovider/javalib/framework-mediaprovider.jar",
- "/apex/com.android.os.statsd/javalib/framework-statsd.jar",
- "/apex/com.android.sdkext/javalib/framework-sdkext.jar",
- "/apex/com.android.wifi/javalib/framework-wifi.jar",
- "/apex/com.android.tethering/javalib/framework-tethering.jar",
- "/dev/null",
- "/dev/socket/zygote",
- "/dev/socket/zygote_secondary",
- "/dev/socket/usap_pool_primary",
- "/dev/socket/usap_pool_secondary",
- "/dev/socket/webview_zygote",
- "/dev/socket/heapprofd",
- "/sys/kernel/debug/tracing/trace_marker",
- "/system/framework/framework-res.apk",
- "/dev/urandom",
- "/dev/ion",
- "/dev/dri/renderD129", // Fixes b/31172436
+ "/apex/com.android.appsearch/javalib/framework-appsearch.jar",
+ "/apex/com.android.conscrypt/javalib/conscrypt.jar",
+ "/apex/com.android.ipsec/javalib/ike.jar",
+ "/apex/com.android.media/javalib/updatable-media.jar",
+ "/apex/com.android.mediaprovider/javalib/framework-mediaprovider.jar",
+ "/apex/com.android.os.statsd/javalib/framework-statsd.jar",
+ "/apex/com.android.permission/javalib/framework-permission.jar",
+ "/apex/com.android.sdkext/javalib/framework-sdkextensions.jar",
+ "/apex/com.android.wifi/javalib/framework-wifi.jar",
+ "/apex/com.android.tethering/javalib/framework-tethering.jar",
+ "/dev/null",
+ "/dev/socket/zygote",
+ "/dev/socket/zygote_secondary",
+ "/dev/socket/usap_pool_primary",
+ "/dev/socket/usap_pool_secondary",
+ "/dev/socket/webview_zygote",
+ "/dev/socket/heapprofd",
+ "/sys/kernel/debug/tracing/trace_marker",
+ "/system/framework/framework-res.apk",
+ "/dev/urandom",
+ "/dev/ion",
+ "/dev/dri/renderD129", // Fixes b/31172436
};
static const char kFdPath[] = "/proc/self/fd";
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index d5a3b5e..cd3887e 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -702,6 +702,12 @@
// CATEGORY: SETTINGS
// OS: R
ACTION_DASHBOARD_VISIBLE_TIME = 1729;
+
+ // ACTION: Allow "Access all files" for an app
+ APP_SPECIAL_PERMISSION_MANAGE_EXT_STRG_ALLOW = 1730;
+
+ // ACTION: Deny "Access all files" for an app
+ APP_SPECIAL_PERMISSION_MANAGE_EXT_STRG_DENY = 1731;
}
/**
@@ -2542,4 +2548,13 @@
// OS: R
FUELGAUGE_BATTERY_SHARE = 1821;
+ // OPEN: Settings -> Apps & Notifications -> Special App Access
+ // CATEGORY: SETTINGS
+ // OS: R
+ MANAGE_EXTERNAL_STORAGE = 1822;
+
+ // Open: Settings > DND > People
+ // OS: R
+ DND_PEOPLE = 1823;
+
}
diff --git a/core/proto/android/os/cpu_usage.proto b/core/proto/android/os/cpu_usage.proto
new file mode 100644
index 0000000..ac98900
--- /dev/null
+++ b/core/proto/android/os/cpu_usage.proto
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+option java_multiple_files = true;
+
+import "frameworks/base/core/proto/android/privacy.proto";
+
+package android.os;
+
+message CpuUsageProto {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ message Load {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional float load1 = 1;
+ optional float load5 = 2;
+ optional float load15 = 3;
+ }
+ optional Load current_load = 1;
+
+ optional int64 now = 2;
+ optional int64 last_sample_time = 3;
+ optional int64 current_sample_time = 4;
+ optional int64 last_sample_real_time = 5;
+ optional int64 current_sample_real_time = 6;
+ optional int64 last_sample_wall_time = 7;
+ optional int64 current_sample_wall_time = 8;
+
+ optional int32 total_user_time = 9;
+ optional int32 total_system_time = 10;
+ optional int32 total_iowait_time = 11;
+ optional int32 total_irq_time = 12;
+ optional int32 total_soft_irq_time = 13;
+ optional int32 total_idle_time = 14;
+ optional int32 total_time = 15;
+
+ message Stat {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+ optional int32 uid = 1;
+ optional int32 pid = 2;
+ optional string name = 3;
+ optional bool added = 4;
+ optional bool removed = 5;
+ optional int32 uptime = 6;
+ optional int32 user_time = 7;
+ optional int32 system_time = 8;
+ optional int32 minor_faults = 9;
+ optional int32 major_faults = 10;
+ optional int32 parent_pid = 11;
+ }
+ repeated Stat processes = 16;
+
+ // Next tag: 17
+}
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index 6cf9424..8f9c041 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -21,6 +21,7 @@
import "frameworks/base/core/proto/android/os/batterytype.proto";
import "frameworks/base/core/proto/android/os/cpufreq.proto";
import "frameworks/base/core/proto/android/os/cpuinfo.proto";
+import "frameworks/base/core/proto/android/os/cpu_usage.proto";
import "frameworks/base/core/proto/android/os/data.proto";
import "frameworks/base/core/proto/android/os/header.proto";
import "frameworks/base/core/proto/android/os/kernelwake.proto";
@@ -469,6 +470,11 @@
(section).args = "dropbox --proto SubsystemRestart"
];
+ optional CpuUsageProto process_cpu_usage = 3047 [
+ (section).type = SECTION_DUMPSYS,
+ (section).args = "cpuinfo --proto"
+ ];
+
// Reserved for OEMs.
extensions 50000 to 100000;
}
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index a98d7db..d5384a1 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -115,7 +115,7 @@
option (android.msg_privacy).dest = DEST_EXPLICIT;
optional SettingProto backup_agent_timeout_parameters = 1;
- optional SettingProto backup_multi_user_enabled = 2;
+ reserved 2; // Used to be backup_multi_user_enabled which was never used
}
optional Backup backup = 146;
diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto
index 06040a5..b71e539 100644
--- a/core/proto/android/server/jobscheduler.proto
+++ b/core/proto/android/server/jobscheduler.proto
@@ -32,8 +32,8 @@
import "frameworks/base/core/proto/android/server/job/enums.proto";
import "frameworks/base/core/proto/android/server/statlogger.proto";
import "frameworks/base/core/proto/android/privacy.proto";
+import "frameworks/base/core/proto/android/util/quotatracker.proto";
-// Next tag: 21
message JobSchedulerServiceDumpProto {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
@@ -160,10 +160,13 @@
optional JobConcurrencyManagerProto concurrency_manager = 20;
optional JobStorePersistStatsProto persist_stats = 21;
+
+ optional .android.util.quota.CountQuotaTrackerProto quota_tracker = 22;
+
+ // Next tag: 23
}
// A com.android.server.job.JobSchedulerService.Constants object.
-// Next tag: 29
message ConstantsProto {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
@@ -246,6 +249,14 @@
// Whether to use heartbeats or rolling window for quota management. True
// will use heartbeats, false will use a rolling window.
reserved 23; // use_heartbeats
+ // Whether to enable quota limits on APIs.
+ optional bool enable_api_quotas = 31;
+ // The maximum number of schedule() calls an app can make in a set amount of time.
+ optional int32 api_quota_schedule_count = 32;
+ // The time window that {@link #API_QUOTA_SCHEDULE_COUNT} should be evaluated over.
+ optional int64 api_quota_schedule_window_ms = 33;
+ // Whether or not to throw an exception when an app hits its schedule quota limit.
+ optional bool api_quota_schedule_throw_exception = 34;
message QuotaController {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
@@ -331,7 +342,7 @@
// In this time after screen turns on, we increase job concurrency.
optional int32 screen_off_job_concurrency_increase_delay_ms = 28;
- // Next tag: 31
+ // Next tag: 35
}
// Next tag: 4
@@ -651,8 +662,7 @@
optional bool is_active = 2;
// The time this timer last became active. Only valid if is_active is true.
optional int64 start_time_elapsed = 3;
- // How many background jobs are currently running. Valid only if the device is_active
- // is true.
+ // How many background jobs are currently running. Valid only if is_active is true.
optional int32 bg_job_count = 4;
// All of the jobs that the Timer is currently tracking.
repeated JobStatusShortInfoProto running_jobs = 5;
diff --git a/core/proto/android/service/graphicsstats.proto b/core/proto/android/service/graphicsstats.proto
index 557075c..2de5b7f 100644
--- a/core/proto/android/service/graphicsstats.proto
+++ b/core/proto/android/service/graphicsstats.proto
@@ -32,6 +32,10 @@
}
message GraphicsStatsProto {
+ enum PipelineType {
+ GL = 0;
+ VULKAN = 1;
+ }
option (android.msg_privacy).dest = DEST_AUTOMATIC;
// The package name of the app
@@ -54,6 +58,9 @@
// The gpu frame time histogram for the package
repeated GraphicsStatsHistogramBucketProto gpu_histogram = 7;
+
+ // HWUI renders pipeline type: GL or Vulkan
+ optional PipelineType pipeline = 8;
}
message GraphicsStatsJankSummaryProto {
diff --git a/core/proto/android/stats/devicepolicy/device_policy_enums.proto b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
index ee5144c..0fca1d1 100644
--- a/core/proto/android/stats/devicepolicy/device_policy_enums.proto
+++ b/core/proto/android/stats/devicepolicy/device_policy_enums.proto
@@ -153,4 +153,6 @@
CROSS_PROFILE_APPS_START_ACTIVITY_AS_USER = 126;
SET_AUTO_TIME = 127;
SET_AUTO_TIME_ZONE = 128;
+ SET_PACKAGES_PROTECTED = 129;
+ SET_FACTORY_RESET_PROTECTION = 130;
}
diff --git a/core/proto/android/stats/launcher/Android.bp b/core/proto/android/stats/launcher/Android.bp
index b8fb6ff..976a0b8 100644
--- a/core/proto/android/stats/launcher/Android.bp
+++ b/core/proto/android/stats/launcher/Android.bp
@@ -25,3 +25,16 @@
"*.proto",
],
}
+
+java_library {
+ name: "launcherprotoslite",
+ proto: {
+ type: "lite",
+ include_dirs: ["external/protobuf/src"],
+ },
+
+ sdk_version: "current",
+ srcs: [
+ "*.proto",
+ ],
+}
diff --git a/core/proto/android/stats/sysui/notification_enums.proto b/core/proto/android/stats/sysui/notification_enums.proto
new file mode 100644
index 0000000..0983702
--- /dev/null
+++ b/core/proto/android/stats/sysui/notification_enums.proto
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package android.stats.sysui;
+
+// Enum used in NotificationReported and NotificationChannelModified atoms
+enum NotificationImportance { // Constants from NotificationManager.java
+ IMPORTANCE_UNSPECIFIED = -1000; // Should not occur for real notifications.
+ IMPORTANCE_NONE = 0; // No importance: does not show in the shade.
+ IMPORTANCE_MIN = 1; // Minimum to show in the shade.
+ IMPORTANCE_LOW = 2; // Shows in shade, maybe status bar, no buzz/beep.
+ IMPORTANCE_DEFAULT = 3; // Shows everywhere, makes noise, no heads-up.
+ IMPORTANCE_HIGH = 4; // Shows everywhere, makes noise, heads-up, may full-screen.
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 1c48a74..f8c5166 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -96,7 +96,6 @@
<protected-broadcast android:name="android.intent.action.USER_ACTIVITY_NOTIFICATION" />
<protected-broadcast android:name="android.intent.action.MY_PACKAGE_SUSPENDED" />
<protected-broadcast android:name="android.intent.action.MY_PACKAGE_UNSUSPENDED" />
- <protected-broadcast android:name="android.intent.action.CUSTOM_BUGREPORT_REQUESTED" />
<protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGED" />
<protected-broadcast android:name="android.os.action.POWER_SAVE_MODE_CHANGING" />
@@ -363,7 +362,6 @@
<protected-broadcast android:name="android.net.wifi.WIFI_STATE_CHANGED" />
<protected-broadcast android:name="android.net.wifi.WIFI_AP_STATE_CHANGED" />
<protected-broadcast android:name="android.net.wifi.WIFI_CREDENTIAL_CHANGED" />
- <protected-broadcast android:name="android.net.wifi.WIFI_SCAN_AVAILABLE" />
<protected-broadcast android:name="android.net.wifi.aware.action.WIFI_AWARE_STATE_CHANGED" />
<protected-broadcast android:name="android.net.wifi.rtt.action.WIFI_RTT_STATE_CHANGED" />
<protected-broadcast android:name="android.net.wifi.SCAN_RESULTS" />
@@ -371,12 +369,14 @@
<protected-broadcast android:name="android.net.wifi.STATE_CHANGE" />
<protected-broadcast android:name="android.net.wifi.LINK_CONFIGURATION_CHANGED" />
<protected-broadcast android:name="android.net.wifi.CONFIGURED_NETWORKS_CHANGE" />
+ <protected-broadcast android:name="android.net.wifi.action.NETWORK_SETTINGS_RESET" />
<protected-broadcast android:name="android.net.wifi.action.PASSPOINT_DEAUTH_IMMINENT" />
<protected-broadcast android:name="android.net.wifi.action.PASSPOINT_ICON" />
<protected-broadcast android:name="android.net.wifi.action.PASSPOINT_OSU_PROVIDERS_LIST" />
<protected-broadcast android:name="android.net.wifi.action.PASSPOINT_SUBSCRIPTION_REMEDIATION" />
<protected-broadcast android:name="android.net.wifi.action.PASSPOINT_LAUNCH_OSU_VIEW" />
<protected-broadcast android:name="android.net.wifi.action.WIFI_NETWORK_SUGGESTION_POST_CONNECTION" />
+ <protected-broadcast android:name="android.net.wifi.action.WIFI_SCAN_AVAILABLE" />
<protected-broadcast android:name="android.net.wifi.supplicant.CONNECTION_CHANGE" />
<protected-broadcast android:name="android.net.wifi.supplicant.STATE_CHANGE" />
<protected-broadcast android:name="android.net.wifi.p2p.STATE_CHANGED" />
@@ -445,6 +445,7 @@
<protected-broadcast android:name="android.internal.policy.action.BURN_IN_PROTECTION" />
<protected-broadcast android:name="android.app.action.SYSTEM_UPDATE_POLICY_CHANGED" />
+ <protected-broadcast android:name="android.app.action.RESET_PROTECTION_POLICY_CHANGED" />
<protected-broadcast android:name="android.app.action.DEVICE_OWNER_CHANGED" />
<protected-broadcast android:name="android.app.action.MANAGED_USER_CREATED" />
@@ -457,7 +458,6 @@
<protected-broadcast android:name="android.intent.action.internal_sim_state_changed" />
<protected-broadcast android:name="android.intent.action.LOCKED_BOOT_COMPLETED" />
<protected-broadcast android:name="android.intent.action.PRECISE_CALL_STATE" />
- <protected-broadcast android:name="android.intent.action.PRECISE_DATA_CONNECTION_STATE_CHANGED" />
<protected-broadcast android:name="android.intent.action.SUBSCRIPTION_PHONE_STATE" />
<protected-broadcast android:name="android.intent.action.USER_INFO_CHANGED" />
<protected-broadcast android:name="android.intent.action.USER_UNLOCKED" />
@@ -641,10 +641,6 @@
<protected-broadcast android:name="android.intent.action.DEVICE_CUSTOMIZATION_READY" />
- <!-- NETWORK_SET_TIME moved from com.android.phone to system server. It should ultimately be
- removed. -->
- <protected-broadcast android:name="android.telephony.action.NETWORK_SET_TIME" />
-
<!-- For tether entitlement recheck-->
<protected-broadcast
android:name="com.android.server.connectivity.tethering.PROVISIONING_RECHECK_ALARM" />
@@ -935,11 +931,10 @@
<!-- Allows an application a broad access to external storage in scoped storage.
Intended to be used by few apps that need to manage files on behalf of the users.
- <p>Protection level: signature|appop
- <p>This protection level is temporary and will most likely be changed to |preinstalled -->
+ <p>Protection level: signature|appop|preinstalled -->
<permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
android:permissionGroup="android.permission-group.UNDEFINED"
- android:protectionLevel="signature|appop" />
+ android:protectionLevel="signature|appop|preinstalled" />
<!-- ====================================================================== -->
<!-- Permissions for accessing the device location -->
@@ -1657,6 +1652,7 @@
<!-- Allows holder to request bluetooth/wifi scan bypassing global "use location" setting and
location permissions.
<p>Not for use by third-party or privileged applications.
+ @SystemApi
@hide
-->
<permission android:name="android.permission.RADIO_SCAN_WITHOUT_LOCATION"
@@ -1792,6 +1788,11 @@
android:label="@string/permlab_preferredPaymentInfo"
android:protectionLevel="normal" />
+ <!-- @SystemApi Allows an internal user to use privileged SecureElement APIs.
+ @hide -->
+ <permission android:name="android.permission.SECURE_ELEMENT_PRIVILEGED"
+ android:protectionLevel="signature|privileged" />
+
<!-- @deprecated This permission used to allow too broad access to sensitive methods and all its
uses have been replaced by a more appropriate permission. Most uses have been replaced with
a NETWORK_STACK or NETWORK_SETTINGS check. Please look up the documentation of the
@@ -2072,8 +2073,10 @@
android:protectionLevel="signature|privileged" />
<!-- Allows read only access to precise phone state.
- @hide Pending API council approval -->
+ Allows reading of detailed information about phone state for special-use applications
+ such as dialers, carrier applications, or ims applications. -->
<permission android:name="android.permission.READ_PRECISE_PHONE_STATE"
+ android:permissionGroup="android.permission-group.UNDEFINED"
android:protectionLevel="signature|privileged" />
<!-- @SystemApi Allows read access to privileged phone state.
@@ -2347,13 +2350,9 @@
<permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"
android:protectionLevel="signature|installer|telephony" />
- <!-- @SystemApi Allows an application to start its own activities, but on a different profile
- associated with the user. For example, an application running on the main profile of a user
- can start an activity on a managed profile of that user.
- This permission is not available to third party applications.
- @hide -->
+ <!-- Allows interaction across profiles in the same profile group. -->
<permission android:name="android.permission.INTERACT_ACROSS_PROFILES"
- android:protectionLevel="signature|privileged" />
+ android:protectionLevel="signature|appop|documenter|wellbeing" />
<!-- @SystemApi @hide Allows an application to call APIs that allow it to query and manage
users on the device. This permission is not available to
@@ -2549,17 +2548,17 @@
<permission android:name="android.permission.READ_WALLPAPER_INTERNAL"
android:protectionLevel="signature|privileged" />
- <!-- ============================================ -->
- <!-- Permissions for changing the system clock -->
- <!-- ============================================ -->
+ <!-- ===================================================== -->
+ <!-- Permissions for changing the system clock / time zone -->
+ <!-- ===================================================== -->
<eat-comment />
- <!-- Allows applications to set the system time.
- <p>Not for use by third-party applications. -->
+ <!-- Allows applications to set the system time directly.
+ <p>Not for use by third-party applications. -->
<permission android:name="android.permission.SET_TIME"
android:protectionLevel="signature|privileged" />
- <!-- Allows applications to set the system time zone.
+ <!-- Allows applications to set the system time zone directly.
<p>Not for use by third-party applications.
-->
<permission android:name="android.permission.SET_TIME_ZONE"
@@ -2567,6 +2566,20 @@
android:description="@string/permdesc_setTimeZone"
android:protectionLevel="signature|privileged" />
+ <!-- Allows telephony to suggest the time / time zone.
+ <p>Not for use by third-party applications.
+ @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) @hide
+ -->
+ <permission android:name="android.permission.SUGGEST_PHONE_TIME_AND_ZONE"
+ android:protectionLevel="signature|telephony" />
+
+ <!-- Allows applications like settings to suggest the user's manually chosen time / time zone.
+ <p>Not for use by third-party applications.
+ @hide
+ -->
+ <permission android:name="android.permission.SUGGEST_MANUAL_TIME_AND_ZONE"
+ android:protectionLevel="signature" />
+
<!-- ==================================================== -->
<!-- Permissions related to changing status bar -->
<!-- ==================================================== -->
@@ -3373,6 +3386,14 @@
<permission android:name="android.permission.NOTIFY_TV_INPUTS"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi Allows an application to interact with tuner resources through
+ Tuner Resource Manager.
+ <p>Protection level: signature|privileged
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.TUNER_RESOURCE_ACCESS"
+ android:protectionLevel="signature|privileged" />
+
<!-- Must be required by a {@link android.media.routing.MediaRouteService}
to ensure that only the system can interact with it.
@hide -->
@@ -3959,6 +3980,13 @@
<permission android:name="android.permission.BACKUP"
android:protectionLevel="signature|privileged" />
+ <!-- @SystemApi Allows an application to make modifications to device settings such that these
+ modifications will be overridden by settings restore..
+ <p>Not for use by third-party applications.
+ @hide -->
+ <permission android:name="android.permission.MODIFY_SETTINGS_OVERRIDEABLE_BY_RESTORE"
+ android:protectionLevel="signature|setup" />
+
<!-- @SystemApi Allows application to manage
{@link android.security.keystore.recovery.RecoveryController}.
<p>Not for use by third-party applications.
@@ -4748,6 +4776,10 @@
<!-- Allows input events to be monitored. Very dangerous! @hide -->
<permission android:name="android.permission.MONITOR_INPUT"
android:protectionLevel="signature" />
+ <!-- Allows the caller to change the associations between input devices and displays.
+ Very dangerous! @hide -->
+ <permission android:name="android.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT"
+ android:protectionLevel="signature" />
<!-- Allows query of any normal app on the device, regardless of manifest declarations. -->
<permission android:name="android.permission.QUERY_ALL_PACKAGES"
@@ -4951,6 +4983,12 @@
android:process=":ui">
</activity>
+ <activity android:name="com.android.internal.app.BlockedAppActivity"
+ android:theme="@style/Theme.Dialog.Confirmation"
+ android:excludeFromRecents="true"
+ android:process=":ui">
+ </activity>
+
<activity android:name="com.android.settings.notification.NotificationAccessConfirmationActivity"
android:theme="@style/Theme.Dialog.Confirmation"
android:excludeFromRecents="true">
diff --git a/core/res/res/drawable/ic_accessibility_color_correction.xml b/core/res/res/drawable/ic_accessibility_color_correction.xml
new file mode 100644
index 0000000..02fa4b8
--- /dev/null
+++ b/core/res/res/drawable/ic_accessibility_color_correction.xml
@@ -0,0 +1,37 @@
+<vector android:height="24dp" android:viewportHeight="192"
+ android:viewportWidth="192" android:width="24dp"
+ xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="#00BCD4" android:pathData="M37.14,173.74L8.61,83.77c-1.72,-5.75 0.26,-11.97 4.97,-15.63L87.15,11c5.2,-4.04 12.46,-3.99 17.61,0.12l73.81,58.94c4.63,3.7 6.53,9.88 4.8,15.57l-28.48,88.18c-1.85,6.06 -7.39,10.19 -13.67,10.19H50.84C44.53,184 38.97,179.83 37.14,173.74z"/>
+ <path android:fillAlpha="0.2" android:fillColor="#FFFFFF"
+ android:pathData="M13.58,69.14L87.15,12c5.2,-4.04 12.46,-3.99 17.61,0.12l73.81,58.94c3.36,2.68 5.27,6.67 5.41,10.82c0.15,-4.51 -1.79,-8.93 -5.41,-11.82l-73.81,-58.94C99.61,7.01 92.35,6.96 87.15,11L13.58,68.14c-3.71,2.88 -5.72,7.36 -5.56,11.94C8.17,75.85 10.14,71.81 13.58,69.14z" android:strokeAlpha="0.2"/>
+ <path android:fillAlpha="0.2" android:fillColor="#263238"
+ android:pathData="M183.35,84.63l-28.48,88.18c-1.85,6.06 -7.39,10.19 -13.67,10.19H50.84c-6.31,0 -11.87,-4.17 -13.7,-10.26L8.61,82.77c-0.36,-1.22 -0.55,-2.46 -0.59,-3.69c-0.06,1.56 0.13,3.14 0.59,4.69l28.53,89.97c1.83,6.09 7.39,10.26 13.7,10.26h90.37c6.28,0 11.82,-4.13 13.67,-10.19l28.48,-88.18c0.48,-1.57 0.67,-3.17 0.61,-4.75C183.92,82.13 183.73,83.39 183.35,84.63z" android:strokeAlpha="0.2"/>
+ <path android:pathData="M60.01,135L60.01,135H60l48.04,49h33.17c6.28,0 11.82,-4.13 13.67,-10.19l18.68,-57.84L129.51,72.2l-0.01,0c0.27,0.27 0.52,0.5 0.77,0.77l0.55,0.55c1.57,1.56 1.57,4.08 -0.04,5.68l-12.56,12.49l6.36,6.3l1.38,1.37l-5.67,5.64l-5.71,-5.68L79.15,135H60.42H60.01z">
+ <aapt:attr name="android:fillColor">
+ <gradient android:endX="155.9627" android:endY="165.1493"
+ android:startX="98.4649" android:startY="107.6516" android:type="linear">
+ <item android:color="#19263238" android:offset="0"/>
+ <item android:color="#00212121" android:offset="1"/>
+ </gradient>
+ </aapt:attr>
+ </path>
+ <path android:fillColor="#0097A7" android:pathData="M68.55,120.35l32.173,-32.173l7.658,7.658l-32.173,32.173z"/>
+ <path android:fillColor="#FFFFFF" android:pathData="M130.83,73.52l-9.42,-9.36c-1.57,-1.56 -4.1,-1.56 -5.67,0l-12.56,12.48L95.42,69l-5.67,5.64l5.71,5.68L60,116.97V135h19.15l35.42,-35.68l5.71,5.68l5.67,-5.64l-7.73,-7.68l12.56,-12.48C132.4,77.6 132.4,75.08 130.83,73.52zM74.98,126.77l-6.43,-6.43l32.17,-32.17l6.43,6.43L74.98,126.77z"/>
+ <path android:fillAlpha="0.1" android:fillColor="#263238"
+ android:pathData="M120.28,105l-5.71,-5.68l-35.42,35.68l-19.15,0l1,1l19.15,0l35.42,-35.68l5.71,5.68l5.68,-5.64l-1,-1z" android:strokeAlpha="0.1"/>
+ <path android:fillAlpha="0.1" android:fillColor="#263238"
+ android:pathData="M90.75,75.64l-0.01,0l4.71,4.68l0.01,0z" android:strokeAlpha="0.1"/>
+ <path android:fillAlpha="0.1" android:fillColor="#263238"
+ android:pathData="M131.83,74.52l-0.97,-0.97c1.54,1.56 1.53,4.06 -0.07,5.65l-12.56,12.48l1,1l12.55,-12.48C133.4,78.6 133.4,76.08 131.83,74.52z" android:strokeAlpha="0.1"/>
+ <path android:fillAlpha="0.1" android:fillColor="#263238"
+ android:pathData="M101.72,89.17l6.67,6.66l0,0l-7.67,-7.66l-32.17,32.17l1,1z" android:strokeAlpha="0.1"/>
+ <path android:pathData="M37.13,173.7L8.62,83.69c-1.7,-5.8 0.3,-12 5,-15.6l73.52,-57.1c5.2,-4 12.5,-4 17.6,0.1l73.82,58.9c4.6,3.7 6.5,9.9 4.8,15.6l-28.51,88.21c-1.8,6.1 -7.4,10.2 -13.7,10.2H50.83C44.53,184 38.93,179.8 37.13,173.7z">
+ <aapt:attr name="android:fillColor">
+ <gradient android:centerX="21.977" android:centerY="23.8809"
+ android:gradientRadius="158.0384" android:type="radial">
+ <item android:color="#19FFFFFF" android:offset="0"/>
+ <item android:color="#00FFFFFF" android:offset="1"/>
+ </gradient>
+ </aapt:attr>
+ </path>
+</vector>
diff --git a/core/res/res/drawable/ic_accessibility_color_inversion.xml b/core/res/res/drawable/ic_accessibility_color_inversion.xml
new file mode 100644
index 0000000..97b30b0
--- /dev/null
+++ b/core/res/res/drawable/ic_accessibility_color_inversion.xml
@@ -0,0 +1,47 @@
+<vector android:height="24dp" android:viewportHeight="192"
+ android:viewportWidth="192" android:width="24dp"
+ xmlns:aapt="http://schemas.android.com/aapt" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="#546E7A" android:pathData="M37.14,173.74L8.61,83.77c-1.72,-5.75 0.26,-11.97 4.97,-15.63L87.15,11c5.2,-4.04 12.46,-3.99 17.61,0.12l73.81,58.94c4.63,3.7 6.53,9.88 4.8,15.57l-28.48,88.18c-1.85,6.06 -7.39,10.19 -13.67,10.19H50.84C44.53,184 38.97,179.83 37.14,173.74z"/>
+ <path android:fillAlpha="0.2" android:fillColor="#263238"
+ android:pathData="M183.37,84.63l-28.48,88.18c-1.85,6.06 -7.39,10.19 -13.67,10.19H50.84c-6.31,0 -11.87,-4.17 -13.7,-10.26L8.61,82.77c-0.36,-1.22 -0.55,-2.46 -0.59,-3.69c-0.06,1.56 0.13,3.14 0.59,4.69l28.53,89.97c1.83,6.09 7.39,10.26 13.7,10.26h90.38c6.28,0 11.82,-4.13 13.67,-10.19l28.48,-88.18c0.48,-1.57 0.67,-3.17 0.61,-4.75C183.94,82.13 183.74,83.39 183.37,84.63z" android:strokeAlpha="0.2"/>
+ <path android:fillAlpha="0.2" android:fillColor="#FFFFFF"
+ android:pathData="M13.58,69.14L87.15,12c5.2,-4.04 12.46,-3.99 17.61,0.12l73.81,58.94c3.36,2.68 5.27,6.67 5.41,10.82c0.15,-4.51 -1.79,-8.93 -5.41,-11.82l-73.81,-58.94C99.61,7.01 92.35,6.96 87.15,11L13.58,68.14c-3.71,2.88 -5.72,7.36 -5.56,11.94C8.17,75.85 10.14,71.81 13.58,69.14z" android:strokeAlpha="0.2"/>
+ <path android:fillAlpha="0.2" android:fillColor="#FFFFFF"
+ android:pathData="M53,130.05l5.03,-4.79L44.16,112c-0.05,0.78 -0.14,1.52 -0.15,2.34C43.62,136.61 61.27,154.56 84,156v-5.31C70.13,149.64 58.56,141.54 53,130.05z" android:strokeAlpha="0.2"/>
+ <path android:fillAlpha="0.2" android:fillColor="#FFFFFF"
+ android:pathData="M109,52v5.31c13.65,1.05 24.67,9.15 30.11,20.64l-4.92,4.79L147.81,96c0.09,-0.78 0.17,-1.53 0.19,-2.34C148.38,71.39 131.36,53.44 109,52z" android:strokeAlpha="0.2"/>
+ <path android:pathData="M154.89,173.81l13.57,-42.02l-46.53,-46.56C125.75,90.5 128,96.98 128,104c0,17.7 -14.3,32 -32,32c-8.64,0 -16.47,-3.42 -22.22,-8.97l0.9,0.91L130.73,184h10.49C147.5,184 153.04,179.87 154.89,173.81z">
+ <aapt:attr name="android:fillColor">
+ <gradient android:endX="153.3523" android:endY="161.6371"
+ android:startX="90.6075" android:startY="98.8923" android:type="linear">
+ <item android:color="#33263238" android:offset="0"/>
+ <item android:color="#05263238" android:offset="1"/>
+ </gradient>
+ </aapt:attr>
+ </path>
+ <path android:pathData="M96,129.6V78.4c-14.11,0 -25.6,11.49 -25.6,25.6S81.89,129.6 96,129.6z">
+ <aapt:attr name="android:fillColor">
+ <gradient android:endX="150.8492" android:endY="164.1401"
+ android:startX="88.1044" android:startY="101.3954" android:type="linear">
+ <item android:color="#33263238" android:offset="0"/>
+ <item android:color="#05263238" android:offset="1"/>
+ </gradient>
+ </aapt:attr>
+ </path>
+ <path android:fillAlpha="0.2" android:fillColor="#263238"
+ android:pathData="M96,136c-17.53,0 -31.72,-14.04 -31.99,-31.5c0,0.17 -0.01,0.33 -0.01,0.5c0,17.7 14.3,32 32,32s32,-14.3 32,-32c0,-0.17 -0.01,-0.33 -0.01,-0.5C127.72,121.96 113.53,136 96,136z" android:strokeAlpha="0.2"/>
+ <path android:fillAlpha="0.2" android:fillColor="#263238"
+ android:pathData="M70.4,104c0,0.17 0.01,0.33 0.01,0.5C70.68,90.62 82.06,79.4 96,79.4v-1C81.89,78.4 70.4,89.88 70.4,104z" android:strokeAlpha="0.2"/>
+ <path android:fillAlpha="0.2" android:fillColor="#FFFFFF"
+ android:pathData="M96,72c-17.7,0 -32,14.3 -32,32s14.3,32 32,32s32,-14.3 32,-32S113.7,72 96,72zM70.4,104c0,-14.11 11.49,-25.6 25.6,-25.6v51.2C81.89,129.6 70.4,118.11 70.4,104z" android:strokeAlpha="0.2"/>
+ <path android:fillColor="#FFFFFF" android:pathData="M96,72c-17.7,0 -32,14.3 -32,32s14.3,32 32,32s32,-14.3 32,-32S113.7,72 96,72zM70.4,104c0,-14.11 11.49,-25.6 25.6,-25.6v51.2C81.89,129.6 70.4,118.11 70.4,104z"/>
+ <path android:pathData="M37.14,173.74L8.61,83.77c-1.72,-5.75 0.26,-11.97 4.97,-15.63L87.15,11c5.2,-4.04 12.46,-3.99 17.61,0.12l73.81,58.94c4.63,3.7 6.53,9.88 4.8,15.57l-28.48,88.18c-1.85,6.06 -7.39,10.19 -13.67,10.19H50.84C44.53,184 38.97,179.83 37.14,173.74z">
+ <aapt:attr name="android:fillColor">
+ <gradient android:endX="156.2451" android:endY="171.4516"
+ android:startX="37.0633" android:startY="52.269802" android:type="linear">
+ <item android:color="#19FFFFFF" android:offset="0"/>
+ <item android:color="#00FFFFFF" android:offset="1"/>
+ </gradient>
+ </aapt:attr>
+ </path>
+</vector>
diff --git a/core/res/res/layout/accessibility_button_chooser_item.xml b/core/res/res/layout/accessibility_button_chooser_item.xml
index 1edd2cd..d19e313 100644
--- a/core/res/res/layout/accessibility_button_chooser_item.xml
+++ b/core/res/res/layout/accessibility_button_chooser_item.xml
@@ -36,7 +36,7 @@
android:id="@+id/accessibility_button_target_label"
android:layout_width="0dp"
android:layout_height="wrap_content"
- android:layout_marginStart="8dp"
+ android:layout_marginStart="14dp"
android:layout_weight="1"
android:textColor="?attr/textColorPrimary"/>
diff --git a/core/res/res/layout/autofill_inline_suggestion.xml b/core/res/res/layout/autofill_inline_suggestion.xml
new file mode 100644
index 0000000..f7ac164
--- /dev/null
+++ b/core/res/res/layout/autofill_inline_suggestion.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="wrap_content"
+ android:layout_height="56dp"
+ android:background="@color/white"
+ android:orientation="horizontal"
+ android:paddingStart="12dp"
+ android:paddingEnd="12dp">
+
+ <ImageView
+ android:id="@+id/autofill_inline_suggestion_start_icon"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_gravity="center"
+ android:contentDescription="autofill_inline_suggestion_start_icon" />
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_gravity="center"
+ android:layout_weight="1"
+ android:layout_marginStart="12dp"
+ android:layout_marginEnd="12dp"
+ android:orientation="vertical"
+ android:gravity="center_vertical">
+
+ <TextView
+ android:id="@+id/autofill_inline_suggestion_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:maxLines="1"
+ tools:text="username1"/>
+
+ <TextView
+ android:id="@+id/autofill_inline_suggestion_subtitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:maxLines="1"
+ tools:text="inline fill service"/>
+ </LinearLayout>
+
+ <ImageView
+ android:id="@+id/autofill_inline_suggestion_end_icon"
+ android:layout_width="24dp"
+ android:layout_height="24dp"
+ android:layout_gravity="center"
+ android:contentDescription="autofill_inline_suggestion_end_icon" />
+</LinearLayout>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 9c08728..4475415 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -3752,6 +3752,8 @@
</p>
-->
<attr name="canRequestFingerprintGestures" format="boolean" />
+ <!-- Attribute whether the accessibility service wants to be able to take screenshot. -->
+ <attr name="canTakeScreenshot" format="boolean" />
<!-- Animated image of the accessibility service purpose or behavior, to help users
understand how the service can help them.-->
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index 6435cdd..94f3b8a 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -2005,6 +2005,15 @@
<attr name="maxSdkVersion" />
</declare-styleable>
+ <!-- The <code>extension-sdk</code> tag is a child of the <uses-sdk> tag,
+ and specifies required extension sdk features. -->
+ <declare-styleable name="AndroidManifestExtensionSdk">
+ <!-- The extension SDK version that this tag refers to. -->
+ <attr name="sdkVersion" format="integer" />
+ <!-- The minimum version of the extension SDK this application requires.-->
+ <attr name="minExtensionVersion" format="integer" />
+ </declare-styleable>
+
<!-- The <code>library</code> tag declares that this apk is providing itself
as a shared library for other applications to use. It can only be used
with apks that are built in to the system image. Other apks can link to
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 57f99a9..a78195b 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1877,6 +1877,8 @@
<string name="config_defaultCallRedirection" translatable="false"></string>
<!-- The name of the package that will hold the call screening role by default. -->
<string name="config_defaultCallScreening" translatable="false"></string>
+ <!-- The name of the package that will hold the system gallery role. -->
+ <string name="config_systemGallery" translatable="false">com.android.gallery</string>
<!-- Enable/disable default bluetooth profiles:
HSP_AG, ObexObjectPush, Audio, NAP -->
@@ -2093,9 +2095,6 @@
<!-- Number of times to try again with the shorter interval, before backing
off until the normal polling interval. A value < 0 indicates infinite. -->
<integer name="config_ntpRetry">3</integer>
- <!-- If the time difference is greater than this threshold in milliseconds,
- then update the time. -->
- <integer name="config_ntpThreshold">5000</integer>
<!-- Timeout to wait for NTP server response in milliseconds. -->
<integer name="config_ntpTimeout">5000</integer>
@@ -4292,4 +4291,10 @@
intent is broadcasted on bugreporting chord (instead of the default full bugreport
generation). -->
<bool name="config_customBugreport">false</bool>
+
+ <!-- Class name of the custom country detector to be used. -->
+ <string name="config_customCountryDetector" translatable="false">com.android.server.location.ComprehensiveCountryDetector</string>
+
+ <!-- Package name of the required service extension package. -->
+ <string name="config_servicesExtensionPackage" translatable="false">android.ext.services</string>
</resources>
diff --git a/core/res/res/values/cross_profile_apps.xml b/core/res/res/values/cross_profile_apps.xml
new file mode 100644
index 0000000..ab6f20d
--- /dev/null
+++ b/core/res/res/values/cross_profile_apps.xml
@@ -0,0 +1,24 @@
+<?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>
+ <!--
+ A collection of apps that have been pre-approved for cross-profile communication.
+ These will not require admin consent, but will still require user consent during provisioning.
+ -->
+ <string-array translatable="false" name="cross_profile_apps">
+ </string-array>
+</resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index bf7558c..d437aa1 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -777,4 +777,9 @@
<!-- Assistant handles -->
<dimen name="assist_handle_shadow_radius">2dp</dimen>
+ <!-- For Waterfall Display -->
+ <dimen name="waterfall_display_left_edge_size">0px</dimen>
+ <dimen name="waterfall_display_top_edge_size">0px</dimen>
+ <dimen name="waterfall_display_right_edge_size">0px</dimen>
+ <dimen name="waterfall_display_bottom_edge_size">0px</dimen>
</resources>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index d12d069..36dbcbd 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -3000,13 +3000,18 @@
<public-group type="attr" first-id="0x01010607">
<public name="importantForContentCapture" />
<public name="forceQueryable" />
- <!-- @hide @SystemApi -->
<public name="resourcesMap" />
<public name="animatedImageDrawable"/>
<public name="htmlDescription"/>
<public name="preferMinimalPostProcessing"/>
<public name="featureId" />
<public name="supportsInlineSuggestions" />
+ <public name="crossProfile" />
+ <public name="canTakeScreenshot"/>
+ <!-- @hide @SystemApi -->
+ <public name="sdkVersion" />
+ <!-- @hide @SystemApi -->
+ <public name="minExtensionVersion" />
</public-group>
<public-group type="drawable" first-id="0x010800b5">
@@ -3030,6 +3035,8 @@
<public name="config_defaultCallRedirection" />
<!-- @hide @SystemApi -->
<public name="config_defaultCallScreening" />
+ <!-- @hide @SystemApi @TestApi -->
+ <public name="config_systemGallery" />
</public-group>
<public-group type="bool" first-id="0x01110005">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 66267d1..a81565a 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -723,11 +723,11 @@
<string name="permgroupdesc_sms">send and view SMS messages</string>
<!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
- <string name="permgrouplab_storage">Storage</string>
+ <string name="permgrouplab_storage">Files and media</string>
<!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permgroupdesc_storage">access photos, media, and files on your device</string>
- <!-- Title of a category of application permissioncds, listed so the user can choose whether they want to allow the application to do this. -->
+ <!-- Title of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permgrouplab_microphone">Microphone</string>
<!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permgroupdesc_microphone">record audio</string>
@@ -794,6 +794,11 @@
<string name="capability_desc_canCaptureFingerprintGestures">Can capture gestures performed on
the device\'s fingerprint sensor.</string>
+ <!-- Title for the capability of an accessibility service to take screenshot. [CHAR LIMIT=32] -->
+ <string name="capability_title_canTakeScreenshot">Take screenshot</string>
+ <!-- Description for the capability of an accessibility service to take screenshot. [CHAR LIMIT=NONE] -->
+ <string name="capability_desc_canTakeScreenshot">Can take a screenshot of the display.</string>
+
<!-- Permissions -->
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
@@ -4358,10 +4363,7 @@
<string name="accessibility_shortcut_menu_button">Empty</string>
<!-- Text in button that edit the accessibility shortcut menu. [CHAR LIMIT=100] -->
- <string name="edit_accessibility_shortcut_menu_button">Edit</string>
-
- <!-- Text in button that save the accessibility shortcut menu changed status. [CHAR LIMIT=100] -->
- <string name="save_accessibility_shortcut_menu_button">Save</string>
+ <string name="edit_accessibility_shortcut_menu_button">Edit shortcuts</string>
<!-- Text in button that cancel the accessibility shortcut menu changed status. [CHAR LIMIT=100] -->
<string name="cancel_accessibility_shortcut_menu_button">Cancel</string>
@@ -4925,6 +4927,13 @@
<!-- Title for button to turn on work profile. [CHAR LIMIT=NONE] -->
<string name="work_mode_turn_on">Turn on</string>
+ <!-- Title of the dialog that is shown when the user tries to launch a suspended application [CHAR LIMIT=50] -->
+ <string name="app_blocked_title">App is not available</string>
+ <!-- Default message shown in the dialog that is shown when the user tries to launch a suspended application [CHAR LIMIT=NONE] -->
+ <string name="app_blocked_message">
+ <xliff:g id="app_name" example="Gmail">%1$s</xliff:g> is not available right now.
+ </string>
+
<!-- Message displayed in dialog when app is too old to run on this verison of android. [CHAR LIMIT=NONE] -->
<string name="deprecated_target_sdk_message">This app was built for an older version of Android and may not work properly. Try checking for updates, or contact the developer.</string>
<!-- Title for button to see application detail in app store which it came from - it may allow user to update to newer version. [CHAR LIMIT=50] -->
@@ -5210,25 +5219,14 @@
<!-- Battery saver strings -->
<!-- The user visible name of the notification channel for battery saver notifications [CHAR_LIMIT=80] -->
<string name="battery_saver_notification_channel_name">Battery Saver</string>
- <!-- Title of notification letting users know why battery saver didn't turn back on automatically after the device was unplugged [CHAR_LIMIT=NONE] -->
- <string name="battery_saver_sticky_disabled_notification_title">Battery Saver won\u2019t reactivate until battery low again</string>
- <!-- Summary of notification letting users know why battery saver didn't turn back on automatically after the device was unplugged [CHAR_LIMIT=NONE] -->
- <string name="battery_saver_sticky_disabled_notification_summary">Battery has been charged to a sufficient level. Battery Saver won\u2019t reactivate until the battery is low again.</string>
+ <!-- Title of notification letting users know that battery saver is now off [CHAR_LIMIT=80] -->
+ <string name="battery_saver_off_notification_title">Battery Saver turned off</string>
<!-- Title of notification letting users know the battery level at the time the notification was posted [CHAR_LIMIT=80] -->
- <string name="battery_saver_charged_notification_title" product="default">Phone <xliff:g id="charge level" example="90%">%1$s</xliff:g> charged</string>
+ <string name="battery_saver_charged_notification_summary" product="default">Phone has enough charge. Features no longer restricted.</string>
<!-- Title of notification letting users know the battery level at the time the notification was posted [CHAR_LIMIT=80] -->
- <string name="battery_saver_charged_notification_title" product="tablet">Tablet <xliff:g id="charge level" example="90%">%1$s</xliff:g> charged</string>
+ <string name="battery_saver_charged_notification_summary" product="tablet">Tablet has enough charge. Features no longer restricted.</string>
<!-- Title of notification letting users know the battery level at the time the notification was posted [CHAR_LIMIT=80] -->
- <string name="battery_saver_charged_notification_title" product="device">Device <xliff:g id="charge level" example="90%">%1$s</xliff:g> charged</string>
- <!-- Summary of notification letting users know that battery saver is now off [CHAR_LIMIT=NONE] -->
- <string name="battery_saver_off_notification_summary">Battery Saver is off. Features no longer restricted.</string>
- <!-- Alternative summary of notification letting users know that battery saver has been turned off.
- If it's easy to translate the difference between "Battery Saver turned off. Features no longer restricted."
- and "Battery Saver is off. Features no longer restricted." into the target language,
- then translate "Battery Saver turned off. Features no longer restricted."
- If the translation doesn't make a difference or the difference is hard to capture in the target language,
- then translate "Battery Saver is off. Features no longer restricted." instead. [CHAR_LIMIT=NONE] -->
- <string name="battery_saver_off_alternative_notification_summary">Battery Saver turned off. Features no longer restricted.</string>
+ <string name="battery_saver_charged_notification_summary" product="device">Device has enough charge. Features no longer restricted.</string>
<!-- Description of media type: folder or directory that contains additional files. [CHAR LIMIT=32] -->
<string name="mime_type_folder">Folder</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 2a17548..669b41e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -434,7 +434,6 @@
<java-symbol type="integer" name="config_ntpPollingInterval" />
<java-symbol type="integer" name="config_ntpPollingIntervalShorter" />
<java-symbol type="integer" name="config_ntpRetry" />
- <java-symbol type="integer" name="config_ntpThreshold" />
<java-symbol type="integer" name="config_ntpTimeout" />
<java-symbol type="integer" name="config_shortPressOnPowerBehavior" />
<java-symbol type="integer" name="config_toastDefaultGravity" />
@@ -1259,6 +1258,7 @@
<java-symbol type="array" name="vendor_disallowed_apps_managed_user" />
<java-symbol type="array" name="vendor_disallowed_apps_managed_profile" />
<java-symbol type="array" name="vendor_disallowed_apps_managed_device" />
+ <java-symbol type="array" name="cross_profile_apps" />
<java-symbol type="drawable" name="default_wallpaper" />
<java-symbol type="drawable" name="default_lock_wallpaper" />
@@ -3036,6 +3036,9 @@
<java-symbol type="string" name="app_suspended_more_details" />
<java-symbol type="string" name="app_suspended_default_message" />
+ <java-symbol type="string" name="app_blocked_title" />
+ <java-symbol type="string" name="app_blocked_message" />
+
<!-- Used internally for assistant to launch activity transitions -->
<java-symbol type="id" name="cross_task_transition" />
@@ -3212,9 +3215,10 @@
<java-symbol type="id" name="accessibility_button_target_switch_item" />
<java-symbol type="string" name="accessibility_magnification_chooser_text" />
<java-symbol type="string" name="edit_accessibility_shortcut_menu_button" />
- <java-symbol type="string" name="save_accessibility_shortcut_menu_button" />
<java-symbol type="string" name="cancel_accessibility_shortcut_menu_button" />
+ <java-symbol type="drawable" name="ic_accessibility_color_inversion" />
+ <java-symbol type="drawable" name="ic_accessibility_color_correction" />
<java-symbol type="drawable" name="ic_accessibility_magnification" />
<java-symbol type="drawable" name="ic_delete_item" />
@@ -3231,6 +3235,7 @@
<java-symbol type="layout" name="autofill_dataset_picker"/>
<java-symbol type="layout" name="autofill_dataset_picker_fullscreen"/>
<java-symbol type="layout" name="autofill_dataset_picker_header_footer"/>
+ <java-symbol type="layout" name="autofill_inline_suggestion" />
<java-symbol type="id" name="autofill" />
<java-symbol type="id" name="autofill_dataset_footer"/>
<java-symbol type="id" name="autofill_dataset_header"/>
@@ -3238,6 +3243,10 @@
<java-symbol type="id" name="autofill_dataset_list"/>
<java-symbol type="id" name="autofill_dataset_picker"/>
<java-symbol type="id" name="autofill_dataset_title" />
+ <java-symbol type="id" name="autofill_inline_suggestion_end_icon" />
+ <java-symbol type="id" name="autofill_inline_suggestion_start_icon" />
+ <java-symbol type="id" name="autofill_inline_suggestion_subtitle" />
+ <java-symbol type="id" name="autofill_inline_suggestion_title" />
<java-symbol type="id" name="autofill_save_custom_subtitle" />
<java-symbol type="id" name="autofill_save_icon" />
<java-symbol type="id" name="autofill_save_no" />
@@ -3605,6 +3614,8 @@
<java-symbol type="bool" name="config_maskMainBuiltInDisplayCutout" />
+ <java-symbol type="string" name="config_customCountryDetector" />
+
<!-- For Foldables -->
<java-symbol type="bool" name="config_lidControlsDisplayFold" />
<java-symbol type="string" name="config_foldedArea" />
@@ -3643,11 +3654,8 @@
<java-symbol type="bool" name="config_useSystemProvidedLauncherForSecondary" />
<java-symbol type="string" name="battery_saver_notification_channel_name" />
- <java-symbol type="string" name="battery_saver_sticky_disabled_notification_title" />
- <java-symbol type="string" name="battery_saver_sticky_disabled_notification_summary" />
- <java-symbol type="string" name="battery_saver_charged_notification_title" />
- <java-symbol type="string" name="battery_saver_off_notification_summary" />
- <java-symbol type="string" name="battery_saver_off_alternative_notification_summary" />
+ <java-symbol type="string" name="battery_saver_off_notification_title" />
+ <java-symbol type="string" name="battery_saver_charged_notification_summary" />
<java-symbol type="string" name="dynamic_mode_notification_channel_name" />
<java-symbol type="string" name="dynamic_mode_notification_title" />
<java-symbol type="string" name="dynamic_mode_notification_summary" />
@@ -3800,5 +3808,15 @@
<!-- Assistant handles -->
<java-symbol type="dimen" name="assist_handle_shadow_radius" />
- <java-symbol type="bool" name="config_customBugreport" />
+ <!-- For Waterfall Display -->
+ <java-symbol type="dimen" name="waterfall_display_left_edge_size" />
+ <java-symbol type="dimen" name="waterfall_display_top_edge_size" />
+ <java-symbol type="dimen" name="waterfall_display_right_edge_size" />
+ <java-symbol type="dimen" name="waterfall_display_bottom_edge_size" />
+
+ <!-- Accessibility take screenshot -->
+ <java-symbol type="string" name="capability_desc_canTakeScreenshot" />
+ <java-symbol type="string" name="capability_title_canTakeScreenshot" />
+
+ <java-symbol type="string" name="config_servicesExtensionPackage" />
</resources>
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/WifiConfigurationHelper.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/WifiConfigurationHelper.java
index f0a8367..a296ca2 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/WifiConfigurationHelper.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/WifiConfigurationHelper.java
@@ -16,6 +16,7 @@
package com.android.connectivitymanagertest;
+import android.net.IpConfiguration;
import android.net.IpConfiguration.IpAssignment;
import android.net.IpConfiguration.ProxySettings;
import android.net.LinkAddress;
@@ -136,7 +137,7 @@
config.enterpriseConfig.setPhase2Method(phase2);
config.enterpriseConfig.setIdentity(identity);
config.enterpriseConfig.setAnonymousIdentity(anonymousIdentity);
- config.enterpriseConfig.setCaCertificateAlias(caCert);
+ config.enterpriseConfig.setCaCertificateAliases(new String[] {caCert});
config.enterpriseConfig.setClientCertificateAlias(clientCert);
return config;
}
@@ -147,8 +148,12 @@
private static WifiConfiguration createGenericConfig(String ssid) {
WifiConfiguration config = new WifiConfiguration();
config.SSID = quotedString(ssid);
- config.setIpAssignment(IpAssignment.DHCP);
- config.setProxySettings(ProxySettings.NONE);
+
+ IpConfiguration ipConfiguration = config.getIpConfiguration();
+ ipConfiguration.setIpAssignment(IpAssignment.DHCP);
+ ipConfiguration.setProxySettings(ProxySettings.NONE);
+ config.setIpConfiguration(ipConfiguration);
+
return config;
}
@@ -237,6 +242,7 @@
throw new IllegalArgumentException();
}
+ IpConfiguration ipConfiguration = config.getIpConfiguration();
if (jsonConfig.has("ip")) {
StaticIpConfiguration staticIpConfig = new StaticIpConfiguration();
@@ -247,13 +253,14 @@
staticIpConfig.dnsServers.add(getInetAddress(jsonConfig.getString("dns1")));
staticIpConfig.dnsServers.add(getInetAddress(jsonConfig.getString("dns2")));
- config.setIpAssignment(IpAssignment.STATIC);
- config.setStaticIpConfiguration(staticIpConfig);
+ ipConfiguration.setIpAssignment(IpAssignment.STATIC);
+ ipConfiguration.setStaticIpConfiguration(staticIpConfig);
} else {
- config.setIpAssignment(IpAssignment.DHCP);
+ ipConfiguration.setIpAssignment(IpAssignment.DHCP);
}
+ ipConfiguration.setProxySettings(ProxySettings.NONE);
+ config.setIpConfiguration(ipConfiguration);
- config.setProxySettings(ProxySettings.NONE);
return config;
}
diff --git a/core/tests/InstantAppResolverTests/Android.bp b/core/tests/InstantAppResolverTests/Android.bp
new file mode 100644
index 0000000..7b01010
--- /dev/null
+++ b/core/tests/InstantAppResolverTests/Android.bp
@@ -0,0 +1,32 @@
+//
+// 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 {
+ name: "FrameworksInstantAppResolverTests",
+ srcs: [ "src/**/*.kt" ],
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ ],
+ platform_apis: true,
+ static_libs: [
+ "androidx.test.ext.junit",
+ "androidx.test.rules",
+ "mockito-target-minus-junit4",
+ "truth-prebuilt",
+ ],
+ test_suites: ["device-tests"],
+}
diff --git a/core/tests/InstantAppResolverTests/AndroidManifest.xml b/core/tests/InstantAppResolverTests/AndroidManifest.xml
new file mode 100644
index 0000000..f95978b
--- /dev/null
+++ b/core/tests/InstantAppResolverTests/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?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="android.app.instantapp.resolver.test"
+ >
+
+ <application>
+ <uses-library android:name="android.test.runner"/>
+ </application>
+
+ <instrumentation
+ android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:label="InstantAppResolverTests"
+ android:targetPackage="android.app.instantapp.resolver.test"
+ />
+
+</manifest>
diff --git a/core/tests/InstantAppResolverTests/AndroidTest.xml b/core/tests/InstantAppResolverTests/AndroidTest.xml
new file mode 100644
index 0000000..fcc6344
--- /dev/null
+++ b/core/tests/InstantAppResolverTests/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?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.
+ -->
+
+<configuration description="Test module config for InstantAppResolverTests">
+ <option name="test-tag" value="InstantAppResolverTests" />
+
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="FrameworksInstantAppResolverTests.apk" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="android.app.instantapp.resolver.test" />
+ </test>
+</configuration>
diff --git a/core/tests/InstantAppResolverTests/src/android/app/instantapp/resolver/test/ResolverServiceMethodFallbackTest.kt b/core/tests/InstantAppResolverTests/src/android/app/instantapp/resolver/test/ResolverServiceMethodFallbackTest.kt
new file mode 100644
index 0000000..2a17ef2
--- /dev/null
+++ b/core/tests/InstantAppResolverTests/src/android/app/instantapp/resolver/test/ResolverServiceMethodFallbackTest.kt
@@ -0,0 +1,174 @@
+/*
+ * 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.instantapp.resolver.test
+
+import android.app.InstantAppResolverService
+import android.app.InstantAppResolverService.InstantAppResolutionCallback
+import android.content.Intent
+import android.content.pm.InstantAppRequestInfo
+import android.net.Uri
+import android.os.Bundle
+import android.os.IRemoteCallback
+import android.os.UserHandle
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.ExpectedException
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.mockito.Answers
+import org.mockito.Mock
+import org.mockito.Mockito.doNothing
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.MockitoAnnotations
+import java.util.UUID
+import kotlin.random.Random
+
+private typealias Method = InstantAppResolverService.(InstantAppRequestInfo) -> Unit
+
+@Suppress("max-line-length")
+@RunWith(Parameterized::class)
+class ResolverServiceMethodFallbackTest @Suppress("UNUSED_PARAMETER") constructor(
+ private val version: Int,
+ private val methodList: List<Method>,
+ private val info: InstantAppRequestInfo,
+ // Remaining only used to print human-readable test name
+ name: String,
+ isWebIntent: Boolean
+) {
+
+ companion object {
+ // Since the resolution callback class is final, mock the IRemoteCallback and have it throw
+ // a unique exception to indicate it was called.
+ class TestRemoteCallbackException : Exception()
+
+ private val testIntentWeb = Intent(Intent.ACTION_VIEW,
+ Uri.parse("https://${this::class.java.canonicalName}.com"))
+ private val testIntentNotWeb = Intent(Intent.ACTION_VIEW,
+ Uri.parse("content://${this::class.java.canonicalName}"))
+
+ private val testRemoteCallback = object : IRemoteCallback {
+ override fun sendResult(data: Bundle?) = throw TestRemoteCallbackException()
+ override fun asBinder() = throw UnsupportedOperationException()
+ }
+ private val testResolutionCallback = InstantAppResolutionCallback(0, testRemoteCallback)
+ private val testArray = IntArray(10) { Random.nextInt() }
+ private val testToken = UUID.randomUUID().toString()
+ private val testUser = UserHandle(Integer.MAX_VALUE)
+ private val testInfoWeb = InstantAppRequestInfo(testIntentWeb, testArray, testUser,
+ false, testToken)
+ private val testInfoNotWeb = InstantAppRequestInfo(testIntentNotWeb, testArray, testUser,
+ false, testToken)
+
+ // Each section defines methods versions with later definitions falling back to
+ // earlier definitions. Each block receives an [InstantAppResolverService] and invokes
+ // the appropriate version with the test data defined above.
+ private val infoOne: Method = { onGetInstantAppResolveInfo(testArray, testToken,
+ testResolutionCallback) }
+ private val infoTwo: Method = { onGetInstantAppResolveInfo(it.intent, testArray, testToken,
+ testResolutionCallback) }
+ private val infoThree: Method = { onGetInstantAppResolveInfo(it.intent, testArray, testUser,
+ testToken, testResolutionCallback) }
+ private val infoFour: Method = { onGetInstantAppResolveInfo(it, testResolutionCallback) }
+
+ private val filterOne: Method = { onGetInstantAppIntentFilter(testArray, testToken,
+ testResolutionCallback) }
+ private val filterTwo: Method = { onGetInstantAppIntentFilter(it.intent, testArray,
+ testToken, testResolutionCallback) }
+ private val filterThree: Method = { onGetInstantAppIntentFilter(it.intent, testArray,
+ testUser, testToken, testResolutionCallback) }
+ private val filterFour: Method = { onGetInstantAppIntentFilter(it, testResolutionCallback) }
+
+ private val infoList = listOf(infoOne, infoTwo, infoThree, infoFour)
+ private val filterList = listOf(filterOne, filterTwo, filterThree, filterFour)
+
+ @JvmStatic
+ @Parameterized.Parameters(name = "{3} version {0}, isWeb = {4}")
+ fun parameters(): Array<Array<*>> {
+ // Sanity check that web intent logic hasn't changed
+ assertThat(testInfoWeb.intent.isWebIntent).isTrue()
+ assertThat(testInfoNotWeb.intent.isWebIntent).isFalse()
+
+ // Declare all the possible params
+ val versions = Array(5) { it }
+ val methods = arrayOf("ResolveInfo" to infoList, "IntentFilter" to filterList)
+ val infos = arrayOf(testInfoWeb, testInfoNotWeb)
+
+ // FlatMap params into every possible combination
+ return infos.flatMap { info ->
+ methods.flatMap { (name, methods) ->
+ versions.map { version ->
+ arrayOf(version, methods, info, name, info.intent.isWebIntent)
+ }
+ }
+ }.toTypedArray()
+ }
+ }
+
+ @field:Mock(answer = Answers.CALLS_REAL_METHODS)
+ lateinit var mockService: InstantAppResolverService
+
+ @get:Rule
+ val expectedException = ExpectedException.none()
+
+ @Before
+ fun setUpMocks() {
+ MockitoAnnotations.initMocks(this)
+ }
+
+ @Test
+ fun onGetInstantApp() {
+ if (version == 0) {
+ // No version of the API was implemented, so expect terminal case
+ if (info.intent.isWebIntent) {
+ // If web intent, terminal is total failure
+ expectedException.expect(IllegalStateException::class.java)
+ } else {
+ // Otherwise, terminal is a fail safe by calling [testRemoteCallback]
+ expectedException.expect(TestRemoteCallbackException::class.java)
+ }
+ } else if (version < 2 && !info.intent.isWebIntent) {
+ // Starting from v2, if resolving a non-web intent and a v2+ method isn't implemented,
+ // it fails safely by calling [testRemoteCallback]
+ expectedException.expect(TestRemoteCallbackException::class.java)
+ }
+
+ // Version 1 is the first method (index 0)
+ val methodIndex = version - 1
+
+ // Implement a method if necessary
+ methodList.getOrNull(methodIndex)?.invoke(doNothing().`when`(mockService), info)
+
+ // Call the latest API
+ methodList.last().invoke(mockService, info)
+
+ // Check all methods before implemented method are never called
+ (0 until methodIndex).forEach {
+ methodList[it].invoke(verify(mockService, never()), info)
+ }
+
+ // Check all methods from implemented method are called
+ (methodIndex until methodList.size).forEach {
+ methodList[it].invoke(verify(mockService), info)
+ }
+
+ verifyNoMoreInteractions(mockService)
+ }
+}
diff --git a/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java b/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java
index 2989df8..5d42915 100644
--- a/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java
+++ b/core/tests/bandwidthtests/src/com/android/bandwidthtest/BandwidthTest.java
@@ -268,7 +268,7 @@
File snd_stat = new File (root_filepath + "tcp_snd");
int tx = BandwidthTestUtil.parseIntValueFromFile(snd_stat);
NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1);
- stats.addValues(NetworkStats.IFACE_ALL, uid, NetworkStats.SET_DEFAULT,
+ stats.addEntry(NetworkStats.IFACE_ALL, uid, NetworkStats.SET_DEFAULT,
NetworkStats.TAG_NONE, rx, 0, tx, 0, 0);
return stats;
}
diff --git a/core/tests/benchmarks/src/android/net/NetworkStatsBenchmark.java b/core/tests/benchmarks/src/android/net/NetworkStatsBenchmark.java
index 707d7b3..239f971 100644
--- a/core/tests/benchmarks/src/android/net/NetworkStatsBenchmark.java
+++ b/core/tests/benchmarks/src/android/net/NetworkStatsBenchmark.java
@@ -54,7 +54,7 @@
recycle.txBytes = 150000;
recycle.txPackets = 1500;
recycle.operations = 0;
- mNetworkStats.addValues(recycle);
+ mNetworkStats.addEntry(recycle);
if (recycle.set == 1) {
uid++;
}
@@ -70,7 +70,7 @@
recycle.txBytes = 180000 * mSize;
recycle.txPackets = 1200 * mSize;
recycle.operations = 0;
- mNetworkStats.addValues(recycle);
+ mNetworkStats.addEntry(recycle);
}
}
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index caae908..3836d6f 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -8,6 +8,7 @@
"EnabledTestApp/src/**/*.java",
"BinderProxyCountingTestApp/src/**/*.java",
"BinderProxyCountingTestService/src/**/*.java",
+ "BinderDeathRecipientHelperApp/src/**/*.java",
"aidl/**/I*.aidl",
],
@@ -59,7 +60,11 @@
resource_dirs: ["res"],
resource_zips: [":FrameworksCoreTests_apks_as_resources"],
- data: [":BstatsTestApp"],
+ data: [
+ ":BstatsTestApp",
+ ":BinderDeathRecipientHelperApp1",
+ ":BinderDeathRecipientHelperApp2",
+ ],
}
// Rules to copy all the test apks to the intermediate raw resource directory
@@ -79,6 +84,11 @@
":FrameworksCoreTests_install_split_feature_a",
":FrameworksCoreTests_install_use_perm_good",
":FrameworksCoreTests_install_uses_feature",
+ ":FrameworksCoreTests_install_uses_sdk_0",
+ ":FrameworksCoreTests_install_uses_sdk_q0",
+ ":FrameworksCoreTests_install_uses_sdk_r",
+ ":FrameworksCoreTests_install_uses_sdk_r0",
+ ":FrameworksCoreTests_install_uses_sdk_r5",
":FrameworksCoreTests_install_verifier_bad",
":FrameworksCoreTests_install_verifier_good",
":FrameworksCoreTests_keyset_permdef_sa_unone",
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 1aea98a..b85a332 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -95,6 +95,7 @@
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
<uses-permission android:name="android.permission.KILL_UID" />
+ <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES" />
<uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
diff --git a/core/tests/coretests/AndroidTest.xml b/core/tests/coretests/AndroidTest.xml
index b40aa87..ed9d3f5 100644
--- a/core/tests/coretests/AndroidTest.xml
+++ b/core/tests/coretests/AndroidTest.xml
@@ -21,6 +21,8 @@
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="FrameworksCoreTests.apk" />
<option name="test-file-name" value="BstatsTestApp.apk" />
+ <option name="test-file-name" value="BinderDeathRecipientHelperApp1.apk" />
+ <option name="test-file-name" value="BinderDeathRecipientHelperApp2.apk" />
</target_preparer>
<option name="test-tag" value="FrameworksCoreTests" />
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/core/tests/coretests/BinderDeathRecipientHelperApp/Android.bp b/core/tests/coretests/BinderDeathRecipientHelperApp/Android.bp
new file mode 100644
index 0000000..25e4fc3
--- /dev/null
+++ b/core/tests/coretests/BinderDeathRecipientHelperApp/Android.bp
@@ -0,0 +1,19 @@
+android_test_helper_app {
+ name: "BinderDeathRecipientHelperApp1",
+
+ srcs: ["**/*.java"],
+
+ sdk_version: "current",
+}
+
+android_test_helper_app {
+ name: "BinderDeathRecipientHelperApp2",
+
+ srcs: ["**/*.java"],
+
+ sdk_version: "current",
+
+ aaptflags: [
+ "--rename-manifest-package com.android.frameworks.coretests.bdr_helper_app2",
+ ],
+}
diff --git a/core/tests/coretests/BinderDeathRecipientHelperApp/AndroidManifest.xml b/core/tests/coretests/BinderDeathRecipientHelperApp/AndroidManifest.xml
new file mode 100644
index 0000000..dbd1774
--- /dev/null
+++ b/core/tests/coretests/BinderDeathRecipientHelperApp/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?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.frameworks.coretests.bdr_helper_app1">
+
+ <application>
+ <receiver android:name="com.android.frameworks.coretests.bdr_helper_app.TestCommsReceiver"
+ android:exported="true"/>
+
+ </application>
+</manifest>
diff --git a/core/tests/coretests/BinderDeathRecipientHelperApp/src/com/android/frameworks/coretests/bdr_helper_app/TestCommsReceiver.java b/core/tests/coretests/BinderDeathRecipientHelperApp/src/com/android/frameworks/coretests/bdr_helper_app/TestCommsReceiver.java
new file mode 100644
index 0000000..ab79e69
--- /dev/null
+++ b/core/tests/coretests/BinderDeathRecipientHelperApp/src/com/android/frameworks/coretests/bdr_helper_app/TestCommsReceiver.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.frameworks.coretests.bdr_helper_app;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * Receiver used to hand off a binder owned by this process to
+ * {@link android.os.BinderDeathRecipientTest}.
+ */
+public class TestCommsReceiver extends BroadcastReceiver {
+ private static final String TAG = TestCommsReceiver.class.getSimpleName();
+ private static final String PACKAGE_NAME = "com.android.frameworks.coretests.bdr_helper_app";
+
+ public static final String ACTION_GET_BINDER = PACKAGE_NAME + ".action.GET_BINDER";
+ public static final String EXTRA_KEY_BINDER = PACKAGE_NAME + ".EXTRA_BINDER";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ switch (intent.getAction()) {
+ case ACTION_GET_BINDER:
+ final Bundle resultExtras = new Bundle();
+ resultExtras.putBinder(EXTRA_KEY_BINDER, new Binder());
+ setResult(Activity.RESULT_OK, null, resultExtras);
+ break;
+ default:
+ Log.e(TAG, "Unknown action " + intent.getAction());
+ break;
+ }
+ }
+}
diff --git a/core/tests/coretests/apks/install_uses_sdk/Android.bp b/core/tests/coretests/apks/install_uses_sdk/Android.bp
new file mode 100644
index 0000000..92b09ed
--- /dev/null
+++ b/core/tests/coretests/apks/install_uses_sdk/Android.bp
@@ -0,0 +1,39 @@
+android_test_helper_app {
+ name: "FrameworksCoreTests_install_uses_sdk_r0",
+ defaults: ["FrameworksCoreTests_apks_defaults"],
+ manifest: "AndroidManifest-r0.xml",
+
+ srcs: ["**/*.java"],
+}
+
+android_test_helper_app {
+ name: "FrameworksCoreTests_install_uses_sdk_r5",
+ defaults: ["FrameworksCoreTests_apks_defaults"],
+ manifest: "AndroidManifest-r5.xml",
+
+ srcs: ["**/*.java"],
+}
+
+android_test_helper_app {
+ name: "FrameworksCoreTests_install_uses_sdk_q0",
+ defaults: ["FrameworksCoreTests_apks_defaults"],
+ manifest: "AndroidManifest-q0.xml",
+
+ srcs: ["**/*.java"],
+}
+
+android_test_helper_app {
+ name: "FrameworksCoreTests_install_uses_sdk_r",
+ defaults: ["FrameworksCoreTests_apks_defaults"],
+ manifest: "AndroidManifest-r.xml",
+
+ srcs: ["**/*.java"],
+}
+
+android_test_helper_app {
+ name: "FrameworksCoreTests_install_uses_sdk_0",
+ defaults: ["FrameworksCoreTests_apks_defaults"],
+ manifest: "AndroidManifest-0.xml",
+
+ srcs: ["**/*.java"],
+}
diff --git a/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-0.xml b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-0.xml
new file mode 100644
index 0000000..634228b
--- /dev/null
+++ b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-0.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.frameworks.coretests.install_uses_sdk">
+
+ <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
+ <!-- This is invalid, because there is no sdk version specified -->
+ <extension-sdk android:minExtensionVersion="5" />
+ </uses-sdk>
+
+ <application>
+ </application>
+</manifest>
diff --git a/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-q0.xml b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-q0.xml
new file mode 100644
index 0000000..8994966
--- /dev/null
+++ b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-q0.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.frameworks.coretests.install_uses_sdk">
+
+ <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
+ <!-- This fails because 29 doesn't have an extension sdk -->
+ <extension-sdk android:sdkVersion="29" android:minExtensionVersion="0" />
+ </uses-sdk>
+
+ <application>
+ </application>
+</manifest>
diff --git a/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-r.xml b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-r.xml
new file mode 100644
index 0000000..0d0d8b9
--- /dev/null
+++ b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-r.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.frameworks.coretests.install_uses_sdk">
+
+ <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
+ <!-- This is invalid, because there is no minimum extension version specified -->
+ <extension-sdk android:sdkVersion="10000" />
+ </uses-sdk>
+
+ <application>
+ </application>
+</manifest>
diff --git a/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-r0.xml b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-r0.xml
new file mode 100644
index 0000000..a987afa
--- /dev/null
+++ b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-r0.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.frameworks.coretests.install_uses_sdk">
+
+ <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
+ <extension-sdk android:sdkVersion="10000" android:minExtensionVersion="0" />
+ </uses-sdk>
+
+ <application>
+ </application>
+</manifest>
diff --git a/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-r5.xml b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-r5.xml
new file mode 100644
index 0000000..9860096
--- /dev/null
+++ b/core/tests/coretests/apks/install_uses_sdk/AndroidManifest-r5.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 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.frameworks.coretests.install_uses_sdk">
+
+ <uses-sdk android:minSdkVersion="4" android:targetSdkVersion="29">
+ <!-- This will fail to install, because minExtensionVersion is not met -->
+ <extension-sdk android:sdkVersion="10000" android:minExtensionVersion="5" />
+ </uses-sdk>
+
+ <application>
+ </application>
+</manifest>
diff --git a/core/tests/coretests/apks/install_uses_sdk/res/values/strings.xml b/core/tests/coretests/apks/install_uses_sdk/res/values/strings.xml
new file mode 100644
index 0000000..3b8b3b1
--- /dev/null
+++ b/core/tests/coretests/apks/install_uses_sdk/res/values/strings.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- Just need this dummy file to have something to build. -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="dummy">dummy</string>
+</resources>
diff --git a/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java b/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java
index 68b9b00..f4709ff 100644
--- a/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java
+++ b/core/tests/coretests/src/android/app/DownloadManagerBaseTest.java
@@ -33,14 +33,15 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
+import android.support.test.uiautomator.UiDevice;
import android.test.InstrumentationTestCase;
import android.util.Log;
-import libcore.io.Streams;
-
import com.google.mockwebserver.MockResponse;
import com.google.mockwebserver.MockWebServer;
+import libcore.io.Streams;
+
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
@@ -63,6 +64,7 @@
private static final String TAG = "DownloadManagerBaseTest";
protected DownloadManager mDownloadManager = null;
private MockWebServer mServer = null;
+ private UiDevice mUiDevice = null;
protected String mFileType = "text/plain";
protected Context mContext = null;
protected MultipleDownloadsCompletedReceiver mReceiver = null;
@@ -234,6 +236,7 @@
@Override
public void setUp() throws Exception {
mContext = getInstrumentation().getContext();
+ mUiDevice = UiDevice.getInstance(getInstrumentation());
mDownloadManager = (DownloadManager)mContext.getSystemService(Context.DOWNLOAD_SERVICE);
mServer = new MockWebServer();
mServer.play();
@@ -512,7 +515,7 @@
Log.i(LOG_TAG, "Setting WiFi State to: " + enable);
WifiManager manager = (WifiManager)mContext.getSystemService(Context.WIFI_SERVICE);
- manager.setWifiEnabled(enable);
+ mUiDevice.executeShellCommand("svc wifi " + (enable ? "enable" : "disable"));
String timeoutMessage = "Timed out waiting for Wifi to be "
+ (enable ? "enabled!" : "disabled!");
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index beaaa37..d8b527c 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -438,8 +438,7 @@
}
private static ClientTransaction newStopTransaction(Activity activity) {
- final StopActivityItem stopStateRequest =
- StopActivityItem.obtain(false /* showWindow */, 0 /* configChanges */);
+ final StopActivityItem stopStateRequest = StopActivityItem.obtain(0 /* configChanges */);
final ClientTransaction transaction = newTransaction(activity);
transaction.setLifecycleStateRequest(stopStateRequest);
diff --git a/core/tests/coretests/src/android/app/appsearch/AppSearchDocumentTest.java b/core/tests/coretests/src/android/app/appsearch/AppSearchDocumentTest.java
new file mode 100644
index 0000000..2091d55
--- /dev/null
+++ b/core/tests/coretests/src/android/app/appsearch/AppSearchDocumentTest.java
@@ -0,0 +1,211 @@
+/*
+ * 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.appsearch;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.app.appsearch.AppSearch.Document;
+
+import androidx.test.filters.SmallTest;
+
+import com.google.android.icing.proto.DocumentProto;
+import com.google.android.icing.proto.PropertyProto;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+
+@SmallTest
+public class AppSearchDocumentTest {
+
+ @Test
+ public void testDocumentEquals_Identical() {
+ Document document1 = Document.newBuilder("uri1", "schemaType1")
+ .setCreationTimestampSecs(0L)
+ .setProperty("longKey1", 1L, 2L, 3L)
+ .setProperty("doubleKey1", 1.0, 2.0, 3.0)
+ .setProperty("booleanKey1", true, false, true)
+ .setProperty("stringKey1", "test-value1", "test-value2", "test-value3")
+ .build();
+ Document document2 = Document.newBuilder("uri1", "schemaType1")
+ .setCreationTimestampSecs(0L)
+ .setProperty("longKey1", 1L, 2L, 3L)
+ .setProperty("doubleKey1", 1.0, 2.0, 3.0)
+ .setProperty("booleanKey1", true, false, true)
+ .setProperty("stringKey1", "test-value1", "test-value2", "test-value3")
+ .build();
+ assertThat(document1).isEqualTo(document2);
+ assertThat(document1.hashCode()).isEqualTo(document2.hashCode());
+ }
+
+ @Test
+ public void testDocumentEquals_DifferentOrder() {
+ Document document1 = Document.newBuilder("uri1", "schemaType1")
+ .setCreationTimestampSecs(0L)
+ .setProperty("longKey1", 1L, 2L, 3L)
+ .setProperty("doubleKey1", 1.0, 2.0, 3.0)
+ .setProperty("booleanKey1", true, false, true)
+ .setProperty("stringKey1", "test-value1", "test-value2", "test-value3")
+ .build();
+
+ // Create second document with same parameter but different order.
+ Document document2 = Document.newBuilder("uri1", "schemaType1")
+ .setCreationTimestampSecs(0L)
+ .setProperty("booleanKey1", true, false, true)
+ .setProperty("stringKey1", "test-value1", "test-value2", "test-value3")
+ .setProperty("doubleKey1", 1.0, 2.0, 3.0)
+ .setProperty("longKey1", 1L, 2L, 3L)
+ .build();
+ assertThat(document1).isEqualTo(document2);
+ assertThat(document1.hashCode()).isEqualTo(document2.hashCode());
+ }
+
+ @Test
+ public void testDocumentEquals_Failure() {
+ Document document1 = Document.newBuilder("uri1", "schemaType1")
+ .setProperty("longKey1", 1L, 2L, 3L)
+ .build();
+
+ // Create second document with same order but different value.
+ Document document2 = Document.newBuilder("uri1", "schemaType1")
+ .setProperty("longKey1", 1L, 2L, 4L) // Different
+ .build();
+ assertThat(document1).isNotEqualTo(document2);
+ assertThat(document1.hashCode()).isNotEqualTo(document2.hashCode());
+ }
+
+ @Test
+ public void testDocumentEquals_Failure_RepeatedFieldOrder() {
+ Document document1 = Document.newBuilder("uri1", "schemaType1")
+ .setProperty("booleanKey1", true, false, true)
+ .build();
+
+ // Create second document with same order but different value.
+ Document document2 = Document.newBuilder("uri1", "schemaType1")
+ .setProperty("booleanKey1", true, true, false) // Different
+ .build();
+ assertThat(document1).isNotEqualTo(document2);
+ assertThat(document1.hashCode()).isNotEqualTo(document2.hashCode());
+ }
+
+ @Test
+ public void testDocumentGetSingleValue() {
+ Document document = Document.newBuilder("uri1", "schemaType1")
+ .setProperty("longKey1", 1L)
+ .setProperty("doubleKey1", 1.0)
+ .setProperty("booleanKey1", true)
+ .setProperty("stringKey1", "test-value1").build();
+ assertThat(document.getUri()).isEqualTo("uri1");
+ assertThat(document.getSchemaType()).isEqualTo("schemaType1");
+ assertThat(document.getPropertyLong("longKey1")).isEqualTo(1L);
+ assertThat(document.getPropertyDouble("doubleKey1")).isEqualTo(1.0);
+ assertThat(document.getPropertyBoolean("booleanKey1")).isTrue();
+ assertThat(document.getPropertyString("stringKey1")).isEqualTo("test-value1");
+ }
+
+ @Test
+ public void testDocumentGetArrayValues() {
+ Document document = Document.newBuilder("uri1", "schemaType1")
+ .setScore(1)
+ .setProperty("longKey1", 1L, 2L, 3L)
+ .setProperty("doubleKey1", 1.0, 2.0, 3.0)
+ .setProperty("booleanKey1", true, false, true)
+ .setProperty("stringKey1", "test-value1", "test-value2", "test-value3")
+ .build();
+
+ assertThat(document.getUri()).isEqualTo("uri1");
+ assertThat(document.getSchemaType()).isEqualTo("schemaType1");
+ assertThat(document.getScore()).isEqualTo(1);
+ assertThat(document.getPropertyLongArray("longKey1")).asList().containsExactly(1L, 2L, 3L);
+ assertThat(document.getPropertyDoubleArray("doubleKey1")).usingExactEquality()
+ .containsExactly(1.0, 2.0, 3.0);
+ assertThat(document.getPropertyBooleanArray("booleanKey1")).asList()
+ .containsExactly(true, false, true);
+ assertThat(document.getPropertyStringArray("stringKey1")).asList()
+ .containsExactly("test-value1", "test-value2", "test-value3");
+ }
+
+ @Test
+ public void testDocumentGetValues_DifferentTypes() {
+ Document document = Document.newBuilder("uri1", "schemaType1")
+ .setScore(1)
+ .setProperty("longKey1", 1L)
+ .setProperty("booleanKey1", true, false, true)
+ .setProperty("stringKey1", "test-value1", "test-value2", "test-value3")
+ .build();
+
+ // Get a value for a key that doesn't exist
+ assertThat(document.getPropertyDouble("doubleKey1")).isNull();
+ assertThat(document.getPropertyDoubleArray("doubleKey1")).isNull();
+
+ // Get a value with a single element as an array and as a single value
+ assertThat(document.getPropertyLong("longKey1")).isEqualTo(1L);
+ assertThat(document.getPropertyLongArray("longKey1")).asList().containsExactly(1L);
+
+ // Get a value with multiple elements as an array and as a single value
+ assertThat(document.getPropertyString("stringKey1")).isEqualTo("test-value1");
+ assertThat(document.getPropertyStringArray("stringKey1")).asList()
+ .containsExactly("test-value1", "test-value2", "test-value3");
+
+ // Get a value of the wrong type
+ assertThat(document.getPropertyDouble("longKey1")).isNull();
+ assertThat(document.getPropertyDoubleArray("longKey1")).isNull();
+ }
+
+ @Test
+ public void testDocumentInvalid() {
+ Document.Builder builder = Document.newBuilder("uri1", "schemaType1");
+ assertThrows(
+ IllegalArgumentException.class, () -> builder.setProperty("test", new boolean[]{}));
+ }
+
+ @Test
+ public void testDocumentProtoPopulation() {
+ Document document = Document.newBuilder("uri1", "schemaType1")
+ .setScore(1)
+ .setCreationTimestampSecs(0)
+ .setProperty("longKey1", 1L)
+ .setProperty("doubleKey1", 1.0)
+ .setProperty("booleanKey1", true)
+ .setProperty("stringKey1", "test-value1")
+ .build();
+
+ // Create the Document proto. Need to sort the property order by key.
+ DocumentProto.Builder documentProtoBuilder = DocumentProto.newBuilder()
+ .setUri("uri1").setSchema("schemaType1").setScore(1).setCreationTimestampSecs(0);
+ HashMap<String, PropertyProto.Builder> propertyProtoMap = new HashMap<>();
+ propertyProtoMap.put("longKey1",
+ PropertyProto.newBuilder().setName("longKey1").addInt64Values(1L));
+ propertyProtoMap.put("doubleKey1",
+ PropertyProto.newBuilder().setName("doubleKey1").addDoubleValues(1.0));
+ propertyProtoMap.put("booleanKey1",
+ PropertyProto.newBuilder().setName("booleanKey1").addBooleanValues(true));
+ propertyProtoMap.put("stringKey1",
+ PropertyProto.newBuilder().setName("stringKey1").addStringValues("test-value1"));
+ List<String> sortedKey = new ArrayList<>(propertyProtoMap.keySet());
+ Collections.sort(sortedKey);
+ for (String key : sortedKey) {
+ documentProtoBuilder.addProperties(propertyProtoMap.get(key));
+ }
+ assertThat(document.getProto()).isEqualTo(documentProtoBuilder.build());
+ }
+}
diff --git a/core/tests/coretests/src/android/app/appsearch/AppSearchEmailTest.java b/core/tests/coretests/src/android/app/appsearch/AppSearchEmailTest.java
new file mode 100644
index 0000000..c50b1da
--- /dev/null
+++ b/core/tests/coretests/src/android/app/appsearch/AppSearchEmailTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.appsearch;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.appsearch.AppSearch.Email;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+@SmallTest
+public class AppSearchEmailTest {
+
+ @Test
+ public void testBuildEmailAndGetValue() {
+ Email email = Email.newBuilder("uri")
+ .setFrom("FakeFromAddress")
+ .setCc("CC1", "CC2")
+ // Score and Property are mixed into the middle to make sure DocumentBuilder's
+ // methods can be interleaved with EmailBuilder's methods.
+ .setScore(1)
+ .setProperty("propertyKey", "propertyValue1", "propertyValue2")
+ .setSubject("subject")
+ .setBody("EmailBody")
+ .build();
+
+ assertThat(email.getUri()).isEqualTo("uri");
+ assertThat(email.getFrom()).isEqualTo("FakeFromAddress");
+ assertThat(email.getTo()).isNull();
+ assertThat(email.getCc()).asList().containsExactly("CC1", "CC2");
+ assertThat(email.getBcc()).isNull();
+ assertThat(email.getScore()).isEqualTo(1);
+ assertThat(email.getPropertyString("propertyKey")).isEqualTo("propertyValue1");
+ assertThat(email.getPropertyStringArray("propertyKey")).asList().containsExactly(
+ "propertyValue1", "propertyValue2");
+ assertThat(email.getSubject()).isEqualTo("subject");
+ assertThat(email.getBody()).isEqualTo("EmailBody");
+ }
+}
diff --git a/core/tests/coretests/src/android/app/appsearch/AppSearchSchemaTest.java b/core/tests/coretests/src/android/app/appsearch/AppSearchSchemaTest.java
new file mode 100644
index 0000000..0be52c1
--- /dev/null
+++ b/core/tests/coretests/src/android/app/appsearch/AppSearchSchemaTest.java
@@ -0,0 +1,154 @@
+/*
+ * 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.appsearch;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.expectThrows;
+
+import android.app.appsearch.AppSearchSchema.IndexingConfig;
+import android.app.appsearch.AppSearchSchema.PropertyConfig;
+
+import androidx.test.filters.SmallTest;
+
+import com.google.android.icing.proto.IndexingConfig.TokenizerType;
+import com.google.android.icing.proto.PropertyConfigProto;
+import com.google.android.icing.proto.SchemaProto;
+import com.google.android.icing.proto.SchemaTypeConfigProto;
+import com.google.android.icing.proto.TermMatchType;
+
+import org.junit.Test;
+
+@SmallTest
+public class AppSearchSchemaTest {
+ @Test
+ public void testSuccess() {
+ AppSearchSchema schema = AppSearchSchema.newBuilder()
+ .addType(AppSearchSchema.newSchemaTypeBuilder("Email")
+ .addProperty(AppSearchSchema.newPropertyBuilder("subject")
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingConfig(AppSearchSchema.newIndexingConfigBuilder()
+ .setTokenizerType(IndexingConfig.TOKENIZER_TYPE_PLAIN)
+ .setTermMatchType(IndexingConfig.TERM_MATCH_TYPE_PREFIX)
+ .build()
+ ).build()
+ ).addProperty(AppSearchSchema.newPropertyBuilder("body")
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingConfig(AppSearchSchema.newIndexingConfigBuilder()
+ .setTokenizerType(IndexingConfig.TOKENIZER_TYPE_PLAIN)
+ .setTermMatchType(IndexingConfig.TERM_MATCH_TYPE_PREFIX)
+ .build()
+ ).build()
+ ).build()
+
+ ).addType(AppSearchSchema.newSchemaTypeBuilder("MusicRecording")
+ .addProperty(AppSearchSchema.newPropertyBuilder("artist")
+ .setDataType(PropertyConfig.DATA_TYPE_STRING)
+ .setCardinality(PropertyConfig.CARDINALITY_REPEATED)
+ .setIndexingConfig(AppSearchSchema.newIndexingConfigBuilder()
+ .setTokenizerType(IndexingConfig.TOKENIZER_TYPE_PLAIN)
+ .setTermMatchType(IndexingConfig.TERM_MATCH_TYPE_PREFIX)
+ .build()
+ ).build()
+ ).addProperty(AppSearchSchema.newPropertyBuilder("pubDate")
+ .setDataType(PropertyConfig.DATA_TYPE_INT64)
+ .setCardinality(PropertyConfig.CARDINALITY_OPTIONAL)
+ .setIndexingConfig(AppSearchSchema.newIndexingConfigBuilder()
+ .setTokenizerType(IndexingConfig.TOKENIZER_TYPE_NONE)
+ .setTermMatchType(IndexingConfig.TERM_MATCH_TYPE_UNKNOWN)
+ .build()
+ ).build()
+ ).build()
+ ).build();
+
+ SchemaProto expectedProto = SchemaProto.newBuilder()
+ .addTypes(SchemaTypeConfigProto.newBuilder()
+ .setSchemaType("Email")
+ .addProperties(PropertyConfigProto.newBuilder()
+ .setPropertyName("subject")
+ .setDataType(PropertyConfigProto.DataType.Code.STRING)
+ .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
+ .setIndexingConfig(
+ com.google.android.icing.proto.IndexingConfig.newBuilder()
+ .setTokenizerType(TokenizerType.Code.PLAIN)
+ .setTermMatchType(TermMatchType.Code.PREFIX)
+ )
+ ).addProperties(PropertyConfigProto.newBuilder()
+ .setPropertyName("body")
+ .setDataType(PropertyConfigProto.DataType.Code.STRING)
+ .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
+ .setIndexingConfig(
+ com.google.android.icing.proto.IndexingConfig.newBuilder()
+ .setTokenizerType(TokenizerType.Code.PLAIN)
+ .setTermMatchType(TermMatchType.Code.PREFIX)
+ )
+ )
+
+ ).addTypes(SchemaTypeConfigProto.newBuilder()
+ .setSchemaType("MusicRecording")
+ .addProperties(PropertyConfigProto.newBuilder()
+ .setPropertyName("artist")
+ .setDataType(PropertyConfigProto.DataType.Code.STRING)
+ .setCardinality(PropertyConfigProto.Cardinality.Code.REPEATED)
+ .setIndexingConfig(
+ com.google.android.icing.proto.IndexingConfig.newBuilder()
+ .setTokenizerType(TokenizerType.Code.PLAIN)
+ .setTermMatchType(TermMatchType.Code.PREFIX)
+ )
+ ).addProperties(PropertyConfigProto.newBuilder()
+ .setPropertyName("pubDate")
+ .setDataType(PropertyConfigProto.DataType.Code.INT64)
+ .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
+ .setIndexingConfig(
+ com.google.android.icing.proto.IndexingConfig.newBuilder()
+ .setTokenizerType(TokenizerType.Code.NONE)
+ .setTermMatchType(TermMatchType.Code.UNKNOWN)
+ )
+ )
+ ).build();
+
+ assertThat(schema.getProto()).isEqualTo(expectedProto);
+ }
+
+ @Test
+ public void testInvalidEnums() {
+ PropertyConfig.Builder builder = AppSearchSchema.newPropertyBuilder("test");
+ assertThrows(IllegalArgumentException.class, () -> builder.setDataType(99));
+ assertThrows(IllegalArgumentException.class, () -> builder.setCardinality(99));
+ }
+
+ @Test
+ public void testMissingFields() {
+ PropertyConfig.Builder builder = AppSearchSchema.newPropertyBuilder("test");
+ Exception e = expectThrows(IllegalSchemaException.class, builder::build);
+ assertThat(e).hasMessageThat().contains("Missing field: dataType");
+
+ builder.setDataType(PropertyConfig.DATA_TYPE_DOCUMENT);
+ e = expectThrows(IllegalSchemaException.class, builder::build);
+ assertThat(e).hasMessageThat().contains("Missing field: schemaType");
+
+ builder.setSchemaType("TestType");
+ e = expectThrows(IllegalSchemaException.class, builder::build);
+ assertThat(e).hasMessageThat().contains("Missing field: cardinality");
+
+ builder.setCardinality(PropertyConfig.CARDINALITY_REPEATED);
+ builder.build();
+ }
+}
diff --git a/core/tests/coretests/src/android/app/appsearch/impl/CustomerDocumentTest.java b/core/tests/coretests/src/android/app/appsearch/impl/CustomerDocumentTest.java
new file mode 100644
index 0000000..4ee4aa6
--- /dev/null
+++ b/core/tests/coretests/src/android/app/appsearch/impl/CustomerDocumentTest.java
@@ -0,0 +1,84 @@
+/*
+ * 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.appsearch.impl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.annotation.NonNull;
+import android.app.appsearch.AppSearch.Document;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+/** Tests that {@link Document} and {@link Document.Builder} are extendable by developers.
+ *
+ * <p>This class is intentionally in a different package than {@link Document} to make sure there
+ * are no package-private methods required for external developers to add custom types.
+ */
+@SmallTest
+public class CustomerDocumentTest {
+ @Test
+ public void testBuildCustomerDocument() {
+ CustomerDocument customerDocument = CustomerDocument.newBuilder("uri1")
+ .setScore(1)
+ .setCreationTimestampSecs(0)
+ .setProperty("longKey1", 1L, 2L, 3L)
+ .setProperty("doubleKey1", 1.0, 2.0, 3.0)
+ .setProperty("booleanKey1", true, false, true)
+ .setProperty("stringKey1", "test-value1", "test-value2", "test-value3")
+ .build();
+
+ assertThat(customerDocument.getUri()).isEqualTo("uri1");
+ assertThat(customerDocument.getSchemaType()).isEqualTo("customerDocument");
+ assertThat(customerDocument.getScore()).isEqualTo(1);
+ assertThat(customerDocument.getCreationTimestampSecs()).isEqualTo(0L);
+ assertThat(customerDocument.getPropertyLongArray("longKey1")).asList()
+ .containsExactly(1L, 2L, 3L);
+ assertThat(customerDocument.getPropertyDoubleArray("doubleKey1")).usingExactEquality()
+ .containsExactly(1.0, 2.0, 3.0);
+ assertThat(customerDocument.getPropertyBooleanArray("booleanKey1")).asList()
+ .containsExactly(true, false, true);
+ assertThat(customerDocument.getPropertyStringArray("stringKey1")).asList()
+ .containsExactly("test-value1", "test-value2", "test-value3");
+ }
+
+ /**
+ * An example document type for test purposes, defined outside of
+ * {@link android.app.appsearch.AppSearch} (the way an external developer would define it).
+ */
+ private static class CustomerDocument extends Document {
+ private CustomerDocument(Document document) {
+ super(document);
+ }
+
+ public static CustomerDocument.Builder newBuilder(String uri) {
+ return new CustomerDocument.Builder(uri);
+ }
+
+ public static class Builder extends Document.Builder<CustomerDocument.Builder> {
+ private Builder(@NonNull String uri) {
+ super(uri, "customerDocument");
+ }
+
+ @Override
+ public CustomerDocument build() {
+ return new CustomerDocument(super.build());
+ }
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index 37d21f0..4b29d59 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -274,30 +274,15 @@
@Test
public void testRecycleStopItem() {
- StopActivityItem emptyItem = StopActivityItem.obtain(false, 0);
- StopActivityItem item = StopActivityItem.obtain(true, 4);
+ StopActivityItem emptyItem = StopActivityItem.obtain(0);
+ StopActivityItem item = StopActivityItem.obtain(4);
assertNotSame(item, emptyItem);
assertFalse(item.equals(emptyItem));
item.recycle();
assertEquals(item, emptyItem);
- StopActivityItem item2 = StopActivityItem.obtain(true, 3);
- assertSame(item, item2);
- assertFalse(item2.equals(emptyItem));
- }
-
- @Test
- public void testRecycleWindowVisibleItem() {
- WindowVisibilityItem emptyItem = WindowVisibilityItem.obtain(false);
- WindowVisibilityItem item = WindowVisibilityItem.obtain(true);
- assertNotSame(item, emptyItem);
- assertFalse(item.equals(emptyItem));
-
- item.recycle();
- assertEquals(item, emptyItem);
-
- WindowVisibilityItem item2 = WindowVisibilityItem.obtain(true);
+ StopActivityItem item2 = StopActivityItem.obtain(3);
assertSame(item, item2);
assertFalse(item2.equals(emptyItem));
}
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 39bf742..ecea901 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -61,6 +61,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -180,31 +181,6 @@
}
@Test
- public void testWindowVisibilityChange() {
- // Write to parcel
- WindowVisibilityItem item = WindowVisibilityItem.obtain(true /* showWindow */);
- writeAndPrepareForReading(item);
-
- // Read from parcel and assert
- WindowVisibilityItem result = WindowVisibilityItem.CREATOR.createFromParcel(mParcel);
-
- assertEquals(item.hashCode(), result.hashCode());
- assertTrue(item.equals(result));
-
- // Check different value
- item = WindowVisibilityItem.obtain(false);
-
- mParcel = Parcel.obtain();
- writeAndPrepareForReading(item);
-
- // Read from parcel and assert
- result = WindowVisibilityItem.CREATOR.createFromParcel(mParcel);
-
- assertEquals(item.hashCode(), result.hashCode());
- assertTrue(item.equals(result));
- }
-
- @Test
public void testDestroy() {
DestroyActivityItem item = DestroyActivityItem.obtain(true /* finished */,
135 /* configChanges */);
@@ -299,8 +275,7 @@
@Test
public void testStop() {
// Write to parcel
- StopActivityItem item = StopActivityItem.obtain(true /* showWindow */,
- 14 /* configChanges */);
+ StopActivityItem item = StopActivityItem.obtain(14 /* configChanges */);
writeAndPrepareForReading(item);
// Read from parcel and assert
@@ -311,14 +286,26 @@
}
@Test
+ public void testStart() {
+ // Write to parcel
+ StartActivityItem item = StartActivityItem.obtain();
+ writeAndPrepareForReading(item);
+
+ // Read from parcel and assert
+ StartActivityItem result = StartActivityItem.CREATOR.createFromParcel(mParcel);
+
+ assertEquals(item.hashCode(), result.hashCode());
+ assertEquals(item, result);
+ }
+
+ @Test
public void testClientTransaction() {
// Write to parcel
- WindowVisibilityItem callback1 = WindowVisibilityItem.obtain(true);
+ NewIntentItem callback1 = NewIntentItem.obtain(new ArrayList<>(), true);
ActivityConfigurationChangeItem callback2 = ActivityConfigurationChangeItem.obtain(
config());
- StopActivityItem lifecycleRequest = StopActivityItem.obtain(true /* showWindow */,
- 78 /* configChanges */);
+ StopActivityItem lifecycleRequest = StopActivityItem.obtain(78 /* configChanges */);
IApplicationThread appThread = new StubAppThread();
Binder activityToken = new Binder();
@@ -340,7 +327,7 @@
@Test
public void testClientTransactionCallbacksOnly() {
// Write to parcel
- WindowVisibilityItem callback1 = WindowVisibilityItem.obtain(true);
+ NewIntentItem callback1 = NewIntentItem.obtain(new ArrayList<>(), true);
ActivityConfigurationChangeItem callback2 = ActivityConfigurationChangeItem.obtain(
config());
@@ -363,8 +350,7 @@
@Test
public void testClientTransactionLifecycleOnly() {
// Write to parcel
- StopActivityItem lifecycleRequest = StopActivityItem.obtain(true /* showWindow */,
- 78 /* configChanges */);
+ StopActivityItem lifecycleRequest = StopActivityItem.obtain(78 /* configChanges */);
IApplicationThread appThread = new StubAppThread();
Binder activityToken = new Binder();
diff --git a/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java b/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java
index de6f8f7..750ffa1 100644
--- a/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/timedetector/ManualTimeSuggestionTest.java
@@ -22,7 +22,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
-import android.util.TimestampedValue;
+import android.os.TimestampedValue;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/app/timedetector/NetworkTimeSuggestionTest.java b/core/tests/coretests/src/android/app/timedetector/NetworkTimeSuggestionTest.java
new file mode 100644
index 0000000..b88c36f
--- /dev/null
+++ b/core/tests/coretests/src/android/app/timedetector/NetworkTimeSuggestionTest.java
@@ -0,0 +1,66 @@
+/*
+ * 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.timedetector;
+
+import static android.app.timezonedetector.ParcelableTestSupport.assertRoundTripParcelable;
+import static android.app.timezonedetector.ParcelableTestSupport.roundTripParcelable;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.os.TimestampedValue;
+
+import org.junit.Test;
+
+public class NetworkTimeSuggestionTest {
+
+ private static final TimestampedValue<Long> ARBITRARY_TIME =
+ new TimestampedValue<>(1111L, 2222L);
+
+ @Test
+ public void testEquals() {
+ NetworkTimeSuggestion one = new NetworkTimeSuggestion(ARBITRARY_TIME);
+ assertEquals(one, one);
+
+ NetworkTimeSuggestion two = new NetworkTimeSuggestion(ARBITRARY_TIME);
+ assertEquals(one, two);
+ assertEquals(two, one);
+
+ TimestampedValue<Long> differentTime = new TimestampedValue<>(
+ ARBITRARY_TIME.getReferenceTimeMillis() + 1,
+ ARBITRARY_TIME.getValue());
+ NetworkTimeSuggestion three = new NetworkTimeSuggestion(differentTime);
+ assertNotEquals(one, three);
+ assertNotEquals(three, one);
+
+ // DebugInfo must not be considered in equals().
+ one.addDebugInfo("Debug info 1");
+ two.addDebugInfo("Debug info 2");
+ assertEquals(one, two);
+ }
+
+ @Test
+ public void testParcelable() {
+ NetworkTimeSuggestion suggestion = new NetworkTimeSuggestion(ARBITRARY_TIME);
+ assertRoundTripParcelable(suggestion);
+
+ // DebugInfo should also be stored (but is not checked by equals()
+ suggestion.addDebugInfo("This is debug info");
+ NetworkTimeSuggestion rtSuggestion = roundTripParcelable(suggestion);
+ assertEquals(suggestion.getDebugInfo(), rtSuggestion.getDebugInfo());
+ }
+}
diff --git a/core/tests/coretests/src/android/app/timedetector/PhoneTimeSuggestionTest.java b/core/tests/coretests/src/android/app/timedetector/PhoneTimeSuggestionTest.java
index bee270e..ba29a97 100644
--- a/core/tests/coretests/src/android/app/timedetector/PhoneTimeSuggestionTest.java
+++ b/core/tests/coretests/src/android/app/timedetector/PhoneTimeSuggestionTest.java
@@ -22,7 +22,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
-import android.util.TimestampedValue;
+import android.os.TimestampedValue;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/content/pm/PackageParserTest.java b/core/tests/coretests/src/android/content/pm/PackageParserTest.java
index 4402190..dfd762b 100644
--- a/core/tests/coretests/src/android/content/pm/PackageParserTest.java
+++ b/core/tests/coretests/src/android/content/pm/PackageParserTest.java
@@ -20,6 +20,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import android.apex.ApexInfo;
import android.content.Context;
@@ -486,4 +487,34 @@
assertTrue((pi.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0);
assertTrue((pi.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0);
}
+
+ @Test
+ public void testUsesSdk() throws Exception {
+ parsePackage("install_uses_sdk.apk_r0", R.raw.install_uses_sdk_r0, x -> x);
+ try {
+ parsePackage("install_uses_sdk.apk_r5", R.raw.install_uses_sdk_r5, x -> x);
+ fail("Expected parsing exception due to incompatible extension SDK version");
+ } catch (PackageParser.PackageParserException expected) {
+ assertEquals(PackageManager.INSTALL_FAILED_OLDER_SDK, expected.error);
+ }
+ try {
+ parsePackage("install_uses_sdk.apk_q0", R.raw.install_uses_sdk_q0, x -> x);
+ fail("Expected parsing exception due to non-existent extension SDK");
+ } catch (PackageParser.PackageParserException expected) {
+ assertEquals(PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, expected.error);
+ }
+ try {
+ parsePackage("install_uses_sdk.apk_r", R.raw.install_uses_sdk_r, x -> x);
+ fail("Expected parsing exception due to unspecified extension SDK version");
+ } catch (PackageParser.PackageParserException expected) {
+ assertEquals(PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, expected.error);
+ }
+ try {
+ parsePackage("install_uses_sdk.apk_0", R.raw.install_uses_sdk_0, x -> x);
+ fail("Expected parsing exception due to unspecified extension SDK");
+ } catch (PackageParser.PackageParserException expected) {
+ assertEquals(PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED, expected.error);
+ }
+
+ }
}
diff --git a/core/tests/coretests/src/android/net/NetworkKeyTest.java b/core/tests/coretests/src/android/net/NetworkKeyTest.java
index c6c0b46..b13bcd1 100644
--- a/core/tests/coretests/src/android/net/NetworkKeyTest.java
+++ b/core/tests/coretests/src/android/net/NetworkKeyTest.java
@@ -18,6 +18,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.net.wifi.ScanResult;
@@ -107,7 +108,7 @@
@Test
public void createFromScanResult_nullSsid() {
- ScanResult scanResult = new ScanResult();
+ ScanResult scanResult = mock(ScanResult.class);
scanResult.BSSID = VALID_BSSID;
assertNull(NetworkKey.createFromScanResult(scanResult));
@@ -115,7 +116,7 @@
@Test
public void createFromScanResult_emptySsid() {
- ScanResult scanResult = new ScanResult();
+ ScanResult scanResult = mock(ScanResult.class);
scanResult.SSID = "";
scanResult.BSSID = VALID_BSSID;
@@ -124,7 +125,7 @@
@Test
public void createFromScanResult_noneSsid() {
- ScanResult scanResult = new ScanResult();
+ ScanResult scanResult = mock(ScanResult.class);
scanResult.SSID = WifiManager.UNKNOWN_SSID;
scanResult.BSSID = VALID_BSSID;
@@ -133,7 +134,7 @@
@Test
public void createFromScanResult_nullBssid() {
- ScanResult scanResult = new ScanResult();
+ ScanResult scanResult = mock(ScanResult.class);
scanResult.SSID = VALID_UNQUOTED_SSID;
assertNull(NetworkKey.createFromScanResult(scanResult));
@@ -141,7 +142,7 @@
@Test
public void createFromScanResult_emptyBssid() {
- ScanResult scanResult = new ScanResult();
+ ScanResult scanResult = mock(ScanResult.class);
scanResult.SSID = VALID_UNQUOTED_SSID;
scanResult.BSSID = "";
@@ -150,7 +151,7 @@
@Test
public void createFromScanResult_invalidBssid() {
- ScanResult scanResult = new ScanResult();
+ ScanResult scanResult = mock(ScanResult.class);
scanResult.SSID = VALID_UNQUOTED_SSID;
scanResult.BSSID = INVALID_BSSID;
@@ -159,7 +160,7 @@
@Test
public void createFromScanResult_validSsid() {
- ScanResult scanResult = new ScanResult();
+ ScanResult scanResult = mock(ScanResult.class);
scanResult.SSID = VALID_UNQUOTED_SSID;
scanResult.BSSID = VALID_BSSID;
diff --git a/core/tests/coretests/src/android/os/BinderDeathRecipientTest.java b/core/tests/coretests/src/android/os/BinderDeathRecipientTest.java
new file mode 100644
index 0000000..2cce43f
--- /dev/null
+++ b/core/tests/coretests/src/android/os/BinderDeathRecipientTest.java
@@ -0,0 +1,162 @@
+/*
+ * 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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Pair;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.frameworks.coretests.bdr_helper_app.TestCommsReceiver;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Set;
+import java.util.concurrent.BrokenBarrierException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.CyclicBarrier;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Tests functionality of {@link android.os.IBinder.DeathRecipient} callbacks.
+ */
+@RunWith(AndroidJUnit4.class)
+public class BinderDeathRecipientTest {
+ private static final String TAG = BinderDeathRecipientTest.class.getSimpleName();
+ private static final String TEST_PACKAGE_NAME_1 =
+ "com.android.frameworks.coretests.bdr_helper_app1";
+ private static final String TEST_PACKAGE_NAME_2 =
+ "com.android.frameworks.coretests.bdr_helper_app2";
+
+ private Context mContext;
+ private Handler mHandler;
+ private ActivityManager mActivityManager;
+ private Set<Pair<IBinder, IBinder.DeathRecipient>> mLinkedDeathRecipients = new ArraySet<>();
+
+ @Before
+ public void setUp() {
+ mContext = InstrumentationRegistry.getTargetContext();
+ mActivityManager = mContext.getSystemService(ActivityManager.class);
+ mHandler = new Handler(Looper.getMainLooper());
+ }
+
+ private IBinder getNewRemoteBinder(String testPackage) throws InterruptedException {
+ final CountDownLatch resultLatch = new CountDownLatch(1);
+ final AtomicInteger resultCode = new AtomicInteger(Activity.RESULT_CANCELED);
+ final AtomicReference<Bundle> resultExtras = new AtomicReference<>();
+
+ final Intent intent = new Intent(TestCommsReceiver.ACTION_GET_BINDER)
+ .setClassName(testPackage, TestCommsReceiver.class.getName());
+ mContext.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ resultCode.set(getResultCode());
+ resultExtras.set(getResultExtras(true));
+ resultLatch.countDown();
+ }
+ }, mHandler, Activity.RESULT_CANCELED, null, null);
+
+ assertTrue("Request for binder timed out", resultLatch.await(5, TimeUnit.SECONDS));
+ assertEquals(Activity.RESULT_OK, resultCode.get());
+ return resultExtras.get().getBinder(TestCommsReceiver.EXTRA_KEY_BINDER);
+ }
+
+ @Test
+ public void binderDied_noArgs() throws Exception {
+ final IBinder testAppBinder = getNewRemoteBinder(TEST_PACKAGE_NAME_1);
+ final CountDownLatch deathNotificationLatch = new CountDownLatch(1);
+ final IBinder.DeathRecipient simpleDeathRecipient =
+ () -> deathNotificationLatch.countDown();
+ testAppBinder.linkToDeath(simpleDeathRecipient, 0);
+ mLinkedDeathRecipients.add(Pair.create(testAppBinder, simpleDeathRecipient));
+
+ mActivityManager.forceStopPackage(TEST_PACKAGE_NAME_1);
+ assertTrue("Death notification did not arrive",
+ deathNotificationLatch.await(10, TimeUnit.SECONDS));
+ }
+
+ @Test
+ public void binderDied_iBinderArg() throws Exception {
+ final IBinder testApp1Binder = getNewRemoteBinder(TEST_PACKAGE_NAME_1);
+ final IBinder testApp2Binder = getNewRemoteBinder(TEST_PACKAGE_NAME_2);
+ final CyclicBarrier barrier = new CyclicBarrier(2);
+
+ final AtomicReference<IBinder> binderThatDied = new AtomicReference<>();
+ final IBinder.DeathRecipient sameDeathRecipient = new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ Log.e(TAG, "Should not have been called!");
+ }
+
+ @Override
+ public void binderDied(IBinder who) {
+ binderThatDied.set(who);
+ try {
+ barrier.await();
+ } catch (InterruptedException | BrokenBarrierException e) {
+ Log.e(TAG, "Unexpected exception while waiting on CyclicBarrier", e);
+ }
+ }
+ };
+ testApp1Binder.linkToDeath(sameDeathRecipient, 0);
+ mLinkedDeathRecipients.add(Pair.create(testApp1Binder, sameDeathRecipient));
+
+ testApp2Binder.linkToDeath(sameDeathRecipient, 0);
+ mLinkedDeathRecipients.add(Pair.create(testApp2Binder, sameDeathRecipient));
+
+ mActivityManager.forceStopPackage(TEST_PACKAGE_NAME_1);
+ try {
+ barrier.await(10, TimeUnit.SECONDS);
+ } catch (TimeoutException e) {
+ fail("Timed out while waiting for 1st death notification: " + e.getMessage());
+ }
+ assertEquals("Different binder received", testApp1Binder, binderThatDied.get());
+
+ barrier.reset();
+ mActivityManager.forceStopPackage(TEST_PACKAGE_NAME_2);
+ try {
+ barrier.await(10, TimeUnit.SECONDS);
+ } catch (TimeoutException e) {
+ fail("Timed out while waiting for 2nd death notification: " + e.getMessage());
+ }
+ assertEquals("Different binder received", testApp2Binder, binderThatDied.get());
+ }
+
+ @After
+ public void tearDown() {
+ for (Pair<IBinder, IBinder.DeathRecipient> linkedPair : mLinkedDeathRecipients) {
+ linkedPair.first.unlinkToDeath(linkedPair.second, 0);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/os/ExternalVibrationTest.java b/core/tests/coretests/src/android/os/ExternalVibrationTest.java
new file mode 100644
index 0000000..3b872d5
--- /dev/null
+++ b/core/tests/coretests/src/android/os/ExternalVibrationTest.java
@@ -0,0 +1,47 @@
+/*
+ * 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 junit.framework.Assert.assertEquals;
+
+import static org.mockito.Mockito.mock;
+
+import android.media.AudioAttributes;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ExternalVibrationTest {
+ @Test
+ public void testSerialization() {
+ AudioAttributes audio = new AudioAttributes.Builder().build();
+ IExternalVibrationController controller = mock(IExternalVibrationController.class);
+ ExternalVibration original = new ExternalVibration(
+ 123, // uid
+ "pkg",
+ audio,
+ controller);
+ Parcel p = Parcel.obtain();
+ original.writeToParcel(p, 0);
+ p.setDataPosition(0);
+ ExternalVibration restored = ExternalVibration.CREATOR.createFromParcel(p);
+ assertEquals(original, restored);
+ }
+}
+
diff --git a/core/tests/coretests/src/android/util/TimestampedValueTest.java b/core/tests/coretests/src/android/os/TimestampedValueTest.java
similarity index 98%
rename from core/tests/coretests/src/android/util/TimestampedValueTest.java
rename to core/tests/coretests/src/android/os/TimestampedValueTest.java
index 6fc2400..f36d9e6 100644
--- a/core/tests/coretests/src/android/util/TimestampedValueTest.java
+++ b/core/tests/coretests/src/android/os/TimestampedValueTest.java
@@ -14,14 +14,12 @@
* limitations under the License.
*/
-package android.util;
+package android.os;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.fail;
-import android.os.Parcel;
-
import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
diff --git a/core/tests/coretests/src/android/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
index ae835e4..84c42db 100644
--- a/core/tests/coretests/src/android/provider/DeviceConfigTest.java
+++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
@@ -20,6 +20,8 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.testng.Assert.assertThrows;
+
import android.content.ContentResolver;
import android.os.Bundle;
import android.platform.test.annotations.Presubmit;
@@ -44,9 +46,11 @@
private static final String KEY = "key1";
private static final String KEY2 = "key2";
private static final String KEY3 = "key3";
+ private static final String KEY4 = "key4";
private static final String VALUE = "value1";
private static final String VALUE2 = "value2";
private static final String VALUE3 = "value3";
+ private static final String NULL_VALUE = "null";
@After
public void cleanUp() {
@@ -561,6 +565,78 @@
assertThat(properties.getFloat("key5", 0f)).isEqualTo(floatValue);
}
+ @Test
+ public void banNamespaceProperties() throws DeviceConfig.BadConfigException {
+ // Given namespace will be permanently banned, thus it needs to be different every time
+ final String namespaceToBan = NAMESPACE + System.currentTimeMillis();
+ Properties properties = new Properties.Builder(namespaceToBan).setString(KEY, VALUE)
+ .setString(KEY4, NULL_VALUE).build();
+ // Set namespace properties
+ DeviceConfig.setProperties(properties);
+ // Ban namespace with related properties
+ DeviceConfig.resetToDefaults(Settings.RESET_MODE_PACKAGE_DEFAULTS, namespaceToBan);
+ // Verify given namespace properties are banned
+ assertThrows(DeviceConfig.BadConfigException.class,
+ () -> DeviceConfig.setProperties(properties));
+ // Modify properties and verify we can set them
+ Properties modifiedProperties = new Properties.Builder(namespaceToBan).setString(KEY, VALUE)
+ .setString(KEY4, NULL_VALUE).setString(KEY2, VALUE2).build();
+ DeviceConfig.setProperties(modifiedProperties);
+ modifiedProperties = DeviceConfig.getProperties(namespaceToBan);
+ assertThat(modifiedProperties.getKeyset()).containsExactly(KEY, KEY2, KEY4);
+ assertThat(modifiedProperties.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE);
+ assertThat(modifiedProperties.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE2);
+ // Since value is null DEFAULT_VALUE should be returned
+ assertThat(modifiedProperties.getString(KEY4, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE);
+ }
+
+ @Test
+ public void banEntireDeviceConfig() throws DeviceConfig.BadConfigException {
+ // Given namespaces will be permanently banned, thus they need to be different every time
+ final String namespaceToBan1 = NAMESPACE + System.currentTimeMillis();
+ final String namespaceToBan2 = NAMESPACE + System.currentTimeMillis() + 1;
+
+ // Set namespaces properties
+ Properties properties1 = new Properties.Builder(namespaceToBan1).setString(KEY, VALUE)
+ .setString(KEY4, NULL_VALUE).build();
+ DeviceConfig.setProperties(properties1);
+ Properties properties2 = new Properties.Builder(namespaceToBan2).setString(KEY2, VALUE2)
+ .setString(KEY4, NULL_VALUE).build();
+ DeviceConfig.setProperties(properties2);
+
+ // Ban entire DeviceConfig
+ DeviceConfig.resetToDefaults(Settings.RESET_MODE_PACKAGE_DEFAULTS, null);
+
+ // Verify given namespace properties are banned
+ assertThrows(DeviceConfig.BadConfigException.class,
+ () -> DeviceConfig.setProperties(properties1));
+ assertThrows(DeviceConfig.BadConfigException.class,
+ () -> DeviceConfig.setProperties(properties2));
+
+ // Modify properties and verify we can set them
+ Properties modifiedProperties1 = new Properties.Builder(namespaceToBan1).setString(KEY,
+ VALUE)
+ .setString(KEY4, NULL_VALUE).setString(KEY2, VALUE2).build();
+ DeviceConfig.setProperties(modifiedProperties1);
+ modifiedProperties1 = DeviceConfig.getProperties(namespaceToBan1);
+ assertThat(modifiedProperties1.getKeyset()).containsExactly(KEY, KEY2, KEY4);
+ assertThat(modifiedProperties1.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE);
+ assertThat(modifiedProperties1.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE2);
+ // Since value is null DEFAULT_VALUE should be returned
+ assertThat(modifiedProperties1.getString(KEY4, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE);
+
+ Properties modifiedProperties2 = new Properties.Builder(namespaceToBan2).setString(KEY,
+ VALUE)
+ .setString(KEY4, NULL_VALUE).setString(KEY2, VALUE2).build();
+ DeviceConfig.setProperties(modifiedProperties1);
+ modifiedProperties2 = DeviceConfig.getProperties(namespaceToBan1);
+ assertThat(modifiedProperties2.getKeyset()).containsExactly(KEY, KEY2, KEY4);
+ assertThat(modifiedProperties2.getString(KEY, DEFAULT_VALUE)).isEqualTo(VALUE);
+ assertThat(modifiedProperties2.getString(KEY2, DEFAULT_VALUE)).isEqualTo(VALUE2);
+ // Since value is null DEFAULT_VALUE should be returned
+ assertThat(modifiedProperties2.getString(KEY4, DEFAULT_VALUE)).isEqualTo(DEFAULT_VALUE);
+ }
+
// TODO(mpape): resolve b/142727848 and re-enable listener tests
// @Test
// public void onPropertiesChangedListener_setPropertyCallback() throws InterruptedException {
diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
index 0e19ca8..d0fd92a 100644
--- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java
@@ -90,12 +90,12 @@
mImeConsumer.onWindowFocusGained();
mImeConsumer.applyImeVisibility(true);
mController.cancelExistingAnimation();
- assertTrue(mController.getSourceConsumer(ime.getType()).isVisible());
+ assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
// test if setVisibility can hide IME
mImeConsumer.applyImeVisibility(false);
mController.cancelExistingAnimation();
- assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+ assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
});
}
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index 179929f..fa61a0a 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -16,11 +16,11 @@
package android.view;
+import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
import static android.view.WindowInsets.Type.systemBars;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -39,8 +39,6 @@
import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams;
import android.view.test.InsetsModeSession;
-import androidx.test.runner.AndroidJUnit4;
-
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
@@ -52,6 +50,8 @@
import java.util.List;
+import androidx.test.runner.AndroidJUnit4;
+
/**
* Tests for {@link InsetsAnimationControlImpl}.
*
@@ -116,7 +116,7 @@
mController = new InsetsAnimationControlImpl(controls,
new Rect(0, 0, 500, 500), mInsetsState, mMockListener, systemBars(),
mMockController, 10 /* durationMs */,
- false /* fade */);
+ false /* fade */, LAYOUT_INSETS_DURING_ANIMATION_SHOWN);
}
@Test
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index a89fc1e..628f7ec 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -16,9 +16,13 @@
package android.view;
+import static android.view.InsetsController.ANIMATION_TYPE_HIDE;
+import static android.view.InsetsController.ANIMATION_TYPE_NONE;
+import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
+import static android.view.ViewRootImpl.NEW_INSETS_MODE_FULL;
import static android.view.WindowInsets.Type.statusBars;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
@@ -40,12 +44,15 @@
import android.view.WindowInsets.Type;
import android.view.WindowManager.BadTokenException;
import android.view.WindowManager.LayoutParams;
+import android.view.test.InsetsModeSession;
import android.widget.TextView;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import org.junit.AfterClass;
import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -68,6 +75,18 @@
private InsetsController mController;
private SurfaceSession mSession = new SurfaceSession();
private SurfaceControl mLeash;
+ private ViewRootImpl mViewRoot;
+ private static InsetsModeSession sInsetsModeSession;
+
+ @BeforeClass
+ public static void setupOnce() {
+ sInsetsModeSession = new InsetsModeSession(NEW_INSETS_MODE_FULL);
+ }
+
+ @AfterClass
+ public static void tearDownOnce() {
+ sInsetsModeSession.close();
+ }
@Before
public void setup() {
@@ -77,14 +96,19 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
Context context = InstrumentationRegistry.getTargetContext();
// cannot mock ViewRootImpl since it's final.
- ViewRootImpl viewRootImpl = new ViewRootImpl(context, context.getDisplay());
+ mViewRoot = new ViewRootImpl(context, context.getDisplay());
try {
- viewRootImpl.setView(new TextView(context), new LayoutParams(), null);
+ mViewRoot.setView(new TextView(context), new LayoutParams(), null);
} catch (BadTokenException e) {
// activity isn't running, we will ignore BadTokenException.
}
- mController = new InsetsController(viewRootImpl);
+ mController = new InsetsController(mViewRoot);
final Rect rect = new Rect(5, 5, 5, 5);
+ mController.getState().getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 10));
+ mController.getState().getSource(ITYPE_NAVIGATION_BAR).setFrame(
+ new Rect(0, 90, 100, 100));
+ mController.getState().getSource(ITYPE_IME).setFrame(new Rect(0, 50, 100, 100));
+ mController.getState().setDisplayFrame(new Rect(0, 0, 100, 100));
mController.calculateInsets(
false,
false,
@@ -92,7 +116,6 @@
Insets.of(10, 10, 10, 10), rect, rect, rect, rect),
rect, rect, SOFT_INPUT_ADJUST_RESIZE);
mController.onFrameChanged(new Rect(0, 0, 100, 100));
- mController.getState().setDisplayFrame(new Rect(0, 0, 100, 100));
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
@@ -117,16 +140,22 @@
@Test
public void testControlsRevoked_duringAnim() {
- InsetsSourceControl control =
- new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point());
- mController.onControlsChanged(new InsetsSourceControl[] { control });
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ InsetsSourceControl control =
+ new InsetsSourceControl(ITYPE_STATUS_BAR, mLeash, new Point());
+ mController.onControlsChanged(new InsetsSourceControl[] { control });
- WindowInsetsAnimationControlListener mockListener =
- mock(WindowInsetsAnimationControlListener.class);
- mController.controlWindowInsetsAnimation(statusBars(), 10 /* durationMs */, mockListener);
- verify(mockListener).onReady(any(), anyInt());
- mController.onControlsChanged(new InsetsSourceControl[0]);
- verify(mockListener).onCancelled();
+ WindowInsetsAnimationControlListener mockListener =
+ mock(WindowInsetsAnimationControlListener.class);
+ mController.controlWindowInsetsAnimation(statusBars(), 10 /* durationMs */,
+ mockListener);
+
+ // Ready gets deferred until next predraw
+ mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
+ verify(mockListener).onReady(any(), anyInt());
+ mController.onControlsChanged(new InsetsSourceControl[0]);
+ verify(mockListener).onCancelled();
+ });
}
@Test
@@ -154,16 +183,16 @@
mController.show(Type.all());
// quickly jump to final state by cancelling it.
mController.cancelExistingAnimation();
- assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible());
- assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible());
- assertTrue(mController.getSourceConsumer(ime.getType()).isVisible());
+ assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+ assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+ assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
mController.applyImeVisibility(false /* setVisible */);
mController.hide(Type.all());
mController.cancelExistingAnimation();
- assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
- assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
- assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+ assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+ assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+ assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
mController.getSourceConsumer(ITYPE_IME).onWindowFocusLost();
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -180,10 +209,10 @@
mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained();
mController.applyImeVisibility(true);
mController.cancelExistingAnimation();
- assertTrue(mController.getSourceConsumer(ime.getType()).isVisible());
+ assertTrue(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
mController.applyImeVisibility(false);
mController.cancelExistingAnimation();
- assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+ assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
mController.getSourceConsumer(ITYPE_IME).onWindowFocusLost();
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -198,19 +227,25 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
int types = Type.navigationBars() | Type.systemBars();
- // test show select types.
- mController.show(types);
+ // test hide select types.
+ mController.hide(types);
+ assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
+ assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR));
mController.cancelExistingAnimation();
- assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible());
- assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible());
- assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+ assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
+ assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_STATUS_BAR));
+ assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+ assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+ assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
// test hide all
- mController.hide(types);
+ mController.show(types);
+ assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
+ assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_STATUS_BAR));
mController.cancelExistingAnimation();
- assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
- assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
- assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+ assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+ assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+ assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
@@ -227,29 +262,29 @@
// test show select types.
mController.show(types);
mController.cancelExistingAnimation();
- assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible());
- assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible());
- assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+ assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+ assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+ assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
// test hide all
mController.hide(Type.all());
mController.cancelExistingAnimation();
- assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
- assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
- assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+ assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+ assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+ assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
// test single show
mController.show(Type.navigationBars());
mController.cancelExistingAnimation();
- assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible());
- assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
- assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+ assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+ assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+ assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
// test single hide
mController.hide(Type.navigationBars());
- assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
- assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
- assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+ assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+ assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+ assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
@@ -264,34 +299,42 @@
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
// start two animations and see if previous is cancelled and final state is reached.
- mController.show(Type.navigationBars());
- mController.show(Type.systemBars());
- mController.cancelExistingAnimation();
- assertTrue(mController.getSourceConsumer(navBar.getType()).isVisible());
- assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible());
- assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
-
mController.hide(Type.navigationBars());
mController.hide(Type.systemBars());
+ assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
+ assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR));
mController.cancelExistingAnimation();
- assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
- assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
- assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+ assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+ assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+ assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
+
+ mController.show(Type.navigationBars());
+ mController.show(Type.systemBars());
+ assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
+ assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ITYPE_STATUS_BAR));
+ mController.cancelExistingAnimation();
+ assertTrue(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+ assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+ assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
int types = Type.navigationBars() | Type.systemBars();
// show two at a time and hide one by one.
mController.show(types);
mController.hide(Type.navigationBars());
+ assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
+ assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_STATUS_BAR));
mController.cancelExistingAnimation();
- assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
- assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible());
- assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+ assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+ assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+ assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
mController.hide(Type.systemBars());
+ assertEquals(ANIMATION_TYPE_NONE, mController.getAnimationType(ITYPE_NAVIGATION_BAR));
+ assertEquals(ANIMATION_TYPE_HIDE, mController.getAnimationType(ITYPE_STATUS_BAR));
mController.cancelExistingAnimation();
- assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
- assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
- assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+ assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+ assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+ assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
@@ -309,15 +352,15 @@
mController.show(types);
mController.hide(Type.navigationBars());
mController.cancelExistingAnimation();
- assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
- assertTrue(mController.getSourceConsumer(statusBar.getType()).isVisible());
- assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+ assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+ assertTrue(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+ assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
mController.hide(Type.systemBars());
mController.cancelExistingAnimation();
- assertFalse(mController.getSourceConsumer(navBar.getType()).isVisible());
- assertFalse(mController.getSourceConsumer(statusBar.getType()).isVisible());
- assertFalse(mController.getSourceConsumer(ime.getType()).isVisible());
+ assertFalse(mController.getSourceConsumer(navBar.getType()).isRequestedVisible());
+ assertFalse(mController.getSourceConsumer(statusBar.getType()).isRequestedVisible());
+ assertFalse(mController.getSourceConsumer(ime.getType()).isRequestedVisible());
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
@@ -336,12 +379,16 @@
ArgumentCaptor<WindowInsetsAnimationController> controllerCaptor =
ArgumentCaptor.forClass(WindowInsetsAnimationController.class);
+
+ // Ready gets deferred until next predraw
+ mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw();
+
verify(mockListener).onReady(controllerCaptor.capture(), anyInt());
controllerCaptor.getValue().finish(false /* shown */);
});
waitUntilNextFrame();
InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
- assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isVisible());
+ assertFalse(mController.getSourceConsumer(ITYPE_STATUS_BAR).isRequestedVisible());
});
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
}
diff --git a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
index 2cf6ff3..492c036 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceConsumerTest.java
@@ -96,14 +96,18 @@
@Test
public void testHide() {
mConsumer.hide();
- assertFalse("Consumer should not be visible", mConsumer.isVisible());
+ assertFalse("Consumer should not be visible", mConsumer.isRequestedVisible());
verify(mSpyInsetsSource).setVisible(eq(false));
}
@Test
public void testShow() {
+
+ // Insets source starts out visible
+ mConsumer.hide();
mConsumer.show();
- assertTrue("Consumer should be visible", mConsumer.isVisible());
+ assertTrue("Consumer should be visible", mConsumer.isRequestedVisible());
+ verify(mSpyInsetsSource).setVisible(eq(false));
verify(mSpyInsetsSource).setVisible(eq(true));
}
diff --git a/core/tests/coretests/src/android/view/InsetsSourceTest.java b/core/tests/coretests/src/android/view/InsetsSourceTest.java
index d7f50ba..e3b08bb 100644
--- a/core/tests/coretests/src/android/view/InsetsSourceTest.java
+++ b/core/tests/coretests/src/android/view/InsetsSourceTest.java
@@ -108,5 +108,30 @@
assertEquals(Insets.of(0, 100, 0, 0), insets);
}
+ @Test
+ public void testCalculateVisibleInsets_default() {
+ mSource.setFrame(new Rect(0, 0, 500, 100));
+ Insets insets = mSource.calculateVisibleInsets(new Rect(100, 0, 500, 500));
+ assertEquals(Insets.of(0, 100, 0, 0), insets);
+ }
+
+ @Test
+ public void testCalculateVisibleInsets_override() {
+ mSource.setFrame(new Rect(0, 0, 500, 100));
+ mSource.setVisibleFrame(new Rect(0, 0, 500, 200));
+ Insets insets = mSource.calculateVisibleInsets(new Rect(100, 0, 500, 500));
+ assertEquals(Insets.of(0, 200, 0, 0), insets);
+ }
+
+ @Test
+ public void testCalculateVisibleInsets_invisible() {
+ mSource.setFrame(new Rect(0, 0, 500, 100));
+ mSource.setVisibleFrame(new Rect(0, 0, 500, 200));
+ mSource.setVisible(false);
+ Insets insets = mSource.calculateVisibleInsets(new Rect(100, 0, 500, 500));
+ assertEquals(Insets.of(0, 0, 0, 0), insets);
+ }
+
+
// Parcel and equals already tested via InsetsStateTest
}
diff --git a/core/tests/coretests/src/android/view/InsetsStateTest.java b/core/tests/coretests/src/android/view/InsetsStateTest.java
index 6062088..4b76fee 100644
--- a/core/tests/coretests/src/android/view/InsetsStateTest.java
+++ b/core/tests/coretests/src/android/view/InsetsStateTest.java
@@ -18,11 +18,14 @@
import static android.view.InsetsState.ISIDE_BOTTOM;
import static android.view.InsetsState.ISIDE_TOP;
+import static android.view.InsetsState.ITYPE_BOTTOM_GESTURES;
import static android.view.InsetsState.ITYPE_CAPTION_BAR;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.WindowInsets.Type.ime;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static org.junit.Assert.assertEquals;
@@ -116,14 +119,18 @@
@Test
public void testCalculateInsets_imeIgnoredWithoutAdjustResize() {
- mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
- mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
- mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
- mState.getSource(ITYPE_IME).setVisible(true);
- WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false,
- DisplayCutout.NO_CUTOUT, null, null, 0, null);
- assertEquals(0, insets.getSystemWindowInsetBottom());
- assertTrue(insets.isVisible(ime()));
+ try (final InsetsModeSession session =
+ new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) {
+ mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
+ mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
+ mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
+ mState.getSource(ITYPE_IME).setVisible(true);
+ WindowInsets insets = mState.calculateInsets(new Rect(0, 0, 100, 300), false, false,
+ DisplayCutout.NO_CUTOUT, null, null, SOFT_INPUT_ADJUST_NOTHING, null);
+ assertEquals(0, insets.getSystemWindowInsetBottom());
+ assertEquals(100, insets.getInsets(ime()).bottom);
+ assertTrue(insets.isVisible(ime()));
+ }
}
@Test
@@ -204,6 +211,42 @@
assertFalse(InsetsState.getDefaultVisibility(ITYPE_IME));
}
+ @Test
+ public void testCalculateVisibleInsets() throws Exception {
+ try (final InsetsModeSession session =
+ new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) {
+ mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
+ mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
+ mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
+ mState.getSource(ITYPE_IME).setVisible(true);
+
+ // Make sure bottom gestures are ignored
+ mState.getSource(ITYPE_BOTTOM_GESTURES).setFrame(new Rect(0, 100, 100, 300));
+ mState.getSource(ITYPE_BOTTOM_GESTURES).setVisible(true);
+ Rect visibleInsets = mState.calculateVisibleInsets(
+ new Rect(0, 0, 100, 300), new Rect(), SOFT_INPUT_ADJUST_PAN);
+ assertEquals(new Rect(0, 100, 0, 100), visibleInsets);
+ }
+ }
+
+ @Test
+ public void testCalculateVisibleInsets_adjustNothing() throws Exception {
+ try (final InsetsModeSession session =
+ new InsetsModeSession(ViewRootImpl.NEW_INSETS_MODE_FULL)) {
+ mState.getSource(ITYPE_STATUS_BAR).setFrame(new Rect(0, 0, 100, 100));
+ mState.getSource(ITYPE_STATUS_BAR).setVisible(true);
+ mState.getSource(ITYPE_IME).setFrame(new Rect(0, 200, 100, 300));
+ mState.getSource(ITYPE_IME).setVisible(true);
+
+ // Make sure bottom gestures are ignored
+ mState.getSource(ITYPE_BOTTOM_GESTURES).setFrame(new Rect(0, 100, 100, 300));
+ mState.getSource(ITYPE_BOTTOM_GESTURES).setVisible(true);
+ Rect visibleInsets = mState.calculateVisibleInsets(
+ new Rect(0, 0, 100, 300), new Rect(), SOFT_INPUT_ADJUST_NOTHING);
+ assertEquals(new Rect(0, 100, 0, 0), visibleInsets);
+ }
+ }
+
private void assertEqualsAndHashCode() {
assertEquals(mState, mState2);
assertEquals(mState.hashCode(), mState2.hashCode());
diff --git a/core/tests/coretests/src/android/view/WindowInsetsTest.java b/core/tests/coretests/src/android/view/WindowInsetsTest.java
index 8c7b28a..e5a4f6d 100644
--- a/core/tests/coretests/src/android/view/WindowInsetsTest.java
+++ b/core/tests/coretests/src/android/view/WindowInsetsTest.java
@@ -63,7 +63,8 @@
b.setInsets(navigationBars(), Insets.of(0, 0, 0, 100));
b.setInsets(ime(), Insets.of(0, 0, 0, 300));
WindowInsets insets = b.build();
- assertEquals(300, insets.getSystemWindowInsets().bottom);
+ assertEquals(100, insets.getSystemWindowInsets().bottom);
+ assertEquals(300, insets.getInsets(ime()).bottom);
}
// TODO: Move this to CTS once API made public
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
similarity index 78%
rename from services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerTest.java
rename to core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
index b9b6d55..e23c51e 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.accessibility;
+package android.view.accessibility;
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertSame;
@@ -29,16 +29,17 @@
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Instrumentation;
+import android.app.PendingIntent;
+import android.app.RemoteAction;
+import android.content.Intent;
+import android.graphics.drawable.Icon;
import android.os.UserHandle;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityManager;
-import android.view.accessibility.IAccessibilityManager;
-import android.view.accessibility.IAccessibilityManagerClient;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.IntPair;
+import com.android.server.accessibility.test.MessageCapturingHandler;
import org.junit.After;
import org.junit.Before;
@@ -57,6 +58,16 @@
public class AccessibilityManagerTest {
private static final boolean WITH_A11Y_ENABLED = true;
private static final boolean WITH_A11Y_DISABLED = false;
+ private static final String LABEL = "label";
+ private static final String INTENT_ACTION = "TESTACTION";
+ private static final String DESCRIPTION = "description";
+ private static final PendingIntent TEST_PENDING_INTENT = PendingIntent.getBroadcast(
+ InstrumentationRegistry.getTargetContext(), 0, new Intent(INTENT_ACTION), 0);
+ private static final RemoteAction TEST_ACTION = new RemoteAction(
+ Icon.createWithContentUri("content://test"),
+ LABEL,
+ DESCRIPTION,
+ TEST_PENDING_INTENT);
@Mock private IAccessibilityManager mMockService;
private MessageCapturingHandler mHandler;
@@ -122,6 +133,29 @@
}
@Test
+ public void testRegisterSystemAction() throws Exception {
+ AccessibilityManager manager = createManager(WITH_A11Y_ENABLED);
+ RemoteAction action = new RemoteAction(
+ Icon.createWithContentUri("content://test"),
+ LABEL,
+ DESCRIPTION,
+ TEST_PENDING_INTENT);
+ final int actionId = 0;
+ manager.registerSystemAction(TEST_ACTION, actionId);
+
+ verify(mMockService).registerSystemAction(TEST_ACTION, actionId);
+ }
+
+ @Test
+ public void testUnregisterSystemAction() throws Exception {
+ AccessibilityManager manager = createManager(WITH_A11Y_ENABLED);
+ final int actionId = 0;
+ manager.unregisterSystemAction(actionId);
+
+ verify(mMockService).unregisterSystemAction(actionId);
+ }
+
+ @Test
public void testIsEnabled() throws Exception {
// Create manager with a11y enabled
AccessibilityManager manager = createManager(WITH_A11Y_ENABLED);
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
index 1bd52af..ade1e0d 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
@@ -46,7 +46,7 @@
// The number of fields tested in the corresponding CTS AccessibilityNodeInfoTest:
// See fullyPopulateAccessibilityNodeInfo, assertEqualsAccessibilityNodeInfo,
// and assertAccessibilityNodeInfoCleared in that class.
- private static final int NUM_MARSHALLED_PROPERTIES = 35;
+ private static final int NUM_MARSHALLED_PROPERTIES = 38;
/**
* The number of properties that are purposely not marshalled
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
index 3586216a..f151b81 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
@@ -19,9 +19,11 @@
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.IAccessibilityServiceConnection;
import android.content.pm.ParceledListSlice;
+import android.graphics.Bitmap;
import android.graphics.Region;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.RemoteCallback;
/**
* Stub implementation of IAccessibilityServiceConnection so each test doesn't need to implement
@@ -124,6 +126,10 @@
public void setSoftKeyboardCallbackEnabled(boolean enabled) {}
+ public boolean switchToInputMethod(String imeId) {
+ return false;
+ }
+
public boolean isAccessibilityButtonAvailable() {
return false;
}
@@ -139,4 +145,14 @@
public IBinder getOverlayWindowToken(int displayId) {
return null;
}
+
+ public int getWindowIdForLeashToken(IBinder token) {
+ return -1;
+ }
+
+ public Bitmap takeScreenshot(int displayId) {
+ return null;
+ }
+
+ public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) {}
}
diff --git a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
index b4f2c91..a602fa3 100644
--- a/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
+++ b/core/tests/coretests/src/android/widget/EditorCursorDragTest.java
@@ -16,15 +16,24 @@
package android.widget;
+import static android.widget.espresso.TextViewActions.clickOnTextAtIndex;
import static android.widget.espresso.TextViewActions.dragOnText;
import static android.widget.espresso.TextViewAssertions.hasInsertionPointerAtIndex;
+import static android.widget.espresso.TextViewAssertions.hasSelection;
import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.replaceText;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
+import static org.hamcrest.Matchers.emptyString;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
import android.app.Activity;
import android.app.Instrumentation;
+import android.view.InputDevice;
+import android.view.MotionEvent;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
@@ -59,6 +68,7 @@
mOriginalFlagValue = Editor.FLAG_ENABLE_CURSOR_DRAG;
Editor.FLAG_ENABLE_CURSOR_DRAG = true;
}
+
@After
public void after() throws Throwable {
Editor.FLAG_ENABLE_CURSOR_DRAG = mOriginalFlagValue;
@@ -70,107 +80,360 @@
onView(withId(R.id.textview)).perform(replaceText(text));
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
- // Drag left to right. The cursor should end up at the position where the finger is lifted.
+ // Swipe left to right to drag the cursor. The cursor should end up at the position where
+ // the finger is lifted.
onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("llo"), text.indexOf("!")));
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(11));
- // Drag right to left. The cursor should end up at the position where the finger is lifted.
+ // Swipe right to left to drag the cursor. The cursor should end up at the position where
+ // the finger is lifted.
onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("!"), text.indexOf("llo")));
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(2));
}
@Test
public void testCursorDrag_horizontal_whenTextViewContentsLargerThanScreen() throws Throwable {
- String text = "Hello world!"
- + Strings.repeat("\n", 500) + "012345middle"
- + Strings.repeat("\n", 10) + "012345last";
+ String text = "Hello world!\n\n"
+ + Strings.repeat("Bla\n\n", 200) + "Bye";
onView(withId(R.id.textview)).perform(replaceText(text));
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
- // Drag left to right. The cursor should end up at the position where the finger is lifted.
+ // Swipe left to right to drag the cursor. The cursor should end up at the position where
+ // the finger is lifted.
onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("llo"), text.indexOf("!")));
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(11));
- // Drag right to left. The cursor should end up at the position where the finger is lifted.
+ // Swipe right to left to drag the cursor. The cursor should end up at the position where
+ // the finger is lifted.
onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("!"), text.indexOf("llo")));
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(2));
}
@Test
+ public void testCursorDrag_diagonal_whenTextViewContentsFitOnScreen() throws Throwable {
+ StringBuilder sb = new StringBuilder();
+ for (int i = 1; i <= 9; i++) {
+ sb.append("line").append(i).append("\n");
+ }
+ String text = sb.toString();
+ onView(withId(R.id.textview)).perform(replaceText(text));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
+
+ // Swipe along a diagonal path. This should drag the cursor.
+ onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("line1"), text.indexOf("2")));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("2")));
+
+ // Swipe along a steeper diagonal path. This should still drag the cursor.
+ onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("line1"), text.indexOf("3")));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("3")));
+
+ // Swipe right-down along a very steep diagonal path. This should not drag the cursor.
+ // Normally this would trigger a scroll, but since the full view fits on the screen there
+ // is nothing to scroll and the gesture will trigger a selection drag.
+ onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("line1"), text.indexOf("7")));
+ onView(withId(R.id.textview)).check(hasSelection(not(emptyString())));
+
+ // Swipe right-up along a very steep diagonal path. This should not drag the cursor.
+ // Normally this would trigger a scroll, but since the full view fits on the screen there
+ // is nothing to scroll and the gesture will trigger a selection drag.
+ int index = text.indexOf("line9");
+ onView(withId(R.id.textview)).perform(clickOnTextAtIndex(index));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(index));
+ onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("line7"), text.indexOf("1")));
+ onView(withId(R.id.textview)).check(hasSelection(not(emptyString())));
+ }
+
+ @Test
public void testCursorDrag_diagonal_whenTextViewContentsLargerThanScreen() throws Throwable {
StringBuilder sb = new StringBuilder();
for (int i = 1; i <= 9; i++) {
sb.append("line").append(i).append("\n");
}
- sb.append(Strings.repeat("0123456789\n\n", 500)).append("Last line");
+ sb.append(Strings.repeat("0123456789\n", 400)).append("Last");
String text = sb.toString();
onView(withId(R.id.textview)).perform(replaceText(text));
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
- // Drag along a diagonal path.
+ // Swipe along a diagonal path. This should drag the cursor.
onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("line1"), text.indexOf("2")));
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("2")));
- // Drag along a steeper diagonal path.
- onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("line1"), text.indexOf("9")));
- onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("9")));
+ // Swipe along a steeper diagonal path. This should still drag the cursor.
+ onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("line1"), text.indexOf("3")));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("3")));
- // Drag along an almost vertical path.
- // TODO(b/145833335): Consider whether this should scroll instead of dragging the cursor.
- onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("ne1"), text.indexOf("9")));
- onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("9")));
+ // Swipe right-down along a very steep diagonal path. This should not drag the cursor.
+ // Normally this would trigger a scroll up, but since the view is already at the top there
+ // is nothing to scroll and the gesture will trigger a selection drag.
+ onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("line1"), text.indexOf("7")));
+ onView(withId(R.id.textview)).check(hasSelection(not(emptyString())));
- // Drag along a vertical path from line 1 to line 9.
- // TODO(b/145833335): Consider whether this should scroll instead of dragging the cursor.
- onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("e1"), text.indexOf("e9")));
- onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("e9")));
-
- // Drag along a vertical path from line 9 to line 1.
- // TODO(b/145833335): Consider whether this should scroll instead of dragging the cursor.
- onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("e9"), text.indexOf("e1")));
- onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("e1")));
+ // Swipe right-up along a very steep diagonal path. This should not drag the cursor. This
+ // will trigger a downward scroll and the cursor position will not change.
+ int index = text.indexOf("line9");
+ onView(withId(R.id.textview)).perform(clickOnTextAtIndex(index));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(index));
+ onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("line7"), text.indexOf("1")));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(index));
}
@Test
public void testCursorDrag_vertical_whenTextViewContentsFitOnScreen() throws Throwable {
- String text = "012first\n\n" + Strings.repeat("012345\n\n", 10) + "012last";
+ String text = "012345_aaa\n"
+ + "0123456789\n"
+ + "012345_bbb\n"
+ + "0123456789\n"
+ + "012345_ccc\n"
+ + "0123456789\n"
+ + "012345_ddd";
onView(withId(R.id.textview)).perform(replaceText(text));
+
+ // Swipe up vertically. This should not drag the cursor. Since there's also nothing to
+ // scroll, the gesture will trigger a selection drag.
+ onView(withId(R.id.textview)).perform(clickOnTextAtIndex(0));
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
+ onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("bbb"), text.indexOf("aaa")));
+ onView(withId(R.id.textview)).check(hasSelection(not(emptyString())));
- // Drag down. Since neither the TextView nor its container require scrolling, the cursor
- // drag should execute and the cursor should end up at the position where the finger is
- // lifted.
- onView(withId(R.id.textview)).perform(
- dragOnText(text.indexOf("first"), text.indexOf("last")));
- onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.length() - 4));
-
- // Drag up. Since neither the TextView nor its container require scrolling, the cursor
- // drag should execute and the cursor should end up at the position where the finger is
- // lifted.
- onView(withId(R.id.textview)).perform(
- dragOnText(text.indexOf("last"), text.indexOf("first")));
- onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(3));
+ // Swipe down vertically. This should not drag the cursor. Since there's also nothing to
+ // scroll, the gesture will trigger a selection drag.
+ onView(withId(R.id.textview)).perform(clickOnTextAtIndex(0));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
+ onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("ccc"), text.indexOf("ddd")));
+ onView(withId(R.id.textview)).check(hasSelection(not(emptyString())));
}
@Test
public void testCursorDrag_vertical_whenTextViewContentsLargerThanScreen() throws Throwable {
- String text = "012345first\n\n"
- + Strings.repeat("0123456789\n\n", 10) + "012345middle"
- + Strings.repeat("0123456789\n\n", 500) + "012345last";
+ String text = "012345_aaa\n"
+ + "0123456789\n"
+ + "012345_bbb\n"
+ + "0123456789\n"
+ + "012345_ccc\n"
+ + "0123456789\n"
+ + "012345_ddd\n"
+ + Strings.repeat("0123456789\n", 400) + "012345_zzz";
onView(withId(R.id.textview)).perform(replaceText(text));
- int initialCursorPosition = 0;
+ onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.indexOf("ddd")));
+ int initialCursorPosition = text.indexOf("ddd");
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(initialCursorPosition));
- // Drag up.
- // TODO(b/145833335): Consider whether this should scroll instead of dragging the cursor.
- onView(withId(R.id.textview)).perform(
- dragOnText(text.indexOf("middle"), text.indexOf("first")));
- onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("first")));
+ // Swipe up vertically. This should trigger a downward scroll.
+ onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("bbb"), text.indexOf("aaa")));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(initialCursorPosition));
- // Drag down.
- // TODO(b/145833335): Consider whether this should scroll instead of dragging the cursor.
- onView(withId(R.id.textview)).perform(
- dragOnText(text.indexOf("first"), text.indexOf("middle")));
- onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(text.indexOf("middle")));
+ // Swipe down vertically. This should trigger an upward scroll.
+ onView(withId(R.id.textview)).perform(dragOnText(text.indexOf("ccc"), text.indexOf("ddd")));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(initialCursorPosition));
+ }
+
+ @Test
+ public void testEditor_onTouchEvent_quickTapAfterDrag() throws Throwable {
+ String text = "Hi world!";
+ onView(withId(R.id.textview)).perform(replaceText(text));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
+
+ TextView tv = mActivity.findViewById(R.id.textview);
+ Editor editor = tv.getEditorForTesting();
+
+ // Simulate a tap-and-drag gesture.
+ long event1Time = 1001;
+ MotionEvent event1 = downEvent(event1Time, event1Time, 5f, 10f);
+ mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1));
+ assertFalse(editor.getInsertionController().isCursorBeingModified());
+ assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+ long event2Time = 1002;
+ MotionEvent event2 = moveEvent(event1Time, event2Time, 50f, 10f);
+ mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event2));
+ assertTrue(editor.getInsertionController().isCursorBeingModified());
+ assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+ long event3Time = 1003;
+ MotionEvent event3 = moveEvent(event1Time, event3Time, 100f, 10f);
+ mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event3));
+ assertTrue(editor.getInsertionController().isCursorBeingModified());
+ assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+ long event4Time = 2004;
+ MotionEvent event4 = upEvent(event1Time, event4Time, 100f, 10f);
+ mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event4));
+ assertFalse(editor.getInsertionController().isCursorBeingModified());
+ assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+ // Simulate a quick tap after the drag, near the location where the drag ended.
+ long event5Time = 2005;
+ MotionEvent event5 = downEvent(event5Time, event5Time, 90f, 10f);
+ mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event5));
+ assertFalse(editor.getInsertionController().isCursorBeingModified());
+ assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+ long event6Time = 2006;
+ MotionEvent event6 = upEvent(event5Time, event6Time, 90f, 10f);
+ mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event6));
+ assertFalse(editor.getInsertionController().isCursorBeingModified());
+ assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+ // Simulate another quick tap in the same location; now selection should be triggered.
+ long event7Time = 2007;
+ MotionEvent event7 = downEvent(event7Time, event7Time, 90f, 10f);
+ mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event7));
+ assertFalse(editor.getInsertionController().isCursorBeingModified());
+ assertTrue(editor.getSelectionController().isCursorBeingModified());
+ }
+
+ @Test
+ public void testEditor_onTouchEvent_mouseDrag() throws Throwable {
+ String text = "testEditor_onTouchEvent_mouseDrag";
+ onView(withId(R.id.textview)).perform(replaceText(text));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
+
+ TextView tv = mActivity.findViewById(R.id.textview);
+ Editor editor = tv.getEditorForTesting();
+
+ // Simulate a mouse click and drag. This should NOT trigger a cursor drag.
+ long event1Time = 1001;
+ MotionEvent event1 = mouseDownEvent(event1Time, event1Time, 20f, 30f);
+ mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1));
+ assertFalse(editor.getInsertionController().isCursorBeingModified());
+ assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+ long event2Time = 1002;
+ MotionEvent event2 = mouseMoveEvent(event1Time, event2Time, 120f, 30f);
+ mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event2));
+ assertFalse(editor.getInsertionController().isCursorBeingModified());
+ assertTrue(editor.getSelectionController().isCursorBeingModified());
+
+ long event3Time = 1003;
+ MotionEvent event3 = mouseUpEvent(event1Time, event3Time, 120f, 30f);
+ mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event3));
+ assertFalse(editor.getInsertionController().isCursorBeingModified());
+ assertFalse(editor.getSelectionController().isCursorBeingModified());
+ }
+
+ @Test
+ public void testEditor_onTouchEvent_cursorDrag() throws Throwable {
+ String text = "testEditor_onTouchEvent_cursorDrag";
+ onView(withId(R.id.textview)).perform(replaceText(text));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
+
+ TextView tv = mActivity.findViewById(R.id.textview);
+ Editor editor = tv.getEditorForTesting();
+
+ // Simulate a tap-and-drag gesture. This should trigger a cursor drag.
+ long event1Time = 1001;
+ MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+ mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1));
+ assertFalse(editor.getInsertionController().isCursorBeingModified());
+ assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+ long event2Time = 1002;
+ MotionEvent event2 = moveEvent(event1Time, event2Time, 21f, 30f);
+ mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event2));
+ assertFalse(editor.getInsertionController().isCursorBeingModified());
+ assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+ long event3Time = 1003;
+ MotionEvent event3 = moveEvent(event1Time, event3Time, 120f, 30f);
+ mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event3));
+ assertTrue(editor.getInsertionController().isCursorBeingModified());
+ assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+ long event4Time = 1004;
+ MotionEvent event4 = upEvent(event1Time, event4Time, 120f, 30f);
+ mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event4));
+ assertFalse(editor.getInsertionController().isCursorBeingModified());
+ assertFalse(editor.getSelectionController().isCursorBeingModified());
+ }
+
+ @Test
+ public void testEditor_onTouchEvent_selectionDrag() throws Throwable {
+ String text = "testEditor_onTouchEvent_selectionDrag";
+ onView(withId(R.id.textview)).perform(replaceText(text));
+ onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex(0));
+
+ TextView tv = mActivity.findViewById(R.id.textview);
+ Editor editor = tv.getEditorForTesting();
+
+ // Simulate a double-tap followed by a drag. This should trigger a selection drag.
+ long event1Time = 1001;
+ MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+ mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1));
+ assertFalse(editor.getInsertionController().isCursorBeingModified());
+ assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+ long event2Time = 1002;
+ MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
+ mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event2));
+ assertFalse(editor.getInsertionController().isCursorBeingModified());
+ assertFalse(editor.getSelectionController().isCursorBeingModified());
+
+ long event3Time = 1003;
+ MotionEvent event3 = downEvent(event3Time, event3Time, 20f, 30f);
+ mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event3));
+ assertFalse(editor.getInsertionController().isCursorBeingModified());
+ assertTrue(editor.getSelectionController().isCursorBeingModified());
+
+ long event4Time = 1004;
+ MotionEvent event4 = moveEvent(event3Time, event4Time, 120f, 30f);
+ mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event4));
+ assertFalse(editor.getInsertionController().isCursorBeingModified());
+ assertTrue(editor.getSelectionController().isCursorBeingModified());
+
+ long event5Time = 1005;
+ MotionEvent event5 = upEvent(event3Time, event5Time, 120f, 30f);
+ mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event5));
+ assertFalse(editor.getInsertionController().isCursorBeingModified());
+ assertFalse(editor.getSelectionController().isCursorBeingModified());
+ }
+
+ @Test // Reproduces b/147366705
+ public void testCursorDrag_nonSelectableTextView() throws Throwable {
+ String text = "Hello world!";
+ TextView tv = mActivity.findViewById(R.id.nonselectable_textview);
+ tv.setText(text);
+ Editor editor = tv.getEditorForTesting();
+
+ // Simulate a tap. No error should be thrown.
+ long event1Time = 1001;
+ MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+ mInstrumentation.runOnMainSync(() -> editor.onTouchEvent(event1));
+
+ // Swipe left to right. No error should be thrown.
+ onView(withId(R.id.nonselectable_textview)).perform(
+ dragOnText(text.indexOf("llo"), text.indexOf("!")));
+ }
+
+ private static MotionEvent downEvent(long downTime, long eventTime, float x, float y) {
+ return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_DOWN, x, y, 0);
+ }
+
+ private static MotionEvent upEvent(long downTime, long eventTime, float x, float y) {
+ return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
+ }
+
+ private static MotionEvent moveEvent(long downTime, long eventTime, float x, float y) {
+ return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0);
+ }
+
+ private static MotionEvent mouseDownEvent(long downTime, long eventTime, float x, float y) {
+ MotionEvent event = downEvent(downTime, eventTime, x, y);
+ event.setSource(InputDevice.SOURCE_MOUSE);
+ event.setButtonState(MotionEvent.BUTTON_PRIMARY);
+ return event;
+ }
+
+ private static MotionEvent mouseUpEvent(long downTime, long eventTime, float x, float y) {
+ MotionEvent event = upEvent(downTime, eventTime, x, y);
+ event.setSource(InputDevice.SOURCE_MOUSE);
+ event.setButtonState(0);
+ return event;
+ }
+
+ private static MotionEvent mouseMoveEvent(long downTime, long eventTime, float x, float y) {
+ MotionEvent event = moveEvent(downTime, eventTime, x, y);
+ event.setSource(InputDevice.SOURCE_MOUSE);
+ event.setButtonState(MotionEvent.BUTTON_PRIMARY);
+ return event;
}
}
diff --git a/core/tests/coretests/src/android/widget/EditorTouchStateTest.java b/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
index 6adb1b8..3dc001d 100644
--- a/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
+++ b/core/tests/coretests/src/android/widget/EditorTouchStateTest.java
@@ -48,24 +48,32 @@
}
@Test
+ public void testIsDistanceWithin() throws Exception {
+ assertTrue(EditorTouchState.isDistanceWithin(0, 0, 0, 0, 8));
+ assertTrue(EditorTouchState.isDistanceWithin(3, 9, 5, 11, 8));
+ assertTrue(EditorTouchState.isDistanceWithin(5, 11, 3, 9, 8));
+ assertFalse(EditorTouchState.isDistanceWithin(5, 10, 5, 20, 8));
+ }
+
+ @Test
public void testUpdate_singleTap() throws Exception {
// Simulate an ACTION_DOWN event.
long event1Time = 1000;
MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
mTouchState.update(event1, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
// Simulate an ACTION_UP event.
long event2Time = 1001;
MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
mTouchState.update(event2, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
+ assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
// Generate an ACTION_DOWN event whose time is after the double-tap timeout.
long event3Time = event2Time + ViewConfiguration.getDoubleTapTimeout() + 1;
MotionEvent event3 = downEvent(event3Time, event3Time, 22f, 33f);
mTouchState.update(event3, mConfig);
- assertSingleTap(mTouchState, 22f, 33f, 20f, 30f, false);
+ assertSingleTap(mTouchState, 22f, 33f, 20f, 30f);
}
@Test
@@ -74,13 +82,13 @@
long event1Time = 1000;
MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
mTouchState.update(event1, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
// Simulate an ACTION_UP event.
long event2Time = 1001;
MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
mTouchState.update(event2, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
+ assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
// Generate an ACTION_DOWN event whose time is within the double-tap timeout.
long event3Time = 1002;
@@ -96,13 +104,13 @@
long event1Time = 1000;
MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
mTouchState.update(event1, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
// Simulate an ACTION_UP event.
long event2Time = 1001;
MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
mTouchState.update(event2, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
+ assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
// Generate an ACTION_DOWN event whose time is within the double-tap timeout.
long event3Time = 1002;
@@ -120,20 +128,74 @@
}
@Test
+ public void testUpdate_doubleTap_delayAfterFirstDownEvent() throws Exception {
+ // Simulate an ACTION_DOWN event.
+ long event1Time = 1000;
+ MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+ mTouchState.update(event1, mConfig);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+
+ // Simulate an ACTION_UP event with a delay that's longer than the double-tap timeout.
+ long event2Time = 1000 + ViewConfiguration.getDoubleTapTimeout() + 1;
+ MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
+ mTouchState.update(event2, mConfig);
+ assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
+
+ // Generate an ACTION_DOWN event whose time is within the double-tap timeout when
+ // calculated from the last ACTION_UP event time. Even though the time between the last up
+ // and this down event is within the double-tap timeout, this should not be considered a
+ // double-tap (since the first down event had a longer delay).
+ long event3Time = event2Time + 1;
+ MotionEvent event3 = downEvent(event3Time, event3Time, 22f, 33f);
+ mTouchState.update(event3, mConfig);
+ assertSingleTap(mTouchState, 22f, 33f, 20f, 30f);
+ }
+
+ @Test
+ public void testUpdate_quickTapAfterDrag() throws Exception {
+ // Simulate an ACTION_DOWN event.
+ long event1Time = 1000;
+ MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+ mTouchState.update(event1, mConfig);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+
+ // Simulate an ACTION_MOVE event.
+ long event2Time = 1001;
+ MotionEvent event2 = moveEvent(event1Time, event2Time, 200f, 31f);
+ mTouchState.update(event2, mConfig);
+ assertDrag(mTouchState, 20f, 30f, 0, 0, false);
+
+ // Simulate an ACTION_UP event with a delay that's longer than the double-tap timeout.
+ long event3Time = 5000;
+ MotionEvent event3 = upEvent(event1Time, event3Time, 200f, 31f);
+ mTouchState.update(event3, mConfig);
+ assertSingleTap(mTouchState, 20f, 30f, 200f, 31f);
+
+ // Generate an ACTION_DOWN event whose time is within the double-tap timeout when
+ // calculated from the last ACTION_UP event time. Even though the time between the last up
+ // and this down event is within the double-tap timeout, this should not be considered a
+ // double-tap (since the first down event had a longer delay).
+ long event4Time = event3Time + 1;
+ MotionEvent event4 = downEvent(event4Time, event4Time, 200f, 31f);
+ mTouchState.update(event4, mConfig);
+ assertSingleTap(mTouchState, 200f, 31f, 200f, 31f);
+ }
+
+ @Test
public void testUpdate_tripleClick_mouse() throws Exception {
// Simulate an ACTION_DOWN event.
long event1Time = 1000;
MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
event1.setSource(InputDevice.SOURCE_MOUSE);
mTouchState.update(event1, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
// Simulate an ACTION_UP event.
long event2Time = 1001;
MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
event2.setSource(InputDevice.SOURCE_MOUSE);
mTouchState.update(event2, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
+ assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
// Generate a second ACTION_DOWN event whose time is within the double-tap timeout.
long event3Time = 1002;
@@ -166,13 +228,13 @@
long event1Time = 1000;
MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
mTouchState.update(event1, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
// Simulate an ACTION_UP event.
long event2Time = 1001;
MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
mTouchState.update(event2, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 20f, 30f, false);
+ assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
// Generate a second ACTION_DOWN event whose time is within the double-tap timeout.
long event3Time = 1002;
@@ -192,7 +254,7 @@
long event5Time = 1004;
MotionEvent event5 = downEvent(event5Time, event5Time, 22f, 32f);
mTouchState.update(event5, mConfig);
- assertSingleTap(mTouchState, 22f, 32f, 21f, 31f, false);
+ assertSingleTap(mTouchState, 22f, 32f, 21f, 31f);
}
@Test
@@ -201,13 +263,13 @@
long event1Time = 1000;
MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
mTouchState.update(event1, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
// Simulate an ACTION_MOVE event whose location is not far enough to start a drag.
long event2Time = 1001;
MotionEvent event2 = moveEvent(event1Time, event2Time, 21f, 30f);
mTouchState.update(event2, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, false);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
// Simulate another ACTION_MOVE event whose location is far enough to start a drag.
int touchSlop = mConfig.getScaledTouchSlop();
@@ -216,21 +278,135 @@
long event3Time = 1002;
MotionEvent event3 = moveEvent(event3Time, event3Time, newX, newY);
mTouchState.update(event3, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 0, 0, true);
+ assertDrag(mTouchState, 20f, 30f, 0, 0, false);
// Simulate an ACTION_UP event.
long event4Time = 1003;
MotionEvent event4 = upEvent(event3Time, event4Time, 200f, 300f);
mTouchState.update(event4, mConfig);
- assertSingleTap(mTouchState, 20f, 30f, 200f, 300f, false);
+ assertSingleTap(mTouchState, 20f, 30f, 200f, 300f);
}
@Test
- public void testIsDistanceWithin() throws Exception {
- assertTrue(EditorTouchState.isDistanceWithin(0, 0, 0, 0, 8));
- assertTrue(EditorTouchState.isDistanceWithin(3, 9, 5, 11, 8));
- assertTrue(EditorTouchState.isDistanceWithin(5, 11, 3, 9, 8));
- assertFalse(EditorTouchState.isDistanceWithin(5, 10, 5, 20, 8));
+ public void testUpdate_drag_startsCloseToVerticalThenHorizontal() throws Exception {
+ // Simulate an ACTION_DOWN event.
+ long event1Time = 1001;
+ MotionEvent event1 = downEvent(event1Time, event1Time, 0f, 0f);
+ mTouchState.update(event1, mConfig);
+ assertSingleTap(mTouchState, 0f, 0f, 0, 0);
+
+ // Simulate an ACTION_MOVE event that is < 30 deg from vertical.
+ long event2Time = 1002;
+ MotionEvent event2 = moveEvent(event1Time, event2Time, 100f, 174f);
+ mTouchState.update(event2, mConfig);
+ assertDrag(mTouchState, 0f, 0f, 0, 0, true);
+
+ // Simulate another ACTION_MOVE event that is horizontal from the original down event.
+ // The value of `isDragCloseToVertical` should NOT change since it should only reflect the
+ // initial direction of movement.
+ long event3Time = 1003;
+ MotionEvent event3 = moveEvent(event1Time, event3Time, 200f, 0f);
+ mTouchState.update(event3, mConfig);
+ assertDrag(mTouchState, 0f, 0f, 0, 0, true);
+
+ // Simulate an ACTION_UP event.
+ long event4Time = 1004;
+ MotionEvent event4 = upEvent(event1Time, event4Time, 200f, 0f);
+ mTouchState.update(event4, mConfig);
+ assertSingleTap(mTouchState, 0f, 0f, 200f, 0f);
+ }
+
+ @Test
+ public void testUpdate_drag_startsHorizontalThenVertical() throws Exception {
+ // Simulate an ACTION_DOWN event.
+ long event1Time = 1001;
+ MotionEvent event1 = downEvent(event1Time, event1Time, 0f, 0f);
+ mTouchState.update(event1, mConfig);
+ assertSingleTap(mTouchState, 0f, 0f, 0, 0);
+
+ // Simulate an ACTION_MOVE event that is > 30 deg from vertical.
+ long event2Time = 1002;
+ MotionEvent event2 = moveEvent(event1Time, event2Time, 100f, 173f);
+ mTouchState.update(event2, mConfig);
+ assertDrag(mTouchState, 0f, 0f, 0, 0, false);
+
+ // Simulate another ACTION_MOVE event that is vertical from the original down event.
+ // The value of `isDragCloseToVertical` should NOT change since it should only reflect the
+ // initial direction of movement.
+ long event3Time = 1003;
+ MotionEvent event3 = moveEvent(event1Time, event3Time, 0f, 200f);
+ mTouchState.update(event3, mConfig);
+ assertDrag(mTouchState, 0f, 0f, 0, 0, false);
+
+ // Simulate an ACTION_UP event.
+ long event4Time = 1004;
+ MotionEvent event4 = upEvent(event1Time, event4Time, 0f, 200f);
+ mTouchState.update(event4, mConfig);
+ assertSingleTap(mTouchState, 0f, 0f, 0f, 200f);
+ }
+
+ @Test
+ public void testUpdate_cancelAfterDown() throws Exception {
+ // Simulate an ACTION_DOWN event.
+ long event1Time = 1001;
+ MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+ mTouchState.update(event1, mConfig);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+
+ // Simulate an ACTION_CANCEL event.
+ long event2Time = 1002;
+ MotionEvent event2 = cancelEvent(event1Time, event2Time, 20f, 30f);
+ mTouchState.update(event2, mConfig);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+ }
+
+ @Test
+ public void testUpdate_cancelAfterDrag() throws Exception {
+ // Simulate an ACTION_DOWN event.
+ long event1Time = 1001;
+ MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+ mTouchState.update(event1, mConfig);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+
+ // Simulate another ACTION_MOVE event whose location is far enough to start a drag.
+ long event2Time = 1002;
+ MotionEvent event2 = moveEvent(event2Time, event2Time, 200f, 30f);
+ mTouchState.update(event2, mConfig);
+ assertDrag(mTouchState, 20f, 30f, 0, 0, false);
+
+ // Simulate an ACTION_CANCEL event.
+ long event3Time = 1003;
+ MotionEvent event3 = cancelEvent(event1Time, event3Time, 200f, 30f);
+ mTouchState.update(event3, mConfig);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+ }
+
+ @Test
+ public void testUpdate_cancelAfterMultitap() throws Exception {
+ // Simulate an ACTION_DOWN event.
+ long event1Time = 1001;
+ MotionEvent event1 = downEvent(event1Time, event1Time, 20f, 30f);
+ mTouchState.update(event1, mConfig);
+ assertSingleTap(mTouchState, 20f, 30f, 0, 0);
+
+ // Simulate an ACTION_UP event.
+ long event2Time = 1002;
+ MotionEvent event2 = upEvent(event1Time, event2Time, 20f, 30f);
+ mTouchState.update(event2, mConfig);
+ assertSingleTap(mTouchState, 20f, 30f, 20f, 30f);
+
+ // Generate an ACTION_DOWN event whose time is within the double-tap timeout.
+ long event3Time = 1003;
+ MotionEvent event3 = downEvent(event3Time, event3Time, 22f, 33f);
+ mTouchState.update(event3, mConfig);
+ assertMultiTap(mTouchState, 22f, 33f, 20f, 30f,
+ MultiTapStatus.DOUBLE_TAP, true);
+
+ // Simulate an ACTION_CANCEL event.
+ long event4Time = 1004;
+ MotionEvent event4 = cancelEvent(event3Time, event4Time, 20f, 30f);
+ mTouchState.update(event4, mConfig);
+ assertSingleTap(mTouchState, 22f, 33f, 20f, 30f);
}
private static MotionEvent downEvent(long downTime, long eventTime, float x, float y) {
@@ -245,8 +421,12 @@
return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0);
}
+ private static MotionEvent cancelEvent(long downTime, long eventTime, float x, float y) {
+ return MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_CANCEL, x, y, 0);
+ }
+
private static void assertSingleTap(EditorTouchState touchState, float lastDownX,
- float lastDownY, float lastUpX, float lastUpY, boolean isMovedEnoughForDrag) {
+ float lastDownY, float lastUpX, float lastUpY) {
assertThat(touchState.getLastDownX(), is(lastDownX));
assertThat(touchState.getLastDownY(), is(lastDownY));
assertThat(touchState.getLastUpX(), is(lastUpX));
@@ -255,7 +435,21 @@
assertThat(touchState.isTripleClick(), is(false));
assertThat(touchState.isMultiTap(), is(false));
assertThat(touchState.isMultiTapInSameArea(), is(false));
- assertThat(touchState.isMovedEnoughForDrag(), is(isMovedEnoughForDrag));
+ assertThat(touchState.isMovedEnoughForDrag(), is(false));
+ }
+
+ private static void assertDrag(EditorTouchState touchState, float lastDownX,
+ float lastDownY, float lastUpX, float lastUpY, boolean isDragCloseToVertical) {
+ assertThat(touchState.getLastDownX(), is(lastDownX));
+ assertThat(touchState.getLastDownY(), is(lastDownY));
+ assertThat(touchState.getLastUpX(), is(lastUpX));
+ assertThat(touchState.getLastUpY(), is(lastUpY));
+ assertThat(touchState.isDoubleTap(), is(false));
+ assertThat(touchState.isTripleClick(), is(false));
+ assertThat(touchState.isMultiTap(), is(false));
+ assertThat(touchState.isMultiTapInSameArea(), is(false));
+ assertThat(touchState.isMovedEnoughForDrag(), is(true));
+ assertThat(touchState.isDragCloseToVertical(), is(isDragCloseToVertical));
}
private static void assertMultiTap(EditorTouchState touchState,
@@ -271,5 +465,6 @@
|| multiTapStatus == MultiTapStatus.TRIPLE_CLICK));
assertThat(touchState.isMultiTapInSameArea(), is(isMultiTapInSameArea));
assertThat(touchState.isMovedEnoughForDrag(), is(false));
+ assertThat(touchState.isDragCloseToVertical(), is(false));
}
}
diff --git a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
index b411668..a0cfb31 100644
--- a/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewActivityMouseTest.java
@@ -48,7 +48,6 @@
import android.view.textclassifier.TextClassifier;
import androidx.test.filters.MediumTest;
-import androidx.test.filters.Suppress;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
@@ -64,7 +63,6 @@
*/
@RunWith(AndroidJUnit4.class)
@MediumTest
-@Suppress // Consistently failing. b/29591177
public class TextViewActivityMouseTest {
@Rule
@@ -86,22 +84,12 @@
onView(withId(R.id.textview)).perform(mouseClick());
onView(withId(R.id.textview)).perform(replaceText(helloWorld));
- assertNoSelectionHandles();
-
onView(withId(R.id.textview)).perform(
mouseDragOnText(helloWorld.indexOf("llo"), helloWorld.indexOf("ld!")));
-
onView(withId(R.id.textview)).check(hasSelection("llo wor"));
- onHandleView(com.android.internal.R.id.selection_start_handle)
- .check(matches(isDisplayed()));
- onHandleView(com.android.internal.R.id.selection_end_handle)
- .check(matches(isDisplayed()));
-
onView(withId(R.id.textview)).perform(mouseClickOnTextAtIndex(helloWorld.indexOf("w")));
onView(withId(R.id.textview)).check(hasSelection(""));
-
- assertNoSelectionHandles();
}
@Test
@@ -196,7 +184,6 @@
onView(withId(R.id.textview)).check(matches(withText("abc ghi.def")));
onView(withId(R.id.textview)).check(hasSelection(""));
- assertNoSelectionHandles();
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex("abc ghi.def".length()));
}
@@ -213,7 +200,6 @@
onView(withId(R.id.textview)).check(matches(withText("abc ghi.def")));
onView(withId(R.id.textview)).check(hasSelection(""));
- assertNoSelectionHandles();
onView(withId(R.id.textview)).check(hasInsertionPointerAtIndex("abc ghi.def".length()));
}
@@ -403,4 +389,29 @@
mouseTripleClickAndDragOnText(text.indexOf("ird"), text.indexOf("First")));
onView(withId(R.id.textview)).check(hasSelection(text));
}
+
+ @Test
+ public void testSelectionHandlesDisplay() {
+ final String helloWorld = "Hello world!";
+ onView(withId(R.id.textview)).perform(mouseClick());
+ onView(withId(R.id.textview)).perform(replaceText(helloWorld));
+
+ onView(withId(R.id.textview)).perform(
+ mouseDragOnText(helloWorld.indexOf("llo"), helloWorld.indexOf("ld!")));
+ onView(withId(R.id.textview)).check(hasSelection("llo wor"));
+
+ // Confirm that selection handles are shown when there is a selection.
+ onHandleView(com.android.internal.R.id.selection_start_handle)
+ .check(matches(isDisplayed()));
+ onHandleView(com.android.internal.R.id.selection_end_handle)
+ .check(matches(isDisplayed()));
+
+ onView(withId(R.id.textview)).perform(mouseClickOnTextAtIndex(helloWorld.indexOf("w")));
+ onView(withId(R.id.textview)).check(hasSelection(""));
+
+ // Confirm that the selection handles are not shown when there is no selection. This
+ // assertion is slow so we only do it in this one test case. The rest of the tests just
+ // assert via `hasSelection("")`.
+ assertNoSelectionHandles();
+ }
}
diff --git a/core/tests/coretests/src/android/widget/espresso/MouseUiController.java b/core/tests/coretests/src/android/widget/espresso/MouseUiController.java
index abee736..1928d25 100644
--- a/core/tests/coretests/src/android/widget/espresso/MouseUiController.java
+++ b/core/tests/coretests/src/android/widget/espresso/MouseUiController.java
@@ -70,6 +70,8 @@
event.setSource(InputDevice.SOURCE_MOUSE);
if (event.getActionMasked() != MotionEvent.ACTION_UP) {
event.setButtonState(mButton);
+ } else {
+ event.setButtonState(0);
}
return mUiController.injectMotionEvent(event);
}
diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
index 82854e5..6784ede 100644
--- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java
@@ -348,7 +348,7 @@
verify(mAlertDialog).show();
verify(mAccessibilityManagerService, atLeastOnce()).getInstalledAccessibilityServiceList(
anyInt());
- verify(mAccessibilityManagerService, times(0)).performAccessibilityShortcut();
+ verify(mAccessibilityManagerService, times(0)).performAccessibilityShortcut(null);
verify(mFrameworkObjectProvider, times(0)).getTextToSpeech(any(), any());
}
@@ -365,7 +365,7 @@
assertEquals(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS,
mLayoutParams.privateFlags
& WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS);
- verify(mAccessibilityManagerService, times(1)).performAccessibilityShortcut();
+ verify(mAccessibilityManagerService, times(1)).performAccessibilityShortcut(null);
}
@Test
@@ -433,7 +433,7 @@
verifyZeroInteractions(mAlertDialogBuilder, mAlertDialog);
verify(mToast).show();
- verify(mAccessibilityManagerService).performAccessibilityShortcut();
+ verify(mAccessibilityManagerService).performAccessibilityShortcut(null);
}
@Test
@@ -459,7 +459,7 @@
when(mServiceInfo.loadSummary(any())).thenReturn(null);
Settings.Secure.putInt(mContentResolver, ACCESSIBILITY_SHORTCUT_DIALOG_SHOWN, 1);
getController().performAccessibilityShortcut();
- verify(mAccessibilityManagerService).performAccessibilityShortcut();
+ verify(mAccessibilityManagerService).performAccessibilityShortcut(null);
}
@Test
@@ -471,7 +471,7 @@
getController().performAccessibilityShortcut();
verifyZeroInteractions(mToast);
- verify(mAccessibilityManagerService).performAccessibilityShortcut();
+ verify(mAccessibilityManagerService).performAccessibilityShortcut(null);
}
@Test
@@ -485,7 +485,7 @@
getController().performAccessibilityShortcut();
verifyZeroInteractions(mToast);
- verify(mAccessibilityManagerService).performAccessibilityShortcut();
+ verify(mAccessibilityManagerService).performAccessibilityShortcut(null);
}
@Test
diff --git a/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java b/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java
index ffc925f..f108eb8 100644
--- a/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java
+++ b/core/tests/coretests/src/com/android/internal/infra/AndroidFutureTest.java
@@ -121,7 +121,12 @@
AndroidFuture future2 = AndroidFuture.CREATOR.createFromParcel(parcel);
ExecutionException executionException =
expectThrows(ExecutionException.class, future2::get);
- assertThat(executionException.getCause()).isInstanceOf(UnsupportedOperationException.class);
+
+ Throwable cause = executionException.getCause();
+ String msg = cause.getMessage();
+ assertThat(cause).isInstanceOf(UnsupportedOperationException.class);
+ assertThat(msg).contains(getClass().getName());
+ assertThat(msg).contains("testWriteToParcel_Exception");
}
@Test
diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
index 66d84aa..9018320 100644
--- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
+++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
@@ -79,138 +79,6 @@
@Test
@UiThreadTest
- public void testWindowVisibilityChange_OnCreate() throws Exception {
- try (ClientMockSession clientSession = new ClientMockSession()) {
- ActivityClientRecord r = clientSession.stubActivityRecord();
-
- clientSession.launchActivity(r);
- assertEquals(ON_CREATE, r.getLifecycleState());
-
- clientSession.changeVisibility(r, true);
- assertEquals(ON_CREATE, r.getLifecycleState());
-
- clientSession.changeVisibility(r, false);
- assertEquals(ON_CREATE, r.getLifecycleState());
- }
- }
-
- @Test
- @UiThreadTest
- public void testWindowVisibilityChange_OnCreate_Finished() throws Exception {
- try (ClientMockSession clientSession = new ClientMockSession()) {
- ActivityClientRecord r = clientSession.stubActivityRecord();
-
- Activity activity = clientSession.launchActivity(r);
- activity.finish();
- assertEquals(ON_CREATE, r.getLifecycleState());
-
- clientSession.changeVisibility(r, true);
- assertEquals(ON_CREATE, r.getLifecycleState());
-
- clientSession.changeVisibility(r, false);
- assertEquals(ON_CREATE, r.getLifecycleState());
- }
- }
-
- @Test
- @UiThreadTest
- public void testWindowVisibilityChange_OnStart() throws Exception {
- try (ClientMockSession clientSession = new ClientMockSession()) {
- ActivityClientRecord r = clientSession.stubActivityRecord();
-
- clientSession.launchActivity(r);
- clientSession.startActivity(r);
- assertEquals(ON_START, r.getLifecycleState());
-
- clientSession.changeVisibility(r, false);
- assertEquals(ON_STOP, r.getLifecycleState());
-
- clientSession.changeVisibility(r, true);
- assertEquals(ON_START, r.getLifecycleState());
- }
- }
-
- @Test
- @UiThreadTest
- public void testWindowVisibilityChange_OnStart_Finished() throws Exception {
- try (ClientMockSession clientSession = new ClientMockSession()) {
- ActivityClientRecord r = clientSession.stubActivityRecord();
-
- Activity activity = clientSession.launchActivity(r);
- clientSession.startActivity(r);
- activity.finish();
- assertEquals(ON_START, r.getLifecycleState());
-
- clientSession.changeVisibility(r, false);
- assertEquals(ON_STOP, r.getLifecycleState());
-
- clientSession.changeVisibility(r, true);
- assertEquals(ON_START, r.getLifecycleState());
- }
- }
-
- @Test
- @UiThreadTest
- public void testWindowVisibilityChange_OnResume() throws Exception {
- try (ClientMockSession clientSession = new ClientMockSession()) {
- ActivityClientRecord r = clientSession.stubActivityRecord();
-
- clientSession.launchActivity(r);
- clientSession.startActivity(r);
- clientSession.resumeActivity(r);
- assertEquals(ON_RESUME, r.getLifecycleState());
-
- clientSession.changeVisibility(r, false);
- assertEquals(ON_STOP, r.getLifecycleState());
-
- clientSession.changeVisibility(r, true);
- assertEquals(ON_START, r.getLifecycleState());
- }
- }
-
- @Test
- @UiThreadTest
- public void testWindowVisibilityChange_OnPause() throws Exception {
- try (ClientMockSession clientSession = new ClientMockSession()) {
- ActivityClientRecord r = clientSession.stubActivityRecord();
-
- clientSession.launchActivity(r);
- clientSession.startActivity(r);
- clientSession.resumeActivity(r);
- clientSession.pauseActivity(r);
- assertEquals(ON_PAUSE, r.getLifecycleState());
-
- clientSession.changeVisibility(r, false);
- assertEquals(ON_STOP, r.getLifecycleState());
-
- clientSession.changeVisibility(r, true);
- assertEquals(ON_START, r.getLifecycleState());
- }
- }
-
- @Test
- @UiThreadTest
- public void testWindowVisibilityChange_OnStop() throws Exception {
- try (ClientMockSession clientSession = new ClientMockSession()) {
- ActivityClientRecord r = clientSession.stubActivityRecord();
-
- clientSession.launchActivity(r);
- clientSession.startActivity(r);
- clientSession.resumeActivity(r);
- clientSession.pauseActivity(r);
- clientSession.stopActivity(r);
- assertEquals(ON_STOP, r.getLifecycleState());
-
- clientSession.changeVisibility(r, true);
- assertEquals(ON_START, r.getLifecycleState());
-
- clientSession.changeVisibility(r, false);
- assertEquals(ON_STOP, r.getLifecycleState());
- }
- }
-
- @Test
- @UiThreadTest
public void testLifecycleAfterFinished_OnCreate() throws Exception {
try (ClientMockSession clientSession = new ClientMockSession()) {
ActivityClientRecord r = clientSession.stubActivityRecord();
@@ -308,7 +176,7 @@
}
private void startActivity(ActivityClientRecord r) {
- mThread.handleStartActivity(r, null /* pendingActions */);
+ mThread.handleStartActivity(r.token, null /* pendingActions */);
}
private void resumeActivity(ActivityClientRecord r) {
@@ -323,7 +191,7 @@
}
private void stopActivity(ActivityClientRecord r) {
- mThread.handleStopActivity(r.token, false /* show */, 0 /* configChanges */,
+ mThread.handleStopActivity(r.token, 0 /* configChanges */,
new PendingTransactionActions(), false /* finalStateRequest */, "test");
}
@@ -332,10 +200,6 @@
false /* getNonConfigInstance */, "test");
}
- private void changeVisibility(ActivityClientRecord r, boolean show) {
- mThread.handleWindowVisibility(r.token, show);
- }
-
private ActivityClientRecord stubActivityRecord() {
ComponentName component = new ComponentName(
InstrumentationRegistry.getInstrumentation().getContext(), TestActivity.class);
diff --git a/core/tests/overlaytests/remount/TEST_MAPPING b/core/tests/overlaytests/remount/TEST_MAPPING
new file mode 100644
index 0000000..54dd431
--- /dev/null
+++ b/core/tests/overlaytests/remount/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name" : "OverlayRemountedTest"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/core/tests/overlaytests/remount/host/Android.bp b/core/tests/overlaytests/remount/host/Android.bp
new file mode 100644
index 0000000..3825c55
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/Android.bp
@@ -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_test_host {
+ name: "OverlayRemountedTest",
+ srcs: ["src/**/*.java"],
+ libs: [
+ "tradefed",
+ "junit",
+ ],
+ test_suites: ["general-tests"],
+ java_resources: [
+ ":OverlayRemountedTest_SharedLibrary",
+ ":OverlayRemountedTest_SharedLibraryOverlay",
+ ":OverlayRemountedTest_Target",
+ ],
+}
diff --git a/core/tests/overlaytests/remount/host/AndroidTest.xml b/core/tests/overlaytests/remount/host/AndroidTest.xml
new file mode 100644
index 0000000..11eadf1a
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?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.
+ -->
+
+<configuration description="Test module config for OverlayRemountedTest">
+ <option name="test-tag" value="OverlayRemountedTest" />
+
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="remount" />
+ </target_preparer>
+
+ <test class="com.android.tradefed.testtype.HostTest">
+ <option name="jar" value="OverlayRemountedTest.jar" />
+ </test>
+</configuration>
diff --git a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlayHostTest.java b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlayHostTest.java
new file mode 100644
index 0000000..84af187
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlayHostTest.java
@@ -0,0 +1,114 @@
+/*
+ * 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 com.android.overlaytest.remounted;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.Rule;
+import org.junit.rules.RuleChain;
+import org.junit.rules.TemporaryFolder;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class OverlayHostTest extends BaseHostJUnit4Test {
+ private static final long TIME_OUT_MS = 30000;
+ private static final String RES_INSTRUMENTATION_ARG = "res";
+ private static final String OVERLAY_INSTRUMENTATION_ARG = "overlays";
+ private static final String RESOURCES_TYPE_SUFFIX = "_type";
+ private static final String RESOURCES_DATA_SUFFIX = "_data";
+
+ public final TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+ public final SystemPreparer mPreparer = new SystemPreparer(mTemporaryFolder, this::getDevice);
+
+ @Rule
+ public final RuleChain ruleChain = RuleChain.outerRule(mTemporaryFolder).around(mPreparer);
+ private Map<String, String> mLastResults;
+
+ /**
+ * Retrieves the values of the resources in the test package. The test package must use the
+ * {@link com.android.overlaytest.remounted.target.ResourceRetrievalRunner} instrumentation.
+ **/
+ void retrieveResource(String testPackageName, List<String> requiredOverlayPaths,
+ String... resourceNames) throws DeviceNotAvailableException {
+ final HashMap<String, String> args = new HashMap<>();
+ if (!requiredOverlayPaths.isEmpty()) {
+ // Enclose the require overlay paths in quotes so the arguments will be string arguments
+ // rather than file arguments.
+ args.put(OVERLAY_INSTRUMENTATION_ARG,
+ String.format("\"%s\"", String.join(" ", requiredOverlayPaths)));
+ }
+
+ if (resourceNames.length == 0) {
+ throw new IllegalArgumentException("Must specify at least one resource to retrieve.");
+ }
+
+ // Pass the names of the resources to retrieve into the test as one string.
+ args.put(RES_INSTRUMENTATION_ARG,
+ String.format("\"%s\"", String.join(" ", resourceNames)));
+
+ runDeviceTests(getDevice(), null, testPackageName, null, null, null, TIME_OUT_MS,
+ TIME_OUT_MS, TIME_OUT_MS, false, false, args);
+
+ // Retrieve the results of the most recently run test.
+ mLastResults = (getLastDeviceRunResults().getRunMetrics() == mLastResults) ? null :
+ getLastDeviceRunResults().getRunMetrics();
+ }
+
+ /** Returns the base resource directories of the specified packages. */
+ List<String> getPackagePaths(String... packageNames)
+ throws DeviceNotAvailableException {
+ final ArrayList<String> paths = new ArrayList<>();
+ for (String packageName : packageNames) {
+ // Use the package manager shell command to find the path of the package.
+ final String result = getDevice().executeShellCommand(
+ String.format("pm dump %s | grep \"resourcePath=\"", packageName));
+ assertNotNull("Failed to find path for package " + packageName, result);
+ int splitIndex = result.indexOf('=');
+ assertTrue(splitIndex >= 0);
+ paths.add(result.substring(splitIndex + 1).trim());
+ }
+ return paths;
+ }
+
+ /** Builds the full name of a resource in the form package:type/entry. */
+ String resourceName(String pkg, String type, String entry) {
+ return String.format("%s:%s/%s", pkg, type, entry);
+ }
+
+ /**
+ * Asserts that the type and data of a a previously retrieved is the same as expected.
+ * @param resourceName the full name of the resource in the form package:type/entry
+ * @param type the expected {@link android.util.TypedValue} type of the resource
+ * @param data the expected value of the resource when coerced to a string using
+ * {@link android.util.TypedValue#coerceToString()}
+ **/
+ void assertResource(String resourceName, int type, String data) {
+ assertNotNull("Failed to get test results", mLastResults);
+ assertNotEquals("No resource values were retrieved", mLastResults.size(), 0);
+ assertEquals("" + type, mLastResults.get(resourceName + RESOURCES_TYPE_SUFFIX));
+ assertEquals("" + data, mLastResults.get(resourceName + RESOURCES_DATA_SUFFIX));
+ }
+}
diff --git a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java
new file mode 100644
index 0000000..4939e16
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/OverlaySharedLibraryTest.java
@@ -0,0 +1,90 @@
+/*
+ * 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 com.android.overlaytest.remounted;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collections;
+import java.util.List;
+
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class OverlaySharedLibraryTest extends OverlayHostTest {
+ private static final String TARGET_APK = "OverlayRemountedTest_Target.apk";
+ private static final String TARGET_PACKAGE = "com.android.overlaytest.remounted.target";
+ private static final String SHARED_LIBRARY_APK =
+ "OverlayRemountedTest_SharedLibrary.apk";
+ private static final String SHARED_LIBRARY_PACKAGE =
+ "com.android.overlaytest.remounted.shared_library";
+ private static final String SHARED_LIBRARY_OVERLAY_APK =
+ "OverlayRemountedTest_SharedLibraryOverlay.apk";
+ private static final String SHARED_LIBRARY_OVERLAY_PACKAGE =
+ "com.android.overlaytest.remounted.shared_library.overlay";
+
+ @Test
+ public void testSharedLibrary() throws Exception {
+ final String targetResource = resourceName(TARGET_PACKAGE, "bool",
+ "uses_shared_library_overlaid");
+ final String libraryResource = resourceName(SHARED_LIBRARY_PACKAGE, "bool",
+ "shared_library_overlaid");
+
+ mPreparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk")
+ .installResourceApk(SHARED_LIBRARY_OVERLAY_APK, SHARED_LIBRARY_OVERLAY_PACKAGE)
+ .reboot()
+ .setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, false)
+ .installResourceApk(TARGET_APK, TARGET_PACKAGE);
+
+ // The shared library resource is not currently overlaid.
+ retrieveResource(Collections.emptyList(), targetResource, libraryResource);
+ assertResource(targetResource, 0x12 /* TYPE_INT_BOOLEAN */, "false");
+ assertResource(libraryResource, 0x12 /* TYPE_INT_BOOLEAN */, "false");
+
+ // Overlay the shared library resource.
+ mPreparer.setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, true);
+ retrieveResource(getPackagePaths(SHARED_LIBRARY_OVERLAY_PACKAGE), targetResource,
+ libraryResource);
+ assertResource(targetResource, 0x12 /* TYPE_INT_BOOLEAN */, "true");
+ assertResource(libraryResource, 0x12 /* TYPE_INT_BOOLEAN */, "true");
+ }
+
+ @Test
+ public void testSharedLibraryPreEnabled() throws Exception {
+ final String targetResource = resourceName(TARGET_PACKAGE, "bool",
+ "uses_shared_library_overlaid");
+ final String libraryResource = resourceName(SHARED_LIBRARY_PACKAGE, "bool",
+ "shared_library_overlaid");
+
+ mPreparer.pushResourceFile(SHARED_LIBRARY_APK, "/product/app/SharedLibrary.apk")
+ .installResourceApk(SHARED_LIBRARY_OVERLAY_APK, SHARED_LIBRARY_OVERLAY_PACKAGE)
+ .setOverlayEnabled(SHARED_LIBRARY_OVERLAY_PACKAGE, true)
+ .reboot()
+ .installResourceApk(TARGET_APK, TARGET_PACKAGE);
+
+ retrieveResource(getPackagePaths(SHARED_LIBRARY_OVERLAY_PACKAGE), targetResource,
+ libraryResource);
+ assertResource(targetResource, 0x12 /* TYPE_INT_BOOLEAN */, "true");
+ assertResource(libraryResource, 0x12 /* TYPE_INT_BOOLEAN */, "true");
+ }
+
+ private void retrieveResource(List<String> requiredOverlayPaths, String... resourceNames)
+ throws DeviceNotAvailableException {
+ retrieveResource(TARGET_PACKAGE, requiredOverlayPaths, resourceNames);
+ }
+}
diff --git a/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java
new file mode 100644
index 0000000..7028f2f
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/src/com/android/overlaytest/remounted/SystemPreparer.java
@@ -0,0 +1,158 @@
+/*
+ * 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 com.android.overlaytest.remounted;
+
+import static org.junit.Assert.assertTrue;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+
+import org.junit.Assert;
+import org.junit.rules.ExternalResource;
+import org.junit.rules.TemporaryFolder;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeoutException;
+
+class SystemPreparer extends ExternalResource {
+ private static final long REBOOT_SLEEP_MS = 30000;
+ private static final long OVERLAY_ENABLE_TIMEOUT_MS = 20000;
+
+ // The paths of the files pushed onto the device through this rule.
+ private ArrayList<String> mPushedFiles = new ArrayList<>();
+
+ // The package names of packages installed through this rule.
+ private ArrayList<String> mInstalledPackages = new ArrayList<>();
+
+ private final TemporaryFolder mHostTempFolder;
+ private final DeviceProvider mDeviceProvider;
+
+ SystemPreparer(TemporaryFolder hostTempFolder, DeviceProvider deviceProvider) {
+ mHostTempFolder = hostTempFolder;
+ mDeviceProvider = deviceProvider;
+ }
+
+ /** Copies a file within the host test jar to a path on device. */
+ SystemPreparer pushResourceFile(String resourcePath,
+ String outputPath) throws DeviceNotAvailableException, IOException {
+ final ITestDevice device = mDeviceProvider.getDevice();
+ assertTrue(device.pushFile(copyResourceToTemp(resourcePath), outputPath));
+ mPushedFiles.add(outputPath);
+ return this;
+ }
+
+ /** Installs an APK within the host test jar onto the device. */
+ SystemPreparer installResourceApk(String resourcePath, String packageName)
+ throws DeviceNotAvailableException, IOException {
+ final ITestDevice device = mDeviceProvider.getDevice();
+ final File tmpFile = copyResourceToTemp(resourcePath);
+ final String result = device.installPackage(tmpFile, true);
+ Assert.assertNull(result);
+ mInstalledPackages.add(packageName);
+ return this;
+ }
+
+ /** Sets the enable state of an overlay pacakage. */
+ SystemPreparer setOverlayEnabled(String packageName, boolean enabled)
+ throws ExecutionException, TimeoutException {
+ final ITestDevice device = mDeviceProvider.getDevice();
+
+ // Wait for the overlay to change its enabled state.
+ final FutureTask<Boolean> enabledListener = new FutureTask<>(() -> {
+ while (true) {
+ device.executeShellCommand(String.format("cmd overlay %s %s",
+ enabled ? "enable" : "disable", packageName));
+
+ final String pattern = (enabled ? "[x]" : "[ ]") + " " + packageName;
+ if (device.executeShellCommand("cmd overlay list").contains(pattern)) {
+ return true;
+ }
+ }
+ });
+
+ final Executor executor = (cmd) -> new Thread(cmd).start();
+ executor.execute(enabledListener);
+ try {
+ enabledListener.get(OVERLAY_ENABLE_TIMEOUT_MS, MILLISECONDS);
+ } catch (InterruptedException ignored) {
+ }
+
+ return this;
+ }
+
+ /** Restarts the device and waits until after boot is completed. */
+ SystemPreparer reboot() throws DeviceNotAvailableException {
+ final ITestDevice device = mDeviceProvider.getDevice();
+ device.executeShellCommand("stop");
+ device.executeShellCommand("start");
+ try {
+ // Sleep until the device is ready for test execution.
+ Thread.sleep(REBOOT_SLEEP_MS);
+ } catch (InterruptedException ignored) {
+ }
+
+ return this;
+ }
+
+ /** Copies a file within the host test jar to a temporary file on the host machine. */
+ private File copyResourceToTemp(String resourcePath) throws IOException {
+ final File tempFile = mHostTempFolder.newFile(resourcePath);
+ final ClassLoader classLoader = getClass().getClassLoader();
+ try (InputStream assetIs = classLoader.getResource(resourcePath).openStream();
+ FileOutputStream assetOs = new FileOutputStream(tempFile)) {
+ if (assetIs == null) {
+ throw new IllegalStateException("Failed to find resource " + resourcePath);
+ }
+
+ int b;
+ while ((b = assetIs.read()) >= 0) {
+ assetOs.write(b);
+ }
+ }
+
+ return tempFile;
+ }
+
+ /** Removes installed packages and files that were pushed to the device. */
+ @Override
+ protected void after() {
+ final ITestDevice device = mDeviceProvider.getDevice();
+ try {
+ for (final String file : mPushedFiles) {
+ device.deleteFile(file);
+ }
+ for (final String packageName : mInstalledPackages) {
+ device.uninstallPackage(packageName);
+ }
+ } catch (DeviceNotAvailableException e) {
+ Assert.fail(e.toString());
+ }
+ }
+
+ interface DeviceProvider {
+ ITestDevice getDevice();
+ }
+}
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/Android.bp b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/Android.bp
new file mode 100644
index 0000000..ffb0572
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/Android.bp
@@ -0,0 +1,19 @@
+// 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: "OverlayRemountedTest_SharedLibrary",
+ sdk_version: "current",
+ aaptflags: ["--shared-lib"],
+}
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/AndroidManifest.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/AndroidManifest.xml
new file mode 100644
index 0000000..06e3f6a
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?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.overlaytest.remounted.shared_library">
+ <application>
+ <library android:name="com.android.overlaytest.remounted.shared_library" />
+ </application>
+</manifest>
\ No newline at end of file
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/overlayable.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/overlayable.xml
new file mode 100644
index 0000000..1b06f6d
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/overlayable.xml
@@ -0,0 +1,24 @@
+<?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>
+ <overlayable name="TestResources">
+ <policy type="public">
+ <item type="bool" name="shared_library_overlaid" />
+ </policy>
+ </overlayable>
+</resources>
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/public.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/public.xml
new file mode 100644
index 0000000..5b9db16
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/public.xml
@@ -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.
+ -->
+
+<resources>
+ <public type="bool" name="shared_library_overlaid" id="0x00050001"/>
+</resources>
\ No newline at end of file
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/values.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/values.xml
new file mode 100644
index 0000000..2dc47a7
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibrary/res/values/values.xml
@@ -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.
+ -->
+
+<resources>
+ <bool name="shared_library_overlaid">false</bool>
+</resources>
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/Android.bp b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/Android.bp
new file mode 100644
index 0000000..0d29aec
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/Android.bp
@@ -0,0 +1,18 @@
+// 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: "OverlayRemountedTest_SharedLibraryOverlay",
+ sdk_version: "current",
+}
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/AndroidManifest.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/AndroidManifest.xml
new file mode 100644
index 0000000..53a4e61
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?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.overlaytest.remounted.shared_library.overlay">
+ <application android:hasCode="false" />
+ <overlay android:targetPackage="com.android.overlaytest.remounted.shared_library"
+ android:targetName="TestResources" />
+</manifest>
\ No newline at end of file
diff --git a/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/res/values/values.xml b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/res/values/values.xml
new file mode 100644
index 0000000..f66448a
--- /dev/null
+++ b/core/tests/overlaytests/remount/host/test-apps/SharedLibraryOverlay/res/values/values.xml
@@ -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.
+ -->
+
+<resources>
+ <bool name="shared_library_overlaid">true</bool>
+</resources>
diff --git a/core/tests/overlaytests/remount/target/Android.bp b/core/tests/overlaytests/remount/target/Android.bp
new file mode 100644
index 0000000..83f9f28
--- /dev/null
+++ b/core/tests/overlaytests/remount/target/Android.bp
@@ -0,0 +1,20 @@
+// 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.
+
+android_test_helper_app {
+ name: "OverlayRemountedTest_Target",
+ srcs: ["src/**/*.java"],
+ sdk_version: "test_current",
+ libs: ["OverlayRemountedTest_SharedLibrary"],
+}
diff --git a/core/tests/overlaytests/remount/target/AndroidManifest.xml b/core/tests/overlaytests/remount/target/AndroidManifest.xml
new file mode 100644
index 0000000..32fec43
--- /dev/null
+++ b/core/tests/overlaytests/remount/target/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?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.overlaytest.remounted.target">
+
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <uses-library android:name="com.android.overlaytest.remounted.shared_library"
+ android:required="true" />
+ </application>
+
+ <instrumentation android:name="com.android.overlaytest.remounted.target.ResourceRetrievalRunner"
+ android:targetPackage="com.android.overlaytest.remounted.target"
+ android:label="Remounted system RRO tests" />
+</manifest>
diff --git a/core/tests/overlaytests/remount/target/res/values/values.xml b/core/tests/overlaytests/remount/target/res/values/values.xml
new file mode 100644
index 0000000..b5f444a
--- /dev/null
+++ b/core/tests/overlaytests/remount/target/res/values/values.xml
@@ -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.
+ -->
+
+<resources xmlns:sharedlib="http://schemas.android.com/apk/res/com.android.overlaytest.remounted.shared_library">
+ <bool name="uses_shared_library_overlaid">@sharedlib:bool/shared_library_overlaid</bool>
+</resources>
diff --git a/core/tests/overlaytests/remount/target/src/com/android/overlaytest/remounted/target/ResourceRetrievalRunner.java b/core/tests/overlaytests/remount/target/src/com/android/overlaytest/remounted/target/ResourceRetrievalRunner.java
new file mode 100644
index 0000000..2e4c211
--- /dev/null
+++ b/core/tests/overlaytests/remount/target/src/com/android/overlaytest/remounted/target/ResourceRetrievalRunner.java
@@ -0,0 +1,140 @@
+/*
+ * 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 com.android.overlaytest.remounted.target;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.util.Log;
+import android.util.TypedValue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.Executor;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * An {@link Instrumentation} that retrieves the value of specified resources within the
+ * application.
+ **/
+public class ResourceRetrievalRunner extends Instrumentation {
+ private static final String TAG = ResourceRetrievalRunner.class.getSimpleName();
+
+ // A list of whitespace separated resource names of which to retrieve the resource values.
+ private static final String RESOURCE_LIST_TAG = "res";
+
+ // A list of whitespace separated overlay package paths that must be present before retrieving
+ // resource values.
+ private static final String REQUIRED_OVERLAYS_LIST_TAG = "overlays";
+
+ // The suffixes of the keys returned from the instrumentation. To retrieve the type of a
+ // resource looked up with the instrumentation, append the {@link #RESOURCES_TYPE_SUFFIX} suffix
+ // to the end of the name of the resource. For the value of a resource, use
+ // {@link #RESOURCES_DATA_SUFFIX} instead.
+ private static final String RESOURCES_TYPE_SUFFIX = "_type";
+ private static final String RESOURCES_DATA_SUFFIX = "_data";
+
+ // The amount of time in seconds to wait for the overlays to be present in the AssetManager.
+ private static final int OVERLAY_PATH_TIMEOUT = 60;
+
+ private final ArrayList<String> mResourceNames = new ArrayList<>();
+ private final ArrayList<String> mOverlayPaths = new ArrayList<>();
+ private final Bundle mResult = new Bundle();
+
+ /**
+ * Receives the instrumentation arguments and runs the resource retrieval.
+ * The entry with key {@link #RESOURCE_LIST_TAG} in the {@link Bundle} arguments is a
+ * whitespace separated string of resource names of which to retrieve the resource values.
+ * The entry with key {@link #REQUIRED_OVERLAYS_LIST_TAG} in the {@link Bundle} arguments is a
+ * whitespace separated string of overlay package paths prefixes that must be present before
+ * retrieving the resource values.
+ */
+ @Override
+ public void onCreate(Bundle arguments) {
+ super.onCreate(arguments);
+ mResourceNames.addAll(Arrays.asList(arguments.getString(RESOURCE_LIST_TAG).split(" ")));
+ if (arguments.containsKey(REQUIRED_OVERLAYS_LIST_TAG)) {
+ mOverlayPaths.addAll(Arrays.asList(
+ arguments.getString(REQUIRED_OVERLAYS_LIST_TAG).split(" ")));
+ }
+ start();
+ }
+
+ @Override
+ public void onStart() {
+ final Resources res = getContext().getResources();
+ res.getAssets().setResourceResolutionLoggingEnabled(true);
+
+ if (!mOverlayPaths.isEmpty()) {
+ Log.d(TAG, String.format("Waiting for overlay paths [%s]",
+ String.join(",", mOverlayPaths)));
+
+ // Wait for all required overlays to be present in the AssetManager.
+ final FutureTask<Boolean> overlayListener = new FutureTask<>(() -> {
+ while (!mOverlayPaths.isEmpty()) {
+ final String[] apkPaths = res.getAssets().getApkPaths();
+ for (String path : apkPaths) {
+ for (String overlayPath : mOverlayPaths) {
+ if (path.startsWith(overlayPath)) {
+ mOverlayPaths.remove(overlayPath);
+ break;
+ }
+ }
+ }
+ }
+ return true;
+ });
+
+ try {
+ final Executor executor = (t) -> new Thread(t).start();
+ executor.execute(overlayListener);
+ overlayListener.get(OVERLAY_PATH_TIMEOUT, TimeUnit.SECONDS);
+ } catch (Exception e) {
+ Log.e(TAG, String.format("Failed to wait for required overlays [%s]",
+ String.join(",", mOverlayPaths)), e);
+ finish(Activity.RESULT_CANCELED, mResult);
+ }
+ }
+
+ // Retrieve the values for each resource passed in.
+ final TypedValue typedValue = new TypedValue();
+ for (final String resourceName : mResourceNames) {
+ try {
+ final int resId = res.getIdentifier(resourceName, null, null);
+ res.getValue(resId, typedValue, true);
+ Log.d(TAG, String.format("Resolution for 0x%s: %s", Integer.toHexString(resId),
+ res.getAssets().getLastResourceResolution()));
+ } catch (Resources.NotFoundException e) {
+ Log.e(TAG, "Failed to retrieve value for resource " + resourceName, e);
+ finish(Activity.RESULT_CANCELED, mResult);
+ }
+
+ putValue(resourceName, typedValue);
+ }
+
+ finish(Activity.RESULT_OK, mResult);
+ }
+
+ private void putValue(String resourceName, TypedValue value) {
+ mResult.putInt(resourceName + RESOURCES_TYPE_SUFFIX, value.type);
+ final CharSequence textValue = value.coerceToString();
+ mResult.putString(resourceName + RESOURCES_DATA_SUFFIX,
+ textValue == null ? "null" : textValue.toString());
+ }
+}
diff --git a/core/tests/utiltests/src/com/android/internal/util/ConnectivityUtilTest.java b/core/tests/utiltests/src/com/android/internal/util/ConnectivityUtilTest.java
new file mode 100644
index 0000000..0809f69
--- /dev/null
+++ b/core/tests/utiltests/src/com/android/internal/util/ConnectivityUtilTest.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.util;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.location.LocationManager;
+import android.os.Binder;
+import android.os.Build;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+import java.util.HashMap;
+
+/** Unit tests for {@link ConnectivityUtil}. */
+public class ConnectivityUtilTest {
+
+ public static final String TAG = "ConnectivityUtilTest";
+
+ // Mock objects for testing
+ @Mock private Context mMockContext;
+ @Mock private PackageManager mMockPkgMgr;
+ @Mock private ApplicationInfo mMockApplInfo;
+ @Mock private AppOpsManager mMockAppOps;
+ @Mock private UserManager mMockUserManager;
+ @Mock private LocationManager mLocationManager;
+
+ private static final String TEST_PKG_NAME = "com.google.somePackage";
+ private static final String TEST_FEATURE_ID = "com.google.someFeature";
+ private static final int MANAGED_PROFILE_UID = 1100000;
+ private static final int OTHER_USER_UID = 1200000;
+
+ private final String mInteractAcrossUsersFullPermission =
+ "android.permission.INTERACT_ACROSS_USERS_FULL";
+ private final String mManifestStringCoarse =
+ Manifest.permission.ACCESS_COARSE_LOCATION;
+ private final String mManifestStringFine =
+ Manifest.permission.ACCESS_FINE_LOCATION;
+
+ // Test variables
+ private int mWifiScanAllowApps;
+ private int mUid;
+ private int mCoarseLocationPermission;
+ private int mAllowCoarseLocationApps;
+ private int mFineLocationPermission;
+ private int mAllowFineLocationApps;
+ private int mCurrentUser;
+ private boolean mIsLocationEnabled;
+ private boolean mThrowSecurityException;
+ private Answer<Integer> mReturnPermission;
+ private HashMap<String, Integer> mPermissionsList = new HashMap<String, Integer>();
+
+ private class TestConnectivityUtil extends ConnectivityUtil {
+
+ TestConnectivityUtil(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected int getCurrentUser() {
+ return mCurrentUser;
+ }
+ }
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ initTestVars();
+ }
+
+ private void setupMocks() throws Exception {
+ when(mMockPkgMgr.getApplicationInfoAsUser(eq(TEST_PKG_NAME), eq(0), any()))
+ .thenReturn(mMockApplInfo);
+ when(mMockContext.getPackageManager()).thenReturn(mMockPkgMgr);
+ when(mMockAppOps.noteOp(AppOpsManager.OPSTR_WIFI_SCAN, mUid, TEST_PKG_NAME,
+ TEST_FEATURE_ID, null)).thenReturn(mWifiScanAllowApps);
+ when(mMockAppOps.noteOp(eq(AppOpsManager.OPSTR_COARSE_LOCATION), eq(mUid),
+ eq(TEST_PKG_NAME), eq(TEST_FEATURE_ID), nullable(String.class)))
+ .thenReturn(mAllowCoarseLocationApps);
+ when(mMockAppOps.noteOp(eq(AppOpsManager.OPSTR_FINE_LOCATION), eq(mUid),
+ eq(TEST_PKG_NAME), eq(TEST_FEATURE_ID), nullable(String.class)))
+ .thenReturn(mAllowFineLocationApps);
+ if (mThrowSecurityException) {
+ doThrow(new SecurityException("Package " + TEST_PKG_NAME + " doesn't belong"
+ + " to application bound to user " + mUid))
+ .when(mMockAppOps).checkPackage(mUid, TEST_PKG_NAME);
+ }
+ when(mMockContext.getSystemService(Context.APP_OPS_SERVICE))
+ .thenReturn(mMockAppOps);
+ when(mMockContext.getSystemService(Context.USER_SERVICE))
+ .thenReturn(mMockUserManager);
+ when(mMockContext.getSystemService(Context.LOCATION_SERVICE)).thenReturn(mLocationManager);
+ }
+
+ private void setupTestCase() throws Exception {
+ setupMocks();
+ setupMockInterface();
+ }
+
+ private void initTestVars() {
+ mPermissionsList.clear();
+ mReturnPermission = createPermissionAnswer();
+ mWifiScanAllowApps = AppOpsManager.MODE_ERRORED;
+ mUid = OTHER_USER_UID;
+ mThrowSecurityException = true;
+ mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.M;
+ mIsLocationEnabled = false;
+ mCurrentUser = UserHandle.USER_SYSTEM;
+ mCoarseLocationPermission = PackageManager.PERMISSION_DENIED;
+ mFineLocationPermission = PackageManager.PERMISSION_DENIED;
+ mAllowCoarseLocationApps = AppOpsManager.MODE_ERRORED;
+ mAllowFineLocationApps = AppOpsManager.MODE_ERRORED;
+ }
+
+ private void setupMockInterface() {
+ Binder.restoreCallingIdentity((((long) mUid) << 32) | Binder.getCallingPid());
+ doAnswer(mReturnPermission).when(mMockContext).checkPermission(
+ anyString(), anyInt(), anyInt());
+ when(mMockUserManager.isSameProfileGroup(UserHandle.SYSTEM,
+ UserHandle.getUserHandleForUid(MANAGED_PROFILE_UID)))
+ .thenReturn(true);
+ when(mMockContext.checkPermission(mManifestStringCoarse, -1, mUid))
+ .thenReturn(mCoarseLocationPermission);
+ when(mMockContext.checkPermission(mManifestStringFine, -1, mUid))
+ .thenReturn(mFineLocationPermission);
+ when(mLocationManager.isLocationEnabledForUser(any())).thenReturn(mIsLocationEnabled);
+ }
+
+ private Answer<Integer> createPermissionAnswer() {
+ return new Answer<Integer>() {
+ @Override
+ public Integer answer(InvocationOnMock invocation) {
+ int myUid = (int) invocation.getArguments()[1];
+ String myPermission = (String) invocation.getArguments()[0];
+ mPermissionsList.get(myPermission);
+ if (mPermissionsList.containsKey(myPermission)) {
+ int uid = mPermissionsList.get(myPermission);
+ if (myUid == uid) {
+ return PackageManager.PERMISSION_GRANTED;
+ }
+ }
+ return PackageManager.PERMISSION_DENIED;
+ }
+ };
+ }
+
+ @Test
+ public void testEnforceLocationPermission_HasAllPermissions_BeforeQ() throws Exception {
+ mIsLocationEnabled = true;
+ mThrowSecurityException = false;
+ mCoarseLocationPermission = PackageManager.PERMISSION_GRANTED;
+ mAllowCoarseLocationApps = AppOpsManager.MODE_ALLOWED;
+ mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
+ mUid = mCurrentUser;
+ setupTestCase();
+ new TestConnectivityUtil(mMockContext)
+ .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null);
+ }
+
+ @Test
+ public void testEnforceLocationPermission_HasAllPermissions_AfterQ() throws Exception {
+ mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.Q;
+ mIsLocationEnabled = true;
+ mThrowSecurityException = false;
+ mUid = mCurrentUser;
+ mFineLocationPermission = PackageManager.PERMISSION_GRANTED;
+ mAllowFineLocationApps = AppOpsManager.MODE_ALLOWED;
+ mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
+ setupTestCase();
+ new TestConnectivityUtil(mMockContext)
+ .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null);
+ }
+
+ @Test
+ public void testEnforceLocationPermission_PkgNameAndUidMismatch() throws Exception {
+ mThrowSecurityException = true;
+ mIsLocationEnabled = true;
+ mFineLocationPermission = PackageManager.PERMISSION_GRANTED;
+ mAllowFineLocationApps = AppOpsManager.MODE_ALLOWED;
+ mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
+ setupTestCase();
+
+ assertThrows(SecurityException.class,
+ () -> new TestConnectivityUtil(mMockContext)
+ .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null));
+ }
+
+ @Test
+ public void testenforceCanAccessScanResults_UserOrProfileNotCurrent() throws Exception {
+ mIsLocationEnabled = true;
+ mThrowSecurityException = false;
+ mCoarseLocationPermission = PackageManager.PERMISSION_GRANTED;
+ mAllowCoarseLocationApps = AppOpsManager.MODE_ALLOWED;
+ mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
+ setupTestCase();
+
+ assertThrows(SecurityException.class,
+ () -> new TestConnectivityUtil(mMockContext)
+ .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null));
+ }
+
+ @Test
+ public void testenforceCanAccessScanResults_NoCoarseLocationPermission() throws Exception {
+ mThrowSecurityException = false;
+ mIsLocationEnabled = true;
+ setupTestCase();
+ assertThrows(SecurityException.class,
+ () -> new TestConnectivityUtil(mMockContext)
+ .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null));
+ }
+
+ @Test
+ public void testenforceCanAccessScanResults_NoFineLocationPermission() throws Exception {
+ mThrowSecurityException = false;
+ mMockApplInfo.targetSdkVersion = Build.VERSION_CODES.Q;
+ mIsLocationEnabled = true;
+ mCoarseLocationPermission = PackageManager.PERMISSION_GRANTED;
+ mAllowFineLocationApps = AppOpsManager.MODE_ERRORED;
+ mUid = MANAGED_PROFILE_UID;
+ setupTestCase();
+
+ assertThrows(SecurityException.class,
+ () -> new TestConnectivityUtil(mMockContext)
+ .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null));
+ verify(mMockAppOps, never()).noteOp(anyInt(), anyInt(), anyString());
+ }
+
+ @Test
+ public void testenforceCanAccessScanResults_LocationModeDisabled() throws Exception {
+ mThrowSecurityException = false;
+ mUid = MANAGED_PROFILE_UID;
+ mWifiScanAllowApps = AppOpsManager.MODE_ALLOWED;
+ mPermissionsList.put(mInteractAcrossUsersFullPermission, mUid);
+ mIsLocationEnabled = false;
+
+ setupTestCase();
+
+ assertThrows(SecurityException.class,
+ () -> new TestConnectivityUtil(mMockContext)
+ .enforceLocationPermission(TEST_PKG_NAME, TEST_FEATURE_ID, mUid, null));
+ }
+
+ private static void assertThrows(Class<? extends Exception> exceptionClass, Runnable r) {
+ try {
+ r.run();
+ Assert.fail("Expected " + exceptionClass + " to be thrown.");
+ } catch (Exception exception) {
+ assertTrue(exceptionClass.isInstance(exception));
+ }
+ }
+}
diff --git a/core/xsd/permission.xsd b/core/xsd/permission.xsd
index 9520db7..5435047 100644
--- a/core/xsd/permission.xsd
+++ b/core/xsd/permission.xsd
@@ -46,6 +46,7 @@
<xs:element name="hidden-api-whitelisted-app" type="hidden-api-whitelisted-app"/>
<xs:element name="allow-association" type="allow-association"/>
<xs:element name="bugreport-whitelisted" type="bugreport-whitelisted"/>
+ <xs:element name="app-data-isolation-whitelisted-app" type="app-data-isolation-whitelisted-app"/>
</xs:choice>
</xs:complexType>
</xs:element>
@@ -126,12 +127,12 @@
</xs:complexType>
<xs:complexType name="privapp-permissions">
<xs:sequence>
- <xs:element name="permission" maxOccurs="unbounded">
+ <xs:element name="permission" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:attribute name="name" type="xs:string"/>
</xs:complexType>
</xs:element>
- <xs:element name="deny-permission" maxOccurs="unbounded">
+ <xs:element name="deny-permission" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:attribute name="name" type="xs:string"/>
</xs:complexType>
@@ -141,12 +142,12 @@
</xs:complexType>
<xs:complexType name="oem-permissions">
<xs:sequence>
- <xs:element name="permission" maxOccurs="unbounded">
+ <xs:element name="permission" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:attribute name="name" type="xs:string"/>
</xs:complexType>
</xs:element>
- <xs:element name="deny-permission" maxOccurs="unbounded">
+ <xs:element name="deny-permission" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:attribute name="name" type="xs:string"/>
</xs:complexType>
@@ -161,6 +162,9 @@
<xs:attribute name="target" type="xs:string"/>
<xs:attribute name="allowed" type="xs:string"/>
</xs:complexType>
+ <xs:complexType name="app-data-isolation-whitelisted-app">
+ <xs:attribute name="package" type="xs:string"/>
+ </xs:complexType>
<xs:complexType name="bugreport-whitelisted">
<xs:attribute name="package" type="xs:string"/>
</xs:complexType>
diff --git a/core/xsd/schema/current.txt b/core/xsd/schema/current.txt
index 771c1df..c36c422 100644
--- a/core/xsd/schema/current.txt
+++ b/core/xsd/schema/current.txt
@@ -45,6 +45,12 @@
method public void set_package(String);
}
+ public class AppDataIsolationWhitelistedApp {
+ ctor public AppDataIsolationWhitelistedApp();
+ method public String get_package();
+ method public void set_package(String);
+ }
+
public class AppLink {
ctor public AppLink();
method public String get_package();
@@ -160,6 +166,7 @@
method public java.util.List<com.android.xml.permission.configfile.AllowInPowerSaveExceptIdle> getAllowInPowerSaveExceptIdle_optional();
method public java.util.List<com.android.xml.permission.configfile.AllowInPowerSave> getAllowInPowerSave_optional();
method public java.util.List<com.android.xml.permission.configfile.AllowUnthrottledLocation> getAllowUnthrottledLocation_optional();
+ method public java.util.List<com.android.xml.permission.configfile.AppDataIsolationWhitelistedApp> getAppDataIsolationWhitelistedApp_optional();
method public java.util.List<com.android.xml.permission.configfile.AppLink> getAppLink_optional();
method public java.util.List<com.android.xml.permission.configfile.AssignPermission> getAssignPermission_optional();
method public java.util.List<com.android.xml.permission.configfile.BackupTransportWhitelistedService> getBackupTransportWhitelistedService_optional();
diff --git a/data/etc/OWNERS b/data/etc/OWNERS
index ea66ee3..70d4678 100644
--- a/data/etc/OWNERS
+++ b/data/etc/OWNERS
@@ -1 +1 @@
-per-file privapp-permissions-platform.xml = hackbod@android.com, jsharkey@android.com, svetoslavganov@google.com, toddke@google.com, yamasani@google.com, cbrubaker@google.com, jeffv@google.com, moltmann@google.com
+per-file privapp-permissions-platform.xml = hackbod@android.com, jsharkey@android.com, svetoslavganov@google.com, toddke@google.com, yamasani@google.com, cbrubaker@google.com, jeffv@google.com, moltmann@google.com, lorenzo@google.com
diff --git a/data/etc/car/Android.bp b/data/etc/car/Android.bp
index 26a40d3..dfb7a16 100644
--- a/data/etc/car/Android.bp
+++ b/data/etc/car/Android.bp
@@ -135,3 +135,10 @@
src: "com.android.car.floatingcardslauncher.xml",
filename_from_src: true,
}
+
+prebuilt_etc {
+ name: "privapp_whitelist_com.android.car.ui.paintbooth",
+ sub_dir: "permissions",
+ src: "com.android.car.ui.paintbooth.xml",
+ filename_from_src: true,
+}
diff --git a/data/etc/car/com.android.car.developeroptions.xml b/data/etc/car/com.android.car.developeroptions.xml
index 5f5e908..7204898 100644
--- a/data/etc/car/com.android.car.developeroptions.xml
+++ b/data/etc/car/com.android.car.developeroptions.xml
@@ -40,6 +40,7 @@
<permission name="android.permission.MOVE_PACKAGE"/>
<permission name="android.permission.OVERRIDE_WIFI_CONFIG"/>
<permission name="android.permission.PACKAGE_USAGE_STATS"/>
+ <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
<permission name="android.permission.READ_SEARCH_INDEXABLES"/>
<permission name="android.permission.REBOOT"/>
<permission name="android.permission.REQUEST_NETWORK_SCORES"/>
diff --git a/data/etc/car/com.android.car.ui.paintbooth.xml b/data/etc/car/com.android.car.ui.paintbooth.xml
new file mode 100644
index 0000000..11bf304
--- /dev/null
+++ b/data/etc/car/com.android.car.ui.paintbooth.xml
@@ -0,0 +1,29 @@
+<?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
+ -->
+<permissions>
+
+ <privapp-permissions package="com.android.car.ui.paintbooth">
+ <!-- For enabling/disabling, and getting list of RROs -->
+ <permission name="android.permission.CHANGE_OVERLAY_PACKAGES"/>
+ <!-- For showing the current active activity -->
+ <permission name="android.permission.REAL_GET_TASKS"/>
+ <!-- For getting list of RROs for current user -->
+ <permission name="android.permission.MANAGE_USERS"/>
+ <!-- For getting list of RROs for current user-->
+ <permission name="android.permission.INTERACT_ACROSS_USERS"/>
+ </privapp-permissions>
+</permissions>
diff --git a/data/etc/com.android.settings.xml b/data/etc/com.android.settings.xml
index ee989cc..70b61e0 100644
--- a/data/etc/com.android.settings.xml
+++ b/data/etc/com.android.settings.xml
@@ -42,8 +42,8 @@
<permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/>
<permission name="android.permission.READ_SEARCH_INDEXABLES"/>
<permission name="android.permission.REBOOT"/>
- <permission name="android.permission.SET_TIME"/>
<permission name="android.permission.STATUS_BAR"/>
+ <permission name="android.permission.SUGGEST_MANUAL_TIME_AND_ZONE"/>
<permission name="android.permission.TETHER_PRIVILEGED"/>
<permission name="android.permission.USE_RESERVED_DISK"/>
<permission name="android.permission.USER_ACTIVITY"/>
diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml
index 1d735af..40de83a 100644
--- a/data/etc/com.android.systemui.xml
+++ b/data/etc/com.android.systemui.xml
@@ -22,7 +22,6 @@
<permission name="android.permission.CHANGE_COMPONENT_ENABLED_STATE"/>
<permission name="android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"/>
<permission name="android.permission.CHANGE_OVERLAY_PACKAGES"/>
- <permission name="android.permission.CONNECTIVITY_INTERNAL"/>
<permission name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS"/>
<permission name="android.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS"/>
<permission name="android.permission.CONTROL_VPN"/>
@@ -38,6 +37,7 @@
<permission name="android.permission.MODIFY_DAY_NIGHT_MODE"/>
<permission name="android.permission.MODIFY_PHONE_STATE"/>
<permission name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
+ <permission name="android.permission.OBSERVE_NETWORK_POLICY"/>
<permission name="android.permission.OVERRIDE_WIFI_CONFIG"/>
<permission name="android.permission.READ_DREAM_STATE"/>
<permission name="android.permission.READ_FRAME_BUFFER"/>
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 0574775..877ef26 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -180,7 +180,6 @@
<assign-permission name="android.permission.ACCESS_LOWPAN_STATE" uid="lowpan" />
<assign-permission name="android.permission.MANAGE_LOWPAN_INTERFACES" uid="lowpan" />
- <assign-permission name="android.permission.BATTERY_STATS" uid="statsd" />
<assign-permission name="android.permission.DUMP" uid="statsd" />
<assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="statsd" />
<assign-permission name="android.permission.STATSCOMPANION" uid="statsd" />
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 38a9f46..ad99ab3 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -160,12 +160,12 @@
<permission name="android.permission.REGISTER_CALL_PROVIDER"/>
<permission name="android.permission.REGISTER_SIM_SUBSCRIPTION"/>
<permission name="android.permission.SEND_RESPOND_VIA_MESSAGE"/>
- <permission name="android.permission.SET_TIME"/>
<permission name="android.permission.SET_TIME_ZONE"/>
<permission name="android.permission.SHUTDOWN"/>
<permission name="android.permission.START_ACTIVITIES_FROM_BACKGROUND"/>
<permission name="android.permission.STATUS_BAR"/>
<permission name="android.permission.STOP_APP_SWITCHES"/>
+ <permission name="android.permission.SUGGEST_PHONE_TIME_AND_ZONE"/>
<permission name="android.permission.UPDATE_APP_OPS_STATS"/>
<permission name="android.permission.UPDATE_DEVICE_STATS"/>
<permission name="android.permission.UPDATE_LOCK"/>
@@ -243,7 +243,9 @@
<permission name="android.permission.MANAGE_USB"/>
<permission name="android.permission.MODIFY_PHONE_STATE"/>
<permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/>
+ <permission name="android.permission.TETHER_PRIVILEGED"/>
<permission name="android.permission.UPDATE_APP_OPS_STATS"/>
+ <permission name="android.permission.UPDATE_DEVICE_STATS"/>
</privapp-permissions>
<privapp-permissions package="com.android.server.telecom">
diff --git a/docs/html/reference/images/camera2/metadata/android.scaler.cropRegion/crop-region-11-ratio.png b/docs/html/reference/images/camera2/metadata/android.scaler.cropRegion/crop-region-11-ratio.png
new file mode 100644
index 0000000..b6c0765
--- /dev/null
+++ b/docs/html/reference/images/camera2/metadata/android.scaler.cropRegion/crop-region-11-ratio.png
Binary files differ
diff --git a/docs/html/reference/images/camera2/metadata/android.scaler.cropRegion/crop-region-169-ratio.png b/docs/html/reference/images/camera2/metadata/android.scaler.cropRegion/crop-region-169-ratio.png
new file mode 100644
index 0000000..4e975e5
--- /dev/null
+++ b/docs/html/reference/images/camera2/metadata/android.scaler.cropRegion/crop-region-169-ratio.png
Binary files differ
diff --git a/docs/html/reference/images/camera2/metadata/android.scaler.cropRegion/crop-region-43-ratio.png b/docs/html/reference/images/camera2/metadata/android.scaler.cropRegion/crop-region-43-ratio.png
new file mode 100644
index 0000000..a331095
--- /dev/null
+++ b/docs/html/reference/images/camera2/metadata/android.scaler.cropRegion/crop-region-43-ratio.png
Binary files differ
diff --git a/docs/html/reference/images/camera2/metadata/android.scaler.cropRegion/crop-region-43-square-ratio.png b/docs/html/reference/images/camera2/metadata/android.scaler.cropRegion/crop-region-43-square-ratio.png
new file mode 100644
index 0000000..41e6668
--- /dev/null
+++ b/docs/html/reference/images/camera2/metadata/android.scaler.cropRegion/crop-region-43-square-ratio.png
Binary files differ
diff --git a/graphics/java/android/graphics/BaseCanvas.java b/graphics/java/android/graphics/BaseCanvas.java
index 6e7f286..bee8d5e 100644
--- a/graphics/java/android/graphics/BaseCanvas.java
+++ b/graphics/java/android/graphics/BaseCanvas.java
@@ -21,7 +21,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Canvas.VertexMode;
import android.graphics.text.MeasuredText;
import android.text.GraphicsOperations;
diff --git a/graphics/java/android/graphics/Bitmap.java b/graphics/java/android/graphics/Bitmap.java
index d900a42..ac094ba 100644
--- a/graphics/java/android/graphics/Bitmap.java
+++ b/graphics/java/android/graphics/Bitmap.java
@@ -21,8 +21,8 @@
import android.annotation.ColorLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
import android.annotation.WorkerThread;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.ResourcesImpl;
import android.hardware.HardwareBuffer;
import android.os.Build;
diff --git a/graphics/java/android/graphics/BitmapFactory.java b/graphics/java/android/graphics/BitmapFactory.java
index 5623a8a..bad487b 100644
--- a/graphics/java/android/graphics/BitmapFactory.java
+++ b/graphics/java/android/graphics/BitmapFactory.java
@@ -20,7 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.os.Trace;
diff --git a/graphics/java/android/graphics/BitmapRegionDecoder.java b/graphics/java/android/graphics/BitmapRegionDecoder.java
index 629d8c1..34eba97 100644
--- a/graphics/java/android/graphics/BitmapRegionDecoder.java
+++ b/graphics/java/android/graphics/BitmapRegionDecoder.java
@@ -15,7 +15,7 @@
package android.graphics;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.AssetManager;
import android.os.Build;
diff --git a/graphics/java/android/graphics/BitmapShader.java b/graphics/java/android/graphics/BitmapShader.java
index 198d1e7..edf53c4 100644
--- a/graphics/java/android/graphics/BitmapShader.java
+++ b/graphics/java/android/graphics/BitmapShader.java
@@ -17,7 +17,7 @@
package android.graphics;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* Shader used to draw a bitmap as a texture. The bitmap can be repeated or
diff --git a/graphics/java/android/graphics/Camera.java b/graphics/java/android/graphics/Camera.java
index cbd4ead..80a3740 100644
--- a/graphics/java/android/graphics/Camera.java
+++ b/graphics/java/android/graphics/Camera.java
@@ -16,7 +16,7 @@
package android.graphics;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* A camera instance can be used to compute 3D transformations and
diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java
index a815f20..9a0ca3e 100644
--- a/graphics/java/android/graphics/Canvas.java
+++ b/graphics/java/android/graphics/Canvas.java
@@ -22,7 +22,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.text.MeasuredText;
import android.os.Build;
diff --git a/graphics/java/android/graphics/CanvasProperty.java b/graphics/java/android/graphics/CanvasProperty.java
index 1275e08..4263772c 100644
--- a/graphics/java/android/graphics/CanvasProperty.java
+++ b/graphics/java/android/graphics/CanvasProperty.java
@@ -16,7 +16,8 @@
package android.graphics;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
import com.android.internal.util.VirtualRefBasePtr;
/**
diff --git a/graphics/java/android/graphics/ColorMatrixColorFilter.java b/graphics/java/android/graphics/ColorMatrixColorFilter.java
index 0f7980c..a8b18a9 100644
--- a/graphics/java/android/graphics/ColorMatrixColorFilter.java
+++ b/graphics/java/android/graphics/ColorMatrixColorFilter.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* A color filter that transforms colors through a 4x5 color matrix. This filter
diff --git a/graphics/java/android/graphics/FontFamily.java b/graphics/java/android/graphics/FontFamily.java
index 5ad93f4..447f043 100644
--- a/graphics/java/android/graphics/FontFamily.java
+++ b/graphics/java/android/graphics/FontFamily.java
@@ -17,9 +17,10 @@
package android.graphics;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.AssetManager;
import android.graphics.fonts.FontVariationAxis;
+import android.os.Build;
import android.text.TextUtils;
import dalvik.annotation.optimization.CriticalNative;
@@ -58,7 +59,8 @@
*
* This cannot be deleted because it's in use by AndroidX.
*/
- @UnsupportedAppUsage(trackingBug = 123768928)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+ publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
public long mNativePtr;
// Points native font family builder. Must be zero after freezing this family.
@@ -67,7 +69,8 @@
/**
* This cannot be deleted because it's in use by AndroidX.
*/
- @UnsupportedAppUsage(trackingBug = 123768928)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+ publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
public FontFamily() {
mBuilderPtr = nInitBuilder(null, 0);
mNativeBuilderCleaner = sBuilderRegistry.registerNativeAllocation(this, mBuilderPtr);
@@ -76,7 +79,8 @@
/**
* This cannot be deleted because it's in use by AndroidX.
*/
- @UnsupportedAppUsage(trackingBug = 123768928)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+ publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
public FontFamily(@Nullable String[] langs, int variant) {
final String langsString;
if (langs == null || langs.length == 0) {
@@ -98,7 +102,8 @@
*
* This cannot be deleted because it's in use by AndroidX.
*/
- @UnsupportedAppUsage(trackingBug = 123768928)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+ publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
public boolean freeze() {
if (mBuilderPtr == 0) {
throw new IllegalStateException("This FontFamily is already frozen");
@@ -115,7 +120,8 @@
/**
* This cannot be deleted because it's in use by AndroidX.
*/
- @UnsupportedAppUsage(trackingBug = 123768928)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+ publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
public void abortCreation() {
if (mBuilderPtr == 0) {
throw new IllegalStateException("This FontFamily is already frozen or abandoned");
@@ -127,7 +133,8 @@
/**
* This cannot be deleted because it's in use by AndroidX.
*/
- @UnsupportedAppUsage(trackingBug = 123768928)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+ publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
public boolean addFont(String path, int ttcIndex, FontVariationAxis[] axes, int weight,
int italic) {
if (mBuilderPtr == 0) {
@@ -151,7 +158,8 @@
/**
* This cannot be deleted because it's in use by AndroidX.
*/
- @UnsupportedAppUsage(trackingBug = 123768928)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+ publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
public boolean addFontFromBuffer(ByteBuffer font, int ttcIndex, FontVariationAxis[] axes,
int weight, int italic) {
if (mBuilderPtr == 0) {
@@ -179,7 +187,8 @@
*
* This cannot be deleted because it's in use by AndroidX.
*/
- @UnsupportedAppUsage(trackingBug = 123768928)
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.Q,
+ publicAlternatives = "Use {@link android.graphics.fonts.FontFamily} instead.")
public boolean addFontFromAssetManager(AssetManager mgr, String path, int cookie,
boolean isAsset, int ttcIndex, int weight, int isItalic,
FontVariationAxis[] axes) {
diff --git a/graphics/java/android/graphics/FontListParser.java b/graphics/java/android/graphics/FontListParser.java
index 21cc375..c146bbd 100644
--- a/graphics/java/android/graphics/FontListParser.java
+++ b/graphics/java/android/graphics/FontListParser.java
@@ -16,7 +16,7 @@
package android.graphics;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.fonts.FontVariationAxis;
import android.text.FontConfig;
import android.util.Xml;
diff --git a/graphics/java/android/graphics/GraphicBuffer.java b/graphics/java/android/graphics/GraphicBuffer.java
index 3b1fc70..99fa5ee 100644
--- a/graphics/java/android/graphics/GraphicBuffer.java
+++ b/graphics/java/android/graphics/GraphicBuffer.java
@@ -16,7 +16,7 @@
package android.graphics;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index aecef8e..83432c3 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -28,8 +28,8 @@
import android.annotation.Nullable;
import android.annotation.Px;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.annotation.WorkerThread;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ContentResolver;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
@@ -277,6 +277,10 @@
assetFd = mResolver.openAssetFileDescriptor(mUri, "r");
}
} catch (FileNotFoundException e) {
+ // Handled below, along with the case where assetFd was set to null.
+ }
+
+ if (assetFd == null) {
// Some images cannot be opened as AssetFileDescriptors (e.g.
// bmp, ico). Open them as InputStreams.
InputStream is = mResolver.openInputStream(mUri);
@@ -286,9 +290,7 @@
return createFromStream(is, true, preferAnimation, this);
}
- if (assetFd == null) {
- throw new FileNotFoundException(mUri.toString());
- }
+
return createFromAssetFileDescriptor(assetFd, preferAnimation, this);
}
}
diff --git a/graphics/java/android/graphics/LightingColorFilter.java b/graphics/java/android/graphics/LightingColorFilter.java
index 62a890f..221dfa1 100644
--- a/graphics/java/android/graphics/LightingColorFilter.java
+++ b/graphics/java/android/graphics/LightingColorFilter.java
@@ -22,7 +22,7 @@
package android.graphics;
import android.annotation.ColorInt;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* A color filter that can be used to simulate simple lighting effects.
diff --git a/graphics/java/android/graphics/LinearGradient.java b/graphics/java/android/graphics/LinearGradient.java
index 12e63c0..3f3ad96 100644
--- a/graphics/java/android/graphics/LinearGradient.java
+++ b/graphics/java/android/graphics/LinearGradient.java
@@ -20,7 +20,7 @@
import android.annotation.ColorLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
public class LinearGradient extends Shader {
diff --git a/graphics/java/android/graphics/Matrix.java b/graphics/java/android/graphics/Matrix.java
index 22b6401..cf914c2 100644
--- a/graphics/java/android/graphics/Matrix.java
+++ b/graphics/java/android/graphics/Matrix.java
@@ -16,7 +16,7 @@
package android.graphics;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
diff --git a/graphics/java/android/graphics/Movie.java b/graphics/java/android/graphics/Movie.java
index 6f030ff..4b3924f 100644
--- a/graphics/java/android/graphics/Movie.java
+++ b/graphics/java/android/graphics/Movie.java
@@ -16,7 +16,7 @@
package android.graphics;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.AssetManager;
import android.os.Build;
diff --git a/graphics/java/android/graphics/NinePatch.java b/graphics/java/android/graphics/NinePatch.java
index c4c1eac..ff32393 100644
--- a/graphics/java/android/graphics/NinePatch.java
+++ b/graphics/java/android/graphics/NinePatch.java
@@ -16,7 +16,7 @@
package android.graphics;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* The NinePatch class permits drawing a bitmap in nine or more sections.
diff --git a/graphics/java/android/graphics/Outline.java b/graphics/java/android/graphics/Outline.java
index 1fc056c..91a60c3 100644
--- a/graphics/java/android/graphics/Outline.java
+++ b/graphics/java/android/graphics/Outline.java
@@ -19,7 +19,7 @@
import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.drawable.Drawable;
import java.lang.annotation.Retention;
diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java
index 109d863..3b58624 100644
--- a/graphics/java/android/graphics/Paint.java
+++ b/graphics/java/android/graphics/Paint.java
@@ -24,7 +24,7 @@
import android.annotation.Nullable;
import android.annotation.Px;
import android.annotation.Size;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.fonts.FontVariationAxis;
import android.os.Build;
import android.os.LocaleList;
diff --git a/graphics/java/android/graphics/Path.java b/graphics/java/android/graphics/Path.java
index 7282d52..1362fd8 100644
--- a/graphics/java/android/graphics/Path.java
+++ b/graphics/java/android/graphics/Path.java
@@ -20,7 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Size;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import dalvik.annotation.optimization.CriticalNative;
import dalvik.annotation.optimization.FastNative;
diff --git a/graphics/java/android/graphics/Picture.java b/graphics/java/android/graphics/Picture.java
index 8d12cbf..390d3d4 100644
--- a/graphics/java/android/graphics/Picture.java
+++ b/graphics/java/android/graphics/Picture.java
@@ -17,7 +17,7 @@
package android.graphics;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import java.io.InputStream;
import java.io.OutputStream;
diff --git a/graphics/java/android/graphics/PorterDuff.java b/graphics/java/android/graphics/PorterDuff.java
index bc1f66f..1275cb9 100644
--- a/graphics/java/android/graphics/PorterDuff.java
+++ b/graphics/java/android/graphics/PorterDuff.java
@@ -16,7 +16,7 @@
package android.graphics;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* <p>This class contains the list of alpha compositing and blending modes
diff --git a/graphics/java/android/graphics/PorterDuffColorFilter.java b/graphics/java/android/graphics/PorterDuffColorFilter.java
index cc2d3a8..50ecb62 100644
--- a/graphics/java/android/graphics/PorterDuffColorFilter.java
+++ b/graphics/java/android/graphics/PorterDuffColorFilter.java
@@ -18,7 +18,7 @@
import android.annotation.ColorInt;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* A color filter that can be used to tint the source pixels using a single
diff --git a/graphics/java/android/graphics/RadialGradient.java b/graphics/java/android/graphics/RadialGradient.java
index acbe3da..96b7b9a 100644
--- a/graphics/java/android/graphics/RadialGradient.java
+++ b/graphics/java/android/graphics/RadialGradient.java
@@ -20,7 +20,7 @@
import android.annotation.ColorLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
public class RadialGradient extends Shader {
@UnsupportedAppUsage
diff --git a/graphics/java/android/graphics/Rect.java b/graphics/java/android/graphics/Rect.java
index 9e1946c..081b851 100644
--- a/graphics/java/android/graphics/Rect.java
+++ b/graphics/java/android/graphics/Rect.java
@@ -19,7 +19,7 @@
import android.annotation.CheckResult;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
diff --git a/graphics/java/android/graphics/Region.java b/graphics/java/android/graphics/Region.java
index ec7f7a0..d8d9641 100644
--- a/graphics/java/android/graphics/Region.java
+++ b/graphics/java/android/graphics/Region.java
@@ -17,7 +17,7 @@
package android.graphics;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Pools.SynchronizedPool;
diff --git a/graphics/java/android/graphics/Shader.java b/graphics/java/android/graphics/Shader.java
index 3050d1d..5335aa4 100644
--- a/graphics/java/android/graphics/Shader.java
+++ b/graphics/java/android/graphics/Shader.java
@@ -20,7 +20,7 @@
import android.annotation.ColorLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import libcore.util.NativeAllocationRegistry;
diff --git a/graphics/java/android/graphics/SurfaceTexture.java b/graphics/java/android/graphics/SurfaceTexture.java
index 99f440d..697daa8 100644
--- a/graphics/java/android/graphics/SurfaceTexture.java
+++ b/graphics/java/android/graphics/SurfaceTexture.java
@@ -17,7 +17,7 @@
package android.graphics;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
diff --git a/graphics/java/android/graphics/SweepGradient.java b/graphics/java/android/graphics/SweepGradient.java
index 667f45a..0852004 100644
--- a/graphics/java/android/graphics/SweepGradient.java
+++ b/graphics/java/android/graphics/SweepGradient.java
@@ -20,7 +20,7 @@
import android.annotation.ColorLong;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
public class SweepGradient extends Shader {
@UnsupportedAppUsage
diff --git a/graphics/java/android/graphics/TableMaskFilter.java b/graphics/java/android/graphics/TableMaskFilter.java
index d81c491..204f970 100644
--- a/graphics/java/android/graphics/TableMaskFilter.java
+++ b/graphics/java/android/graphics/TableMaskFilter.java
@@ -16,7 +16,7 @@
package android.graphics;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* @hide
diff --git a/graphics/java/android/graphics/TemporaryBuffer.java b/graphics/java/android/graphics/TemporaryBuffer.java
index 0ae2c70..ef3f7f7 100644
--- a/graphics/java/android/graphics/TemporaryBuffer.java
+++ b/graphics/java/android/graphics/TemporaryBuffer.java
@@ -16,7 +16,8 @@
package android.graphics;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
import com.android.internal.util.ArrayUtils;
/**
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index 6d20ec3..a2dd9a8 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -25,7 +25,7 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.AssetManager;
import android.graphics.fonts.Font;
import android.graphics.fonts.FontFamily;
diff --git a/graphics/java/android/graphics/Xfermode.java b/graphics/java/android/graphics/Xfermode.java
index 6f4adfd..e79fb76 100644
--- a/graphics/java/android/graphics/Xfermode.java
+++ b/graphics/java/android/graphics/Xfermode.java
@@ -21,7 +21,7 @@
package android.graphics;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* Xfermode is the base class for objects that are called to implement custom
diff --git a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
index 82f5870..d894600 100644
--- a/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedImageDrawable.java
@@ -19,7 +19,7 @@
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
diff --git a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java
index b29fd4d..686f146 100644
--- a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java
@@ -18,23 +18,23 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Rect;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.content.res.Resources.Theme;
+import android.os.SystemClock;
import android.util.AttributeSet;
import android.util.TypedValue;
-import android.os.SystemClock;
+
+import com.android.internal.R;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
-import com.android.internal.R;
-
/**
* @hide
*/
diff --git a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
index 11a46c4..06159d8 100644
--- a/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedStateListDrawable.java
@@ -20,7 +20,7 @@
import android.animation.TimeInterpolator;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
diff --git a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
index 66947da..1acf6c5 100644
--- a/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimatedVectorDrawable.java
@@ -25,9 +25,9 @@
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityThread;
import android.app.Application;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo.Config;
import android.content.res.ColorStateList;
import android.content.res.Resources;
diff --git a/graphics/java/android/graphics/drawable/AnimationDrawable.java b/graphics/java/android/graphics/drawable/AnimationDrawable.java
index 57764c2..8c3fa44 100644
--- a/graphics/java/android/graphics/drawable/AnimationDrawable.java
+++ b/graphics/java/android/graphics/drawable/AnimationDrawable.java
@@ -16,20 +16,20 @@
package android.graphics.drawable;
-import com.android.internal.R;
+import android.annotation.NonNull;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.os.SystemClock;
+import android.util.AttributeSet;
-import java.io.IOException;
+import com.android.internal.R;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
-import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.content.res.Resources.Theme;
-import android.os.SystemClock;
-import android.util.AttributeSet;
+import java.io.IOException;
/**
* An object used to create frame-by-frame animations, defined by a series of
diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java
index e4aa774..4e768c9 100644
--- a/graphics/java/android/graphics/drawable/BitmapDrawable.java
+++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java
@@ -17,7 +17,7 @@
package android.graphics.drawable;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo.Config;
import android.content.res.ColorStateList;
import android.content.res.Resources;
diff --git a/graphics/java/android/graphics/drawable/ClipDrawable.java b/graphics/java/android/graphics/drawable/ClipDrawable.java
index 31fdb02..69ed9b4 100644
--- a/graphics/java/android/graphics/drawable/ClipDrawable.java
+++ b/graphics/java/android/graphics/drawable/ClipDrawable.java
@@ -16,21 +16,23 @@
package android.graphics.drawable;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.Gravity;
+
import com.android.internal.R;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.content.res.Resources.Theme;
-import android.graphics.*;
-import android.view.Gravity;
-import android.util.AttributeSet;
-
import java.io.IOException;
/**
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
index 17aacb9..fedde42 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyGeneratorSpi.java
@@ -308,6 +308,9 @@
if (spec.isStrongBoxBacked()) {
flags |= KeyStore.FLAG_STRONGBOX;
}
+ if (spec.isCriticalToDeviceEncryption()) {
+ flags |= KeyStore.FLAG_CRITICAL_TO_DEVICE_ENCRYPTION;
+ }
String keyAliasInKeystore = Credentials.USER_PRIVATE_KEY + spec.getKeystoreAlias();
KeyCharacteristics resultingKeyCharacteristics = new KeyCharacteristics();
boolean success = false;
diff --git a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
index 91aac83..c52fd48 100644
--- a/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
+++ b/keystore/java/android/security/keystore/AndroidKeyStoreKeyPairGeneratorSpi.java
@@ -18,10 +18,8 @@
import android.annotation.Nullable;
import android.security.Credentials;
-import android.security.GateKeeper;
import android.security.KeyPairGeneratorSpec;
import android.security.KeyStore;
-import android.security.KeyStoreException;
import android.security.keymaster.KeyCharacteristics;
import android.security.keymaster.KeymasterArguments;
import android.security.keymaster.KeymasterCertificateChain;
@@ -458,6 +456,9 @@
if (mSpec.isStrongBoxBacked()) {
flags |= KeyStore.FLAG_STRONGBOX;
}
+ if (mSpec.isCriticalToDeviceEncryption()) {
+ flags |= KeyStore.FLAG_CRITICAL_TO_DEVICE_ENCRYPTION;
+ }
byte[] additionalEntropy =
KeyStoreCryptoOperationUtils.getRandomBytesToMixIntoKeystoreRng(
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index 52ff9e0..450dd33 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -271,6 +271,7 @@
private final boolean mIsStrongBoxBacked;
private final boolean mUserConfirmationRequired;
private final boolean mUnlockedDeviceRequired;
+ private final boolean mCriticalToDeviceEncryption;
/*
* ***NOTE***: All new fields MUST also be added to the following:
* ParcelableKeyGenParameterSpec class.
@@ -307,7 +308,8 @@
boolean invalidatedByBiometricEnrollment,
boolean isStrongBoxBacked,
boolean userConfirmationRequired,
- boolean unlockedDeviceRequired) {
+ boolean unlockedDeviceRequired,
+ boolean criticalToDeviceEncryption) {
if (TextUtils.isEmpty(keyStoreAlias)) {
throw new IllegalArgumentException("keyStoreAlias must not be empty");
}
@@ -357,6 +359,7 @@
mIsStrongBoxBacked = isStrongBoxBacked;
mUserConfirmationRequired = userConfirmationRequired;
mUnlockedDeviceRequired = unlockedDeviceRequired;
+ mCriticalToDeviceEncryption = criticalToDeviceEncryption;
}
/**
@@ -710,6 +713,16 @@
}
/**
+ * Return whether this key is critical to the device encryption flow.
+ *
+ * @see android.security.KeyStore#FLAG_CRITICAL_TO_DEVICE_ENCRYPTION
+ * @hide
+ */
+ public boolean isCriticalToDeviceEncryption() {
+ return mCriticalToDeviceEncryption;
+ }
+
+ /**
* Builder of {@link KeyGenParameterSpec} instances.
*/
public final static class Builder {
@@ -741,6 +754,7 @@
private boolean mIsStrongBoxBacked = false;
private boolean mUserConfirmationRequired;
private boolean mUnlockedDeviceRequired = false;
+ private boolean mCriticalToDeviceEncryption = false;
/**
* Creates a new instance of the {@code Builder}.
@@ -804,6 +818,7 @@
mIsStrongBoxBacked = sourceSpec.isStrongBoxBacked();
mUserConfirmationRequired = sourceSpec.isUserConfirmationRequired();
mUnlockedDeviceRequired = sourceSpec.isUnlockedDeviceRequired();
+ mCriticalToDeviceEncryption = sourceSpec.isCriticalToDeviceEncryption();
}
/**
@@ -1339,6 +1354,20 @@
}
/**
+ * Set whether this key is critical to the device encryption flow
+ *
+ * This is a special flag only available to system servers to indicate the current key
+ * is part of the device encryption flow.
+ *
+ * @see android.security.KeyStore#FLAG_CRITICAL_TO_DEVICE_ENCRYPTION
+ * @hide
+ */
+ public Builder setCriticalToDeviceEncryption(boolean critical) {
+ mCriticalToDeviceEncryption = critical;
+ return this;
+ }
+
+ /**
* Builds an instance of {@code KeyGenParameterSpec}.
*/
@NonNull
@@ -1370,7 +1399,8 @@
mInvalidatedByBiometricEnrollment,
mIsStrongBoxBacked,
mUserConfirmationRequired,
- mUnlockedDeviceRequired);
+ mUnlockedDeviceRequired,
+ mCriticalToDeviceEncryption);
}
}
}
diff --git a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
index d8030fb..98e4589 100644
--- a/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/ParcelableKeyGenParameterSpec.java
@@ -16,8 +16,8 @@
package android.security.keystore;
-import android.os.Parcelable;
import android.os.Parcel;
+import android.os.Parcelable;
import java.math.BigInteger;
import java.security.spec.AlgorithmParameterSpec;
@@ -105,6 +105,7 @@
out.writeBoolean(mSpec.isStrongBoxBacked());
out.writeBoolean(mSpec.isUserConfirmationRequired());
out.writeBoolean(mSpec.isUnlockedDeviceRequired());
+ out.writeBoolean(mSpec.isCriticalToDeviceEncryption());
}
private static Date readDateOrNull(Parcel in) {
@@ -160,6 +161,7 @@
final boolean isStrongBoxBacked = in.readBoolean();
final boolean userConfirmationRequired = in.readBoolean();
final boolean unlockedDeviceRequired = in.readBoolean();
+ final boolean criticalToDeviceEncryption = in.readBoolean();
// The KeyGenParameterSpec is intentionally not constructed using a Builder here:
// The intention is for this class to break if new parameters are added to the
// KeyGenParameterSpec constructor (whereas using a builder would silently drop them).
@@ -190,7 +192,8 @@
invalidatedByBiometricEnrollment,
isStrongBoxBacked,
userConfirmationRequired,
- unlockedDeviceRequired);
+ unlockedDeviceRequired,
+ criticalToDeviceEncryption);
}
public static final @android.annotation.NonNull Creator<ParcelableKeyGenParameterSpec> CREATOR = new Creator<ParcelableKeyGenParameterSpec>() {
diff --git a/keystore/tests/src/android/security/ParcelableKeyGenParameterSpecTest.java b/keystore/tests/src/android/security/ParcelableKeyGenParameterSpecTest.java
index fca2775..b7d72fc 100644
--- a/keystore/tests/src/android/security/ParcelableKeyGenParameterSpecTest.java
+++ b/keystore/tests/src/android/security/ParcelableKeyGenParameterSpecTest.java
@@ -84,6 +84,7 @@
.setIsStrongBoxBacked(true)
.setUserConfirmationRequired(true)
.setUnlockedDeviceRequired(true)
+ .setCriticalToDeviceEncryption(true)
.build();
}
@@ -115,6 +116,7 @@
assertThat(spec.isStrongBoxBacked(), is(true));
assertThat(spec.isUserConfirmationRequired(), is(true));
assertThat(spec.isUnlockedDeviceRequired(), is(true));
+ assertThat(spec.isCriticalToDeviceEncryption(), is(true));
}
private Parcel parcelForReading(ParcelableKeyGenParameterSpec spec) {
diff --git a/libs/androidfw/Android.bp b/libs/androidfw/Android.bp
index 8765719..3f2f349 100644
--- a/libs/androidfw/Android.bp
+++ b/libs/androidfw/Android.bp
@@ -44,6 +44,7 @@
"AttributeResolution.cpp",
"ChunkIterator.cpp",
"ConfigDescription.cpp",
+ "DynamicLibManager.cpp",
"Idmap.cpp",
"LoadedArsc.cpp",
"Locale.cpp",
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index ca4143f..8cfd2d8 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -25,6 +25,7 @@
#include "android-base/logging.h"
#include "android-base/stringprintf.h"
+#include "androidfw/DynamicLibManager.h"
#include "androidfw/ResourceUtils.h"
#include "androidfw/Util.h"
#include "utils/ByteOrder.h"
@@ -66,7 +67,12 @@
StringPoolRef entry_string_ref;
};
-AssetManager2::AssetManager2() {
+AssetManager2::AssetManager2() : dynamic_lib_manager_(std::make_unique<DynamicLibManager>()) {
+ memset(&configuration_, 0, sizeof(configuration_));
+}
+
+AssetManager2::AssetManager2(DynamicLibManager* dynamic_lib_manager)
+ : dynamic_lib_manager_(dynamic_lib_manager) {
memset(&configuration_, 0, sizeof(configuration_));
}
@@ -85,25 +91,45 @@
package_groups_.clear();
package_ids_.fill(0xff);
- // A mapping from apk assets path to the runtime package id of its first loaded package.
+ // Overlay resources are not directly referenced by an application so their resource ids
+ // can change throughout the application's lifetime. Assign overlay package ids last.
+ std::vector<const ApkAssets*> sorted_apk_assets(apk_assets_);
+ std::stable_partition(sorted_apk_assets.begin(), sorted_apk_assets.end(), [](const ApkAssets* a) {
+ return !a->IsOverlay();
+ });
+
std::unordered_map<std::string, uint8_t> apk_assets_package_ids;
+ std::unordered_map<std::string, uint8_t> package_name_package_ids;
- // 0x01 is reserved for the android package.
- int next_package_id = 0x02;
- const size_t apk_assets_count = apk_assets_.size();
- for (size_t i = 0; i < apk_assets_count; i++) {
- const ApkAssets* apk_assets = apk_assets_[i];
- const LoadedArsc* loaded_arsc = apk_assets->GetLoadedArsc();
-
- for (const std::unique_ptr<const LoadedPackage>& package : loaded_arsc->GetPackages()) {
- // Get the package ID or assign one if a shared library.
- int package_id;
- if (package->IsDynamic()) {
- package_id = next_package_id++;
- } else {
- package_id = package->GetPackageId();
+ // Assign stable package ids to application packages.
+ uint8_t next_available_package_id = 0U;
+ for (const auto& apk_assets : sorted_apk_assets) {
+ for (const auto& package : apk_assets->GetLoadedArsc()->GetPackages()) {
+ uint8_t package_id = package->GetPackageId();
+ if (package->IsOverlay()) {
+ package_id = GetDynamicLibManager()->FindUnassignedId(next_available_package_id);
+ next_available_package_id = package_id + 1;
+ } else if (package->IsDynamic()) {
+ package_id = GetDynamicLibManager()->GetAssignedId(package->GetPackageName());
}
+ // Map the path of the apk assets to the package id of its first loaded package.
+ apk_assets_package_ids[apk_assets->GetPath()] = package_id;
+
+ // Map the package name of the package to the first loaded package with that package id.
+ package_name_package_ids[package->GetPackageName()] = package_id;
+ }
+ }
+
+ const int apk_assets_count = apk_assets_.size();
+ for (int i = 0; i < apk_assets_count; i++) {
+ const auto& apk_assets = apk_assets_[i];
+ for (const auto& package : apk_assets->GetLoadedArsc()->GetPackages()) {
+ const auto package_id_entry = package_name_package_ids.find(package->GetPackageName());
+ CHECK(package_id_entry != package_name_package_ids.end())
+ << "no package id assgined to package " << package->GetPackageName();
+ const uint8_t package_id = package_id_entry->second;
+
// Add the mapping for package ID to index if not present.
uint8_t idx = package_ids_[package_id];
if (idx == 0xff) {
@@ -115,7 +141,10 @@
// to take effect.
const auto& loaded_idmap = apk_assets->GetLoadedIdmap();
auto target_package_iter = apk_assets_package_ids.find(loaded_idmap->TargetApkPath());
- if (target_package_iter != apk_assets_package_ids.end()) {
+ if (target_package_iter == apk_assets_package_ids.end()) {
+ LOG(INFO) << "failed to find target package for overlay "
+ << loaded_idmap->OverlayApkPath();
+ } else {
const uint8_t target_package_id = target_package_iter->second;
const uint8_t target_idx = package_ids_[target_package_id];
CHECK(target_idx != 0xff) << "overlay added to apk_assets_package_ids but does not"
@@ -123,7 +152,7 @@
PackageGroup& target_package_group = package_groups_[target_idx];
- // Create a special dynamic reference table for the overlay to rewite references to
+ // Create a special dynamic reference table for the overlay to rewrite references to
// overlay resources as references to the target resources they overlay.
auto overlay_table = std::make_shared<OverlayDynamicRefTable>(
loaded_idmap->GetOverlayDynamicRefTable(target_package_id));
@@ -153,8 +182,6 @@
package_group->dynamic_ref_table->mEntries.replaceValueFor(
package_name, static_cast<uint8_t>(entry.package_id));
}
-
- apk_assets_package_ids.insert(std::make_pair(apk_assets->GetPath(), package_id));
}
}
@@ -567,7 +594,7 @@
if (resource_resolution_logging_enabled_) {
last_resolution_.steps.push_back(
Resolution::Step{Resolution::Step::Type::OVERLAID, overlay_result.config.toString(),
- &package_group.packages_[0].loaded_package_->GetPackageName()});
+ overlay_result.package_name});
}
}
}
@@ -1279,6 +1306,16 @@
return 0;
}
+DynamicLibManager* AssetManager2::GetDynamicLibManager() const {
+ auto dynamic_lib_manager =
+ std::get_if<std::unique_ptr<DynamicLibManager>>(&dynamic_lib_manager_);
+ if (dynamic_lib_manager) {
+ return (*dynamic_lib_manager).get();
+ } else {
+ return *std::get_if<DynamicLibManager*>(&dynamic_lib_manager_);
+ }
+}
+
std::unique_ptr<Theme> AssetManager2::NewTheme() {
return std::unique_ptr<Theme>(new Theme(this));
}
diff --git a/libs/androidfw/DynamicLibManager.cpp b/libs/androidfw/DynamicLibManager.cpp
new file mode 100644
index 0000000..895b769
--- /dev/null
+++ b/libs/androidfw/DynamicLibManager.cpp
@@ -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.
+ */
+
+#include "androidfw/DynamicLibManager.h"
+
+namespace android {
+
+uint8_t DynamicLibManager::GetAssignedId(const std::string& library_package_name) {
+ auto lib_entry = shared_lib_package_ids_.find(library_package_name);
+ if (lib_entry != shared_lib_package_ids_.end()) {
+ return lib_entry->second;
+ }
+
+ return shared_lib_package_ids_[library_package_name] = next_package_id_++;
+}
+
+uint8_t DynamicLibManager::FindUnassignedId(uint8_t start_package_id) {
+ return (start_package_id < next_package_id_) ? next_package_id_ : start_package_id;
+}
+
+} // namespace android
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index 00cbbca..b2cec2a 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -27,6 +27,7 @@
#include "androidfw/ApkAssets.h"
#include "androidfw/Asset.h"
#include "androidfw/AssetManager.h"
+#include "androidfw/DynamicLibManager.h"
#include "androidfw/ResourceTypes.h"
#include "androidfw/Util.h"
@@ -94,6 +95,7 @@
};
AssetManager2();
+ explicit AssetManager2(DynamicLibManager* dynamic_lib_manager);
// Sets/resets the underlying ApkAssets for this AssetManager. The ApkAssets
// are not owned by the AssetManager, and must have a longer lifetime.
@@ -371,6 +373,8 @@
// Retrieve the assigned package id of the package if loaded into this AssetManager
uint8_t GetAssignedPackageId(const LoadedPackage* package) const;
+ DynamicLibManager* GetDynamicLibManager() const;
+
// The ordered list of ApkAssets to search. These are not owned by the AssetManager, and must
// have a longer lifetime.
std::vector<const ApkAssets*> apk_assets_;
@@ -389,6 +393,9 @@
// may need to be purged.
ResTable_config configuration_;
+ // Component responsible for assigning package ids to shared libraries.
+ std::variant<std::unique_ptr<DynamicLibManager>, DynamicLibManager*> dynamic_lib_manager_;
+
// Cached set of bags. These are cached because they can inherit keys from parent bags,
// which involves some calculation.
std::unordered_map<uint32_t, util::unique_cptr<ResolvedBag>> cached_bags_;
diff --git a/libs/androidfw/include/androidfw/DynamicLibManager.h b/libs/androidfw/include/androidfw/DynamicLibManager.h
new file mode 100644
index 0000000..1ff7079
--- /dev/null
+++ b/libs/androidfw/include/androidfw/DynamicLibManager.h
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROIDFW_DYNAMICLIBMANAGER_H
+#define ANDROIDFW_DYNAMICLIBMANAGER_H
+
+#include <string>
+#include <unordered_map>
+
+#include "android-base/macros.h"
+
+namespace android {
+
+// Manages assigning resource ids for dynamic resources.
+class DynamicLibManager {
+ public:
+ DynamicLibManager() = default;
+
+ // Retrieves the assigned package id for the library.
+ uint8_t GetAssignedId(const std::string& library_package_name);
+
+ // Queries in ascending order for the first available package id that is not currently assigned to
+ // a library.
+ uint8_t FindUnassignedId(uint8_t start_package_id);
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DynamicLibManager);
+
+ uint8_t next_package_id_ = 0x02;
+ std::unordered_map<std::string, uint8_t> shared_lib_package_ids_;
+};
+
+} // namespace android
+
+#endif //ANDROIDFW_DYNAMICLIBMANAGER_H
diff --git a/libs/androidfw/include/androidfw/MutexGuard.h b/libs/androidfw/include/androidfw/MutexGuard.h
index 64924f4..8891512 100644
--- a/libs/androidfw/include/androidfw/MutexGuard.h
+++ b/libs/androidfw/include/androidfw/MutexGuard.h
@@ -47,7 +47,8 @@
static_assert(!std::is_pointer<T>::value, "T must not be a raw pointer");
public:
- explicit Guarded() : guarded_() {
+ template <typename ...Args>
+ explicit Guarded(Args&& ...args) : guarded_(std::forward<Args>(args)...) {
}
template <typename U = T>
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index b3190be..2f6f3df 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -214,6 +214,25 @@
EXPECT_EQ(fix_package_id(appaslib::R::array::integerArray1, 0x02), value.data);
}
+TEST_F(AssetManager2Test, AssignsUnchangingPackageIdToSharedLibrary) {
+ DynamicLibManager lib_manager;
+ AssetManager2 assetmanager(&lib_manager);
+ assetmanager.SetApkAssets(
+ {lib_one_assets_.get(), lib_two_assets_.get(), libclient_assets_.get()});
+
+ AssetManager2 assetmanager2(&lib_manager);
+ assetmanager2.SetApkAssets(
+ {lib_two_assets_.get(), lib_one_assets_.get(), libclient_assets_.get()});
+
+ uint32_t res_id = assetmanager.GetResourceId("com.android.lib_one:string/foo");
+ ASSERT_NE(0U, res_id);
+
+ uint32_t res_id_2 = assetmanager2.GetResourceId("com.android.lib_one:string/foo");
+ ASSERT_NE(0U, res_id_2);
+
+ ASSERT_EQ(res_id, res_id_2);
+}
+
TEST_F(AssetManager2Test, GetSharedLibraryResourceName) {
AssetManager2 assetmanager;
assetmanager.SetApkAssets({lib_one_assets_.get()});
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index d945fc4..51270f5 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -25,11 +25,6 @@
// GCC false-positives on this warning, and since we -Werror that's
// a problem
"-Wno-free-nonheap-object",
-
- // Clang is producing non-determistic binary when the new pass manager is
- // enabled. Disable the new PM as a temporary workaround.
- // b/142372146
- "-fno-experimental-new-pass-manager",
],
include_dirs: [
@@ -215,6 +210,7 @@
android: {
srcs: [
+ "pipeline/skia/ATraceMemoryDump.cpp",
"pipeline/skia/GLFunctorDrawable.cpp",
"pipeline/skia/LayerDrawable.cpp",
"pipeline/skia/ShaderCache.cpp",
@@ -244,7 +240,6 @@
"DeviceInfo.cpp",
"FrameInfo.cpp",
"FrameInfoVisualizer.cpp",
- "GpuMemoryTracker.cpp",
"HardwareBitmapUploader.cpp",
"HWUIProperties.sysprop",
"JankTracker.cpp",
@@ -325,7 +320,6 @@
"tests/unit/DamageAccumulatorTests.cpp",
"tests/unit/DeferredLayerUpdaterTests.cpp",
"tests/unit/FatVectorTests.cpp",
- "tests/unit/GpuMemoryTrackerTests.cpp",
"tests/unit/GraphicsStatsServiceTests.cpp",
"tests/unit/LayerUpdateQueueTests.cpp",
"tests/unit/LinearAllocatorTests.cpp",
diff --git a/libs/hwui/DeviceInfo.cpp b/libs/hwui/DeviceInfo.cpp
index e53f3db..6d4a0c6 100644
--- a/libs/hwui/DeviceInfo.cpp
+++ b/libs/hwui/DeviceInfo.cpp
@@ -52,8 +52,8 @@
DeviceInfo::get()->mMaxTextureSize = maxTextureSize;
}
-void DeviceInfo::onDisplayConfigChanged() {
- updateDisplayInfo();
+void DeviceInfo::onRefreshRateChanged(int64_t vsyncPeriod) {
+ mVsyncPeriod = vsyncPeriod;
}
void DeviceInfo::updateDisplayInfo() {
@@ -113,10 +113,11 @@
ADisplay* primaryDisplay = mDisplays[mPhysicalDisplayIndex];
status_t status = ADisplay_getCurrentConfig(primaryDisplay, &mCurrentConfig);
LOG_ALWAYS_FATAL_IF(status, "Failed to get display config, error %d", status);
+
mWidth = ADisplayConfig_getWidth(mCurrentConfig);
mHeight = ADisplayConfig_getHeight(mCurrentConfig);
mDensity = ADisplayConfig_getDensity(mCurrentConfig);
- mRefreshRate = ADisplayConfig_getFps(mCurrentConfig);
+ mVsyncPeriod = static_cast<int64_t>(1000000000 / ADisplayConfig_getFps(mCurrentConfig));
mCompositorOffset = ADisplayConfig_getCompositorOffsetNanos(mCurrentConfig);
mAppOffset = ADisplayConfig_getAppVsyncOffsetNanos(mCurrentConfig);
}
diff --git a/libs/hwui/DeviceInfo.h b/libs/hwui/DeviceInfo.h
index a420746..16a22f4 100644
--- a/libs/hwui/DeviceInfo.h
+++ b/libs/hwui/DeviceInfo.h
@@ -37,7 +37,7 @@
static int32_t getWidth() { return get()->mWidth; }
static int32_t getHeight() { return get()->mHeight; }
static float getDensity() { return get()->mDensity; }
- static float getRefreshRate() { return get()->mRefreshRate; }
+ static int64_t getVsyncPeriod() { return get()->mVsyncPeriod; }
static int64_t getCompositorOffset() { return get()->mCompositorOffset; }
static int64_t getAppOffset() { return get()->mAppOffset; }
@@ -47,7 +47,8 @@
sk_sp<SkColorSpace> getWideColorSpace() const { return mWideColorSpace; }
SkColorType getWideColorType() const { return mWideColorType; }
- void onDisplayConfigChanged();
+ // This method should be called whenever the display refresh rate changes.
+ void onRefreshRateChanged(int64_t vsyncPeriod);
private:
friend class renderthread::RenderThread;
@@ -68,7 +69,7 @@
int32_t mWidth = 1080;
int32_t mHeight = 1920;
float mDensity = 2.0;
- float mRefreshRate = 60.0;
+ int64_t mVsyncPeriod = 16666666;
int64_t mCompositorOffset = 0;
int64_t mAppOffset = 0;
};
diff --git a/libs/hwui/GpuMemoryTracker.cpp b/libs/hwui/GpuMemoryTracker.cpp
deleted file mode 100644
index a9a7af8..0000000
--- a/libs/hwui/GpuMemoryTracker.cpp
+++ /dev/null
@@ -1,122 +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.
- */
-
-#include "utils/StringUtils.h"
-
-#include <GpuMemoryTracker.h>
-#include <cutils/compiler.h>
-#include <utils/Trace.h>
-#include <array>
-#include <sstream>
-#include <unordered_set>
-#include <vector>
-
-namespace android {
-namespace uirenderer {
-
-pthread_t gGpuThread = 0;
-
-#define NUM_TYPES static_cast<int>(GpuObjectType::TypeCount)
-
-const char* TYPE_NAMES[] = {
- "Texture", "OffscreenBuffer", "Layer",
-};
-
-struct TypeStats {
- int totalSize = 0;
- int count = 0;
-};
-
-static std::array<TypeStats, NUM_TYPES> gObjectStats;
-static std::unordered_set<GpuMemoryTracker*> gObjectSet;
-
-void GpuMemoryTracker::notifySizeChanged(int newSize) {
- int delta = newSize - mSize;
- mSize = newSize;
- gObjectStats[static_cast<int>(mType)].totalSize += delta;
-}
-
-void GpuMemoryTracker::startTrackingObject() {
- auto result = gObjectSet.insert(this);
- LOG_ALWAYS_FATAL_IF(!result.second,
- "startTrackingObject() on %p failed, already being tracked!", this);
- gObjectStats[static_cast<int>(mType)].count++;
-}
-
-void GpuMemoryTracker::stopTrackingObject() {
- size_t removed = gObjectSet.erase(this);
- LOG_ALWAYS_FATAL_IF(removed != 1, "stopTrackingObject removed %zd, is %p not being tracked?",
- removed, this);
- gObjectStats[static_cast<int>(mType)].count--;
-}
-
-void GpuMemoryTracker::onGpuContextCreated() {
- LOG_ALWAYS_FATAL_IF(gGpuThread != 0,
- "We already have a gpu thread? "
- "current = %lu, gpu thread = %lu",
- pthread_self(), gGpuThread);
- gGpuThread = pthread_self();
-}
-
-void GpuMemoryTracker::onGpuContextDestroyed() {
- gGpuThread = 0;
- if (CC_UNLIKELY(gObjectSet.size() > 0)) {
- std::stringstream os;
- dump(os);
- ALOGE("%s", os.str().c_str());
- LOG_ALWAYS_FATAL("Leaked %zd GPU objects!", gObjectSet.size());
- }
-}
-
-void GpuMemoryTracker::dump() {
- std::stringstream strout;
- dump(strout);
- ALOGD("%s", strout.str().c_str());
-}
-
-void GpuMemoryTracker::dump(std::ostream& stream) {
- for (int type = 0; type < NUM_TYPES; type++) {
- const TypeStats& stats = gObjectStats[type];
- stream << TYPE_NAMES[type];
- stream << " is using " << SizePrinter{stats.totalSize};
- stream << ", count = " << stats.count;
- stream << std::endl;
- }
-}
-
-int GpuMemoryTracker::getInstanceCount(GpuObjectType type) {
- return gObjectStats[static_cast<int>(type)].count;
-}
-
-int GpuMemoryTracker::getTotalSize(GpuObjectType type) {
- return gObjectStats[static_cast<int>(type)].totalSize;
-}
-
-void GpuMemoryTracker::onFrameCompleted() {
- if (ATRACE_ENABLED()) {
- char buf[128];
- for (int type = 0; type < NUM_TYPES; type++) {
- snprintf(buf, 128, "hwui_%s", TYPE_NAMES[type]);
- const TypeStats& stats = gObjectStats[type];
- ATRACE_INT(buf, stats.totalSize);
- snprintf(buf, 128, "hwui_%s_count", TYPE_NAMES[type]);
- ATRACE_INT(buf, stats.count);
- }
- }
-}
-
-} // namespace uirenderer
-} // namespace android;
diff --git a/libs/hwui/GpuMemoryTracker.h b/libs/hwui/GpuMemoryTracker.h
deleted file mode 100644
index de3ca99..0000000
--- a/libs/hwui/GpuMemoryTracker.h
+++ /dev/null
@@ -1,77 +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.
- */
-#pragma once
-
-#include <pthread.h>
-#include <ostream>
-
-#include <log/log.h>
-
-namespace android {
-namespace uirenderer {
-
-extern pthread_t gGpuThread;
-
-#define ASSERT_GPU_THREAD() \
- LOG_ALWAYS_FATAL_IF(!pthread_equal(gGpuThread, pthread_self()), \
- "Error, %p of type %d (size=%d) used on wrong thread! cur thread %lu " \
- "!= gpu thread %lu", \
- this, static_cast<int>(mType), mSize, pthread_self(), gGpuThread)
-
-enum class GpuObjectType {
- Texture = 0,
- OffscreenBuffer,
- Layer,
-
- TypeCount,
-};
-
-class GpuMemoryTracker {
-public:
- GpuObjectType objectType() { return mType; }
- int objectSize() { return mSize; }
-
- static void onGpuContextCreated();
- static void onGpuContextDestroyed();
- static void dump();
- static void dump(std::ostream& stream);
- static int getInstanceCount(GpuObjectType type);
- static int getTotalSize(GpuObjectType type);
- static void onFrameCompleted();
-
-protected:
- explicit GpuMemoryTracker(GpuObjectType type) : mType(type) {
- ASSERT_GPU_THREAD();
- startTrackingObject();
- }
-
- ~GpuMemoryTracker() {
- notifySizeChanged(0);
- stopTrackingObject();
- }
-
- void notifySizeChanged(int newSize);
-
-private:
- void startTrackingObject();
- void stopTrackingObject();
-
- int mSize = 0;
- GpuObjectType mType;
-};
-
-} // namespace uirenderer
-} // namespace android;
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index 10e7160..d25fc4b 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -81,7 +81,7 @@
JankTracker::JankTracker(ProfileDataContainer* globalData) {
mGlobalData = globalData;
- nsecs_t frameIntervalNanos = static_cast<nsecs_t>(1_s / DeviceInfo::getRefreshRate());
+ nsecs_t frameIntervalNanos = DeviceInfo::getVsyncPeriod();
nsecs_t sfOffset = DeviceInfo::getCompositorOffset();
nsecs_t offsetDelta = sfOffset - DeviceInfo::getAppOffset();
// There are two different offset cases. If the offsetDelta is positive
diff --git a/libs/hwui/ProfileData.cpp b/libs/hwui/ProfileData.cpp
index 7921662..a8e36e3 100644
--- a/libs/hwui/ProfileData.cpp
+++ b/libs/hwui/ProfileData.cpp
@@ -15,6 +15,7 @@
*/
#include "ProfileData.h"
+#include "Properties.h"
#include <cinttypes>
@@ -102,6 +103,7 @@
mGPUFrameCounts[i] >>= divider;
mGPUFrameCounts[i] += other.mGPUFrameCounts[i];
}
+ mPipelineType = other.mPipelineType;
}
void ProfileData::dump(int fd) const {
@@ -157,6 +159,7 @@
mTotalFrameCount = 0;
mJankFrameCount = 0;
mStatStartTime = systemTime(SYSTEM_TIME_MONOTONIC);
+ mPipelineType = Properties::getRenderPipelineType();
}
void ProfileData::reportFrame(int64_t duration) {
diff --git a/libs/hwui/ProfileData.h b/libs/hwui/ProfileData.h
index ccbffc6..dd3ba66 100644
--- a/libs/hwui/ProfileData.h
+++ b/libs/hwui/ProfileData.h
@@ -16,6 +16,7 @@
#pragma once
+#include "Properties.h"
#include "utils/Macros.h"
#include <utils/Timers.h>
@@ -65,6 +66,7 @@
uint32_t jankFrameCount() const { return mJankFrameCount; }
nsecs_t statsStartTime() const { return mStatStartTime; }
uint32_t jankTypeCount(JankType type) const { return mJankTypeCounts[static_cast<int>(type)]; }
+ RenderPipelineType pipelineType() const { return mPipelineType; }
struct HistogramEntry {
uint32_t renderTimeMs;
@@ -103,6 +105,9 @@
uint32_t mTotalFrameCount;
uint32_t mJankFrameCount;
nsecs_t mStatStartTime;
+
+ // true if HWUI renders with Vulkan pipeline
+ RenderPipelineType mPipelineType;
};
// For testing
diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp
index 2314072..5790150 100644
--- a/libs/hwui/SkiaCanvas.cpp
+++ b/libs/hwui/SkiaCanvas.cpp
@@ -217,13 +217,16 @@
canvas->setMatrix(mMatrix);
switch (mType) {
case Type::Rect:
- canvas->clipRect(mRRect.rect(), mOp);
+ // Don't anti-alias rectangular clips
+ canvas->clipRect(mRRect.rect(), mOp, false);
break;
case Type::RRect:
- canvas->clipRRect(mRRect, mOp);
+ // Ensure rounded rectangular clips are anti-aliased
+ canvas->clipRRect(mRRect, mOp, true);
break;
case Type::Path:
- canvas->clipPath(mPath.value(), mOp);
+ // Ensure path clips are anti-aliased
+ canvas->clipPath(mPath.value(), mOp, true);
break;
}
}
@@ -392,7 +395,7 @@
bool SkiaCanvas::clipPath(const SkPath* path, SkClipOp op) {
this->recordClip(*path, op);
- mCanvas->clipPath(*path, op);
+ mCanvas->clipPath(*path, op, true);
return !mCanvas->isClipEmpty();
}
@@ -746,7 +749,10 @@
glyphFunc(buffer.glyphs, buffer.pos);
sk_sp<SkTextBlob> textBlob(builder.make());
- mCanvas->drawTextBlob(textBlob, 0, 0, paintCopy);
+
+ apply_looper(&paintCopy, [&](const SkPaint& p) {
+ mCanvas->drawTextBlob(textBlob, 0, 0, p);
+ });
drawTextDecorations(x, y, totalAdvance, paintCopy);
}
@@ -783,8 +789,10 @@
xform[i - start].fTx = pos.x() - tan.y() * y - halfWidth * tan.x();
xform[i - start].fTy = pos.y() + tan.x() * y - halfWidth * tan.y();
}
-
- this->asSkCanvas()->drawTextBlob(builder.make(), 0, 0, paintCopy);
+ auto* finalCanvas = this->asSkCanvas();
+ apply_looper(&paintCopy, [&](const SkPaint& p) {
+ finalCanvas->drawTextBlob(builder.make(), 0, 0, paintCopy);
+ });
}
// ----------------------------------------------------------------------------
diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp
index f4149b9..84549e8 100644
--- a/libs/hwui/hwui/Bitmap.cpp
+++ b/libs/hwui/hwui/Bitmap.cpp
@@ -152,7 +152,9 @@
AHardwareBuffer_describe(hardwareBuffer, &bufferDesc);
SkImageInfo info = uirenderer::BufferDescriptionToImageInfo(bufferDesc, colorSpace);
- const size_t rowBytes = info.bytesPerPixel() * bufferDesc.stride;
+ // If the stride is 0 we have to use the width as an approximation (eg, compressed buffer)
+ const auto bufferStride = bufferDesc.stride > 0 ? bufferDesc.stride : bufferDesc.width;
+ const size_t rowBytes = info.bytesPerPixel() * bufferStride;
return sk_sp<Bitmap>(new Bitmap(hardwareBuffer, info, rowBytes, palette));
}
diff --git a/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp b/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp
new file mode 100644
index 0000000..2c78b02
--- /dev/null
+++ b/libs/hwui/pipeline/skia/ATraceMemoryDump.cpp
@@ -0,0 +1,175 @@
+/*
+ * 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.
+ */
+
+#include "ATraceMemoryDump.h"
+
+#include <utils/Trace.h>
+
+#include <cstring>
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+// When purgeable is INVALID_TIME it won't be logged at all.
+#define INVALID_TIME -1
+
+/**
+ * Skia invokes the following SkTraceMemoryDump functions:
+ * 1. dumpNumericValue (dumpName, units="bytes", valueName="size")
+ * 2. dumpStringValue (dumpName, valueName="type") [optional -> for example CPU memory does not
+ * invoke dumpStringValue]
+ * 3. dumpNumericValue (dumpName, units="bytes", valueName="purgeable_size") [optional]
+ * 4. setMemoryBacking(dumpName, backingType) [optional -> for example Vulkan GPU resources do not
+ * invoke setMemoryBacking]
+ *
+ * ATraceMemoryDump calculates memory category first by looking at the "type" string passed to
+ * dumpStringValue and then by looking at "backingType" passed to setMemoryBacking.
+ * Only GPU Texture memory is tracked separately and everything else is grouped as one
+ * "GPU Memory" category.
+ */
+static std::unordered_map<const char*, const char*> sResourceMap = {
+ {"malloc", "Graphics CPU Memory"}, // taken from setMemoryBacking(backingType)
+ {"gl_texture", "Graphics Texture Memory"}, // taken from setMemoryBacking(backingType)
+ {"Texture",
+ "Graphics Texture Memory"}, // taken from dumpStringValue(value, valueName="type")
+ // Uncomment categories below to split "GPU Memory" into more brackets for debugging.
+ /*{"vk_buffer", "vk_buffer"},
+ {"gl_renderbuffer", "gl_renderbuffer"},
+ {"gl_buffer", "gl_buffer"},
+ {"RenderTarget", "RenderTarget"},
+ {"Stencil", "Stencil"},
+ {"Path Data", "Path Data"},
+ {"Buffer Object", "Buffer Object"},
+ {"Surface", "Surface"},*/
+};
+
+ATraceMemoryDump::ATraceMemoryDump() {
+ mLastDumpName.reserve(100);
+ mCategory.reserve(100);
+}
+
+void ATraceMemoryDump::dumpNumericValue(const char* dumpName, const char* valueName,
+ const char* units, uint64_t value) {
+ if (!strcmp(units, "bytes")) {
+ recordAndResetCountersIfNeeded(dumpName);
+ if (!strcmp(valueName, "size")) {
+ mLastDumpValue = value;
+ } else if (!strcmp(valueName, "purgeable_size")) {
+ mLastPurgeableDumpValue = value;
+ }
+ }
+}
+
+void ATraceMemoryDump::dumpStringValue(const char* dumpName, const char* valueName,
+ const char* value) {
+ if (!strcmp(valueName, "type")) {
+ recordAndResetCountersIfNeeded(dumpName);
+ auto categoryIt = sResourceMap.find(value);
+ if (categoryIt != sResourceMap.end()) {
+ mCategory = categoryIt->second;
+ }
+ }
+}
+
+void ATraceMemoryDump::setMemoryBacking(const char* dumpName, const char* backingType,
+ const char* backingObjectId) {
+ recordAndResetCountersIfNeeded(dumpName);
+ auto categoryIt = sResourceMap.find(backingType);
+ if (categoryIt != sResourceMap.end()) {
+ mCategory = categoryIt->second;
+ }
+}
+
+/**
+ * startFrame is invoked before dumping anything. It resets counters from the previous frame.
+ * This is important, because if there is no new data for a given category trace would assume
+ * usage has not changed (instead of reporting 0).
+ */
+void ATraceMemoryDump::startFrame() {
+ resetCurrentCounter("");
+ for (auto& it : mCurrentValues) {
+ // Once a category is observed in at least one frame, it is always reported in subsequent
+ // frames (even if it is 0). Not logging a category to ATRACE would mean its value has not
+ // changed since the previous frame, which is not what we want.
+ it.second.time = 0;
+ // If purgeableTime is INVALID_TIME, then logTraces won't log it at all.
+ if (it.second.purgeableTime != INVALID_TIME) {
+ it.second.purgeableTime = 0;
+ }
+ }
+}
+
+/**
+ * logTraces reads from mCurrentValues and logs the counters with ATRACE.
+ */
+void ATraceMemoryDump::logTraces() {
+ // Accumulate data from last dumpName
+ recordAndResetCountersIfNeeded("");
+ for (auto& it : mCurrentValues) {
+ ATRACE_INT64(it.first.c_str(), it.second.time);
+ if (it.second.purgeableTime != INVALID_TIME) {
+ ATRACE_INT64((std::string("Purgeable ") + it.first).c_str(), it.second.purgeableTime);
+ }
+ }
+}
+
+/**
+ * recordAndResetCountersIfNeeded reads memory usage from mLastDumpValue/mLastPurgeableDumpValue and
+ * accumulates in mCurrentValues[category]. It makes provision to create a new category and track
+ * purgeable memory only if there is at least one observation.
+ * recordAndResetCountersIfNeeded won't do anything until all the information for a given dumpName
+ * is received.
+ */
+void ATraceMemoryDump::recordAndResetCountersIfNeeded(const char* dumpName) {
+ if (!mLastDumpName.compare(dumpName)) {
+ // Still waiting for more data for current dumpName.
+ return;
+ }
+
+ // First invocation will have an empty mLastDumpName.
+ if (!mLastDumpName.empty()) {
+ // A new dumpName observed -> store the data already collected.
+ auto memoryCounter = mCurrentValues.find(mCategory);
+ if (memoryCounter != mCurrentValues.end()) {
+ memoryCounter->second.time += mLastDumpValue;
+ if (mLastPurgeableDumpValue != INVALID_TIME) {
+ if (memoryCounter->second.purgeableTime == INVALID_TIME) {
+ memoryCounter->second.purgeableTime = mLastPurgeableDumpValue;
+ } else {
+ memoryCounter->second.purgeableTime += mLastPurgeableDumpValue;
+ }
+ }
+ } else {
+ mCurrentValues[mCategory] = {mLastDumpValue, mLastPurgeableDumpValue};
+ }
+ }
+
+ // Reset counters and default category for the newly observed "dumpName".
+ resetCurrentCounter(dumpName);
+}
+
+void ATraceMemoryDump::resetCurrentCounter(const char* dumpName) {
+ mLastDumpValue = 0;
+ mLastPurgeableDumpValue = INVALID_TIME;
+ mLastDumpName = dumpName;
+ // Categories not listed in sResourceMap are reported as "GPU memory"
+ mCategory = "GPU Memory";
+}
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
diff --git a/libs/hwui/pipeline/skia/ATraceMemoryDump.h b/libs/hwui/pipeline/skia/ATraceMemoryDump.h
new file mode 100644
index 0000000..aa5c401
--- /dev/null
+++ b/libs/hwui/pipeline/skia/ATraceMemoryDump.h
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <SkString.h>
+#include <SkTraceMemoryDump.h>
+
+#include <string>
+#include <unordered_map>
+#include <utility>
+
+namespace android {
+namespace uirenderer {
+namespace skiapipeline {
+
+class ATraceMemoryDump : public SkTraceMemoryDump {
+public:
+ ATraceMemoryDump();
+ ~ATraceMemoryDump() override {}
+
+ void dumpNumericValue(const char* dumpName, const char* valueName, const char* units,
+ uint64_t value) override;
+
+ void dumpStringValue(const char* dumpName, const char* valueName, const char* value) override;
+
+ LevelOfDetail getRequestedDetails() const override {
+ return SkTraceMemoryDump::kLight_LevelOfDetail;
+ }
+
+ bool shouldDumpWrappedObjects() const override { return false; }
+
+ void setMemoryBacking(const char* dumpName, const char* backingType,
+ const char* backingObjectId) override;
+
+ void setDiscardableMemoryBacking(const char*, const SkDiscardableMemory&) override {}
+
+ void startFrame();
+
+ void logTraces();
+
+private:
+ std::string mLastDumpName;
+
+ uint64_t mLastDumpValue;
+
+ uint64_t mLastPurgeableDumpValue;
+
+ std::string mCategory;
+
+ struct TraceValue {
+ uint64_t time;
+ uint64_t purgeableTime;
+ };
+
+ // keys are define in sResourceMap
+ std::unordered_map<std::string, TraceValue> mCurrentValues;
+
+ void recordAndResetCountersIfNeeded(const char* dumpName);
+
+ void resetCurrentCounter(const char* dumpName);
+};
+
+} /* namespace skiapipeline */
+} /* namespace uirenderer */
+} /* namespace android */
\ No newline at end of file
diff --git a/libs/hwui/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 11dc013..35a885f 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -457,7 +457,10 @@
const Rect& contentDrawBounds, SkCanvas* canvas,
const SkMatrix& preTransform) {
SkAutoCanvasRestore saver(canvas, true);
- canvas->androidFramework_setDeviceClipRestriction(preTransform.mapRect(clip).roundOut());
+ auto clipRestriction = preTransform.mapRect(clip).roundOut();
+ canvas->androidFramework_setDeviceClipRestriction(clipRestriction);
+ canvas->drawAnnotation(SkRect::Make(clipRestriction), "AndroidDeviceClipRestriction",
+ nullptr);
canvas->concat(preTransform);
// STOPSHIP: Revert, temporary workaround to clear always F16 frame buffer for b/74976293
diff --git a/libs/hwui/protos/graphicsstats.proto b/libs/hwui/protos/graphicsstats.proto
index 0cd5c62..dd5676c 100644
--- a/libs/hwui/protos/graphicsstats.proto
+++ b/libs/hwui/protos/graphicsstats.proto
@@ -29,6 +29,11 @@
}
message GraphicsStatsProto {
+ enum PipelineType {
+ GL = 0;
+ VULKAN = 1;
+ }
+
// The package name of the app
optional string package_name = 1;
@@ -49,6 +54,9 @@
// The gpu frame time histogram for the package
repeated GraphicsStatsHistogramBucketProto gpu_histogram = 7;
+
+ // HWUI renders pipeline type: GL or Vulkan
+ optional PipelineType pipeline = 8;
}
message GraphicsStatsJankSummaryProto {
diff --git a/libs/hwui/renderstate/RenderState.cpp b/libs/hwui/renderstate/RenderState.cpp
index fad9440..7e8c96d 100644
--- a/libs/hwui/renderstate/RenderState.cpp
+++ b/libs/hwui/renderstate/RenderState.cpp
@@ -16,7 +16,6 @@
#include "renderstate/RenderState.h"
#include "renderthread/RenderThread.h"
-#include "GpuMemoryTracker.h"
namespace android {
namespace uirenderer {
@@ -25,15 +24,10 @@
mThreadId = pthread_self();
}
-void RenderState::onContextCreated() {
- GpuMemoryTracker::onGpuContextCreated();
-}
-
void RenderState::onContextDestroyed() {
for(auto callback : mContextCallbacks) {
callback->onContextDestroyed();
}
- GpuMemoryTracker::onGpuContextDestroyed();
}
void RenderState::postDecStrong(VirtualLightRefBase* object) {
diff --git a/libs/hwui/renderstate/RenderState.h b/libs/hwui/renderstate/RenderState.h
index ff5d02f..e08d32a 100644
--- a/libs/hwui/renderstate/RenderState.h
+++ b/libs/hwui/renderstate/RenderState.h
@@ -62,7 +62,6 @@
~RenderState() {}
// Context notifications are only to be triggered by renderthread::RenderThread
- void onContextCreated();
void onContextDestroyed();
std::set<IGpuContextCallback*> mContextCallbacks;
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index eaed46c..d177855 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -20,10 +20,12 @@
#include "Layer.h"
#include "Properties.h"
#include "RenderThread.h"
+#include "pipeline/skia/ATraceMemoryDump.h"
#include "pipeline/skia/ShaderCache.h"
#include "pipeline/skia/SkiaMemoryTracer.h"
#include "renderstate/RenderState.h"
#include "thread/CommonPool.h"
+#include <utils/Trace.h>
#include <GrContextOptions.h>
#include <SkExecutor.h>
@@ -184,6 +186,18 @@
gpuTracer.logTotals(log);
}
+void CacheManager::onFrameCompleted() {
+ if (ATRACE_ENABLED()) {
+ static skiapipeline::ATraceMemoryDump tracer;
+ tracer.startFrame();
+ SkGraphics::DumpMemoryStatistics(&tracer);
+ if (mGrContext) {
+ mGrContext->dumpMemoryStatistics(&tracer);
+ }
+ tracer.logTraces();
+ }
+}
+
} /* namespace renderthread */
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/renderthread/CacheManager.h b/libs/hwui/renderthread/CacheManager.h
index 968251e..b009cc4 100644
--- a/libs/hwui/renderthread/CacheManager.h
+++ b/libs/hwui/renderthread/CacheManager.h
@@ -50,6 +50,7 @@
size_t getCacheSize() const { return mMaxResourceBytes; }
size_t getBackgroundCacheSize() const { return mBackgroundResourceBytes; }
+ void onFrameCompleted();
private:
friend class RenderThread;
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 8490221..5993e17 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -16,7 +16,6 @@
#include "CanvasContext.h"
-#include <GpuMemoryTracker.h>
#include <apex/window.h>
#include <fcntl.h>
#include <strings.h>
@@ -558,7 +557,7 @@
mJankTracker.finishGpuDraw(*forthBehind);
}
- GpuMemoryTracker::onFrameCompleted();
+ mRenderThread.cacheManager().onFrameCompleted();
}
// Called by choreographer to do an RT-driven animation
diff --git a/libs/hwui/renderthread/RenderThread.cpp b/libs/hwui/renderthread/RenderThread.cpp
index a446858..cae3e3b 100644
--- a/libs/hwui/renderthread/RenderThread.cpp
+++ b/libs/hwui/renderthread/RenderThread.cpp
@@ -34,7 +34,6 @@
#include <GrContextOptions.h>
#include <gl/GrGLInterface.h>
-#include <gui/DisplayEventReceiver.h>
#include <sys/resource.h>
#include <utils/Condition.h>
#include <utils/Log.h>
@@ -45,53 +44,43 @@
namespace uirenderer {
namespace renderthread {
-// Number of events to read at a time from the DisplayEventReceiver pipe.
-// The value should be large enough that we can quickly drain the pipe
-// using just a few large reads.
-static const size_t EVENT_BUFFER_SIZE = 100;
-
static bool gHasRenderThreadInstance = false;
static JVMAttachHook gOnStartHook = nullptr;
-class DisplayEventReceiverWrapper : public VsyncSource {
+void RenderThread::frameCallback(int64_t frameTimeNanos, void* data) {
+ RenderThread* rt = reinterpret_cast<RenderThread*>(data);
+ rt->mVsyncRequested = false;
+ if (rt->timeLord().vsyncReceived(frameTimeNanos) && !rt->mFrameCallbackTaskPending) {
+ ATRACE_NAME("queue mFrameCallbackTask");
+ rt->mFrameCallbackTaskPending = true;
+ nsecs_t runAt = (frameTimeNanos + rt->mDispatchFrameDelay);
+ rt->queue().postAt(runAt, [=]() { rt->dispatchFrameCallbacks(); });
+ }
+}
+
+void RenderThread::refreshRateCallback(int64_t vsyncPeriod, void* data) {
+ ATRACE_NAME("refreshRateCallback");
+ RenderThread* rt = reinterpret_cast<RenderThread*>(data);
+ DeviceInfo::get()->onRefreshRateChanged(vsyncPeriod);
+ rt->setupFrameInterval();
+}
+
+class ChoreographerSource : public VsyncSource {
public:
- DisplayEventReceiverWrapper(std::unique_ptr<DisplayEventReceiver>&& receiver,
- const std::function<void()>& onDisplayConfigChanged)
- : mDisplayEventReceiver(std::move(receiver))
- , mOnDisplayConfigChanged(onDisplayConfigChanged) {}
+ ChoreographerSource(RenderThread* renderThread) : mRenderThread(renderThread) {}
virtual void requestNextVsync() override {
- status_t status = mDisplayEventReceiver->requestNextVsync();
- LOG_ALWAYS_FATAL_IF(status != NO_ERROR, "requestNextVsync failed with status: %d", status);
+ AChoreographer_postFrameCallback64(mRenderThread->mChoreographer,
+ RenderThread::frameCallback, mRenderThread);
}
- virtual nsecs_t latestVsyncEvent() override {
- DisplayEventReceiver::Event buf[EVENT_BUFFER_SIZE];
- nsecs_t latest = 0;
- ssize_t n;
- while ((n = mDisplayEventReceiver->getEvents(buf, EVENT_BUFFER_SIZE)) > 0) {
- for (ssize_t i = 0; i < n; i++) {
- const DisplayEventReceiver::Event& ev = buf[i];
- switch (ev.header.type) {
- case DisplayEventReceiver::DISPLAY_EVENT_VSYNC:
- latest = ev.header.timestamp;
- break;
- case DisplayEventReceiver::DISPLAY_EVENT_CONFIG_CHANGED:
- mOnDisplayConfigChanged();
- break;
- }
- }
- }
- if (n < 0) {
- ALOGW("Failed to get events from display event receiver, status=%d", status_t(n));
- }
- return latest;
+ virtual void drainPendingEvents() override {
+ AChoreographer_handlePendingEvents(mRenderThread->mChoreographer, mRenderThread);
}
private:
- std::unique_ptr<DisplayEventReceiver> mDisplayEventReceiver;
- std::function<void()> mOnDisplayConfigChanged;
+ RenderThread* mRenderThread;
};
class DummyVsyncSource : public VsyncSource {
@@ -99,11 +88,14 @@
DummyVsyncSource(RenderThread* renderThread) : mRenderThread(renderThread) {}
virtual void requestNextVsync() override {
- mRenderThread->queue().postDelayed(16_ms,
- [this]() { mRenderThread->drainDisplayEventQueue(); });
+ mRenderThread->queue().postDelayed(16_ms, [this]() {
+ RenderThread::frameCallback(systemTime(SYSTEM_TIME_MONOTONIC), mRenderThread);
+ });
}
- virtual nsecs_t latestVsyncEvent() override { return systemTime(SYSTEM_TIME_MONOTONIC); }
+ virtual void drainPendingEvents() override {
+ RenderThread::frameCallback(systemTime(SYSTEM_TIME_MONOTONIC), mRenderThread);
+ }
private:
RenderThread* mRenderThread;
@@ -145,29 +137,24 @@
}
RenderThread::~RenderThread() {
+ // Note that if this fatal assertion is removed then member variables must
+ // be properly destroyed.
LOG_ALWAYS_FATAL("Can't destroy the render thread");
}
-void RenderThread::initializeDisplayEventReceiver() {
- LOG_ALWAYS_FATAL_IF(mVsyncSource, "Initializing a second DisplayEventReceiver?");
+void RenderThread::initializeChoreographer() {
+ LOG_ALWAYS_FATAL_IF(mVsyncSource, "Initializing a second Choreographer?");
if (!Properties::isolatedProcess) {
- auto receiver = std::make_unique<DisplayEventReceiver>(
- ISurfaceComposer::eVsyncSourceApp,
- ISurfaceComposer::eConfigChangedDispatch);
- status_t status = receiver->initCheck();
- LOG_ALWAYS_FATAL_IF(status != NO_ERROR,
- "Initialization of DisplayEventReceiver "
- "failed with status: %d",
- status);
+ mChoreographer = AChoreographer_create();
+ LOG_ALWAYS_FATAL_IF(mChoreographer == nullptr, "Initialization of Choreographer failed");
+ AChoreographer_registerRefreshRateCallback(mChoreographer,
+ RenderThread::refreshRateCallback, this);
// Register the FD
- mLooper->addFd(receiver->getFd(), 0, Looper::EVENT_INPUT,
- RenderThread::displayEventReceiverCallback, this);
- mVsyncSource = new DisplayEventReceiverWrapper(std::move(receiver), [this] {
- DeviceInfo::get()->onDisplayConfigChanged();
- setupFrameInterval();
- });
+ mLooper->addFd(AChoreographer_getFd(mChoreographer), 0, Looper::EVENT_INPUT,
+ RenderThread::choreographerCallback, this);
+ mVsyncSource = new ChoreographerSource(this);
} else {
mVsyncSource = new DummyVsyncSource(this);
}
@@ -175,7 +162,7 @@
void RenderThread::initThreadLocals() {
setupFrameInterval();
- initializeDisplayEventReceiver();
+ initializeChoreographer();
mEglManager = new EglManager();
mRenderState = new RenderState(*this);
mVkManager = new VulkanManager();
@@ -183,7 +170,7 @@
}
void RenderThread::setupFrameInterval() {
- nsecs_t frameIntervalNanos = static_cast<nsecs_t>(1000000000 / DeviceInfo::getRefreshRate());
+ nsecs_t frameIntervalNanos = DeviceInfo::getVsyncPeriod();
mTimeLord.setFrameInterval(frameIntervalNanos);
mDispatchFrameDelay = static_cast<nsecs_t>(frameIntervalNanos * .25f);
}
@@ -283,12 +270,11 @@
}
mGrContext = std::move(context);
if (mGrContext) {
- mRenderState->onContextCreated();
DeviceInfo::setMaxTextureSize(mGrContext->maxRenderTargetSize());
}
}
-int RenderThread::displayEventReceiverCallback(int fd, int events, void* data) {
+int RenderThread::choreographerCallback(int fd, int events, void* data) {
if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) {
ALOGE("Display event receiver pipe was closed or an error occurred. "
"events=0x%x",
@@ -302,24 +288,10 @@
events);
return 1; // keep the callback
}
+ RenderThread* rt = reinterpret_cast<RenderThread*>(data);
+ AChoreographer_handlePendingEvents(rt->mChoreographer, data);
- reinterpret_cast<RenderThread*>(data)->drainDisplayEventQueue();
-
- return 1; // keep the callback
-}
-
-void RenderThread::drainDisplayEventQueue() {
- ATRACE_CALL();
- nsecs_t vsyncEvent = mVsyncSource->latestVsyncEvent();
- if (vsyncEvent > 0) {
- mVsyncRequested = false;
- if (mTimeLord.vsyncReceived(vsyncEvent) && !mFrameCallbackTaskPending) {
- ATRACE_NAME("queue mFrameCallbackTask");
- mFrameCallbackTaskPending = true;
- nsecs_t runAt = (vsyncEvent + mDispatchFrameDelay);
- queue().postAt(runAt, [this]() { dispatchFrameCallbacks(); });
- }
- }
+ return 1;
}
void RenderThread::dispatchFrameCallbacks() {
@@ -360,7 +332,7 @@
processQueue();
if (mPendingRegistrationFrameCallbacks.size() && !mFrameCallbackTaskPending) {
- drainDisplayEventQueue();
+ mVsyncSource->drainPendingEvents();
mFrameCallbacks.insert(mPendingRegistrationFrameCallbacks.begin(),
mPendingRegistrationFrameCallbacks.end());
mPendingRegistrationFrameCallbacks.clear();
diff --git a/libs/hwui/renderthread/RenderThread.h b/libs/hwui/renderthread/RenderThread.h
index da79e97..8be46a6 100644
--- a/libs/hwui/renderthread/RenderThread.h
+++ b/libs/hwui/renderthread/RenderThread.h
@@ -19,6 +19,7 @@
#include <GrContext.h>
#include <SkBitmap.h>
+#include <apex/choreographer.h>
#include <cutils/compiler.h>
#include <thread/ThreadBase.h>
#include <utils/Looper.h>
@@ -73,10 +74,11 @@
struct VsyncSource {
virtual void requestNextVsync() = 0;
- virtual nsecs_t latestVsyncEvent() = 0;
+ virtual void drainPendingEvents() = 0;
virtual ~VsyncSource() {}
};
+class ChoreographerSource;
class DummyVsyncSource;
typedef void (*JVMAttachHook)(const char* name);
@@ -136,6 +138,7 @@
friend class DispatchFrameCallbacks;
friend class RenderProxy;
friend class DummyVsyncSource;
+ friend class ChoreographerSource;
friend class android::uirenderer::AutoBackendTextureRelease;
friend class android::uirenderer::TestUtils;
friend class android::uirenderer::WebViewFunctor;
@@ -149,13 +152,21 @@
static RenderThread& getInstance();
void initThreadLocals();
- void initializeDisplayEventReceiver();
+ void initializeChoreographer();
void setupFrameInterval();
- static int displayEventReceiverCallback(int fd, int events, void* data);
+ // Callbacks for choreographer events:
+ // choreographerCallback will call AChoreograper_handleEvent to call the
+ // corresponding callbacks for each display event type
+ static int choreographerCallback(int fd, int events, void* data);
+ // Callback that will be run on vsync ticks.
+ static void frameCallback(int64_t frameTimeNanos, void* data);
+ // Callback that will be run whenver there is a refresh rate change.
+ static void refreshRateCallback(int64_t vsyncPeriod, void* data);
void drainDisplayEventQueue();
void dispatchFrameCallbacks();
void requestVsync();
+ AChoreographer* mChoreographer;
VsyncSource* mVsyncSource;
bool mVsyncRequested;
std::set<IFrameCallback*> mFrameCallbacks;
diff --git a/libs/hwui/service/GraphicsStatsService.cpp b/libs/hwui/service/GraphicsStatsService.cpp
index 12c5b83..c418617 100644
--- a/libs/hwui/service/GraphicsStatsService.cpp
+++ b/libs/hwui/service/GraphicsStatsService.cpp
@@ -16,24 +16,28 @@
#include "GraphicsStatsService.h"
-#include "JankTracker.h"
-#include "protos/graphicsstats.pb.h"
-
-#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
-#include <log/log.h>
-
#include <errno.h>
#include <fcntl.h>
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
#include <inttypes.h>
+#include <log/log.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
+#include <algorithm>
+#include <map>
+#include <vector>
+
+#include "JankTracker.h"
+#include "protos/graphicsstats.pb.h"
+
namespace android {
namespace uirenderer {
using namespace google::protobuf;
+using namespace uirenderer::protos;
constexpr int32_t sCurrentFileVersion = 1;
constexpr int32_t sHeaderSize = 4;
@@ -42,9 +46,9 @@
constexpr int sHistogramSize = ProfileData::HistogramSize();
constexpr int sGPUHistogramSize = ProfileData::GPUHistogramSize();
-static bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto,
- const std::string& package, int64_t versionCode,
- int64_t startTime, int64_t endTime, const ProfileData* data);
+static bool mergeProfileDataIntoProto(protos::GraphicsStatsProto* proto, const std::string& package,
+ int64_t versionCode, int64_t startTime, int64_t endTime,
+ const ProfileData* data);
static void dumpAsTextToFd(protos::GraphicsStatsProto* proto, int outFd);
class FileDescriptor {
@@ -57,7 +61,7 @@
}
}
bool valid() { return mFd != -1; }
- operator int() { return mFd; } // NOLINT(google-explicit-constructor)
+ operator int() { return mFd; } // NOLINT(google-explicit-constructor)
private:
int mFd;
@@ -167,6 +171,8 @@
}
proto->set_package_name(package);
proto->set_version_code(versionCode);
+ proto->set_pipeline(data->pipelineType() == RenderPipelineType::SkiaGL ?
+ GraphicsStatsProto_PipelineType_GL : GraphicsStatsProto_PipelineType_VULKAN);
auto summary = proto->mutable_summary();
summary->set_total_frames(summary->total_frames() + data->totalFrameCount());
summary->set_janky_frames(summary->janky_frames() + data->jankFrameCount());
@@ -179,8 +185,8 @@
summary->set_slow_bitmap_upload_count(summary->slow_bitmap_upload_count() +
data->jankTypeCount(kSlowSync));
summary->set_slow_draw_count(summary->slow_draw_count() + data->jankTypeCount(kSlowRT));
- summary->set_missed_deadline_count(summary->missed_deadline_count()
- + data->jankTypeCount(kMissedDeadline));
+ summary->set_missed_deadline_count(summary->missed_deadline_count() +
+ data->jankTypeCount(kMissedDeadline));
bool creatingHistogram = false;
if (proto->histogram_size() == 0) {
@@ -365,17 +371,69 @@
class GraphicsStatsService::Dump {
public:
- Dump(int outFd, DumpType type) : mFd(outFd), mType(type) {}
+ Dump(int outFd, DumpType type) : mFd(outFd), mType(type) {
+ if (mFd == -1 && mType == DumpType::Protobuf) {
+ mType = DumpType::ProtobufStatsd;
+ }
+ }
int fd() { return mFd; }
DumpType type() { return mType; }
protos::GraphicsStatsServiceDumpProto& proto() { return mProto; }
+ void mergeStat(const protos::GraphicsStatsProto& stat);
+ void updateProto();
private:
+ // use package name and app version for a key
+ typedef std::pair<std::string, int64_t> DumpKey;
+
+ std::map<DumpKey, protos::GraphicsStatsProto> mStats;
int mFd;
DumpType mType;
protos::GraphicsStatsServiceDumpProto mProto;
};
+void GraphicsStatsService::Dump::mergeStat(const protos::GraphicsStatsProto& stat) {
+ auto dumpKey = std::make_pair(stat.package_name(), stat.version_code());
+ auto findIt = mStats.find(dumpKey);
+ if (findIt == mStats.end()) {
+ mStats[dumpKey] = stat;
+ } else {
+ auto summary = findIt->second.mutable_summary();
+ summary->set_total_frames(summary->total_frames() + stat.summary().total_frames());
+ summary->set_janky_frames(summary->janky_frames() + stat.summary().janky_frames());
+ summary->set_missed_vsync_count(summary->missed_vsync_count() +
+ stat.summary().missed_vsync_count());
+ summary->set_high_input_latency_count(summary->high_input_latency_count() +
+ stat.summary().high_input_latency_count());
+ summary->set_slow_ui_thread_count(summary->slow_ui_thread_count() +
+ stat.summary().slow_ui_thread_count());
+ summary->set_slow_bitmap_upload_count(summary->slow_bitmap_upload_count() +
+ stat.summary().slow_bitmap_upload_count());
+ summary->set_slow_draw_count(summary->slow_draw_count() + stat.summary().slow_draw_count());
+ summary->set_missed_deadline_count(summary->missed_deadline_count() +
+ stat.summary().missed_deadline_count());
+ for (int bucketIndex = 0; bucketIndex < findIt->second.histogram_size(); bucketIndex++) {
+ auto bucket = findIt->second.mutable_histogram(bucketIndex);
+ bucket->set_frame_count(bucket->frame_count() +
+ stat.histogram(bucketIndex).frame_count());
+ }
+ for (int bucketIndex = 0; bucketIndex < findIt->second.gpu_histogram_size();
+ bucketIndex++) {
+ auto bucket = findIt->second.mutable_gpu_histogram(bucketIndex);
+ bucket->set_frame_count(bucket->frame_count() +
+ stat.gpu_histogram(bucketIndex).frame_count());
+ }
+ findIt->second.set_stats_start(std::min(findIt->second.stats_start(), stat.stats_start()));
+ findIt->second.set_stats_end(std::max(findIt->second.stats_end(), stat.stats_end()));
+ }
+}
+
+void GraphicsStatsService::Dump::updateProto() {
+ for (auto& stat : mStats) {
+ mProto.add_stats()->CopyFrom(stat.second);
+ }
+}
+
GraphicsStatsService::Dump* GraphicsStatsService::createDump(int outFd, DumpType type) {
return new Dump(outFd, type);
}
@@ -396,8 +454,9 @@
path.empty() ? "<empty>" : path.c_str(), data);
return;
}
-
- if (dump->type() == DumpType::Protobuf) {
+ if (dump->type() == DumpType::ProtobufStatsd) {
+ dump->mergeStat(statsProto);
+ } else if (dump->type() == DumpType::Protobuf) {
dump->proto().add_stats()->CopyFrom(statsProto);
} else {
dumpAsTextToFd(&statsProto, dump->fd());
@@ -409,7 +468,9 @@
if (!parseFromFile(path, &statsProto)) {
return;
}
- if (dump->type() == DumpType::Protobuf) {
+ if (dump->type() == DumpType::ProtobufStatsd) {
+ dump->mergeStat(statsProto);
+ } else if (dump->type() == DumpType::Protobuf) {
dump->proto().add_stats()->CopyFrom(statsProto);
} else {
dumpAsTextToFd(&statsProto, dump->fd());
@@ -424,5 +485,79 @@
delete dump;
}
+class MemOutputStreamLite : public io::ZeroCopyOutputStream {
+public:
+ explicit MemOutputStreamLite() : mCopyAdapter(), mImpl(&mCopyAdapter) {}
+ virtual ~MemOutputStreamLite() {}
+
+ virtual bool Next(void** data, int* size) override { return mImpl.Next(data, size); }
+
+ virtual void BackUp(int count) override { mImpl.BackUp(count); }
+
+ virtual int64 ByteCount() const override { return mImpl.ByteCount(); }
+
+ bool Flush() { return mImpl.Flush(); }
+
+ void copyData(const DumpMemoryFn& reader, void* param1, void* param2) {
+ int bufferOffset = 0;
+ int totalSize = mCopyAdapter.mBuffersSize - mCopyAdapter.mCurrentBufferUnusedSize;
+ int totalDataLeft = totalSize;
+ for (auto& it : mCopyAdapter.mBuffers) {
+ int bufferSize = std::min(totalDataLeft, (int)it.size()); // last buffer is not full
+ reader(it.data(), bufferOffset, bufferSize, totalSize, param1, param2);
+ bufferOffset += bufferSize;
+ totalDataLeft -= bufferSize;
+ }
+ }
+
+private:
+ struct MemAdapter : public io::CopyingOutputStream {
+ // Data is stored in an array of buffers.
+ // JNI SetByteArrayRegion assembles data in one continuous Java byte[] buffer.
+ std::vector<std::vector<unsigned char>> mBuffers;
+ int mBuffersSize = 0; // total bytes allocated in mBuffers
+ int mCurrentBufferUnusedSize = 0; // unused bytes in the last buffer mBuffers.back()
+ unsigned char* mCurrentBuffer = nullptr; // pointer to next free byte in mBuffers.back()
+
+ explicit MemAdapter() {}
+ virtual ~MemAdapter() {}
+
+ virtual bool Write(const void* buffer, int size) override {
+ while (size > 0) {
+ if (0 == mCurrentBufferUnusedSize) {
+ mCurrentBufferUnusedSize =
+ std::max(size, mBuffersSize ? 2 * mBuffersSize : 10000);
+ mBuffers.emplace_back();
+ mBuffers.back().resize(mCurrentBufferUnusedSize);
+ mCurrentBuffer = mBuffers.back().data();
+ mBuffersSize += mCurrentBufferUnusedSize;
+ }
+ int dataMoved = std::min(mCurrentBufferUnusedSize, size);
+ memcpy(mCurrentBuffer, buffer, dataMoved);
+ mCurrentBufferUnusedSize -= dataMoved;
+ mCurrentBuffer += dataMoved;
+ buffer = reinterpret_cast<const unsigned char*>(buffer) + dataMoved;
+ size -= dataMoved;
+ }
+ return true;
+ }
+ };
+
+ MemOutputStreamLite::MemAdapter mCopyAdapter;
+ io::CopyingOutputStreamAdaptor mImpl;
+};
+
+void GraphicsStatsService::finishDumpInMemory(Dump* dump, const DumpMemoryFn& reader, void* param1,
+ void* param2) {
+ MemOutputStreamLite stream;
+ dump->updateProto();
+ bool success = dump->proto().SerializeToZeroCopyStream(&stream) && stream.Flush();
+ delete dump;
+ if (!success) {
+ return;
+ }
+ stream.copyData(reader, param1, param2);
+}
+
} /* namespace uirenderer */
} /* namespace android */
diff --git a/libs/hwui/service/GraphicsStatsService.h b/libs/hwui/service/GraphicsStatsService.h
index 389f599..4bed9633 100644
--- a/libs/hwui/service/GraphicsStatsService.h
+++ b/libs/hwui/service/GraphicsStatsService.h
@@ -27,6 +27,9 @@
class GraphicsStatsProto;
}
+typedef void (*DumpMemoryFn)(void* buffer, int bufferOffset, int bufferSize, int totalSize,
+ void* param1, void* param2);
+
/*
* The exported entry points used by GraphicsStatsService.java in f/b/services/core
*
@@ -40,6 +43,7 @@
enum class DumpType {
Text,
Protobuf,
+ ProtobufStatsd,
};
ANDROID_API static void saveBuffer(const std::string& path, const std::string& package,
@@ -52,6 +56,8 @@
int64_t startTime, int64_t endTime, const ProfileData* data);
ANDROID_API static void addToDump(Dump* dump, const std::string& path);
ANDROID_API static void finishDump(Dump* dump);
+ ANDROID_API static void finishDumpInMemory(Dump* dump, const DumpMemoryFn& reader, void* param1,
+ void* param2);
// Visible for testing
static bool parseFromFile(const std::string& path, protos::GraphicsStatsProto* output);
diff --git a/libs/hwui/tests/common/TestContext.cpp b/libs/hwui/tests/common/TestContext.cpp
index 0a54aca..e075d80 100644
--- a/libs/hwui/tests/common/TestContext.cpp
+++ b/libs/hwui/tests/common/TestContext.cpp
@@ -25,16 +25,16 @@
static const int IDENT_DISPLAYEVENT = 1;
static android::DisplayInfo DUMMY_DISPLAY{
- 1080, // w
- 1920, // h
- 320.0, // xdpi
- 320.0, // ydpi
- 60.0, // fps
- 2.0, // density
- 0, // orientation
- false, // secure?
- 0, // appVsyncOffset
- 0, // presentationDeadline
+ 1080, // w
+ 1920, // h
+ 320.0, // xdpi
+ 320.0, // ydpi
+ 60.0, // fps
+ 2.0, // density
+ ui::ROTATION_0, // orientation
+ false, // secure?
+ 0, // appVsyncOffset
+ 0, // presentationDeadline
};
DisplayInfo getInternalDisplay() {
diff --git a/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp b/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp
deleted file mode 100644
index dac888c..0000000
--- a/libs/hwui/tests/unit/GpuMemoryTrackerTests.cpp
+++ /dev/null
@@ -1,65 +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.
- */
-
-#include <GpuMemoryTracker.h>
-#include <gtest/gtest.h>
-
-#include "renderthread/EglManager.h"
-#include "renderthread/RenderThread.h"
-#include "tests/common/TestUtils.h"
-
-#include <utils/StrongPointer.h>
-
-using namespace android;
-using namespace android::uirenderer;
-using namespace android::uirenderer::renderthread;
-
-class TestGPUObject : public GpuMemoryTracker {
-public:
- TestGPUObject() : GpuMemoryTracker(GpuObjectType::Texture) {}
-
- void changeSize(int newSize) { notifySizeChanged(newSize); }
-};
-
-// Other tests may have created a renderthread and EGL context.
-// This will destroy the EGLContext on RenderThread if it exists so that the
-// current thread can spoof being a GPU thread
-static void destroyEglContext() {
- if (TestUtils::isRenderThreadRunning()) {
- TestUtils::runOnRenderThread([](RenderThread& thread) { thread.destroyRenderingContext(); });
- }
-}
-
-TEST(GpuMemoryTracker, sizeCheck) {
- destroyEglContext();
-
- GpuMemoryTracker::onGpuContextCreated();
- ASSERT_EQ(0, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
- ASSERT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture));
- {
- TestGPUObject myObj;
- ASSERT_EQ(1, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture));
- myObj.changeSize(500);
- ASSERT_EQ(500, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
- myObj.changeSize(1000);
- ASSERT_EQ(1000, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
- myObj.changeSize(300);
- ASSERT_EQ(300, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
- }
- ASSERT_EQ(0, GpuMemoryTracker::getTotalSize(GpuObjectType::Texture));
- ASSERT_EQ(0, GpuMemoryTracker::getInstanceCount(GpuObjectType::Texture));
- GpuMemoryTracker::onGpuContextDestroyed();
-}
diff --git a/libs/hwui/utils/Color.cpp b/libs/hwui/utils/Color.cpp
index b93759f..c445885 100644
--- a/libs/hwui/utils/Color.cpp
+++ b/libs/hwui/utils/Color.cpp
@@ -108,10 +108,26 @@
}
}
+// FIXME: Share with the version in android_bitmap.cpp?
+// Skia's SkNamedGamut::kDCIP3 is based on a white point of D65. This gamut
+// matches the white point used by ColorSpace.Named.DCIP3.
+static constexpr skcms_Matrix3x3 kDCIP3 = {{
+ {0.486143, 0.323835, 0.154234},
+ {0.226676, 0.710327, 0.0629966},
+ {0.000800549, 0.0432385, 0.78275},
+}};
+
sk_sp<SkColorSpace> DataSpaceToColorSpace(android_dataspace dataspace) {
if (dataspace == HAL_DATASPACE_UNKNOWN) {
return SkColorSpace::MakeSRGB();
}
+ if (dataspace == HAL_DATASPACE_DCI_P3) {
+ // This cannot be handled by the switch statements below because it
+ // needs to use the locally-defined kDCIP3 gamut, rather than the one in
+ // Skia (SkNamedGamut), which is used for other data spaces with
+ // HAL_DATASPACE_STANDARD_DCI_P3 (e.g. HAL_DATASPACE_DISPLAY_P3).
+ return SkColorSpace::MakeRGB({2.6f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, kDCIP3);
+ }
skcms_Matrix3x3 gamut;
switch (dataspace & HAL_DATASPACE_STANDARD_MASK) {
@@ -152,10 +168,12 @@
return SkColorSpace::MakeRGB({2.6f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, gamut);
case HAL_DATASPACE_TRANSFER_GAMMA2_8:
return SkColorSpace::MakeRGB({2.8f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f}, gamut);
+ case HAL_DATASPACE_TRANSFER_ST2084:
+ return SkColorSpace::MakeRGB(SkNamedTransferFn::kPQ, gamut);
+ case HAL_DATASPACE_TRANSFER_SMPTE_170M:
+ return SkColorSpace::MakeRGB(SkNamedTransferFn::kRec2020, gamut);
case HAL_DATASPACE_TRANSFER_UNSPECIFIED:
return nullptr;
- case HAL_DATASPACE_TRANSFER_SMPTE_170M:
- case HAL_DATASPACE_TRANSFER_ST2084:
case HAL_DATASPACE_TRANSFER_HLG:
default:
ALOGV("Unsupported Gamma: %d", dataspace);
diff --git a/location/java/android/location/Criteria.java b/location/java/android/location/Criteria.java
index 1370b10..26f73f7 100644
--- a/location/java/android/location/Criteria.java
+++ b/location/java/android/location/Criteria.java
@@ -16,9 +16,16 @@
package android.location;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* A class indicating the application criteria for selecting a
* location provider. Providers may be ordered according to accuracy,
@@ -26,6 +33,25 @@
* cost.
*/
public class Criteria implements Parcelable {
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({NO_REQUIREMENT, POWER_LOW, POWER_MEDIUM, POWER_HIGH})
+ public @interface PowerRequirement {
+ }
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({NO_REQUIREMENT, ACCURACY_LOW, ACCURACY_MEDIUM, ACCURACY_HIGH})
+ public @interface AccuracyRequirement {
+ }
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({NO_REQUIREMENT, ACCURACY_FINE, ACCURACY_COARSE})
+ public @interface LocationAccuracyRequirement {
+ }
+
/**
* A constant indicating that the application does not choose to
* place requirement on a particular feature.
@@ -81,15 +107,15 @@
*/
public static final int ACCURACY_HIGH = 3;
- private int mHorizontalAccuracy = NO_REQUIREMENT;
- private int mVerticalAccuracy = NO_REQUIREMENT;
- private int mSpeedAccuracy = NO_REQUIREMENT;
- private int mBearingAccuracy = NO_REQUIREMENT;
- private int mPowerRequirement = NO_REQUIREMENT;
- private boolean mAltitudeRequired = false;
- private boolean mBearingRequired = false;
- private boolean mSpeedRequired = false;
- private boolean mCostAllowed = false;
+ private int mHorizontalAccuracy = NO_REQUIREMENT;
+ private int mVerticalAccuracy = NO_REQUIREMENT;
+ private int mSpeedAccuracy = NO_REQUIREMENT;
+ private int mBearingAccuracy = NO_REQUIREMENT;
+ private int mPowerRequirement = NO_REQUIREMENT;
+ private boolean mAltitudeRequired = false;
+ private boolean mBearingRequired = false;
+ private boolean mSpeedRequired = false;
+ private boolean mCostAllowed = false;
/**
* Constructs a new Criteria object. The new object will have no
@@ -97,7 +123,8 @@
* require altitude, speed, or bearing; and will not allow monetary
* cost.
*/
- public Criteria() {}
+ public Criteria() {
+ }
/**
* Constructs a new Criteria object that is a copy of the given criteria.
@@ -115,125 +142,121 @@
}
/**
- * Indicates the desired horizontal accuracy (latitude and longitude).
- * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM},
- * {@link #ACCURACY_HIGH} or {@link #NO_REQUIREMENT}.
- * More accurate location may consume more power and may take longer.
+ * Indicates the desired horizontal accuracy (latitude and longitude). Accuracy may be
+ * {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM}, {@link #ACCURACY_HIGH} or
+ * {@link #NO_REQUIREMENT}. More accurate location may consume more power and may take longer.
*
* @throws IllegalArgumentException if accuracy is not one of the supported constants
*/
- public void setHorizontalAccuracy(int accuracy) {
- if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_HIGH) {
- throw new IllegalArgumentException("accuracy=" + accuracy);
- }
- mHorizontalAccuracy = accuracy;
+ public void setHorizontalAccuracy(@AccuracyRequirement int accuracy) {
+ mHorizontalAccuracy = Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT,
+ ACCURACY_HIGH, "accuracy");
}
/**
* Returns a constant indicating the desired horizontal accuracy (latitude and longitude).
- * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM},
- * {@link #ACCURACY_HIGH} or {@link #NO_REQUIREMENT}.
+ *
+ * @see #setHorizontalAccuracy(int)
*/
+ @AccuracyRequirement
public int getHorizontalAccuracy() {
return mHorizontalAccuracy;
}
/**
- * Indicates the desired vertical accuracy (altitude).
- * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_MEDIUM},
- * {@link #ACCURACY_HIGH} or {@link #NO_REQUIREMENT}.
- * More accurate location may consume more power and may take longer.
+ * Indicates the desired vertical accuracy (altitude). Accuracy may be {@link #ACCURACY_LOW},
+ * {@link #ACCURACY_MEDIUM}, {@link #ACCURACY_HIGH} or {@link #NO_REQUIREMENT}. More accurate
+ * location may consume more power and may take longer.
*
* @throws IllegalArgumentException if accuracy is not one of the supported constants
*/
- public void setVerticalAccuracy(int accuracy) {
- if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_HIGH) {
- throw new IllegalArgumentException("accuracy=" + accuracy);
- }
- mVerticalAccuracy = accuracy;
+ public void setVerticalAccuracy(@AccuracyRequirement int accuracy) {
+ mVerticalAccuracy = Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT,
+ ACCURACY_HIGH, "accuracy");
}
/**
* Returns a constant indicating the desired vertical accuracy (altitude).
- * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH},
- * or {@link #NO_REQUIREMENT}.
+ *
+ * @see #setVerticalAccuracy(int)
*/
+ @AccuracyRequirement
public int getVerticalAccuracy() {
return mVerticalAccuracy;
}
/**
- * Indicates the desired speed accuracy.
- * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH},
- * or {@link #NO_REQUIREMENT}.
- * More accurate location may consume more power and may take longer.
+ * Indicates the desired speed accuracy. Accuracy may be {@link #ACCURACY_LOW},
+ * {@link #ACCURACY_MEDIUM}, {@link #ACCURACY_HIGH}, or {@link #NO_REQUIREMENT}. More accurate
+ * location may consume more power and may take longer.
*
* @throws IllegalArgumentException if accuracy is not one of the supported constants
*/
- public void setSpeedAccuracy(int accuracy) {
- if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_HIGH) {
- throw new IllegalArgumentException("accuracy=" + accuracy);
- }
- mSpeedAccuracy = accuracy;
+ public void setSpeedAccuracy(@AccuracyRequirement int accuracy) {
+ mSpeedAccuracy = Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT, ACCURACY_HIGH,
+ "accuracy");
}
/**
- * Returns a constant indicating the desired speed accuracy
- * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH},
- * or {@link #NO_REQUIREMENT}.
+ * Returns a constant indicating the desired speed accuracy.
+ *
+ * @see #setSpeedAccuracy(int)
*/
+ @AccuracyRequirement
public int getSpeedAccuracy() {
return mSpeedAccuracy;
}
/**
- * Indicates the desired bearing accuracy.
- * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH},
- * or {@link #NO_REQUIREMENT}.
- * More accurate location may consume more power and may take longer.
+ * Indicates the desired bearing accuracy. Accuracy may be {@link #ACCURACY_LOW},
+ * {@link #ACCURACY_MEDIUM}, {@link #ACCURACY_HIGH}, or {@link #NO_REQUIREMENT}. More accurate
+ * location may consume more power and may take longer.
*
* @throws IllegalArgumentException if accuracy is not one of the supported constants
*/
- public void setBearingAccuracy(int accuracy) {
- if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_HIGH) {
- throw new IllegalArgumentException("accuracy=" + accuracy);
- }
- mBearingAccuracy = accuracy;
+ public void setBearingAccuracy(@AccuracyRequirement int accuracy) {
+ mBearingAccuracy = Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT,
+ ACCURACY_HIGH, "accuracy");
}
/**
* Returns a constant indicating the desired bearing accuracy.
- * Accuracy may be {@link #ACCURACY_LOW}, {@link #ACCURACY_HIGH},
- * or {@link #NO_REQUIREMENT}.
+ *
+ * @see #setBearingAccuracy(int)
*/
+ @AccuracyRequirement
public int getBearingAccuracy() {
return mBearingAccuracy;
}
/**
- * Indicates the desired accuracy for latitude and longitude. Accuracy
- * may be {@link #ACCURACY_FINE} if desired location
- * is fine, else it can be {@link #ACCURACY_COARSE}.
- * More accurate location may consume more power and may take longer.
+ * Indicates the desired accuracy for latitude and longitude. Accuracy may be
+ * {@link #ACCURACY_FINE} or {@link #ACCURACY_COARSE}. More accurate location may consume more
+ * power and may take longer.
*
* @throws IllegalArgumentException if accuracy is not one of the supported constants
*/
- public void setAccuracy(int accuracy) {
- if (accuracy < NO_REQUIREMENT || accuracy > ACCURACY_COARSE) {
- throw new IllegalArgumentException("accuracy=" + accuracy);
- }
- if (accuracy == ACCURACY_FINE) {
- mHorizontalAccuracy = ACCURACY_HIGH;
- } else {
- mHorizontalAccuracy = ACCURACY_LOW;
+ public void setAccuracy(@LocationAccuracyRequirement int accuracy) {
+ Preconditions.checkArgumentInRange(accuracy, NO_REQUIREMENT, ACCURACY_COARSE, "accuracy");
+ switch (accuracy) {
+ case NO_REQUIREMENT:
+ setHorizontalAccuracy(NO_REQUIREMENT);
+ break;
+ case ACCURACY_FINE:
+ setHorizontalAccuracy(ACCURACY_HIGH);
+ break;
+ case ACCURACY_COARSE:
+ setHorizontalAccuracy(ACCURACY_LOW);
+ break;
}
}
/**
- * Returns a constant indicating desired accuracy of location
- * Accuracy may be {@link #ACCURACY_FINE} if desired location
- * is fine, else it can be {@link #ACCURACY_COARSE}.
+ * Returns a constant indicating desired accuracy of location.
+ *
+ * @see #setAccuracy(int)
*/
+ @LocationAccuracyRequirement
public int getAccuracy() {
if (mHorizontalAccuracy >= ACCURACY_HIGH) {
return ACCURACY_FINE;
@@ -243,21 +266,20 @@
}
/**
- * Indicates the desired maximum power level. The level parameter
- * must be one of NO_REQUIREMENT, POWER_LOW, POWER_MEDIUM, or
- * POWER_HIGH.
+ * Indicates the desired maximum power requirement. The power requirement parameter may be
+ * {@link #NO_REQUIREMENT}, {@link #POWER_LOW}, {@link #POWER_MEDIUM}, or {@link #POWER_HIGH}.
*/
- public void setPowerRequirement(int level) {
- if (level < NO_REQUIREMENT || level > POWER_HIGH) {
- throw new IllegalArgumentException("level=" + level);
- }
- mPowerRequirement = level;
+ public void setPowerRequirement(@PowerRequirement int powerRequirement) {
+ mPowerRequirement = Preconditions.checkArgumentInRange(powerRequirement, NO_REQUIREMENT,
+ POWER_HIGH, "powerRequirement");
}
/**
- * Returns a constant indicating the desired power requirement. The
- * returned
+ * Returns a constant indicating the desired maximum power requirement.
+ *
+ * @see #setPowerRequirement(int)
*/
+ @PowerRequirement
public int getPowerRequirement() {
return mPowerRequirement;
}
@@ -277,8 +299,8 @@
}
/**
- * Indicates whether the provider must provide altitude information.
- * Not all fixes are guaranteed to contain such information.
+ * Indicates whether the provider must provide altitude information. Not all fixes are
+ * guaranteed to contain such information.
*/
public void setAltitudeRequired(boolean altitudeRequired) {
mAltitudeRequired = altitudeRequired;
@@ -286,15 +308,16 @@
/**
* Returns whether the provider must provide altitude information.
- * Not all fixes are guaranteed to contain such information.
+ *
+ * @see #setAltitudeRequired(boolean)
*/
public boolean isAltitudeRequired() {
return mAltitudeRequired;
}
/**
- * Indicates whether the provider must provide speed information.
- * Not all fixes are guaranteed to contain such information.
+ * Indicates whether the provider must provide speed information. Not all fixes are guaranteed
+ * to contain such information.
*/
public void setSpeedRequired(boolean speedRequired) {
mSpeedRequired = speedRequired;
@@ -302,15 +325,16 @@
/**
* Returns whether the provider must provide speed information.
- * Not all fixes are guaranteed to contain such information.
+ *
+ * @see #setSpeedRequired(boolean)
*/
public boolean isSpeedRequired() {
return mSpeedRequired;
}
/**
- * Indicates whether the provider must provide bearing information.
- * Not all fixes are guaranteed to contain such information.
+ * Indicates whether the provider must provide bearing information. Not all fixes are guaranteed
+ * to contain such information.
*/
public void setBearingRequired(boolean bearingRequired) {
mBearingRequired = bearingRequired;
@@ -318,34 +342,36 @@
/**
* Returns whether the provider must provide bearing information.
- * Not all fixes are guaranteed to contain such information.
+ *
+ * @see #setBearingRequired(boolean)
*/
public boolean isBearingRequired() {
return mBearingRequired;
}
- public static final @android.annotation.NonNull Parcelable.Creator<Criteria> CREATOR =
- new Parcelable.Creator<Criteria>() {
- @Override
- public Criteria createFromParcel(Parcel in) {
- Criteria c = new Criteria();
- c.mHorizontalAccuracy = in.readInt();
- c.mVerticalAccuracy = in.readInt();
- c.mSpeedAccuracy = in.readInt();
- c.mBearingAccuracy = in.readInt();
- c.mPowerRequirement = in.readInt();
- c.mAltitudeRequired = in.readInt() != 0;
- c.mBearingRequired = in.readInt() != 0;
- c.mSpeedRequired = in.readInt() != 0;
- c.mCostAllowed = in.readInt() != 0;
- return c;
- }
+ @NonNull
+ public static final Parcelable.Creator<Criteria> CREATOR =
+ new Parcelable.Creator<Criteria>() {
+ @Override
+ public Criteria createFromParcel(Parcel in) {
+ Criteria c = new Criteria();
+ c.mHorizontalAccuracy = in.readInt();
+ c.mVerticalAccuracy = in.readInt();
+ c.mSpeedAccuracy = in.readInt();
+ c.mBearingAccuracy = in.readInt();
+ c.mPowerRequirement = in.readInt();
+ c.mAltitudeRequired = in.readInt() != 0;
+ c.mBearingRequired = in.readInt() != 0;
+ c.mSpeedRequired = in.readInt() != 0;
+ c.mCostAllowed = in.readInt() != 0;
+ return c;
+ }
- @Override
- public Criteria[] newArray(int size) {
- return new Criteria[size];
- }
- };
+ @Override
+ public Criteria[] newArray(int size) {
+ return new Criteria[size];
+ }
+ };
@Override
public int describeContents() {
@@ -365,42 +391,57 @@
parcel.writeInt(mCostAllowed ? 1 : 0);
}
- private static String powerToString(int power) {
- switch (power) {
- case NO_REQUIREMENT:
- return "NO_REQ";
- case POWER_LOW:
- return "LOW";
- case POWER_MEDIUM:
- return "MEDIUM";
- case POWER_HIGH:
- return "HIGH";
- default:
- return "???";
- }
- }
-
- private static String accuracyToString(int accuracy) {
- switch (accuracy) {
- case NO_REQUIREMENT:
- return "---";
- case ACCURACY_HIGH:
- return "HIGH";
- case ACCURACY_MEDIUM:
- return "MEDIUM";
- case ACCURACY_LOW:
- return "LOW";
- default:
- return "???";
- }
- }
-
@Override
public String toString() {
StringBuilder s = new StringBuilder();
- s.append("Criteria[power=").append(powerToString(mPowerRequirement));
- s.append(" acc=").append(accuracyToString(mHorizontalAccuracy));
+ s.append("Criteria[");
+ s.append("power=").append(requirementToString(mPowerRequirement)).append(", ");
+ s.append("accuracy=").append(requirementToString(mHorizontalAccuracy));
+ if (mVerticalAccuracy != NO_REQUIREMENT) {
+ s.append(", verticalAccuracy=").append(requirementToString(mVerticalAccuracy));
+ }
+ if (mSpeedAccuracy != NO_REQUIREMENT) {
+ s.append(", speedAccuracy=").append(requirementToString(mSpeedAccuracy));
+ }
+ if (mBearingAccuracy != NO_REQUIREMENT) {
+ s.append(", bearingAccuracy=").append(requirementToString(mBearingAccuracy));
+ }
+ if (mAltitudeRequired || mBearingRequired || mSpeedRequired) {
+ s.append(", required=[");
+ if (mAltitudeRequired) {
+ s.append("altitude, ");
+ }
+ if (mBearingRequired) {
+ s.append("bearing, ");
+ }
+ if (mSpeedRequired) {
+ s.append("speed, ");
+ }
+ s.setLength(s.length() - 2);
+ s.append("]");
+ }
+ if (mCostAllowed) {
+ s.append(", costAllowed");
+ }
s.append(']');
return s.toString();
}
+
+ private static String requirementToString(int power) {
+ switch (power) {
+ case NO_REQUIREMENT:
+ return "None";
+ //case ACCURACY_LOW:
+ case POWER_LOW:
+ return "Low";
+ //case ACCURACY_MEDIUM:
+ case POWER_MEDIUM:
+ return "Medium";
+ //case ACCURACY_HIGH:
+ case POWER_HIGH:
+ return "High";
+ default:
+ return "???";
+ }
+ }
}
diff --git a/location/java/com/android/internal/location/ProviderProperties.java b/location/java/com/android/internal/location/ProviderProperties.java
index def96f0..68f9ec3 100644
--- a/location/java/com/android/internal/location/ProviderProperties.java
+++ b/location/java/com/android/internal/location/ProviderProperties.java
@@ -16,15 +16,36 @@
package com.android.internal.location;
+import android.annotation.IntDef;
+import android.location.Criteria;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
/**
* A Parcelable containing (legacy) location provider properties.
* This object is just used inside the framework and system services.
+ *
* @hide
*/
public final class ProviderProperties implements Parcelable {
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({Criteria.POWER_LOW, Criteria.POWER_MEDIUM, Criteria.POWER_HIGH})
+ public @interface PowerRequirement {
+ }
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({Criteria.ACCURACY_FINE, Criteria.ACCURACY_COARSE})
+ public @interface Accuracy {
+ }
+
/**
* True if provider requires access to a
* data network (e.g., the Internet), false otherwise.
@@ -79,58 +100,58 @@
/**
* Power requirement for this provider.
- *
- * @return the power requirement for this provider, as one of the
- * constants Criteria.POWER_*.
*/
+ @PowerRequirement
public final int mPowerRequirement;
/**
* Constant describing the horizontal accuracy returned
* by this provider.
- *
- * @return the horizontal accuracy for this provider, as one of the
- * constants Criteria.ACCURACY_COARSE or Criteria.ACCURACY_FINE
*/
+ @Accuracy
public final int mAccuracy;
- public ProviderProperties(boolean mRequiresNetwork,
- boolean mRequiresSatellite, boolean mRequiresCell, boolean mHasMonetaryCost,
- boolean mSupportsAltitude, boolean mSupportsSpeed, boolean mSupportsBearing,
- int mPowerRequirement, int mAccuracy) {
- this.mRequiresNetwork = mRequiresNetwork;
- this.mRequiresSatellite = mRequiresSatellite;
- this.mRequiresCell = mRequiresCell;
- this.mHasMonetaryCost = mHasMonetaryCost;
- this.mSupportsAltitude = mSupportsAltitude;
- this.mSupportsSpeed = mSupportsSpeed;
- this.mSupportsBearing = mSupportsBearing;
- this.mPowerRequirement = mPowerRequirement;
- this.mAccuracy = mAccuracy;
+ public ProviderProperties(boolean requiresNetwork, boolean requiresSatellite,
+ boolean requiresCell, boolean hasMonetaryCost, boolean supportsAltitude,
+ boolean supportsSpeed, boolean supportsBearing, @PowerRequirement int powerRequirement,
+ @Accuracy int accuracy) {
+ mRequiresNetwork = requiresNetwork;
+ mRequiresSatellite = requiresSatellite;
+ mRequiresCell = requiresCell;
+ mHasMonetaryCost = hasMonetaryCost;
+ mSupportsAltitude = supportsAltitude;
+ mSupportsSpeed = supportsSpeed;
+ mSupportsBearing = supportsBearing;
+ mPowerRequirement = Preconditions.checkArgumentInRange(powerRequirement, Criteria.POWER_LOW,
+ Criteria.POWER_HIGH, "powerRequirement");
+ mAccuracy = Preconditions.checkArgumentInRange(accuracy, Criteria.ACCURACY_FINE,
+ Criteria.ACCURACY_COARSE, "accuracy");
}
public static final Parcelable.Creator<ProviderProperties> CREATOR =
new Parcelable.Creator<ProviderProperties>() {
- @Override
- public ProviderProperties createFromParcel(Parcel in) {
- boolean requiresNetwork = in.readInt() == 1;
- boolean requiresSatellite = in.readInt() == 1;
- boolean requiresCell = in.readInt() == 1;
- boolean hasMonetaryCost = in.readInt() == 1;
- boolean supportsAltitude = in.readInt() == 1;
- boolean supportsSpeed = in.readInt() == 1;
- boolean supportsBearing = in.readInt() == 1;
- int powerRequirement = in.readInt();
- int accuracy = in.readInt();
- return new ProviderProperties(requiresNetwork, requiresSatellite,
- requiresCell, hasMonetaryCost, supportsAltitude, supportsSpeed, supportsBearing,
- powerRequirement, accuracy);
- }
- @Override
- public ProviderProperties[] newArray(int size) {
- return new ProviderProperties[size];
- }
- };
+ @Override
+ public ProviderProperties createFromParcel(Parcel in) {
+ boolean requiresNetwork = in.readInt() == 1;
+ boolean requiresSatellite = in.readInt() == 1;
+ boolean requiresCell = in.readInt() == 1;
+ boolean hasMonetaryCost = in.readInt() == 1;
+ boolean supportsAltitude = in.readInt() == 1;
+ boolean supportsSpeed = in.readInt() == 1;
+ boolean supportsBearing = in.readInt() == 1;
+ int powerRequirement = in.readInt();
+ int accuracy = in.readInt();
+ return new ProviderProperties(requiresNetwork, requiresSatellite,
+ requiresCell, hasMonetaryCost, supportsAltitude, supportsSpeed,
+ supportsBearing,
+ powerRequirement, accuracy);
+ }
+
+ @Override
+ public ProviderProperties[] newArray(int size) {
+ return new ProviderProperties[size];
+ }
+ };
@Override
public int describeContents() {
@@ -149,4 +170,67 @@
parcel.writeInt(mPowerRequirement);
parcel.writeInt(mAccuracy);
}
+
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder("ProviderProperties[");
+ b.append("power=").append(powerToString(mPowerRequirement)).append(", ");
+ b.append("accuracy=").append(accuracyToString(mAccuracy));
+ if (mRequiresNetwork || mRequiresSatellite || mRequiresCell) {
+ b.append(", requires=");
+ if (mRequiresNetwork) {
+ b.append("network,");
+ }
+ if (mRequiresSatellite) {
+ b.append("satellite,");
+ }
+ if (mRequiresCell) {
+ b.append("cell,");
+ }
+ b.setLength(b.length() - 1);
+ }
+ if (mHasMonetaryCost) {
+ b.append(", hasMonetaryCost");
+ }
+ if (mSupportsBearing || mSupportsSpeed || mSupportsAltitude) {
+ b.append(", supports=[");
+ if (mSupportsBearing) {
+ b.append("bearing, ");
+ }
+ if (mSupportsSpeed) {
+ b.append("speed, ");
+ }
+ if (mSupportsAltitude) {
+ b.append("altitude, ");
+ }
+ b.setLength(b.length() - 2);
+ b.append("]");
+ }
+ b.append("]");
+ return b.toString();
+ }
+
+ private static String powerToString(@PowerRequirement int power) {
+ switch (power) {
+ case Criteria.POWER_LOW:
+ return "Low";
+ case Criteria.POWER_MEDIUM:
+ return "Medium";
+ case Criteria.POWER_HIGH:
+ return "High";
+ default:
+ return "???";
+ }
+ }
+
+ private static String accuracyToString(@Accuracy int accuracy) {
+ switch (accuracy) {
+ case Criteria.ACCURACY_COARSE:
+ return "Coarse";
+ case Criteria.ACCURACY_FINE:
+ return "Fine";
+ default:
+ return "???";
+ }
+ }
}
diff --git a/location/java/com/android/internal/location/ProviderRequest.java b/location/java/com/android/internal/location/ProviderRequest.java
index c23f499..8d8df45 100644
--- a/location/java/com/android/internal/location/ProviderRequest.java
+++ b/location/java/com/android/internal/location/ProviderRequest.java
@@ -20,33 +20,42 @@
import android.location.LocationRequest;
import android.os.Parcel;
import android.os.Parcelable;
+import android.os.WorkSource;
import android.util.TimeUtils;
+import com.android.internal.util.Preconditions;
+
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
/** @hide */
public final class ProviderRequest implements Parcelable {
+
+ public static final ProviderRequest EMPTY_REQUEST = new ProviderRequest(false, Long.MAX_VALUE,
+ false, false,
+ Collections.emptyList(), new WorkSource());
+
/** Location reporting is requested (true) */
@UnsupportedAppUsage
- public boolean reportLocation = false;
+ public final boolean reportLocation;
/** The smallest requested interval */
@UnsupportedAppUsage
- public long interval = Long.MAX_VALUE;
+ public final long interval;
+
+ /**
+ * Whether provider shall make stronger than normal tradeoffs to substantially restrict power
+ * use.
+ */
+ public final boolean lowPowerMode;
/**
* When this flag is true, providers should ignore all location settings, user consents, power
* restrictions or any other restricting factors and always satisfy this request to the best of
* their ability. This flag should only be used in event of an emergency.
*/
- public boolean locationSettingsIgnored = false;
-
- /**
- * Whether provider shall make stronger than normal tradeoffs to substantially restrict power
- * use.
- */
- public boolean lowPowerMode = false;
+ public final boolean locationSettingsIgnored;
/**
* A more detailed set of requests.
@@ -56,26 +65,37 @@
* low power fast interval request.
*/
@UnsupportedAppUsage
- public final List<LocationRequest> locationRequests = new ArrayList<>();
+ public final List<LocationRequest> locationRequests;
- @UnsupportedAppUsage
- public ProviderRequest() {
+ public final WorkSource workSource;
+
+ private ProviderRequest(boolean reportLocation, long interval, boolean lowPowerMode,
+ boolean locationSettingsIgnored, List<LocationRequest> locationRequests,
+ WorkSource workSource) {
+ this.reportLocation = reportLocation;
+ this.interval = interval;
+ this.lowPowerMode = lowPowerMode;
+ this.locationSettingsIgnored = locationSettingsIgnored;
+ this.locationRequests = Preconditions.checkNotNull(locationRequests);
+ this.workSource = Preconditions.checkNotNull(workSource);
}
public static final Parcelable.Creator<ProviderRequest> CREATOR =
new Parcelable.Creator<ProviderRequest>() {
@Override
public ProviderRequest createFromParcel(Parcel in) {
- ProviderRequest request = new ProviderRequest();
- request.reportLocation = in.readInt() == 1;
- request.interval = in.readLong();
- request.lowPowerMode = in.readBoolean();
- request.locationSettingsIgnored = in.readBoolean();
+ boolean reportLocation = in.readInt() == 1;
+ long interval = in.readLong();
+ boolean lowPowerMode = in.readBoolean();
+ boolean locationSettingsIgnored = in.readBoolean();
int count = in.readInt();
+ ArrayList<LocationRequest> locationRequests = new ArrayList<>(count);
for (int i = 0; i < count; i++) {
- request.locationRequests.add(LocationRequest.CREATOR.createFromParcel(in));
+ locationRequests.add(LocationRequest.CREATOR.createFromParcel(in));
}
- return request;
+ WorkSource workSource = in.readParcelable(null);
+ return new ProviderRequest(reportLocation, interval, lowPowerMode,
+ locationSettingsIgnored, locationRequests, workSource);
}
@Override
@@ -106,14 +126,13 @@
StringBuilder s = new StringBuilder();
s.append("ProviderRequest[");
if (reportLocation) {
- s.append("ON");
- s.append(" interval=");
+ s.append("interval=");
TimeUtils.formatDuration(interval, s);
if (lowPowerMode) {
- s.append(" lowPowerMode");
+ s.append(", lowPowerMode");
}
if (locationSettingsIgnored) {
- s.append(" locationSettingsIgnored");
+ s.append(", locationSettingsIgnored");
}
} else {
s.append("OFF");
@@ -121,4 +140,67 @@
s.append(']');
return s.toString();
}
+
+ /**
+ * A Builder for {@link ProviderRequest}s.
+ */
+ public static class Builder {
+ private long mInterval = Long.MAX_VALUE;
+ private boolean mLowPowerMode;
+ private boolean mLocationSettingsIgnored;
+ private List<LocationRequest> mLocationRequests = Collections.emptyList();
+ private WorkSource mWorkSource = new WorkSource();
+
+ public long getInterval() {
+ return mInterval;
+ }
+
+ public void setInterval(long interval) {
+ this.mInterval = interval;
+ }
+
+ public boolean isLowPowerMode() {
+ return mLowPowerMode;
+ }
+
+ public void setLowPowerMode(boolean lowPowerMode) {
+ this.mLowPowerMode = lowPowerMode;
+ }
+
+ public boolean isLocationSettingsIgnored() {
+ return mLocationSettingsIgnored;
+ }
+
+ public void setLocationSettingsIgnored(boolean locationSettingsIgnored) {
+ this.mLocationSettingsIgnored = locationSettingsIgnored;
+ }
+
+ public List<LocationRequest> getLocationRequests() {
+ return mLocationRequests;
+ }
+
+ public void setLocationRequests(List<LocationRequest> locationRequests) {
+ this.mLocationRequests = Preconditions.checkNotNull(locationRequests);
+ }
+
+ public WorkSource getWorkSource() {
+ return mWorkSource;
+ }
+
+ public void setWorkSource(WorkSource workSource) {
+ mWorkSource = Preconditions.checkNotNull(workSource);
+ }
+
+ /**
+ * Builds a ProviderRequest object with the set information.
+ */
+ public ProviderRequest build() {
+ if (mInterval == Long.MAX_VALUE) {
+ return EMPTY_REQUEST;
+ } else {
+ return new ProviderRequest(true, mInterval, mLowPowerMode,
+ mLocationSettingsIgnored, mLocationRequests, mWorkSource);
+ }
+ }
+ }
}
diff --git a/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java b/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java
index 751bb6a..127d00c 100644
--- a/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java
+++ b/location/java/com/android/internal/location/gnssmetrics/GnssMetrics.java
@@ -110,7 +110,6 @@
* Logs the status of a location report received from the HAL
*/
public void logReceivedLocationStatus(boolean isSuccessful) {
- StatsLog.write(StatsLog.GPS_LOCATION_STATUS_REPORTED, isSuccessful);
if (!isSuccessful) {
mLocationFailureStatistics.addItem(1.0);
return;
@@ -127,7 +126,6 @@
DEFAULT_TIME_BETWEEN_FIXES_MILLISECS, desiredTimeBetweenFixesMilliSeconds)) - 1;
if (numReportMissed > 0) {
for (int i = 0; i < numReportMissed; i++) {
- StatsLog.write(StatsLog.GPS_LOCATION_STATUS_REPORTED, false);
mLocationFailureStatistics.addItem(1.0);
}
}
@@ -138,7 +136,6 @@
*/
public void logTimeToFirstFixMilliSecs(int timeToFirstFixMilliSeconds) {
mTimeToFirstFixSecStatistics.addItem((double) (timeToFirstFixMilliSeconds / 1000));
- StatsLog.write(StatsLog.GPS_TIME_TO_FIRST_FIX_REPORTED, timeToFirstFixMilliSeconds);
}
/**
diff --git a/media/OWNERS b/media/OWNERS
index 8bd037a..be60583 100644
--- a/media/OWNERS
+++ b/media/OWNERS
@@ -5,6 +5,7 @@
etalvala@google.com
gkasten@google.com
hdmoon@google.com
+hkuang@google.com
hunga@google.com
insun@google.com
jaewan@google.com
diff --git a/media/java/android/media/AudioAttributes.java b/media/java/android/media/AudioAttributes.java
index ece5335..8cd3c6e 100644
--- a/media/java/android/media/AudioAttributes.java
+++ b/media/java/android/media/AudioAttributes.java
@@ -598,6 +598,11 @@
private boolean mMuteHapticChannels = true;
private HashSet<String> mTags = new HashSet<String>();
private Bundle mBundle;
+ private int mPrivacySensitive = PRIVACY_SENSITIVE_DEFAULT;
+
+ private static final int PRIVACY_SENSITIVE_DEFAULT = -1;
+ private static final int PRIVACY_SENSITIVE_DISABLED = 0;
+ private static final int PRIVACY_SENSITIVE_ENABLED = 1;
/**
* Constructs a new Builder with the defaults.
@@ -638,11 +643,20 @@
if (mMuteHapticChannels) {
aa.mFlags |= FLAG_MUTE_HAPTIC;
}
- // capturing for camcorder of communication is private by default to
- // reflect legacy behavior
- if (aa.mSource == MediaRecorder.AudioSource.VOICE_COMMUNICATION
- || aa.mSource == MediaRecorder.AudioSource.CAMCORDER) {
+
+ if (mPrivacySensitive == PRIVACY_SENSITIVE_DEFAULT) {
+ // capturing for camcorder or communication is private by default to
+ // reflect legacy behavior
+ if (mSource == MediaRecorder.AudioSource.VOICE_COMMUNICATION
+ || mSource == MediaRecorder.AudioSource.CAMCORDER) {
+ aa.mFlags |= FLAG_CAPTURE_PRIVATE;
+ } else {
+ aa.mFlags &= ~FLAG_CAPTURE_PRIVATE;
+ }
+ } else if (mPrivacySensitive == PRIVACY_SENSITIVE_ENABLED) {
aa.mFlags |= FLAG_CAPTURE_PRIVATE;
+ } else {
+ aa.mFlags &= ~FLAG_CAPTURE_PRIVATE;
}
aa.mTags = (HashSet<String>) mTags.clone();
aa.mFormattedTags = TextUtils.join(";", mTags);
@@ -967,6 +981,20 @@
mMuteHapticChannels = muted;
return this;
}
+
+ /**
+ * @hide
+ * Indicates if an AudioRecord build with this AudioAttributes is privacy sensitive or not.
+ * See {@link AudioRecord.Builder#setPrivacySensitive(boolean)}.
+ * @param privacySensitive True if capture must be marked as privacy sensitive,
+ * false otherwise.
+ * @return the same Builder instance.
+ */
+ public @NonNull Builder setPrivacySensitive(boolean privacySensitive) {
+ mPrivacySensitive =
+ privacySensitive ? PRIVACY_SENSITIVE_ENABLED : PRIVACY_SENSITIVE_DISABLED;
+ return this;
+ }
};
@Override
diff --git a/media/java/android/media/AudioDeviceInfo.java b/media/java/android/media/AudioDeviceInfo.java
index 8293b5f..cb132f5 100644
--- a/media/java/android/media/AudioDeviceInfo.java
+++ b/media/java/android/media/AudioDeviceInfo.java
@@ -18,6 +18,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.SystemApi;
import android.util.SparseIntArray;
import java.lang.annotation.Retention;
@@ -127,6 +128,23 @@
* A device type describing a Hearing Aid.
*/
public static final int TYPE_HEARING_AID = 23;
+ /**
+ * A device type describing the speaker system (i.e. a mono speaker or stereo speakers) built
+ * in a device, that is specifically tuned for outputting sounds like notifications and alarms
+ * (i.e. sounds the user couldn't necessarily anticipate).
+ * <p>Note that this physical audio device may be the same as {@link #TYPE_BUILTIN_SPEAKER}
+ * but is driven differently to safely accommodate the different use case.</p>
+ */
+ public static final int TYPE_BUILTIN_SPEAKER_SAFE = 24;
+ /**
+ * @hide
+ * A device type for rerouting audio within the Android framework between mixes and
+ * system applications. Typically created when using
+ * {@link android.media.audiopolicy.AudioPolicy} for mixes created with the
+ * {@link android.media.audiopolicy.AudioMix#ROUTE_FLAG_RENDER} flag.
+ */
+ @SystemApi
+ public static final int TYPE_REMOTE_SUBMIX = 25;
/** @hide */
@IntDef(flag = false, prefix = "TYPE", value = {
@@ -228,6 +246,8 @@
case TYPE_IP:
case TYPE_BUS:
case TYPE_HEARING_AID:
+ case TYPE_BUILTIN_SPEAKER_SAFE:
+ case TYPE_REMOTE_SUBMIX:
return true;
default:
return false;
@@ -253,6 +273,7 @@
case TYPE_LINE_DIGITAL:
case TYPE_IP:
case TYPE_BUS:
+ case TYPE_REMOTE_SUBMIX:
return true;
default:
return false;
@@ -449,6 +470,9 @@
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_IP, TYPE_IP);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_BUS, TYPE_BUS);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_HEARING_AID, TYPE_HEARING_AID);
+ INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_SPEAKER_SAFE,
+ TYPE_BUILTIN_SPEAKER_SAFE);
+ INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_OUT_REMOTE_SUBMIX, TYPE_REMOTE_SUBMIX);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BUILTIN_MIC, TYPE_BUILTIN_MIC);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BLUETOOTH_SCO_HEADSET, TYPE_BLUETOOTH_SCO);
@@ -468,10 +492,7 @@
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP, TYPE_BLUETOOTH_A2DP);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_IP, TYPE_IP);
INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_BUS, TYPE_BUS);
-
- // not covered here, legacy
- //AudioSystem.DEVICE_OUT_REMOTE_SUBMIX
- //AudioSystem.DEVICE_IN_REMOTE_SUBMIX
+ INT_TO_EXT_DEVICE_MAPPING.put(AudioSystem.DEVICE_IN_REMOTE_SUBMIX, TYPE_REMOTE_SUBMIX);
// privileges mapping to output device
EXT_TO_INT_DEVICE_MAPPING = new SparseIntArray();
@@ -498,6 +519,9 @@
EXT_TO_INT_DEVICE_MAPPING.put(TYPE_IP, AudioSystem.DEVICE_OUT_IP);
EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BUS, AudioSystem.DEVICE_OUT_BUS);
EXT_TO_INT_DEVICE_MAPPING.put(TYPE_HEARING_AID, AudioSystem.DEVICE_OUT_HEARING_AID);
+ EXT_TO_INT_DEVICE_MAPPING.put(TYPE_BUILTIN_SPEAKER_SAFE,
+ AudioSystem.DEVICE_OUT_SPEAKER_SAFE);
+ EXT_TO_INT_DEVICE_MAPPING.put(TYPE_REMOTE_SUBMIX, AudioSystem.DEVICE_OUT_REMOTE_SUBMIX);
}
}
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index e410882..1b870e8 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -4331,6 +4331,26 @@
}
}
+ /**
+ * @hide
+ * Get the audio devices that would be used for the routing of the given audio attributes.
+ * @param attributes the {@link AudioAttributes} for which the routing is being queried
+ * @return an empty list if there was an issue with the request, a list of audio devices
+ * otherwise (typically one device, except for duplicated paths).
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ public @NonNull List<AudioDeviceAddress> getDevicesForAttributes(
+ @NonNull AudioAttributes attributes) {
+ Objects.requireNonNull(attributes);
+ final IAudioService service = getService();
+ try {
+ return service.getDevicesForAttributes(attributes);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
/**
* Indicate wired accessory connection state change.
* @param device type of device connected/disconnected (AudioManager.DEVICE_OUT_xxx)
diff --git a/media/java/android/media/AudioRecord.java b/media/java/android/media/AudioRecord.java
index fd3523d..4d26b8d 100644
--- a/media/java/android/media/AudioRecord.java
+++ b/media/java/android/media/AudioRecord.java
@@ -315,7 +315,7 @@
* @hide
* Class constructor with {@link AudioAttributes} and {@link AudioFormat}.
* @param attributes a non-null {@link AudioAttributes} instance. Use
- * {@link AudioAttributes.Builder#setAudioSource(int)} for configuring the audio
+ * {@link AudioAttributes.Builder#setCapturePreset(int)} for configuring the audio
* source for this instance.
* @param format a non-null {@link AudioFormat} instance describing the format of the data
* that will be recorded through this AudioRecord. See {@link AudioFormat.Builder} for
@@ -754,17 +754,10 @@
"Cannot request private capture with source: " + source);
}
- int flags = mAttributes.getAllFlags();
- if (mPrivacySensitive == PRIVACY_SENSITIVE_DISABLED) {
- flags &= ~AudioAttributes.FLAG_CAPTURE_PRIVATE;
- } else if (mPrivacySensitive == PRIVACY_SENSITIVE_ENABLED) {
- flags |= AudioAttributes.FLAG_CAPTURE_PRIVATE;
- }
- if (flags != mAttributes.getAllFlags()) {
- mAttributes = new AudioAttributes.Builder(mAttributes)
- .replaceFlags(flags)
- .build();
- }
+ mAttributes = new AudioAttributes.Builder(mAttributes)
+ .setInternalCapturePreset(source)
+ .setPrivacySensitive(mPrivacySensitive == PRIVACY_SENSITIVE_ENABLED)
+ .build();
}
try {
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index e584add..fe57e71 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -33,6 +33,7 @@
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
/* IF YOU CHANGE ANY OF THE CONSTANTS IN THIS FILE, DO NOT FORGET
@@ -1077,6 +1078,41 @@
@UnsupportedAppUsage
public static native int getDevicesForStream(int stream);
+ /**
+ * Do not use directly, see {@link AudioManager#getDevicesForAttributes(AudioAttributes)}
+ * Get the audio devices that would be used for the routing of the given audio attributes.
+ * @param attributes the {@link AudioAttributes} for which the routing is being queried
+ * @return an empty list if there was an issue with the request, a list of audio devices
+ * otherwise (typically one device, except for duplicated paths).
+ */
+ public static @NonNull ArrayList<AudioDeviceAddress> getDevicesForAttributes(
+ @NonNull AudioAttributes attributes) {
+ Objects.requireNonNull(attributes);
+ final AudioDeviceAddress[] devices = new AudioDeviceAddress[MAX_DEVICE_ROUTING];
+ final int res = getDevicesForAttributes(attributes, devices);
+ final ArrayList<AudioDeviceAddress> routeDevices = new ArrayList<>();
+ if (res != SUCCESS) {
+ Log.e(TAG, "error " + res + " in getDevicesForAttributes for " + attributes);
+ return routeDevices;
+ }
+
+ for (AudioDeviceAddress device : devices) {
+ if (device != null) {
+ routeDevices.add(device);
+ }
+ }
+ return routeDevices;
+ }
+
+ /**
+ * Maximum number of audio devices a track is ever routed to, determines the size of the
+ * array passed to {@link #getDevicesForAttributes(AudioAttributes, AudioDeviceAddress[])}
+ */
+ private static final int MAX_DEVICE_ROUTING = 4;
+
+ private static native int getDevicesForAttributes(@NonNull AudioAttributes aa,
+ @NonNull AudioDeviceAddress[] devices);
+
/** @hide returns true if master mono is enabled. */
public static native boolean getMasterMono();
/** @hide enables or disables the master mono mode. */
diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl
index ad7335e..8e8385d 100644
--- a/media/java/android/media/IAudioService.aidl
+++ b/media/java/android/media/IAudioService.aidl
@@ -272,6 +272,8 @@
AudioDeviceAddress getPreferredDeviceForStrategy(in int strategy);
+ List<AudioDeviceAddress> getDevicesForAttributes(in AudioAttributes attributes);
+
// WARNING: read warning at top of file, new methods that need to be used by native
// code via IAudioManager.h need to be added to the top section.
}
diff --git a/media/java/android/media/IMediaRoute2Provider.aidl b/media/java/android/media/IMediaRoute2Provider.aidl
index 02a3816..5dd0b1c 100644
--- a/media/java/android/media/IMediaRoute2Provider.aidl
+++ b/media/java/android/media/IMediaRoute2Provider.aidl
@@ -25,12 +25,12 @@
oneway interface IMediaRoute2Provider {
void setClient(IMediaRoute2ProviderClient client);
void requestCreateSession(String packageName, String routeId,
- String controlCategory, long requestId);
- void releaseSession(int sessionId);
+ String routeFeature, long requestId);
+ void releaseSession(String sessionId);
- void selectRoute(int sessionId, String routeId);
- void deselectRoute(int sessionId, String routeId);
- void transferToRoute(int sessionId, String routeId);
+ void selectRoute(String sessionId, String routeId);
+ void deselectRoute(String sessionId, String routeId);
+ void transferToRoute(String sessionId, String routeId);
void notifyControlRequestSent(String id, in Intent request);
void requestSetVolume(String id, int volume);
diff --git a/media/java/android/media/IMediaRoute2ProviderClient.aidl b/media/java/android/media/IMediaRoute2ProviderClient.aidl
index bcb2336..0fccb3a 100644
--- a/media/java/android/media/IMediaRoute2ProviderClient.aidl
+++ b/media/java/android/media/IMediaRoute2ProviderClient.aidl
@@ -18,15 +18,17 @@
import android.media.MediaRoute2ProviderInfo;
import android.media.MediaRoute2Info;
-import android.media.RouteSessionInfo;
+import android.media.RoutingSessionInfo;
import android.os.Bundle;
/**
* @hide
*/
oneway interface IMediaRoute2ProviderClient {
- void updateState(in MediaRoute2ProviderInfo providerInfo,
- in List<RouteSessionInfo> sessionInfos);
- void notifySessionCreated(in @nullable RouteSessionInfo sessionInfo, long requestId);
- void notifySessionInfoChanged(in RouteSessionInfo sessionInfo);
+ // TODO: Change it to updateRoutes?
+ void updateState(in MediaRoute2ProviderInfo providerInfo);
+ void notifySessionCreated(in RoutingSessionInfo sessionInfo, long requestId);
+ void notifySessionCreationFailed(long requestId);
+ void notifySessionUpdated(in RoutingSessionInfo sessionInfo);
+ void notifySessionReleased(in RoutingSessionInfo sessionInfo);
}
diff --git a/media/java/android/media/IMediaRouter2Client.aidl b/media/java/android/media/IMediaRouter2Client.aidl
index 18a6428..bc7ebea 100644
--- a/media/java/android/media/IMediaRouter2Client.aidl
+++ b/media/java/android/media/IMediaRouter2Client.aidl
@@ -17,7 +17,7 @@
package android.media;
import android.media.MediaRoute2Info;
-import android.media.RouteSessionInfo;
+import android.media.RoutingSessionInfo;
import android.os.Bundle;
/**
@@ -28,6 +28,7 @@
void notifyRoutesAdded(in List<MediaRoute2Info> routes);
void notifyRoutesRemoved(in List<MediaRoute2Info> routes);
void notifyRoutesChanged(in List<MediaRoute2Info> routes);
- void notifySessionCreated(in @nullable RouteSessionInfo sessionInfo, int requestId);
- void notifySessionInfoChanged(in RouteSessionInfo sessionInfo);
+ void notifySessionCreated(in @nullable RoutingSessionInfo sessionInfo, int requestId);
+ void notifySessionInfoChanged(in RoutingSessionInfo sessionInfo);
+ void notifySessionReleased(in RoutingSessionInfo sessionInfo);
}
diff --git a/media/java/android/media/IMediaRouter2Manager.aidl b/media/java/android/media/IMediaRouter2Manager.aidl
index b7cb705..e9add17 100644
--- a/media/java/android/media/IMediaRouter2Manager.aidl
+++ b/media/java/android/media/IMediaRouter2Manager.aidl
@@ -24,7 +24,7 @@
*/
oneway interface IMediaRouter2Manager {
void notifyRouteSelected(String packageName, in MediaRoute2Info route);
- void notifyControlCategoriesChanged(String packageName, in List<String> categories);
+ void notifyPreferredFeaturesChanged(String packageName, in List<String> preferredFeatures);
void notifyRoutesAdded(in List<MediaRoute2Info> routes);
void notifyRoutesRemoved(in List<MediaRoute2Info> routes);
void notifyRoutesChanged(in List<MediaRoute2Info> routes);
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index 4b7d802..3cdaa07 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -22,7 +22,8 @@
import android.media.IMediaRouterClient;
import android.media.MediaRoute2Info;
import android.media.MediaRouterClientState;
-import android.media.RouteSessionInfo;
+import android.media.RouteDiscoveryPreference;
+import android.media.RoutingSessionInfo;
/**
* {@hide}
@@ -51,11 +52,12 @@
void requestUpdateVolume2(IMediaRouter2Client client, in MediaRoute2Info route, int direction);
void requestCreateSession(IMediaRouter2Client client, in MediaRoute2Info route,
- String controlCategory, int requestId);
- void setControlCategories(IMediaRouter2Client client, in List<String> categories);
+ String routeFeature, int requestId);
+ void setDiscoveryRequest2(IMediaRouter2Client client, in RouteDiscoveryPreference preference);
void selectRoute(IMediaRouter2Client client, String sessionId, in MediaRoute2Info route);
void deselectRoute(IMediaRouter2Client client, String sessionId, in MediaRoute2Info route);
void transferToRoute(IMediaRouter2Client client, String sessionId, in MediaRoute2Info route);
+ void releaseSession(IMediaRouter2Client client, String sessionId);
void registerManager(IMediaRouter2Manager manager, String packageName);
void unregisterManager(IMediaRouter2Manager manager);
@@ -68,5 +70,5 @@
void requestUpdateVolume2Manager(IMediaRouter2Manager manager,
in MediaRoute2Info route, int direction);
- List<RouteSessionInfo> getActiveSessions(IMediaRouter2Manager manager);
+ List<RoutingSessionInfo> getActiveSessions(IMediaRouter2Manager manager);
}
diff --git a/media/java/android/media/MediaMetadataRetriever.java b/media/java/android/media/MediaMetadataRetriever.java
index 7fca03c..4cd581b 100644
--- a/media/java/android/media/MediaMetadataRetriever.java
+++ b/media/java/android/media/MediaMetadataRetriever.java
@@ -17,6 +17,7 @@
package android.media;
import android.annotation.IntDef;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
@@ -354,8 +355,8 @@
* is less than or equal to 0.
* @see {@link #getScaledFrameAtTime(long, int, int, int, BitmapParams)}
*/
- public @Nullable Bitmap getScaledFrameAtTime(
- long timeUs, @Option int option, int dstWidth, int dstHeight) {
+ public @Nullable Bitmap getScaledFrameAtTime(long timeUs, @Option int option,
+ @IntRange(from=1) int dstWidth, @IntRange(from=1) int dstHeight) {
validate(option, dstWidth, dstHeight);
return _getFrameAtTime(timeUs, option, dstWidth, dstHeight, null);
}
@@ -400,7 +401,8 @@
* @see {@link #getScaledFrameAtTime(long, int, int, int)}
*/
public @Nullable Bitmap getScaledFrameAtTime(long timeUs, @Option int option,
- int dstWidth, int dstHeight, @NonNull BitmapParams params) {
+ @IntRange(from=1) int dstWidth, @IntRange(from=1) int dstHeight,
+ @NonNull BitmapParams params) {
validate(option, dstWidth, dstHeight);
return _getFrameAtTime(timeUs, option, dstWidth, dstHeight, params);
}
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 506d616..239dfed 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -16,6 +16,8 @@
package android.media;
+import static android.media.MediaRouter2Utils.toUniqueId;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -34,7 +36,6 @@
/**
* Describes the properties of a route.
- * @hide
*/
public final class MediaRoute2Info implements Parcelable {
@NonNull
@@ -54,7 +55,7 @@
@IntDef({CONNECTION_STATE_DISCONNECTED, CONNECTION_STATE_CONNECTING,
CONNECTION_STATE_CONNECTED})
@Retention(RetentionPolicy.SOURCE)
- private @interface ConnectionState {}
+ public @interface ConnectionState {}
/**
* The default connection state indicating the route is disconnected.
@@ -83,28 +84,29 @@
* controlled from this object. An example of fixed playback volume is a remote player,
* playing over HDMI where the user prefers to control the volume on the HDMI sink, rather
* than attenuate at the source.
+ *
* @see #getVolumeHandling()
*/
public static final int PLAYBACK_VOLUME_FIXED = 0;
/**
* Playback information indicating the playback volume is variable and can be controlled
* from this object.
+ *
* @see #getVolumeHandling()
*/
public static final int PLAYBACK_VOLUME_VARIABLE = 1;
/** @hide */
@IntDef({
- DEVICE_TYPE_UNKNOWN, DEVICE_TYPE_TV,
- DEVICE_TYPE_SPEAKER, DEVICE_TYPE_BLUETOOTH})
+ DEVICE_TYPE_UNKNOWN, DEVICE_TYPE_REMOTE_TV,
+ DEVICE_TYPE_REMOTE_SPEAKER, DEVICE_TYPE_BLUETOOTH})
@Retention(RetentionPolicy.SOURCE)
- private @interface DeviceType {}
+ public @interface DeviceType {}
/**
* The default receiver device type of the route indicating the type is unknown.
*
* @see #getDeviceType
- * @hide
*/
public static final int DEVICE_TYPE_UNKNOWN = 0;
@@ -114,7 +116,7 @@
*
* @see #getDeviceType
*/
- public static final int DEVICE_TYPE_TV = 1;
+ public static final int DEVICE_TYPE_REMOTE_TV = 1;
/**
* A receiver device type of the route indicating the presentation of the media is happening
@@ -122,86 +124,214 @@
*
* @see #getDeviceType
*/
- public static final int DEVICE_TYPE_SPEAKER = 2;
+ public static final int DEVICE_TYPE_REMOTE_SPEAKER = 2;
/**
* A receiver device type of the route indicating the presentation of the media is happening
* on a bluetooth device such as a bluetooth speaker.
*
* @see #getDeviceType
- * @hide
*/
public static final int DEVICE_TYPE_BLUETOOTH = 3;
- @NonNull
final String mId;
- @Nullable
- final String mProviderId;
- @NonNull
final CharSequence mName;
- @Nullable
- final CharSequence mDescription;
- @Nullable
- final @ConnectionState int mConnectionState;
- @Nullable
+ final List<String> mFeatures;
+ @DeviceType
+ final int mDeviceType;
final Uri mIconUri;
- @Nullable
+ final CharSequence mDescription;
+ @ConnectionState
+ final int mConnectionState;
final String mClientPackageName;
- @NonNull
- final List<String> mSupportedCategories;
final int mVolume;
final int mVolumeMax;
final int mVolumeHandling;
- final @DeviceType int mDeviceType;
- @Nullable
final Bundle mExtras;
-
- private final String mUniqueId;
+ final String mProviderId;
MediaRoute2Info(@NonNull Builder builder) {
mId = builder.mId;
- mProviderId = builder.mProviderId;
mName = builder.mName;
+ mFeatures = builder.mFeatures;
+ mDeviceType = builder.mDeviceType;
+ mIconUri = builder.mIconUri;
mDescription = builder.mDescription;
mConnectionState = builder.mConnectionState;
- mIconUri = builder.mIconUri;
mClientPackageName = builder.mClientPackageName;
- mSupportedCategories = builder.mSupportedCategories;
- mVolume = builder.mVolume;
- mVolumeMax = builder.mVolumeMax;
mVolumeHandling = builder.mVolumeHandling;
- mDeviceType = builder.mDeviceType;
+ mVolumeMax = builder.mVolumeMax;
+ mVolume = builder.mVolume;
mExtras = builder.mExtras;
- mUniqueId = createUniqueId();
+ mProviderId = builder.mProviderId;
}
MediaRoute2Info(@NonNull Parcel in) {
mId = in.readString();
- mProviderId = in.readString();
mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+ mFeatures = in.createStringArrayList();
+ mDeviceType = in.readInt();
+ mIconUri = in.readParcelable(null);
mDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
mConnectionState = in.readInt();
- mIconUri = in.readParcelable(null);
mClientPackageName = in.readString();
- mSupportedCategories = in.createStringArrayList();
- mVolume = in.readInt();
- mVolumeMax = in.readInt();
mVolumeHandling = in.readInt();
- mDeviceType = in.readInt();
+ mVolumeMax = in.readInt();
+ mVolume = in.readInt();
mExtras = in.readBundle();
- mUniqueId = createUniqueId();
+ mProviderId = in.readString();
}
- private String createUniqueId() {
- String uniqueId = null;
+ /**
+ * Gets the id of the route. The routes which are given by {@link MediaRouter2} will have
+ * unique IDs.
+ * <p>
+ * In order to ensure uniqueness in {@link MediaRouter2} side, the value of this method
+ * can be different from what was set in {@link MediaRoute2ProviderService}.
+ *
+ * @see Builder#Builder(String, CharSequence)
+ */
+ @NonNull
+ public String getId() {
if (mProviderId != null) {
- uniqueId = toUniqueId(mProviderId, mId);
+ return toUniqueId(mProviderId, mId);
+ } else {
+ return mId;
}
- return uniqueId;
}
- static String toUniqueId(String providerId, String routeId) {
- return providerId + ":" + routeId;
+ /**
+ * Gets the user-visible name of the route.
+ */
+ @NonNull
+ public CharSequence getName() {
+ return mName;
+ }
+
+ /**
+ * Gets the supported features of the route.
+ */
+ @NonNull
+ public List<String> getFeatures() {
+ return mFeatures;
+ }
+
+ /**
+ * Gets the type of the receiver device associated with this route.
+ *
+ * @return The type of the receiver device associated with this route:
+ * {@link #DEVICE_TYPE_REMOTE_TV}, {@link #DEVICE_TYPE_REMOTE_SPEAKER},
+ * {@link #DEVICE_TYPE_BLUETOOTH}.
+ */
+ @DeviceType
+ public int getDeviceType() {
+ return mDeviceType;
+ }
+
+ /**
+ * Gets the URI of the icon representing this route.
+ * <p>
+ * This icon will be used in picker UIs if available.
+ *
+ * @return The URI of the icon representing this route, or null if none.
+ */
+ @Nullable
+ public Uri getIconUri() {
+ return mIconUri;
+ }
+
+ /**
+ * Gets the user-visible description of the route.
+ */
+ @Nullable
+ public CharSequence getDescription() {
+ return mDescription;
+ }
+
+ /**
+ * Gets the connection state of the route.
+ *
+ * @return The connection state of this route: {@link #CONNECTION_STATE_DISCONNECTED},
+ * {@link #CONNECTION_STATE_CONNECTING}, or {@link #CONNECTION_STATE_CONNECTED}.
+ */
+ @ConnectionState
+ public int getConnectionState() {
+ return mConnectionState;
+ }
+
+ /**
+ * Gets the package name of the client that uses the route.
+ * Returns null if no clients use this route.
+ * @hide
+ */
+ @Nullable
+ public String getClientPackageName() {
+ return mClientPackageName;
+ }
+
+ /**
+ * Gets information about how volume is handled on the route.
+ *
+ * @return {@link #PLAYBACK_VOLUME_FIXED} or {@link #PLAYBACK_VOLUME_VARIABLE}
+ */
+ public int getVolumeHandling() {
+ return mVolumeHandling;
+ }
+
+ /**
+ * Gets the maximum volume of the route.
+ */
+ public int getVolumeMax() {
+ return mVolumeMax;
+ }
+
+ /**
+ * Gets the current volume of the route. This may be invalid if the route is not selected.
+ */
+ public int getVolume() {
+ return mVolume;
+ }
+
+ @Nullable
+ public Bundle getExtras() {
+ return mExtras == null ? null : new Bundle(mExtras);
+ }
+
+ /**
+ * Gets the original id set by {@link Builder#Builder(String, CharSequence)}.
+ * @hide
+ */
+ @NonNull
+ public String getOriginalId() {
+ return mId;
+ }
+
+ /**
+ * Gets the provider id of the route. It is assigned automatically by
+ * {@link com.android.server.media.MediaRouterService}.
+ *
+ * @return provider id of the route or null if it's not set.
+ * @hide
+ */
+ @Nullable
+ public String getProviderId() {
+ return mProviderId;
+ }
+
+ /**
+ * Returns if the route has at least one of the specified route features.
+ *
+ * @param features the list of route features to consider
+ * @return true if the route has at least one feature in the list
+ */
+ public boolean hasAnyFeatures(@NonNull Collection<String> features) {
+ Objects.requireNonNull(features, "features must not be null");
+ for (String feature : features) {
+ if (getFeatures().contains(feature)) {
+ return true;
+ }
+ }
+ return false;
}
/**
@@ -228,184 +358,49 @@
return false;
}
MediaRoute2Info other = (MediaRoute2Info) obj;
+
+ // Note: mExtras is not included.
return Objects.equals(mId, other.mId)
- && Objects.equals(mProviderId, other.mProviderId)
&& Objects.equals(mName, other.mName)
+ && Objects.equals(mFeatures, other.mFeatures)
+ && (mDeviceType == other.mDeviceType)
+ && Objects.equals(mIconUri, other.mIconUri)
&& Objects.equals(mDescription, other.mDescription)
&& (mConnectionState == other.mConnectionState)
- && Objects.equals(mIconUri, other.mIconUri)
&& Objects.equals(mClientPackageName, other.mClientPackageName)
- && Objects.equals(mSupportedCategories, other.mSupportedCategories)
- && (mVolume == other.mVolume)
- && (mVolumeMax == other.mVolumeMax)
&& (mVolumeHandling == other.mVolumeHandling)
- && (mDeviceType == other.mDeviceType)
- //TODO: This will be evaluated as false in most cases. Try not to.
- && Objects.equals(mExtras, other.mExtras);
+ && (mVolumeMax == other.mVolumeMax)
+ && (mVolume == other.mVolume)
+ && Objects.equals(mProviderId, other.mProviderId);
}
@Override
public int hashCode() {
- return Objects.hash(mId, mName, mDescription, mConnectionState, mIconUri,
- mSupportedCategories, mVolume, mVolumeMax, mVolumeHandling, mDeviceType);
+ // Note: mExtras is not included.
+ return Objects.hash(mId, mName, mFeatures, mDeviceType, mIconUri, mDescription,
+ mConnectionState, mClientPackageName, mVolumeHandling, mVolumeMax, mVolume,
+ mProviderId);
}
- /**
- * Gets the id of the route.
- * Use {@link #getUniqueId()} if you need a unique identifier.
- *
- * @see #getUniqueId()
- */
- @NonNull
- public String getId() {
- return mId;
- }
-
- /**
- * Gets the unique id of the route. A route obtained from
- * {@link com.android.server.media.MediaRouterService} always has a unique id.
- *
- * @return unique id of the route or null if it has no unique id.
- */
- @Nullable
- public String getUniqueId() {
- return mUniqueId;
- }
-
- /**
- * Gets the provider id of the route. It is assigned automatically by
- * {@link com.android.server.media.MediaRouterService}.
- *
- * @return provider id of the route or null if it's not set.
- * @hide
- */
- @Nullable
- public String getProviderId() {
- return mProviderId;
- }
-
- @NonNull
- public CharSequence getName() {
- return mName;
- }
-
- @Nullable
- public CharSequence getDescription() {
- return mDescription;
- }
-
- /**
- * Gets the connection state of the route.
- *
- * @return The connection state of this route: {@link #CONNECTION_STATE_DISCONNECTED},
- * {@link #CONNECTION_STATE_CONNECTING}, or {@link #CONNECTION_STATE_CONNECTED}.
- */
- @ConnectionState
- public int getConnectionState() {
- return mConnectionState;
- }
-
- /**
- * Gets the URI of the icon representing this route.
- * <p>
- * This icon will be used in picker UIs if available.
- *
- * @return The URI of the icon representing this route, or null if none.
- */
- @Nullable
- public Uri getIconUri() {
- return mIconUri;
- }
-
- /**
- * Gets the package name of the client that uses the route.
- * Returns null if no clients use this.
- * @hide
- */
- @Nullable
- public String getClientPackageName() {
- return mClientPackageName;
- }
-
- /**
- * Gets the supported categories of the route.
- */
- @NonNull
- public List<String> getSupportedCategories() {
- return mSupportedCategories;
- }
-
- //TODO: once device types are confirmed, reflect those into the comment.
- /**
- * Gets the type of the receiver device associated with this route.
- *
- * @return The type of the receiver device associated with this route:
- * {@link #DEVICE_TYPE_TV} or {@link #DEVICE_TYPE_SPEAKER}.
- */
- @DeviceType
- public int getDeviceType() {
- return mDeviceType;
- }
-
- /**
- * Gets the current volume of the route. This may be invalid if the route is not selected.
- */
- public int getVolume() {
- return mVolume;
- }
-
- /**
- * Gets the maximum volume of the route.
- */
- public int getVolumeMax() {
- return mVolumeMax;
- }
-
- /**
- * Gets information about how volume is handled on the route.
- *
- * @return {@link #PLAYBACK_VOLUME_FIXED} or {@link #PLAYBACK_VOLUME_VARIABLE}
- */
- public int getVolumeHandling() {
- return mVolumeHandling;
- }
-
- @Nullable
- public Bundle getExtras() {
- return mExtras;
- }
-
- /**
- * Returns if the route supports the specified control category
- *
- * @param controlCategory control category to consider
- * @return true if the route supports at the category
- */
- public boolean supportsControlCategory(@NonNull String controlCategory) {
- Objects.requireNonNull(controlCategory, "control category must not be null");
- for (String supportedCategory : getSupportedCategories()) {
- if (TextUtils.equals(controlCategory, supportedCategory)) {
- return true;
- }
- }
- return false;
- }
-
- //TODO: Move this if we re-define control category / selector things.
- /**
- * Returns if the route supports at least one of the specified control categories
- *
- * @param controlCategories the list of control categories to consider
- * @return true if the route supports at least one category
- */
- public boolean supportsControlCategories(@NonNull Collection<String> controlCategories) {
- Objects.requireNonNull(controlCategories, "control categories must not be null");
- for (String controlCategory : controlCategories) {
- if (supportsControlCategory(controlCategory)) {
- return true;
- }
- }
- return false;
+ @Override
+ public String toString() {
+ // Note: mExtras is not printed here.
+ StringBuilder result = new StringBuilder()
+ .append("MediaRoute2Info{ ")
+ .append("id=").append(getId())
+ .append(", name=").append(getName())
+ .append(", features=").append(getFeatures())
+ .append(", deviceType=").append(getDeviceType())
+ .append(", iconUri=").append(getIconUri())
+ .append(", description=").append(getDescription())
+ .append(", connectionState=").append(getConnectionState())
+ .append(", clientPackageName=").append(getClientPackageName())
+ .append(", volumeHandling=").append(getVolumeHandling())
+ .append(", volumeMax=").append(getVolumeMax())
+ .append(", volume=").append(getVolume())
+ .append(", providerId=").append(getProviderId())
+ .append(" }");
+ return result.toString();
}
@Override
@@ -416,142 +411,128 @@
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(mId);
- dest.writeString(mProviderId);
TextUtils.writeToParcel(mName, dest, flags);
+ dest.writeStringList(mFeatures);
+ dest.writeInt(mDeviceType);
+ dest.writeParcelable(mIconUri, flags);
TextUtils.writeToParcel(mDescription, dest, flags);
dest.writeInt(mConnectionState);
- dest.writeParcelable(mIconUri, flags);
dest.writeString(mClientPackageName);
- dest.writeStringList(mSupportedCategories);
- dest.writeInt(mVolume);
- dest.writeInt(mVolumeMax);
dest.writeInt(mVolumeHandling);
- dest.writeInt(mDeviceType);
+ dest.writeInt(mVolumeMax);
+ dest.writeInt(mVolume);
dest.writeBundle(mExtras);
- }
-
- @Override
- public String toString() {
- StringBuilder result = new StringBuilder()
- .append("MediaRouteInfo{ ")
- .append("id=").append(getId())
- .append(", name=").append(getName())
- .append(", description=").append(getDescription())
- .append(", connectionState=").append(getConnectionState())
- .append(", iconUri=").append(getIconUri())
- .append(", volume=").append(getVolume())
- .append(", volumeMax=").append(getVolumeMax())
- .append(", volumeHandling=").append(getVolumeHandling())
- .append(", deviceType=").append(getDeviceType())
- .append(", providerId=").append(getProviderId())
- .append(" }");
- return result.toString();
+ dest.writeString(mProviderId);
}
/**
* Builder for {@link MediaRoute2Info media route info}.
*/
public static final class Builder {
- String mId;
- String mProviderId;
- CharSequence mName;
+ final String mId;
+ final CharSequence mName;
+ final List<String> mFeatures;
+
+ @DeviceType
+ int mDeviceType = DEVICE_TYPE_UNKNOWN;
+ Uri mIconUri;
CharSequence mDescription;
@ConnectionState
int mConnectionState;
- Uri mIconUri;
String mClientPackageName;
- List<String> mSupportedCategories;
- int mVolume;
- int mVolumeMax;
int mVolumeHandling = PLAYBACK_VOLUME_FIXED;
- @DeviceType
- int mDeviceType = DEVICE_TYPE_UNKNOWN;
+ int mVolumeMax;
+ int mVolume;
Bundle mExtras;
+ String mProviderId;
+ /**
+ * Constructor for builder to create {@link MediaRoute2Info}.
+ * <p>
+ * In order to ensure ID uniqueness, the {@link MediaRoute2Info#getId() ID} of a route info
+ * obtained from {@link MediaRouter2} can be different from what was set in
+ * {@link MediaRoute2ProviderService}.
+ * </p>
+ * @param id The ID of the route. Must not be empty.
+ * @param name The user-visible name of the route.
+ */
public Builder(@NonNull String id, @NonNull CharSequence name) {
- setId(id);
- setName(name);
- mSupportedCategories = new ArrayList<>();
+ if (TextUtils.isEmpty(id)) {
+ throw new IllegalArgumentException("id must not be empty");
+ }
+ if (TextUtils.isEmpty(name)) {
+ throw new IllegalArgumentException("name must not be empty");
+ }
+ mId = id;
+ mName = name;
+ mFeatures = new ArrayList<>();
}
+ /**
+ * Constructor for builder to create {@link MediaRoute2Info} with
+ * existing {@link MediaRoute2Info} instance.
+ *
+ * @param routeInfo the existing instance to copy data from.
+ */
public Builder(@NonNull MediaRoute2Info routeInfo) {
- if (routeInfo == null) {
- throw new IllegalArgumentException("route info must not be null");
- }
+ Objects.requireNonNull(routeInfo, "routeInfo must not be null");
- setId(routeInfo.mId);
- if (!TextUtils.isEmpty(routeInfo.mProviderId)) {
- setProviderId(routeInfo.mProviderId);
- }
- setName(routeInfo.mName);
+ mId = routeInfo.mId;
+ mName = routeInfo.mName;
+ mFeatures = new ArrayList<>(routeInfo.mFeatures);
+ mDeviceType = routeInfo.mDeviceType;
+ mIconUri = routeInfo.mIconUri;
mDescription = routeInfo.mDescription;
mConnectionState = routeInfo.mConnectionState;
- mIconUri = routeInfo.mIconUri;
- setClientPackageName(routeInfo.mClientPackageName);
- setSupportedCategories(routeInfo.mSupportedCategories);
- setVolume(routeInfo.mVolume);
- setVolumeMax(routeInfo.mVolumeMax);
- setVolumeHandling(routeInfo.mVolumeHandling);
- setDeviceType(routeInfo.mDeviceType);
+ mClientPackageName = routeInfo.mClientPackageName;
+ mVolumeHandling = routeInfo.mVolumeHandling;
+ mVolumeMax = routeInfo.mVolumeMax;
+ mVolume = routeInfo.mVolume;
if (routeInfo.mExtras != null) {
mExtras = new Bundle(routeInfo.mExtras);
}
+ mProviderId = routeInfo.mProviderId;
}
/**
- * Sets the unique id of the route.
+ * Adds a feature for the route.
*/
@NonNull
- public Builder setId(@NonNull String id) {
- if (TextUtils.isEmpty(id)) {
- throw new IllegalArgumentException("id must not be null or empty");
+ public Builder addFeature(@NonNull String feature) {
+ if (TextUtils.isEmpty(feature)) {
+ throw new IllegalArgumentException("feature must not be null or empty");
}
- mId = id;
+ mFeatures.add(feature);
return this;
}
/**
- * Sets the provider id of the route.
- * @hide
+ * Adds features for the route. A route must support at least one route type.
*/
@NonNull
- public Builder setProviderId(@NonNull String providerId) {
- if (TextUtils.isEmpty(providerId)) {
- throw new IllegalArgumentException("providerId must not be null or empty");
+ public Builder addFeatures(@NonNull Collection<String> features) {
+ Objects.requireNonNull(features, "features must not be null");
+ for (String feature : features) {
+ addFeature(feature);
}
- mProviderId = providerId;
return this;
}
/**
- * Sets the user-visible name of the route.
+ * Clears the features of the route. A route must support at least one route type.
*/
@NonNull
- public Builder setName(@NonNull CharSequence name) {
- Objects.requireNonNull(name, "name must not be null");
- mName = name;
+ public Builder clearFeatures() {
+ mFeatures.clear();
return this;
}
/**
- * Sets the user-visible description of the route.
+ * Sets the route's device type.
*/
@NonNull
- public Builder setDescription(@Nullable String description) {
- mDescription = description;
- return this;
- }
-
- /**
- * Sets the route's connection state.
- *
- * {@link #CONNECTION_STATE_DISCONNECTED},
- * {@link #CONNECTION_STATE_CONNECTING}, or
- * {@link #CONNECTION_STATE_CONNECTED}.
- */
- @NonNull
- public Builder setConnectionState(@ConnectionState int connectionState) {
- mConnectionState = connectionState;
+ public Builder setDeviceType(@DeviceType int deviceType) {
+ mDeviceType = deviceType;
return this;
}
@@ -576,6 +557,28 @@
}
/**
+ * Sets the user-visible description of the route.
+ */
+ @NonNull
+ public Builder setDescription(@Nullable CharSequence description) {
+ mDescription = description;
+ return this;
+ }
+
+ /**
+ * Sets the route's connection state.
+ *
+ * {@link #CONNECTION_STATE_DISCONNECTED},
+ * {@link #CONNECTION_STATE_CONNECTING}, or
+ * {@link #CONNECTION_STATE_CONNECTED}.
+ */
+ @NonNull
+ public Builder setConnectionState(@ConnectionState int connectionState) {
+ mConnectionState = connectionState;
+ return this;
+ }
+
+ /**
* Sets the package name of the app using the route.
*/
@NonNull
@@ -585,57 +588,6 @@
}
/**
- * Sets the supported categories of the route.
- */
- @NonNull
- public Builder setSupportedCategories(@NonNull Collection<String> categories) {
- mSupportedCategories = new ArrayList<>();
- return addSupportedCategories(categories);
- }
-
- /**
- * Adds supported categories for the route.
- */
- @NonNull
- public Builder addSupportedCategories(@NonNull Collection<String> categories) {
- Objects.requireNonNull(categories, "categories must not be null");
- for (String category: categories) {
- addSupportedCategory(category);
- }
- return this;
- }
-
- /**
- * Add a supported category for the route.
- */
- @NonNull
- public Builder addSupportedCategory(@NonNull String category) {
- if (TextUtils.isEmpty(category)) {
- throw new IllegalArgumentException("category must not be null or empty");
- }
- mSupportedCategories.add(category);
- return this;
- }
-
- /**
- * Sets the route's current volume, or 0 if unknown.
- */
- @NonNull
- public Builder setVolume(int volume) {
- mVolume = volume;
- return this;
- }
-
- /**
- * Sets the route's maximum volume, or 0 if unknown.
- */
- @NonNull
- public Builder setVolumeMax(int volumeMax) {
- mVolumeMax = volumeMax;
- return this;
- }
-
- /**
* Sets the route's volume handling.
*/
@NonNull
@@ -645,28 +597,61 @@
}
/**
- * Sets the route's device type.
+ * Sets the route's maximum volume, or 0 if unknown.
*/
@NonNull
- public Builder setDeviceType(@DeviceType int deviceType) {
- mDeviceType = deviceType;
+ public Builder setVolumeMax(int volumeMax) {
+ mVolumeMax = volumeMax;
+ return this;
+ }
+
+ /**
+ * Sets the route's current volume, or 0 if unknown.
+ */
+ @NonNull
+ public Builder setVolume(int volume) {
+ mVolume = volume;
return this;
}
/**
* Sets a bundle of extras for the route.
+ * <p>
+ * Note: The extras will not affect the result of {@link MediaRoute2Info#equals(Object)}.
*/
@NonNull
public Builder setExtras(@Nullable Bundle extras) {
- mExtras = extras;
+ if (extras == null) {
+ mExtras = null;
+ return this;
+ }
+ mExtras = new Bundle(extras);
+ return this;
+ }
+
+ /**
+ * Sets the provider id of the route.
+ * @hide
+ */
+ @NonNull
+ public Builder setProviderId(@NonNull String providerId) {
+ if (TextUtils.isEmpty(providerId)) {
+ throw new IllegalArgumentException("providerId must not be null or empty");
+ }
+ mProviderId = providerId;
return this;
}
/**
* Builds the {@link MediaRoute2Info media route info}.
+ *
+ * @throws IllegalArgumentException if no features are added.
*/
@NonNull
public MediaRoute2Info build() {
+ if (mFeatures.isEmpty()) {
+ throw new IllegalArgumentException("features must not be empty!");
+ }
return new MediaRoute2Info(this);
}
}
diff --git a/media/java/android/media/MediaRoute2ProviderInfo.java b/media/java/android/media/MediaRoute2ProviderInfo.java
index 7078d4a..c9a2ec7 100644
--- a/media/java/android/media/MediaRoute2ProviderInfo.java
+++ b/media/java/android/media/MediaRoute2ProviderInfo.java
@@ -25,6 +25,7 @@
import java.util.Arrays;
import java.util.Collection;
+import java.util.Map;
import java.util.Objects;
/**
@@ -92,6 +93,9 @@
/**
* Gets the route for the given route id or null if no matching route exists.
+ * Please note that id should be original id.
+ *
+ * @see MediaRoute2Info#getOriginalId()
*/
@Nullable
public MediaRoute2Info getRoute(@NonNull String routeId) {
@@ -161,14 +165,17 @@
return this;
}
mUniqueId = uniqueId;
- final int count = mRoutes.size();
- for (int i = 0; i < count; i++) {
- MediaRoute2Info route = mRoutes.valueAt(i);
- mRoutes.setValueAt(i, new MediaRoute2Info.Builder(route)
+
+ final ArrayMap<String, MediaRoute2Info> newRoutes = new ArrayMap<>();
+ for (Map.Entry<String, MediaRoute2Info> entry : mRoutes.entrySet()) {
+ MediaRoute2Info routeWithProviderId = new MediaRoute2Info.Builder(entry.getValue())
.setProviderId(mUniqueId)
- .build());
+ .build();
+ newRoutes.put(routeWithProviderId.getOriginalId(), routeWithProviderId);
}
+ mRoutes.clear();
+ mRoutes.putAll(newRoutes);
return this;
}
@@ -179,14 +186,14 @@
public Builder addRoute(@NonNull MediaRoute2Info route) {
Objects.requireNonNull(route, "route must not be null");
- if (mRoutes.containsValue(route)) {
- throw new IllegalArgumentException("route descriptor already added");
+ if (mRoutes.containsKey(route.getOriginalId())) {
+ throw new IllegalArgumentException("A route with the same id is already added");
}
if (mUniqueId != null) {
- mRoutes.put(route.getId(),
+ mRoutes.put(route.getOriginalId(),
new MediaRoute2Info.Builder(route).setProviderId(mUniqueId).build());
} else {
- mRoutes.put(route.getId(), route);
+ mRoutes.put(route.getOriginalId(), route);
}
return this;
}
diff --git a/media/java/android/media/MediaRoute2ProviderService.java b/media/java/android/media/MediaRoute2ProviderService.java
index 99bd1dc..6d9aea5 100644
--- a/media/java/android/media/MediaRoute2ProviderService.java
+++ b/media/java/android/media/MediaRoute2ProviderService.java
@@ -29,24 +29,45 @@
import android.os.Looper;
import android.os.Process;
import android.os.RemoteException;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
/**
- * @hide
+ * Base class for media route provider services.
+ * <p>
+ * The system media router service will bind to media route provider services when a
+ * {@link RouteDiscoveryPreference discovery preference} is registered via
+ * a {@link MediaRouter2 media router} by an application.
+ * </p><p>
+ * To implement your own media route provider service, extend this class and
+ * override {@link #onDiscoveryPreferenceChanged(RouteDiscoveryPreference)} to publish
+ * {@link MediaRoute2Info routes}.
+ * </p>
*/
public abstract class MediaRoute2ProviderService extends Service {
private static final String TAG = "MR2ProviderService";
public static final String SERVICE_INTERFACE = "android.media.MediaRoute2ProviderService";
+ /**
+ * The request ID to pass {@link #notifySessionCreated(RoutingSessionInfo, long)}
+ * when {@link MediaRoute2ProviderService} created a session although there was no creation
+ * request.
+ *
+ * @see #notifySessionCreated(RoutingSessionInfo, long)
+ * @hide
+ */
+ public static final long REQUEST_ID_UNKNOWN = 0;
+
private final Handler mHandler;
private final Object mSessionLock = new Object();
private final AtomicBoolean mStatePublishScheduled = new AtomicBoolean(false);
@@ -55,13 +76,14 @@
private MediaRoute2ProviderInfo mProviderInfo;
@GuardedBy("mSessionLock")
- private ArrayMap<Integer, RouteSessionInfo> mSessionInfo = new ArrayMap<>();
+ private ArrayMap<String, RoutingSessionInfo> mSessionInfo = new ArrayMap<>();
public MediaRoute2ProviderService() {
mHandler = new Handler(Looper.getMainLooper());
}
@Override
+ @NonNull
public IBinder onBind(@NonNull Intent intent) {
//TODO: Allow binding from media router service only?
if (SERVICE_INTERFACE.equals(intent.getAction())) {
@@ -78,6 +100,7 @@
*
* @param routeId the id of the target route
* @param request the media control request intent
+ * @hide
*/
//TODO: Discuss what to use for request (e.g., Intent? Request class?)
public abstract void onControlRequest(@NonNull String routeId, @NonNull Intent request);
@@ -87,6 +110,7 @@
*
* @param routeId the id of the route
* @param volume the target volume
+ * @hide
*/
public abstract void onSetVolume(@NonNull String routeId, int volume);
@@ -95,6 +119,7 @@
*
* @param routeId id of the route
* @param delta the delta to add to the current volume
+ * @hide
*/
public abstract void onUpdateVolume(@NonNull String routeId, int delta);
@@ -103,112 +128,64 @@
*
* @param sessionId id of the session
* @return information of the session with the given id.
- * null if the session is destroyed or id is not valid.
+ * null if the session is released or ID is not valid.
+ * @hide
*/
@Nullable
- public final RouteSessionInfo getSessionInfo(int sessionId) {
+ public final RoutingSessionInfo getSessionInfo(@NonNull String sessionId) {
+ if (TextUtils.isEmpty(sessionId)) {
+ throw new IllegalArgumentException("sessionId must not be empty");
+ }
synchronized (mSessionLock) {
return mSessionInfo.get(sessionId);
}
}
/**
- * Gets the list of {@link RouteSessionInfo session info} that the provider service maintains.
+ * Gets the list of {@link RoutingSessionInfo session info} that the provider service maintains.
+ * @hide
*/
@NonNull
- public final List<RouteSessionInfo> getAllSessionInfo() {
+ public final List<RoutingSessionInfo> getAllSessionInfo() {
synchronized (mSessionLock) {
return new ArrayList<>(mSessionInfo.values());
}
}
/**
- * Updates the information of a session.
- * If the session is destroyed or not created before, it will be ignored.
- * A session will be destroyed if it has no selected route.
- * Call {@link #updateProviderInfo(MediaRoute2ProviderInfo)} to notify clients of
- * session info changes.
- *
- * @param sessionInfo new session information
- * @see #notifySessionCreated(RouteSessionInfo, long)
- */
- public final void updateSessionInfo(@NonNull RouteSessionInfo sessionInfo) {
- Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
- int sessionId = sessionInfo.getSessionId();
- if (sessionInfo.getSelectedRoutes().isEmpty()) {
- releaseSession(sessionId);
- return;
- }
-
- synchronized (mSessionLock) {
- if (mSessionInfo.containsKey(sessionId)) {
- mSessionInfo.put(sessionId, sessionInfo);
- schedulePublishState();
- } else {
- Log.w(TAG, "Ignoring unknown session info.");
- return;
- }
- }
- }
-
- /**
- * Notifies the session is changed.
- *
- * TODO: This method is temporary, only created for tests. Remove when the alternative is ready.
- * @hide
- */
- public final void notifySessionInfoChanged(@NonNull RouteSessionInfo sessionInfo) {
- Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
-
- int sessionId = sessionInfo.getSessionId();
- synchronized (mSessionLock) {
- if (mSessionInfo.containsKey(sessionId)) {
- mSessionInfo.put(sessionId, sessionInfo);
- } else {
- Log.w(TAG, "Ignoring unknown session info.");
- return;
- }
- }
-
- if (mClient == null) {
- return;
- }
- try {
- mClient.notifySessionInfoChanged(sessionInfo);
- } catch (RemoteException ex) {
- Log.w(TAG, "Failed to notify session info changed.");
- }
- }
-
- /**
- * Notifies clients of that the session is created and ready for use. If the session can be
- * controlled, pass a {@link Bundle} that contains how to control it.
+ * Notifies clients of that the session is created and ready for use.
+ * <p>
+ * If this session is created without any creation request, use {@link #REQUEST_ID_UNKNOWN}
+ * as the request ID.
*
* @param sessionInfo information of the new session.
- * The {@link RouteSessionInfo#getSessionId() id} of the session must be
- * unique. Pass {@code null} to reject the request or inform clients that
- * session creation is failed.
- * @param requestId id of the previous request to create this session
+ * The {@link RoutingSessionInfo#getId() id} of the session must be unique.
+ * @param requestId id of the previous request to create this session provided in
+ * {@link #onCreateSession(String, String, String, long)}
+ * @see #onCreateSession(String, String, String, long)
+ * @hide
*/
- // TODO: fail reason?
- // TODO: Maybe better to create notifySessionCreationFailed?
- public final void notifySessionCreated(@Nullable RouteSessionInfo sessionInfo, long requestId) {
- if (sessionInfo != null) {
- int sessionId = sessionInfo.getSessionId();
- synchronized (mSessionLock) {
- if (mSessionInfo.containsKey(sessionId)) {
- Log.w(TAG, "Ignoring duplicate session id.");
- return;
- }
- mSessionInfo.put(sessionInfo.getSessionId(), sessionInfo);
+ public final void notifySessionCreated(@NonNull RoutingSessionInfo sessionInfo,
+ long requestId) {
+ Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
+
+ String sessionId = sessionInfo.getId();
+ synchronized (mSessionLock) {
+ if (mSessionInfo.containsKey(sessionId)) {
+ Log.w(TAG, "Ignoring duplicate session id.");
+ return;
}
- schedulePublishState();
+ mSessionInfo.put(sessionInfo.getId(), sessionInfo);
}
+ schedulePublishState();
if (mClient == null) {
return;
}
try {
+ // TODO: Calling binder calls in multiple thread may cause timing issue.
+ // Consider to change implementations to avoid the problems.
+ // For example, post binder calls, always send all sessions at once, etc.
mClient.notifySessionCreated(sessionInfo, requestId);
} catch (RemoteException ex) {
Log.w(TAG, "Failed to notify session created.");
@@ -216,95 +193,195 @@
}
/**
- * Releases a session with the given id.
- * {@link #onDestroySession} is called if the session is released.
+ * Notifies clients of that the session could not be created.
*
- * @param sessionId id of the session to be released
- * @see #onDestroySession(int, RouteSessionInfo)
+ * @param requestId id of the previous request to create the session provided in
+ * {@link #onCreateSession(String, String, String, long)}.
+ * @see #onCreateSession(String, String, String, long)
+ * @hide
*/
- public final void releaseSession(int sessionId) {
- //TODO: notify media router service of release.
- RouteSessionInfo sessionInfo;
- synchronized (mSessionLock) {
- sessionInfo = mSessionInfo.remove(sessionId);
+ public final void notifySessionCreationFailed(long requestId) {
+ if (mClient == null) {
+ return;
}
- if (sessionInfo != null) {
- mHandler.sendMessage(obtainMessage(
- MediaRoute2ProviderService::onDestroySession, this, sessionId, sessionInfo));
- schedulePublishState();
+ try {
+ mClient.notifySessionCreationFailed(requestId);
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Failed to notify session creation failed.");
}
}
/**
- * Called when a session should be created.
+ * Notifies the existing session is updated. For example, when
+ * {@link RoutingSessionInfo#getSelectedRoutes() selected routes} are changed.
+ *
+ * @hide
+ */
+ public final void notifySessionUpdated(@NonNull RoutingSessionInfo sessionInfo) {
+ Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
+
+ String sessionId = sessionInfo.getId();
+ synchronized (mSessionLock) {
+ if (mSessionInfo.containsKey(sessionId)) {
+ mSessionInfo.put(sessionId, sessionInfo);
+ } else {
+ Log.w(TAG, "Ignoring unknown session info.");
+ return;
+ }
+ }
+
+ if (mClient == null) {
+ return;
+ }
+ try {
+ mClient.notifySessionUpdated(sessionInfo);
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Failed to notify session info changed.");
+ }
+ }
+
+ /**
+ * Notifies that the session is released.
+ *
+ * @param sessionId id of the released session.
+ * @see #onReleaseSession(String)
+ * @hide
+ */
+ public final void notifySessionReleased(@NonNull String sessionId) {
+ if (TextUtils.isEmpty(sessionId)) {
+ throw new IllegalArgumentException("sessionId must not be empty");
+ }
+ RoutingSessionInfo sessionInfo;
+ synchronized (mSessionLock) {
+ sessionInfo = mSessionInfo.remove(sessionId);
+ }
+
+ if (sessionInfo == null) {
+ Log.w(TAG, "Ignoring unknown session info.");
+ return;
+ }
+
+ if (mClient == null) {
+ return;
+ }
+ try {
+ mClient.notifySessionReleased(sessionInfo);
+ } catch (RemoteException ex) {
+ Log.w(TAG, "Failed to notify session info changed.");
+ }
+ }
+
+ /**
+ * Called when the service receives a request to create a session.
+ * <p>
* You should create and maintain your own session and notifies the client of
- * session info. Call {@link #notifySessionCreated(RouteSessionInfo, long)}
+ * session info. Call {@link #notifySessionCreated(RoutingSessionInfo, long)}
* with the given {@code requestId} to notify the information of a new session.
- * If you can't create the session or want to reject the request, pass {@code null}
- * as session info in {@link #notifySessionCreated(RouteSessionInfo, long)}
- * with the given {@code requestId}.
+ * The created session must have the same route feature and must include the given route
+ * specified by {@code routeId}.
+ * <p>
+ * If the session can be controlled, you can optionally pass the control hints to
+ * {@link RoutingSessionInfo.Builder#setControlHints(Bundle)}. Control hints is a
+ * {@link Bundle} which contains how to control the session.
+ * <p>
+ * If you can't create the session or want to reject the request, call
+ * {@link #notifySessionCreationFailed(long)} with the given {@code requestId}.
*
* @param packageName the package name of the application that selected the route
* @param routeId the id of the route initially being connected
- * @param controlCategory the control category of the new session
+ * @param routeFeature the route feature of the new session
* @param requestId the id of this session creation request
+ *
+ * @see RoutingSessionInfo.Builder#Builder(String, String, String)
+ * @see RoutingSessionInfo.Builder#addSelectedRoute(String)
+ * @see RoutingSessionInfo.Builder#setControlHints(Bundle)
+ * @hide
*/
public abstract void onCreateSession(@NonNull String packageName, @NonNull String routeId,
- @NonNull String controlCategory, long requestId);
+ @NonNull String routeFeature, long requestId);
/**
- * Called when a session is about to be destroyed.
- * You can clean up your session here. This can happen by the
- * client or provider itself.
+ * Called when the session should be released. A client of the session or system can request
+ * a session to be released.
+ * <p>
+ * After releasing the session, call {@link #notifySessionReleased(String)}
+ * with the ID of the released session.
*
- * @param sessionId id of the session being destroyed.
- * @param lastSessionInfo information of the session being destroyed.
- * @see #releaseSession(int)
+ * Note: Calling {@link #notifySessionReleased(String)} will <em>NOT</em> trigger
+ * this method to be called.
+ *
+ * @param sessionId id of the session being released.
+ * @see #notifySessionReleased(String)
+ * @see #getSessionInfo(String)
+ * @hide
*/
- public abstract void onDestroySession(int sessionId, @NonNull RouteSessionInfo lastSessionInfo);
+ public abstract void onReleaseSession(@NonNull String sessionId);
//TODO: make a way to reject the request
/**
* Called when a client requests selecting a route for the session.
- * After the route is selected, call {@link #updateSessionInfo(RouteSessionInfo)} to update
- * session info and call {@link #updateProviderInfo(MediaRoute2ProviderInfo)} to notify
- * clients of updated session info.
+ * After the route is selected, call {@link #notifySessionUpdated(RoutingSessionInfo)}
+ * to update session info.
*
* @param sessionId id of the session
* @param routeId id of the route
- * @see #updateSessionInfo(RouteSessionInfo)
+ * @hide
*/
- public abstract void onSelectRoute(int sessionId, @NonNull String routeId);
+ public abstract void onSelectRoute(@NonNull String sessionId, @NonNull String routeId);
//TODO: make a way to reject the request
/**
* Called when a client requests deselecting a route from the session.
- * After the route is deselected, call {@link #updateSessionInfo(RouteSessionInfo)} to update
- * session info and call {@link #updateProviderInfo(MediaRoute2ProviderInfo)} to notify
- * clients of updated session info.
+ * After the route is deselected, call {@link #notifySessionUpdated(RoutingSessionInfo)}
+ * to update session info.
*
* @param sessionId id of the session
* @param routeId id of the route
+ * @hide
*/
- public abstract void onDeselectRoute(int sessionId, @NonNull String routeId);
+ public abstract void onDeselectRoute(@NonNull String sessionId, @NonNull String routeId);
//TODO: make a way to reject the request
/**
* Called when a client requests transferring a session to a route.
- * After the transfer is finished, call {@link #updateSessionInfo(RouteSessionInfo)} to update
- * session info and call {@link #updateProviderInfo(MediaRoute2ProviderInfo)} to notify
- * clients of updated session info.
+ * After the transfer is finished, call {@link #notifySessionUpdated(RoutingSessionInfo)}
+ * to update session info.
*
* @param sessionId id of the session
* @param routeId id of the route
+ * @hide
*/
- public abstract void onTransferToRoute(int sessionId, @NonNull String routeId);
+ public abstract void onTransferToRoute(@NonNull String sessionId, @NonNull String routeId);
/**
- * Updates provider info and publishes routes and session info.
+ * Called when the {@link RouteDiscoveryPreference discovery preference} has changed.
+ * <p>
+ * Whenever an application registers a {@link MediaRouter2.RouteCallback callback},
+ * it also provides a discovery preference to specify features of routes that it is interested
+ * in. The media router combines all of these discovery request into a single discovery
+ * preference and notifies each provider.
+ * </p><p>
+ * The provider should examine {@link RouteDiscoveryPreference#getPreferredFeatures()
+ * preferred features} in the discovery preference to determine what kind of routes it should
+ * try to discover and whether it should perform active or passive scans. In many cases,
+ * the provider may be able to save power by not performing any scans when the request doesn't
+ * have any matching route features.
+ * </p>
+ *
+ * @param preference the new discovery preference
+ *
+ * TODO: This method needs tests.
*/
- public final void updateProviderInfo(@NonNull MediaRoute2ProviderInfo providerInfo) {
- mProviderInfo = Objects.requireNonNull(providerInfo, "providerInfo must not be null");
+ public void onDiscoveryPreferenceChanged(@NonNull RouteDiscoveryPreference preference) {}
+
+ /**
+ * Updates routes of the provider and notifies the system media router service.
+ */
+ public final void notifyRoutes(@NonNull Collection<MediaRoute2Info> routes) {
+ Objects.requireNonNull(routes, "routes must not be null");
+ mProviderInfo = new MediaRoute2ProviderInfo.Builder()
+ .addRoutes(routes)
+ .build();
schedulePublishState();
}
@@ -328,12 +405,12 @@
return;
}
- List<RouteSessionInfo> sessionInfos;
+ List<RoutingSessionInfo> sessionInfos;
synchronized (mSessionLock) {
sessionInfos = new ArrayList<>(mSessionInfo.values());
}
try {
- mClient.updateState(mProviderInfo, sessionInfos);
+ mClient.updateState(mProviderInfo);
} catch (RemoteException ex) {
Log.w(TAG, "Failed to send onProviderInfoUpdated");
}
@@ -357,46 +434,63 @@
@Override
public void requestCreateSession(String packageName, String routeId,
- String controlCategory, long requestId) {
+ String routeFeature, long requestId) {
if (!checkCallerisSystem()) {
return;
}
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onCreateSession,
- MediaRoute2ProviderService.this, packageName, routeId, controlCategory,
+ MediaRoute2ProviderService.this, packageName, routeId, routeFeature,
requestId));
}
+
@Override
- public void releaseSession(int sessionId) {
+ public void releaseSession(@NonNull String sessionId) {
if (!checkCallerisSystem()) {
return;
}
- mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::releaseSession,
+ if (TextUtils.isEmpty(sessionId)) {
+ Log.w(TAG, "releaseSession: Ignoring empty sessionId from system service.");
+ return;
+ }
+ mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onReleaseSession,
MediaRoute2ProviderService.this, sessionId));
}
@Override
- public void selectRoute(int sessionId, String routeId) {
+ public void selectRoute(@NonNull String sessionId, String routeId) {
if (!checkCallerisSystem()) {
return;
}
+ if (TextUtils.isEmpty(sessionId)) {
+ Log.w(TAG, "selectRoute: Ignoring empty sessionId from system service.");
+ return;
+ }
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onSelectRoute,
MediaRoute2ProviderService.this, sessionId, routeId));
}
@Override
- public void deselectRoute(int sessionId, String routeId) {
+ public void deselectRoute(@NonNull String sessionId, String routeId) {
if (!checkCallerisSystem()) {
return;
}
+ if (TextUtils.isEmpty(sessionId)) {
+ Log.w(TAG, "deselectRoute: Ignoring empty sessionId from system service.");
+ return;
+ }
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onDeselectRoute,
MediaRoute2ProviderService.this, sessionId, routeId));
}
@Override
- public void transferToRoute(int sessionId, String routeId) {
+ public void transferToRoute(@NonNull String sessionId, String routeId) {
if (!checkCallerisSystem()) {
return;
}
+ if (TextUtils.isEmpty(sessionId)) {
+ Log.w(TAG, "transferToRoute: Ignoring empty sessionId from system service.");
+ return;
+ }
mHandler.sendMessage(obtainMessage(MediaRoute2ProviderService::onTransferToRoute,
MediaRoute2ProviderService.this, sessionId, routeId));
}
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 9100032..bc4da10 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -18,10 +18,7 @@
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
import android.annotation.CallbackExecutor;
-import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
@@ -37,9 +34,7 @@
import com.android.internal.annotations.GuardedBy;
-import java.lang.annotation.Retention;
import java.util.ArrayList;
-import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -48,57 +43,21 @@
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
/**
- * A new Media Router
- * @hide
+ * Media Router 2 allows applications to control the routing of media channels
+ * and streams from the current device to remote speakers and devices.
*
* TODO: Add method names at the beginning of log messages. (e.g. changeSessionInfoOnHandler)
* Not only MediaRouter2, but also to service / manager / provider.
*/
public class MediaRouter2 {
-
- /** @hide */
- @Retention(SOURCE)
- @IntDef(value = {
- SELECT_REASON_UNKNOWN,
- SELECT_REASON_USER_SELECTED,
- SELECT_REASON_FALLBACK,
- SELECT_REASON_SYSTEM_SELECTED})
- public @interface SelectReason {}
-
- /**
- * Passed to {@link Callback#onRouteSelected(MediaRoute2Info, int, Bundle)} when the reason
- * the route was selected is unknown.
- */
- public static final int SELECT_REASON_UNKNOWN = 0;
-
- /**
- * Passed to {@link Callback#onRouteSelected(MediaRoute2Info, int, Bundle)} when the route
- * is selected in response to a user's request. For example, when a user has selected
- * a different device to play media to.
- */
- public static final int SELECT_REASON_USER_SELECTED = 1;
-
- /**
- * Passed to {@link Callback#onRouteSelected(MediaRoute2Info, int, Bundle)} when the route
- * is selected as a fallback route. For example, when Wi-Fi is disconnected, the device speaker
- * may be selected as a fallback route.
- */
- public static final int SELECT_REASON_FALLBACK = 2;
-
- /**
- * This is passed from {@link com.android.server.media.MediaRouterService} when the route
- * is selected in response to a request from other apps (e.g. System UI).
- * @hide
- */
- public static final int SELECT_REASON_SYSTEM_SELECTED = 3;
-
private static final String TAG = "MR2";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private static final Object sLock = new Object();
+ private static final Object sRouterLock = new Object();
- @GuardedBy("sLock")
+ @GuardedBy("sRouterLock")
private static MediaRouter2 sInstance;
private final Context mContext;
@@ -114,31 +73,33 @@
new CopyOnWriteArrayList<>();
private final String mPackageName;
- @GuardedBy("sLock")
+ @GuardedBy("sRouterLock")
final Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
- @GuardedBy("sLock")
- private List<String> mControlCategories = Collections.emptyList();
+ @GuardedBy("sRouterLock")
+ private RouteDiscoveryPreference mDiscoveryPreference = RouteDiscoveryPreference.EMPTY;
// TODO: Make MediaRouter2 is always connected to the MediaRouterService.
- @GuardedBy("sLock")
- private Client2 mClient;
+ @GuardedBy("sRouterLock")
+ Client2 mClient;
- private Map<String, RouteSessionController> mSessionControllers = new ArrayMap<>();
+ @GuardedBy("sRouterLock")
+ private Map<String, RoutingController> mRoutingControllers = new ArrayMap<>();
private AtomicInteger mSessionCreationRequestCnt = new AtomicInteger(1);
final Handler mHandler;
- @GuardedBy("sLock")
+ @GuardedBy("sRouterLock")
private boolean mShouldUpdateRoutes;
private volatile List<MediaRoute2Info> mFilteredRoutes = Collections.emptyList();
/**
* Gets an instance of the media router associated with the context.
*/
+ @NonNull
public static MediaRouter2 getInstance(@NonNull Context context) {
Objects.requireNonNull(context, "context must not be null");
- synchronized (sLock) {
+ synchronized (sRouterLock) {
if (sInstance == null) {
sInstance = new MediaRouter2(context.getApplicationContext());
}
@@ -151,7 +112,6 @@
mMediaRouterService = IMediaRouterService.Stub.asInterface(
ServiceManager.getService(Context.MEDIA_ROUTER_SERVICE));
mPackageName = mContext.getPackageName();
- //TODO: read control categories from the manifest
mHandler = new Handler(Looper.getMainLooper());
List<MediaRoute2Info> currentSystemRoutes = null;
@@ -176,9 +136,9 @@
* @hide
*/
public static boolean checkRouteListContainsRouteId(@NonNull List<MediaRoute2Info> routeList,
- @NonNull String uniqueRouteId) {
+ @NonNull String routeId) {
for (MediaRoute2Info info : routeList) {
- if (TextUtils.equals(uniqueRouteId, info.getUniqueId())) {
+ if (TextUtils.equals(routeId, info.getId())) {
return true;
}
}
@@ -187,43 +147,36 @@
/**
* Registers a callback to discover routes and to receive events when they change.
- */
- public void registerRouteCallback(@NonNull @CallbackExecutor Executor executor,
- @NonNull RouteCallback routeCallback) {
- registerRouteCallback(executor, routeCallback, 0);
- }
-
- /**
- * Registers a callback to discover routes and to receive events when they change.
* <p>
* If you register the same callback twice or more, it will be ignored.
* </p>
*/
public void registerRouteCallback(@NonNull @CallbackExecutor Executor executor,
- @NonNull RouteCallback routeCallback, int flags) {
+ @NonNull RouteCallback routeCallback,
+ @NonNull RouteDiscoveryPreference preference) {
Objects.requireNonNull(executor, "executor must not be null");
Objects.requireNonNull(routeCallback, "callback must not be null");
+ Objects.requireNonNull(preference, "preference must not be null");
- RouteCallbackRecord record = new RouteCallbackRecord(executor, routeCallback, flags);
+ RouteCallbackRecord record = new RouteCallbackRecord(executor, routeCallback, preference);
if (!mRouteCallbackRecords.addIfAbsent(record)) {
Log.w(TAG, "Ignoring the same callback");
return;
}
- synchronized (sLock) {
+ synchronized (sRouterLock) {
if (mClient == null) {
Client2 client = new Client2();
try {
mMediaRouterService.registerClient2(client, mPackageName);
- mMediaRouterService.setControlCategories(client, mControlCategories);
+ updateDiscoveryRequestLocked();
+ mMediaRouterService.setDiscoveryRequest2(client, mDiscoveryPreference);
mClient = client;
} catch (RemoteException ex) {
Log.e(TAG, "Unable to register media router.", ex);
}
}
}
-
- //TODO: Update discovery request here.
}
/**
@@ -237,12 +190,12 @@
Objects.requireNonNull(routeCallback, "callback must not be null");
if (!mRouteCallbackRecords.remove(
- new RouteCallbackRecord(null, routeCallback, 0))) {
+ new RouteCallbackRecord(null, routeCallback, null))) {
Log.w(TAG, "Ignoring unknown callback");
return;
}
- synchronized (sLock) {
+ synchronized (sRouterLock) {
if (mRouteCallbackRecords.size() == 0 && mClient != null) {
try {
mMediaRouterService.unregisterClient2(mClient);
@@ -255,30 +208,10 @@
}
}
- //TODO(b/139033746): Rename "Control Category" when it's finalized.
- /**
- * Sets the control categories of the application.
- * Routes that support at least one of the given control categories are handled
- * by the media router.
- */
- public void setControlCategories(@NonNull Collection<String> controlCategories) {
- Objects.requireNonNull(controlCategories, "control categories must not be null");
-
- List<String> newControlCategories = new ArrayList<>(controlCategories);
-
- synchronized (sLock) {
- mShouldUpdateRoutes = true;
-
- // invoke callbacks due to control categories change
- handleControlCategoriesChangedLocked(newControlCategories);
- if (mClient != null) {
- try {
- mMediaRouterService.setControlCategories(mClient, mControlCategories);
- } catch (RemoteException ex) {
- Log.e(TAG, "Unable to set control categories.", ex);
- }
- }
- }
+ private void updateDiscoveryRequestLocked() {
+ mDiscoveryPreference = new RouteDiscoveryPreference.Builder(
+ mRouteCallbackRecords.stream().map(record -> record.mPreference).collect(
+ Collectors.toList())).build();
}
/**
@@ -286,18 +219,18 @@
* known to the media router.
* Please note that the list can be changed before callbacks are invoked.
*
- * @return the list of routes that support at least one of the control categories set by
- * the application
+ * @return the list of routes that contains at least one of the route features in discovery
+ * preferences registered by the application
*/
@NonNull
public List<MediaRoute2Info> getRoutes() {
- synchronized (sLock) {
+ synchronized (sRouterLock) {
if (mShouldUpdateRoutes) {
mShouldUpdateRoutes = false;
List<MediaRoute2Info> filteredRoutes = new ArrayList<>();
for (MediaRoute2Info route : mRoutes.values()) {
- if (route.supportsControlCategories(mControlCategories)) {
+ if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
filteredRoutes.add(route);
}
}
@@ -308,12 +241,13 @@
}
/**
- * Registers a callback to get updates on creations and changes of route sessions.
+ * Registers a callback to get updates on creations and changes of routing sessions.
* If you register the same callback twice or more, it will be ignored.
*
* @param executor the executor to execute the callback on
* @param callback the callback to register
* @see #unregisterSessionCallback
+ * @hide
*/
@NonNull
public void registerSessionCallback(@CallbackExecutor Executor executor,
@@ -334,6 +268,7 @@
*
* @param callback the callback to unregister
* @see #registerSessionCallback
+ * @hide
*/
@NonNull
public void unregisterSessionCallback(@NonNull SessionCallback callback) {
@@ -349,36 +284,35 @@
* Requests the media route provider service to create a session with the given route.
*
* @param route the route you want to create a session with.
- * @param controlCategory the control category of the session. Should not be empty
+ * @param routeFeature the route feature of the session. Should not be empty.
*
* @see SessionCallback#onSessionCreated
* @see SessionCallback#onSessionCreationFailed
+ * @hide
*/
@NonNull
public void requestCreateSession(@NonNull MediaRoute2Info route,
- @NonNull String controlCategory) {
+ @NonNull String routeFeature) {
Objects.requireNonNull(route, "route must not be null");
- if (TextUtils.isEmpty(controlCategory)) {
- throw new IllegalArgumentException("controlCategory must not be empty");
+ if (TextUtils.isEmpty(routeFeature)) {
+ throw new IllegalArgumentException("routeFeature must not be empty");
}
// TODO: Check the given route exists
- // TODO: Check the route supports the given controlCategory
+ // TODO: Check the route supports the given routeFeature
final int requestId;
requestId = mSessionCreationRequestCnt.getAndIncrement();
- SessionCreationRequest request = new SessionCreationRequest(
- requestId, route, controlCategory);
+ SessionCreationRequest request = new SessionCreationRequest(requestId, route, routeFeature);
mSessionCreationRequests.add(request);
Client2 client;
- synchronized (sLock) {
+ synchronized (sRouterLock) {
client = mClient;
}
if (client != null) {
try {
- mMediaRouterService.requestCreateSession(
- client, route, controlCategory, requestId);
+ mMediaRouterService.requestCreateSession(client, route, routeFeature, requestId);
} catch (RemoteException ex) {
Log.e(TAG, "Unable to request to create session.", ex);
mHandler.sendMessage(obtainMessage(MediaRouter2::createControllerOnHandler,
@@ -392,6 +326,7 @@
*
* @param route the route that will receive the control request
* @param request the media control request
+ * @hide
*/
//TODO: Discuss what to use for request (e.g., Intent? Request class?)
//TODO: Provide a way to obtain the result
@@ -400,7 +335,7 @@
Objects.requireNonNull(request, "request must not be null");
Client2 client;
- synchronized (sLock) {
+ synchronized (sRouterLock) {
client = mClient;
}
if (client != null) {
@@ -419,12 +354,13 @@
* </p>
*
* @param volume The new volume value between 0 and {@link MediaRoute2Info#getVolumeMax}.
+ * @hide
*/
public void requestSetVolume(@NonNull MediaRoute2Info route, int volume) {
Objects.requireNonNull(route, "route must not be null");
Client2 client;
- synchronized (sLock) {
+ synchronized (sRouterLock) {
client = mClient;
}
if (client != null) {
@@ -443,12 +379,13 @@
* </p>
*
* @param delta The delta to add to the current volume.
+ * @hide
*/
public void requestUpdateVolume(@NonNull MediaRoute2Info route, int delta) {
Objects.requireNonNull(route, "route must not be null");
Client2 client;
- synchronized (sLock) {
+ synchronized (sRouterLock) {
client = mClient;
}
if (client != null) {
@@ -460,46 +397,16 @@
}
}
- private void handleControlCategoriesChangedLocked(List<String> newControlCategories) {
- List<MediaRoute2Info> addedRoutes = new ArrayList<>();
- List<MediaRoute2Info> removedRoutes = new ArrayList<>();
-
- List<String> prevControlCategories = mControlCategories;
- mControlCategories = newControlCategories;
-
- for (MediaRoute2Info route : mRoutes.values()) {
- boolean preSupported = route.supportsControlCategories(prevControlCategories);
- boolean postSupported = route.supportsControlCategories(newControlCategories);
- if (preSupported == postSupported) {
- continue;
- }
- if (preSupported) {
- removedRoutes.add(route);
- } else {
- addedRoutes.add(route);
- }
- }
-
- if (removedRoutes.size() > 0) {
- mHandler.sendMessage(obtainMessage(MediaRouter2::notifyRoutesRemoved,
- MediaRouter2.this, removedRoutes));
- }
- if (addedRoutes.size() > 0) {
- mHandler.sendMessage(obtainMessage(MediaRouter2::notifyRoutesAdded,
- MediaRouter2.this, addedRoutes));
- }
- }
-
void addRoutesOnHandler(List<MediaRoute2Info> routes) {
// TODO: When onRoutesAdded is first called,
// 1) clear mRoutes before adding the routes
// 2) Call onRouteSelected(system_route, reason_fallback) if previously selected route
// does not exist anymore. => We may need 'boolean MediaRoute2Info#isSystemRoute()'.
List<MediaRoute2Info> addedRoutes = new ArrayList<>();
- synchronized (sLock) {
+ synchronized (sRouterLock) {
for (MediaRoute2Info route : routes) {
- mRoutes.put(route.getUniqueId(), route);
- if (route.supportsControlCategories(mControlCategories)) {
+ mRoutes.put(route.getId(), route);
+ if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
addedRoutes.add(route);
}
}
@@ -512,10 +419,10 @@
void removeRoutesOnHandler(List<MediaRoute2Info> routes) {
List<MediaRoute2Info> removedRoutes = new ArrayList<>();
- synchronized (sLock) {
+ synchronized (sRouterLock) {
for (MediaRoute2Info route : routes) {
- mRoutes.remove(route.getUniqueId());
- if (route.supportsControlCategories(mControlCategories)) {
+ mRoutes.remove(route.getId());
+ if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
removedRoutes.add(route);
}
}
@@ -528,10 +435,10 @@
void changeRoutesOnHandler(List<MediaRoute2Info> routes) {
List<MediaRoute2Info> changedRoutes = new ArrayList<>();
- synchronized (sLock) {
+ synchronized (sRouterLock) {
for (MediaRoute2Info route : routes) {
- mRoutes.put(route.getUniqueId(), route);
- if (route.supportsControlCategories(mControlCategories)) {
+ mRoutes.put(route.getId(), route);
+ if (route.hasAnyFeatures(mDiscoveryPreference.getPreferredFeatures())) {
changedRoutes.add(route);
}
}
@@ -548,7 +455,7 @@
* <p>
* Pass {@code null} to sessionInfo for the failure case.
*/
- void createControllerOnHandler(@Nullable RouteSessionInfo sessionInfo, int requestId) {
+ void createControllerOnHandler(@Nullable RoutingSessionInfo sessionInfo, int requestId) {
SessionCreationRequest matchingRequest = null;
for (SessionCreationRequest request : mSessionCreationRequests) {
if (request.mRequestId == requestId) {
@@ -561,27 +468,27 @@
mSessionCreationRequests.remove(matchingRequest);
MediaRoute2Info requestedRoute = matchingRequest.mRoute;
- String requestedControlCategory = matchingRequest.mControlCategory;
+ String requestedRouteFeature = matchingRequest.mRouteFeature;
if (sessionInfo == null) {
// TODO: We may need to distinguish between failure and rejection.
// One way can be introducing 'reason'.
- notifySessionCreationFailed(requestedRoute, requestedControlCategory);
+ notifySessionCreationFailed(requestedRoute, requestedRouteFeature);
return;
- } else if (!TextUtils.equals(requestedControlCategory,
- sessionInfo.getControlCategory())) {
- Log.w(TAG, "The session has different control category from what we requested. "
- + "(requested=" + requestedControlCategory
- + ", actual=" + sessionInfo.getControlCategory()
+ } else if (!TextUtils.equals(requestedRouteFeature,
+ sessionInfo.getRouteFeature())) {
+ Log.w(TAG, "The session has different route feature from what we requested. "
+ + "(requested=" + requestedRouteFeature
+ + ", actual=" + sessionInfo.getRouteFeature()
+ ")");
- notifySessionCreationFailed(requestedRoute, requestedControlCategory);
+ notifySessionCreationFailed(requestedRoute, requestedRouteFeature);
return;
} else if (!sessionInfo.getSelectedRoutes().contains(requestedRoute.getId())) {
Log.w(TAG, "The session does not contain the requested route. "
+ "(requestedRouteId=" + requestedRoute.getId()
+ ", actualRoutes=" + sessionInfo.getSelectedRoutes()
+ ")");
- notifySessionCreationFailed(requestedRoute, requestedControlCategory);
+ notifySessionCreationFailed(requestedRoute, requestedRouteFeature);
return;
} else if (!TextUtils.equals(requestedRoute.getProviderId(),
sessionInfo.getProviderId())) {
@@ -589,81 +496,136 @@
+ "(requested route's providerId=" + requestedRoute.getProviderId()
+ ", actual providerId=" + sessionInfo.getProviderId()
+ ")");
- notifySessionCreationFailed(requestedRoute, requestedControlCategory);
+ notifySessionCreationFailed(requestedRoute, requestedRouteFeature);
return;
}
}
if (sessionInfo != null) {
- RouteSessionController controller = new RouteSessionController(sessionInfo);
- mSessionControllers.put(controller.getUniqueSessionId(), controller);
+ RoutingController controller = new RoutingController(sessionInfo);
+ synchronized (sRouterLock) {
+ mRoutingControllers.put(controller.getSessionId(), controller);
+ }
notifySessionCreated(controller);
}
}
- void changeSessionInfoOnHandler(RouteSessionInfo sessionInfo) {
+ void changeSessionInfoOnHandler(RoutingSessionInfo sessionInfo) {
if (sessionInfo == null) {
Log.w(TAG, "changeSessionInfoOnHandler: Ignoring null sessionInfo.");
return;
}
- RouteSessionController matchingController = mSessionControllers.get(
- sessionInfo.getUniqueSessionId());
+ RoutingController matchingController;
+ synchronized (sRouterLock) {
+ matchingController = mRoutingControllers.get(sessionInfo.getId());
+ }
if (matchingController == null) {
Log.w(TAG, "changeSessionInfoOnHandler: Matching controller not found. uniqueSessionId="
- + sessionInfo.getUniqueSessionId());
+ + sessionInfo.getId());
return;
}
- RouteSessionInfo oldInfo = matchingController.getRouteSessionInfo();
+ RoutingSessionInfo oldInfo = matchingController.getRoutingSessionInfo();
if (!TextUtils.equals(oldInfo.getProviderId(), sessionInfo.getProviderId())) {
Log.w(TAG, "changeSessionInfoOnHandler: Provider IDs are not matched. old="
+ oldInfo.getProviderId() + ", new=" + sessionInfo.getProviderId());
return;
}
- matchingController.setRouteSessionInfo(sessionInfo);
+ matchingController.setRoutingSessionInfo(sessionInfo);
notifySessionInfoChanged(matchingController, oldInfo, sessionInfo);
}
+ void releaseControllerOnHandler(RoutingSessionInfo sessionInfo) {
+ if (sessionInfo == null) {
+ Log.w(TAG, "releaseControllerOnHandler: Ignoring null sessionInfo.");
+ return;
+ }
+
+ final String uniqueSessionId = sessionInfo.getId();
+ RoutingController matchingController;
+ synchronized (sRouterLock) {
+ matchingController = mRoutingControllers.get(uniqueSessionId);
+ }
+
+ if (matchingController == null) {
+ if (DEBUG) {
+ Log.d(TAG, "releaseControllerOnHandler: Matching controller not found. "
+ + "uniqueSessionId=" + sessionInfo.getId());
+ }
+ return;
+ }
+
+ RoutingSessionInfo oldInfo = matchingController.getRoutingSessionInfo();
+ if (!TextUtils.equals(oldInfo.getProviderId(), sessionInfo.getProviderId())) {
+ Log.w(TAG, "releaseControllerOnHandler: Provider IDs are not matched. old="
+ + oldInfo.getProviderId() + ", new=" + sessionInfo.getProviderId());
+ return;
+ }
+
+ synchronized (sRouterLock) {
+ mRoutingControllers.remove(uniqueSessionId, matchingController);
+ }
+ matchingController.release();
+ notifyControllerReleased(matchingController);
+ }
+
+ private List<MediaRoute2Info> filterRoutes(List<MediaRoute2Info> routes,
+ RouteDiscoveryPreference discoveryRequest) {
+ return routes.stream()
+ .filter(
+ route -> route.hasAnyFeatures(discoveryRequest.getPreferredFeatures()))
+ .collect(Collectors.toList());
+ }
+
private void notifyRoutesAdded(List<MediaRoute2Info> routes) {
for (RouteCallbackRecord record: mRouteCallbackRecords) {
- record.mExecutor.execute(
- () -> record.mRouteCallback.onRoutesAdded(routes));
+ List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mPreference);
+ if (!filteredRoutes.isEmpty()) {
+ record.mExecutor.execute(
+ () -> record.mRouteCallback.onRoutesAdded(filteredRoutes));
+ }
}
}
private void notifyRoutesRemoved(List<MediaRoute2Info> routes) {
for (RouteCallbackRecord record: mRouteCallbackRecords) {
- record.mExecutor.execute(
- () -> record.mRouteCallback.onRoutesRemoved(routes));
+ List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mPreference);
+ if (!filteredRoutes.isEmpty()) {
+ record.mExecutor.execute(
+ () -> record.mRouteCallback.onRoutesRemoved(filteredRoutes));
+ }
}
}
private void notifyRoutesChanged(List<MediaRoute2Info> routes) {
for (RouteCallbackRecord record: mRouteCallbackRecords) {
- record.mExecutor.execute(
- () -> record.mRouteCallback.onRoutesChanged(routes));
+ List<MediaRoute2Info> filteredRoutes = filterRoutes(routes, record.mPreference);
+ if (!filteredRoutes.isEmpty()) {
+ record.mExecutor.execute(
+ () -> record.mRouteCallback.onRoutesChanged(filteredRoutes));
+ }
}
}
- private void notifySessionCreated(RouteSessionController controller) {
+ private void notifySessionCreated(RoutingController controller) {
for (SessionCallbackRecord record: mSessionCallbackRecords) {
record.mExecutor.execute(
() -> record.mSessionCallback.onSessionCreated(controller));
}
}
- private void notifySessionCreationFailed(MediaRoute2Info route, String controlCategory) {
+ private void notifySessionCreationFailed(MediaRoute2Info route, String routeFeature) {
for (SessionCallbackRecord record: mSessionCallbackRecords) {
record.mExecutor.execute(
- () -> record.mSessionCallback.onSessionCreationFailed(route, controlCategory));
+ () -> record.mSessionCallback.onSessionCreationFailed(route, routeFeature));
}
}
- private void notifySessionInfoChanged(RouteSessionController controller,
- RouteSessionInfo oldInfo, RouteSessionInfo newInfo) {
+ private void notifySessionInfoChanged(RoutingController controller,
+ RoutingSessionInfo oldInfo, RoutingSessionInfo newInfo) {
for (SessionCallbackRecord record: mSessionCallbackRecords) {
record.mExecutor.execute(
() -> record.mSessionCallback.onSessionInfoChanged(
@@ -671,6 +633,13 @@
}
}
+ private void notifyControllerReleased(RoutingController controller) {
+ for (SessionCallbackRecord record: mSessionCallbackRecords) {
+ record.mExecutor.execute(
+ () -> record.mSessionCallback.onSessionReleased(controller));
+ }
+ }
+
/**
* Callback for receiving events about media route discovery.
*/
@@ -705,23 +674,24 @@
/**
* Callback for receiving a result of session creation and session updates.
+ * @hide
*/
public static class SessionCallback {
/**
- * Called when the route session is created by the route provider.
+ * Called when the routing session is created by the route provider.
*
* @param controller the controller to control the created session
*/
- public void onSessionCreated(@NonNull RouteSessionController controller) {}
+ public void onSessionCreated(@NonNull RoutingController controller) {}
/**
* Called when the session creation request failed.
*
* @param requestedRoute the route info which was used for the request
- * @param requestedControlCategory the control category which was used for the request
+ * @param requestedRouteFeature the route feature which was used for the request
*/
public void onSessionCreationFailed(@NonNull MediaRoute2Info requestedRoute,
- @NonNull String requestedControlCategory) {}
+ @NonNull String requestedRouteFeature) {}
/**
* Called when the session info has changed.
@@ -732,77 +702,72 @@
* TODO: (Discussion) Do we really need newInfo? The controller has the newInfo.
* However. there can be timing issue if there is no newInfo.
*/
- public void onSessionInfoChanged(@NonNull RouteSessionController controller,
- @NonNull RouteSessionInfo oldInfo,
- @NonNull RouteSessionInfo newInfo) {}
+ public void onSessionInfoChanged(@NonNull RoutingController controller,
+ @NonNull RoutingSessionInfo oldInfo,
+ @NonNull RoutingSessionInfo newInfo) {}
/**
- * Called when the session is released. Session can be released by the controller using
- * {@link RouteSessionController#release(boolean)}, or by the
- * {@link MediaRoute2ProviderService} itself. One can do clean-ups here.
+ * Called when the session is released by {@link MediaRoute2ProviderService}.
+ * Before this method is called, the controller would be released by the system,
+ * which means the {@link RoutingController#isReleased()} will always return true
+ * for the {@code controller} here.
+ * <p>
+ * Note: Calling {@link RoutingController#release()} will <em>NOT</em> trigger
+ * this method to be called.
*
- * TODO: When Provider#notifySessionDestroyed is introduced, add @see for the method.
+ * TODO: Add tests for checking whether this method is called.
+ * TODO: When service process dies, this should be called.
+ *
+ * @see RoutingController#isReleased()
*/
- public void onSessionReleased(@NonNull RouteSessionController controller, int reason,
- boolean shouldStop) {}
+ public void onSessionReleased(@NonNull RoutingController controller) {}
}
/**
- * A class to control media route session in media route provider.
+ * A class to control media routing session in media route provider.
* For example, selecting/deselcting/transferring routes to session can be done through this
* class. Instances are created by {@link MediaRouter2}.
*
- * TODO: Need to add toString()
+ * @hide
*/
- public final class RouteSessionController {
- private final Object mLock = new Object();
+ public final class RoutingController {
+ private final Object mControllerLock = new Object();
- @GuardedBy("mLock")
- private RouteSessionInfo mSessionInfo;
+ @GuardedBy("mControllerLock")
+ private RoutingSessionInfo mSessionInfo;
- @GuardedBy("mLock")
+ @GuardedBy("mControllerLock")
private volatile boolean mIsReleased;
- RouteSessionController(@NonNull RouteSessionInfo sessionInfo) {
+ RoutingController(@NonNull RoutingSessionInfo sessionInfo) {
mSessionInfo = sessionInfo;
}
/**
* @return the ID of the session
*/
- public int getSessionId() {
- synchronized (mLock) {
- return mSessionInfo.getSessionId();
+ public String getSessionId() {
+ synchronized (mControllerLock) {
+ return mSessionInfo.getId();
}
}
/**
- * @return the unique ID of the session
- * @hide
+ * @return the feature which is used by the session mainly.
*/
@NonNull
- public String getUniqueSessionId() {
- synchronized (mLock) {
- return mSessionInfo.getUniqueSessionId();
+ public String getRouteFeature() {
+ synchronized (mControllerLock) {
+ return mSessionInfo.getRouteFeature();
}
}
/**
- * @return the category of routes that the session includes.
- */
- @NonNull
- public String getControlCategory() {
- synchronized (mLock) {
- return mSessionInfo.getControlCategory();
- }
- }
-
- /**
- * @return the control hints used to control route session if available.
+ * @return the control hints used to control routing session if available.
*/
@Nullable
public Bundle getControlHints() {
- synchronized (mLock) {
+ synchronized (mControllerLock) {
return mSessionInfo.getControlHints();
}
}
@@ -812,7 +777,7 @@
*/
@NonNull
public List<MediaRoute2Info> getSelectedRoutes() {
- synchronized (mLock) {
+ synchronized (mControllerLock) {
return getRoutesWithIdsLocked(mSessionInfo.getSelectedRoutes());
}
}
@@ -822,7 +787,7 @@
*/
@NonNull
public List<MediaRoute2Info> getSelectableRoutes() {
- synchronized (mLock) {
+ synchronized (mControllerLock) {
return getRoutesWithIdsLocked(mSessionInfo.getSelectableRoutes());
}
}
@@ -832,7 +797,7 @@
*/
@NonNull
public List<MediaRoute2Info> getDeselectableRoutes() {
- synchronized (mLock) {
+ synchronized (mControllerLock) {
return getRoutesWithIdsLocked(mSessionInfo.getDeselectableRoutes());
}
}
@@ -842,7 +807,7 @@
*/
@NonNull
public List<MediaRoute2Info> getTransferrableRoutes() {
- synchronized (mLock) {
+ synchronized (mControllerLock) {
return getRoutesWithIdsLocked(mSessionInfo.getTransferrableRoutes());
}
}
@@ -853,10 +818,9 @@
* Also, any operations to this instance will be ignored once released.
*
* @see #release
- * @see SessionCallback#onSessionReleased
*/
public boolean isReleased() {
- synchronized (mLock) {
+ synchronized (mControllerLock) {
return mIsReleased;
}
}
@@ -876,26 +840,32 @@
*/
public void selectRoute(@NonNull MediaRoute2Info route) {
Objects.requireNonNull(route, "route must not be null");
+ synchronized (mControllerLock) {
+ if (mIsReleased) {
+ Log.w(TAG, "selectRoute() called on released controller. Ignoring.");
+ return;
+ }
+ }
List<MediaRoute2Info> selectedRoutes = getSelectedRoutes();
- if (checkRouteListContainsRouteId(selectedRoutes, route.getUniqueId())) {
+ if (checkRouteListContainsRouteId(selectedRoutes, route.getId())) {
Log.w(TAG, "Ignoring selecting a route that is already selected. route=" + route);
return;
}
List<MediaRoute2Info> selectableRoutes = getSelectableRoutes();
- if (!checkRouteListContainsRouteId(selectableRoutes, route.getUniqueId())) {
+ if (!checkRouteListContainsRouteId(selectableRoutes, route.getId())) {
Log.w(TAG, "Ignoring selecting a non-selectable route=" + route);
return;
}
Client2 client;
- synchronized (sLock) {
+ synchronized (sRouterLock) {
client = mClient;
}
if (client != null) {
try {
- mMediaRouterService.selectRoute(mClient, getUniqueSessionId(), route);
+ mMediaRouterService.selectRoute(client, getSessionId(), route);
} catch (RemoteException ex) {
Log.e(TAG, "Unable to select route for session.", ex);
}
@@ -917,26 +887,32 @@
*/
public void deselectRoute(@NonNull MediaRoute2Info route) {
Objects.requireNonNull(route, "route must not be null");
+ synchronized (mControllerLock) {
+ if (mIsReleased) {
+ Log.w(TAG, "deselectRoute() called on released controller. Ignoring.");
+ return;
+ }
+ }
List<MediaRoute2Info> selectedRoutes = getSelectedRoutes();
- if (!checkRouteListContainsRouteId(selectedRoutes, route.getUniqueId())) {
+ if (!checkRouteListContainsRouteId(selectedRoutes, route.getId())) {
Log.w(TAG, "Ignoring deselecting a route that is not selected. route=" + route);
return;
}
List<MediaRoute2Info> deselectableRoutes = getDeselectableRoutes();
- if (!checkRouteListContainsRouteId(deselectableRoutes, route.getUniqueId())) {
+ if (!checkRouteListContainsRouteId(deselectableRoutes, route.getId())) {
Log.w(TAG, "Ignoring deselecting a non-deselectable route=" + route);
return;
}
Client2 client;
- synchronized (sLock) {
+ synchronized (sRouterLock) {
client = mClient;
}
if (client != null) {
try {
- mMediaRouterService.deselectRoute(mClient, getUniqueSessionId(), route);
+ mMediaRouterService.deselectRoute(client, getSessionId(), route);
} catch (RemoteException ex) {
Log.e(TAG, "Unable to remove route from session.", ex);
}
@@ -958,27 +934,33 @@
*/
public void transferToRoute(@NonNull MediaRoute2Info route) {
Objects.requireNonNull(route, "route must not be null");
+ synchronized (mControllerLock) {
+ if (mIsReleased) {
+ Log.w(TAG, "transferToRoute() called on released controller. Ignoring.");
+ return;
+ }
+ }
List<MediaRoute2Info> selectedRoutes = getSelectedRoutes();
- if (checkRouteListContainsRouteId(selectedRoutes, route.getUniqueId())) {
+ if (checkRouteListContainsRouteId(selectedRoutes, route.getId())) {
Log.w(TAG, "Ignoring transferring to a route that is already added. route="
+ route);
return;
}
List<MediaRoute2Info> transferrableRoutes = getTransferrableRoutes();
- if (!checkRouteListContainsRouteId(transferrableRoutes, route.getUniqueId())) {
+ if (!checkRouteListContainsRouteId(transferrableRoutes, route.getId())) {
Log.w(TAG, "Ignoring transferring to a non-transferrable route=" + route);
return;
}
Client2 client;
- synchronized (sLock) {
+ synchronized (sRouterLock) {
client = mClient;
}
if (client != null) {
try {
- mMediaRouterService.transferToRoute(mClient, getUniqueSessionId(), route);
+ mMediaRouterService.transferToRoute(client, getSessionId(), route);
} catch (RemoteException ex) {
Log.e(TAG, "Unable to transfer to route for session.", ex);
}
@@ -986,48 +968,93 @@
}
/**
- * Release this session.
- * Any operation on this session after calling this method will be ignored.
+ * Release this controller and corresponding session.
+ * Any operations on this controller after calling this method will be ignored.
+ * The devices that are playing media will stop playing it.
*
- * @param stopMedia Should the media that is playing on the device be stopped after this
- * session is released.
- * @see SessionCallback#onSessionReleased
+ * TODO: Add tests using {@link MediaRouter2Manager#getActiveSessions()}.
*/
- public void release(boolean stopMedia) {
- synchronized (mLock) {
+ public void release() {
+ synchronized (mControllerLock) {
if (mIsReleased) {
+ Log.w(TAG, "release() called on released controller. Ignoring.");
return;
}
mIsReleased = true;
}
- // TODO: Use stopMedia variable when the actual connection logic is implemented.
+
+ Client2 client;
+ synchronized (sRouterLock) {
+ mRoutingControllers.remove(getSessionId(), this);
+ client = mClient;
+ }
+ if (client != null) {
+ try {
+ mMediaRouterService.releaseSession(client, getSessionId());
+ } catch (RemoteException ex) {
+ Log.e(TAG, "Unable to notify of controller release", ex);
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ // To prevent logging spam, we only print the ID of each route.
+ List<String> selectedRoutes = getSelectedRoutes().stream()
+ .map(MediaRoute2Info::getId).collect(Collectors.toList());
+ List<String> selectableRoutes = getSelectableRoutes().stream()
+ .map(MediaRoute2Info::getId).collect(Collectors.toList());
+ List<String> deselectableRoutes = getDeselectableRoutes().stream()
+ .map(MediaRoute2Info::getId).collect(Collectors.toList());
+ List<String> transferrableRoutes = getTransferrableRoutes().stream()
+ .map(MediaRoute2Info::getId).collect(Collectors.toList());
+
+ StringBuilder result = new StringBuilder()
+ .append("RoutingController{ ")
+ .append("sessionId=").append(getSessionId())
+ .append(", routeFeature=").append(getRouteFeature())
+ .append(", selectedRoutes={")
+ .append(selectedRoutes)
+ .append("}")
+ .append(", selectableRoutes={")
+ .append(selectableRoutes)
+ .append("}")
+ .append(", deselectableRoutes={")
+ .append(deselectableRoutes)
+ .append("}")
+ .append(", transferrableRoutes={")
+ .append(transferrableRoutes)
+ .append("}")
+ .append(" }");
+ return result.toString();
}
/**
+ * TODO: Change this to package private. (Hidden for debugging purposes)
* @hide
*/
@NonNull
- public RouteSessionInfo getRouteSessionInfo() {
- synchronized (mLock) {
+ public RoutingSessionInfo getRoutingSessionInfo() {
+ synchronized (mControllerLock) {
return mSessionInfo;
}
}
- /**
- * @hide
- */
- public void setRouteSessionInfo(@NonNull RouteSessionInfo info) {
- synchronized (mLock) {
+ void setRoutingSessionInfo(@NonNull RoutingSessionInfo info) {
+ synchronized (mControllerLock) {
mSessionInfo = info;
}
}
+ // TODO: This method uses two locks (mLock outside, sLock inside).
+ // Check if there is any possiblity of deadlock.
private List<MediaRoute2Info> getRoutesWithIdsLocked(List<String> routeIds) {
+
List<MediaRoute2Info> routes = new ArrayList<>();
- synchronized (mLock) {
+ synchronized (sRouterLock) {
+ // TODO: Maybe able to change using Collection.stream()?
for (String routeId : routeIds) {
- MediaRoute2Info route = mRoutes.get(
- MediaRoute2Info.toUniqueId(mSessionInfo.mProviderId, routeId));
+ MediaRoute2Info route = mRoutes.get(routeId);
if (route != null) {
routes.add(route);
}
@@ -1040,13 +1067,13 @@
final class RouteCallbackRecord {
public final Executor mExecutor;
public final RouteCallback mRouteCallback;
- public final int mFlags;
+ public final RouteDiscoveryPreference mPreference;
RouteCallbackRecord(@Nullable Executor executor, @NonNull RouteCallback routeCallback,
- int flags) {
+ @Nullable RouteDiscoveryPreference preference) {
mRouteCallback = routeCallback;
mExecutor = executor;
- mFlags = flags;
+ mPreference = preference;
}
@Override
@@ -1095,13 +1122,13 @@
final class SessionCreationRequest {
public final MediaRoute2Info mRoute;
- public final String mControlCategory;
+ public final String mRouteFeature;
public final int mRequestId;
SessionCreationRequest(int requestId, @NonNull MediaRoute2Info route,
- @NonNull String controlCategory) {
+ @NonNull String routeFeature) {
mRoute = route;
- mControlCategory = controlCategory;
+ mRouteFeature = routeFeature;
mRequestId = requestId;
}
}
@@ -1129,15 +1156,21 @@
}
@Override
- public void notifySessionCreated(@Nullable RouteSessionInfo sessionInfo, int requestId) {
+ public void notifySessionCreated(@Nullable RoutingSessionInfo sessionInfo, int requestId) {
mHandler.sendMessage(obtainMessage(MediaRouter2::createControllerOnHandler,
MediaRouter2.this, sessionInfo, requestId));
}
@Override
- public void notifySessionInfoChanged(@Nullable RouteSessionInfo sessionInfo) {
+ public void notifySessionInfoChanged(@Nullable RoutingSessionInfo sessionInfo) {
mHandler.sendMessage(obtainMessage(MediaRouter2::changeSessionInfoOnHandler,
MediaRouter2.this, sessionInfo));
}
+
+ @Override
+ public void notifySessionReleased(RoutingSessionInfo sessionInfo) {
+ mHandler.sendMessage(obtainMessage(MediaRouter2::releaseControllerOnHandler,
+ MediaRouter2.this, sessionInfo));
+ }
}
}
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 2e68e42..7022933 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -65,7 +65,7 @@
@GuardedBy("mRoutesLock")
private final Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
@NonNull
- final ConcurrentMap<String, List<String>> mControlCategoryMap = new ConcurrentHashMap<>();
+ final ConcurrentMap<String, List<String>> mPreferredFeaturesMap = new ConcurrentHashMap<>();
private AtomicInteger mNextRequestId = new AtomicInteger(1);
@@ -144,7 +144,7 @@
}
//TODO: clear mRoutes?
mClient = null;
- mControlCategoryMap.clear();
+ mPreferredFeaturesMap.clear();
}
}
}
@@ -160,14 +160,14 @@
public List<MediaRoute2Info> getAvailableRoutes(@NonNull String packageName) {
Objects.requireNonNull(packageName, "packageName must not be null");
- List<String> controlCategories = mControlCategoryMap.get(packageName);
- if (controlCategories == null) {
+ List<String> preferredFeatures = mPreferredFeaturesMap.get(packageName);
+ if (preferredFeatures == null) {
return Collections.emptyList();
}
List<MediaRoute2Info> routes = new ArrayList<>();
synchronized (mRoutesLock) {
for (MediaRoute2Info route : mRoutes.values()) {
- if (route.supportsControlCategories(controlCategories)) {
+ if (route.hasAnyFeatures(preferredFeatures)) {
routes.add(route);
}
}
@@ -176,7 +176,7 @@
}
@NonNull
- public List<RouteSessionInfo> getActiveSessions() {
+ public List<RoutingSessionInfo> getActiveSessions() {
Client client;
synchronized (sLock) {
client = mClient;
@@ -295,7 +295,7 @@
void addRoutesOnHandler(List<MediaRoute2Info> routes) {
synchronized (mRoutesLock) {
for (MediaRoute2Info route : routes) {
- mRoutes.put(route.getUniqueId(), route);
+ mRoutes.put(route.getId(), route);
}
}
if (routes.size() > 0) {
@@ -306,7 +306,7 @@
void removeRoutesOnHandler(List<MediaRoute2Info> routes) {
synchronized (mRoutesLock) {
for (MediaRoute2Info route : routes) {
- mRoutes.remove(route.getUniqueId());
+ mRoutes.remove(route.getId());
}
}
if (routes.size() > 0) {
@@ -317,7 +317,7 @@
void changeRoutesOnHandler(List<MediaRoute2Info> routes) {
synchronized (mRoutesLock) {
for (MediaRoute2Info route : routes) {
- mRoutes.put(route.getUniqueId(), route);
+ mRoutes.put(route.getId(), route);
}
}
if (routes.size() > 0) {
@@ -352,15 +352,15 @@
}
}
- void updateControlCategories(String packageName, List<String> categories) {
- List<String> prevCategories = mControlCategoryMap.put(packageName, categories);
- if ((prevCategories == null && categories.size() == 0)
- || Objects.equals(categories, prevCategories)) {
+ void updatePreferredFeatures(String packageName, List<String> preferredFeatures) {
+ List<String> prevFeatures = mPreferredFeaturesMap.put(packageName, preferredFeatures);
+ if ((prevFeatures == null && preferredFeatures.size() == 0)
+ || Objects.equals(preferredFeatures, prevFeatures)) {
return;
}
for (CallbackRecord record : mCallbackRecords) {
- record.mExecutor.execute(
- () -> record.mCallback.onControlCategoriesChanged(packageName, categories));
+ record.mExecutor.execute(() -> record.mCallback
+ .onControlCategoriesChanged(packageName, preferredFeatures));
}
}
@@ -398,13 +398,13 @@
/**
- * Called when the control categories of an app is changed.
+ * Called when the preferred route features of an app is changed.
*
* @param packageName the package name of the application
- * @param controlCategories the list of control categories set by an application.
+ * @param preferredFeatures the list of preferred route features set by an application.
*/
public void onControlCategoriesChanged(@NonNull String packageName,
- @NonNull List<String> controlCategories) {}
+ @NonNull List<String> preferredFeatures) {}
}
final class CallbackRecord {
@@ -440,10 +440,9 @@
MediaRouter2Manager.this, packageName, route));
}
- @Override
- public void notifyControlCategoriesChanged(String packageName, List<String> categories) {
- mHandler.sendMessage(obtainMessage(MediaRouter2Manager::updateControlCategories,
- MediaRouter2Manager.this, packageName, categories));
+ public void notifyPreferredFeaturesChanged(String packageName, List<String> features) {
+ mHandler.sendMessage(obtainMessage(MediaRouter2Manager::updatePreferredFeatures,
+ MediaRouter2Manager.this, packageName, features));
}
@Override
diff --git a/media/java/android/media/MediaRouter2Utils.java b/media/java/android/media/MediaRouter2Utils.java
new file mode 100644
index 0000000..4904582
--- /dev/null
+++ b/media/java/android/media/MediaRouter2Utils.java
@@ -0,0 +1,100 @@
+/*
+ * 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.media;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * @hide
+ */
+public class MediaRouter2Utils {
+
+ static final String TAG = "MR2Utils";
+ static final String SEPARATOR = ":";
+
+ /**
+ * @hide
+ */
+ @NonNull
+ public static String toUniqueId(@NonNull String providerId, @NonNull String id) {
+ if (TextUtils.isEmpty(providerId)) {
+ Log.w(TAG, "toUniqueId: providerId shouldn't be empty");
+ return null;
+ }
+ if (TextUtils.isEmpty(id)) {
+ Log.w(TAG, "toUniqueId: id shouldn't be null");
+ return null;
+ }
+
+ return providerId + SEPARATOR + id;
+ }
+
+ /**
+ * Gets provider ID from unique ID.
+ * If the corresponding provider ID could not be generated, it will return null.
+ *
+ * @hide
+ */
+ @Nullable
+ public static String getProviderId(@NonNull String uniqueId) {
+ if (TextUtils.isEmpty(uniqueId)) {
+ Log.w(TAG, "getProviderId: uniqueId shouldn't be empty");
+ return null;
+ }
+
+ int firstIndexOfSeparator = uniqueId.indexOf(SEPARATOR);
+ if (firstIndexOfSeparator == -1) {
+ return null;
+ }
+
+ String providerId = uniqueId.substring(0, firstIndexOfSeparator);
+ if (TextUtils.isEmpty(providerId)) {
+ return null;
+ }
+
+ return providerId;
+ }
+
+ /**
+ * Gets the original ID (i.e. non-unique route/session ID) from unique ID.
+ * If the corresponding ID could not be generated, it will return null.
+ *
+ * @hide
+ */
+ @Nullable
+ public static String getOriginalId(@NonNull String uniqueId) {
+ if (TextUtils.isEmpty(uniqueId)) {
+ Log.w(TAG, "getOriginalId: uniqueId shouldn't be empty");
+ return null;
+ }
+
+ int firstIndexOfSeparator = uniqueId.indexOf(SEPARATOR);
+ if (firstIndexOfSeparator == -1 || firstIndexOfSeparator + 1 >= uniqueId.length()) {
+ return null;
+ }
+
+ String providerId = uniqueId.substring(firstIndexOfSeparator + 1);
+ if (TextUtils.isEmpty(providerId)) {
+ return null;
+ }
+
+ return providerId;
+ }
+}
diff --git a/media/java/android/media/MediaScannerConnection.java b/media/java/android/media/MediaScannerConnection.java
index 40e9073..05fa511 100644
--- a/media/java/android/media/MediaScannerConnection.java
+++ b/media/java/android/media/MediaScannerConnection.java
@@ -16,7 +16,7 @@
package android.media;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
diff --git a/media/java/android/media/MediaTranscodeManager.java b/media/java/android/media/MediaTranscodeManager.java
new file mode 100644
index 0000000..2c431b9
--- /dev/null
+++ b/media/java/android/media/MediaTranscodeManager.java
@@ -0,0 +1,403 @@
+/*
+ * 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.media;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.net.Uri;
+import android.util.Log;
+
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.Executor;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * MediaTranscodeManager provides an interface to the system's media transcode service.
+ * Transcode requests are put in a queue and processed in order. When a transcode operation is
+ * completed the caller is notified via its OnTranscodingFinishedListener. In the meantime the
+ * caller may use the returned TranscodingJob object to cancel or check the status of a specific
+ * transcode operation.
+ * The currently supported media types are video and still images.
+ *
+ * TODO(lnilsson): Add sample code when API is settled.
+ *
+ * @hide
+ */
+public final class MediaTranscodeManager {
+ private static final String TAG = "MediaTranscodeManager";
+
+ // Invalid ID passed from native means the request was never enqueued.
+ private static final long ID_INVALID = -1;
+
+ // Events passed from native.
+ private static final int EVENT_JOB_STARTED = 1;
+ private static final int EVENT_JOB_PROGRESSED = 2;
+ private static final int EVENT_JOB_FINISHED = 3;
+
+ @IntDef(prefix = { "EVENT_" }, value = {
+ EVENT_JOB_STARTED,
+ EVENT_JOB_PROGRESSED,
+ EVENT_JOB_FINISHED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Event {}
+
+ private static MediaTranscodeManager sMediaTranscodeManager;
+ private final ConcurrentMap<Long, TranscodingJob> mPendingTranscodingJobs =
+ new ConcurrentHashMap<>();
+ private final Context mContext;
+
+ /**
+ * Listener that gets notified when a transcoding operation has finished.
+ * This listener gets notified regardless of how the operation finished. It is up to the
+ * listener implementation to check the result and take appropriate action.
+ */
+ @FunctionalInterface
+ public interface OnTranscodingFinishedListener {
+ /**
+ * Called when the transcoding operation has finished. The receiver may use the
+ * TranscodingJob to check the result, i.e. whether the operation succeeded, was canceled or
+ * if an error occurred.
+ * @param transcodingJob The TranscodingJob instance for the finished transcoding operation.
+ */
+ void onTranscodingFinished(@NonNull TranscodingJob transcodingJob);
+ }
+
+ /**
+ * Class describing a transcode operation to be performed. The caller uses this class to
+ * configure a transcoding operation that can then be enqueued using MediaTranscodeManager.
+ */
+ public static final class TranscodingRequest {
+ private Uri mSrcUri;
+ private Uri mDstUri;
+ private MediaFormat mDstFormat;
+
+ private TranscodingRequest(Builder b) {
+ mSrcUri = b.mSrcUri;
+ mDstUri = b.mDstUri;
+ mDstFormat = b.mDstFormat;
+ }
+
+ /** TranscodingRequest builder class. */
+ public static class Builder {
+ private Uri mSrcUri;
+ private Uri mDstUri;
+ private MediaFormat mDstFormat;
+
+ /**
+ * Specifies the source media file.
+ * @param uri Content uri for the source media file.
+ * @return The builder instance.
+ */
+ public Builder setSourceUri(Uri uri) {
+ mSrcUri = uri;
+ return this;
+ }
+
+ /**
+ * Specifies the destination media file.
+ * @param uri Content uri for the destination media file.
+ * @return The builder instance.
+ */
+ public Builder setDestinationUri(Uri uri) {
+ mDstUri = uri;
+ return this;
+ }
+
+ /**
+ * Specifies the media format of the transcoded media file.
+ * @param dstFormat MediaFormat containing the desired destination format.
+ * @return The builder instance.
+ */
+ public Builder setDestinationFormat(MediaFormat dstFormat) {
+ mDstFormat = dstFormat;
+ return this;
+ }
+
+ /**
+ * Builds a new TranscodingRequest with the configuration set on this builder.
+ * @return A new TranscodingRequest.
+ */
+ public TranscodingRequest build() {
+ return new TranscodingRequest(this);
+ }
+ }
+ }
+
+ /**
+ * Handle to an enqueued transcoding operation. An instance of this class represents a single
+ * enqueued transcoding operation. The caller can use that instance to query the status or
+ * progress, and to get the result once the operation has completed.
+ */
+ public static final class TranscodingJob {
+ /** The job is enqueued but not yet running. */
+ public static final int STATUS_PENDING = 1;
+ /** The job is currently running. */
+ public static final int STATUS_RUNNING = 2;
+ /** The job is finished. */
+ public static final int STATUS_FINISHED = 3;
+
+ @IntDef(prefix = { "STATUS_" }, value = {
+ STATUS_PENDING,
+ STATUS_RUNNING,
+ STATUS_FINISHED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Status {}
+
+ /** The job does not have a result yet. */
+ public static final int RESULT_NONE = 1;
+ /** The job completed successfully. */
+ public static final int RESULT_SUCCESS = 2;
+ /** The job encountered an error while running. */
+ public static final int RESULT_ERROR = 3;
+ /** The job was canceled by the caller. */
+ public static final int RESULT_CANCELED = 4;
+
+ @IntDef(prefix = { "RESULT_" }, value = {
+ RESULT_NONE,
+ RESULT_SUCCESS,
+ RESULT_ERROR,
+ RESULT_CANCELED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Result {}
+
+ /** Listener that gets notified when the progress changes. */
+ @FunctionalInterface
+ public interface OnProgressChangedListener {
+
+ /**
+ * Called when the progress changes. The progress is between 0 and 1, where 0 means
+ * that the job has not yet started and 1 means that it has finished.
+ * @param progress The new progress.
+ */
+ void onProgressChanged(float progress);
+ }
+
+ private final Executor mExecutor;
+ private final OnTranscodingFinishedListener mListener;
+ private final ReentrantLock mStatusChangeLock = new ReentrantLock();
+ private Executor mProgressChangedExecutor;
+ private OnProgressChangedListener mProgressChangedListener;
+ private long mID;
+ private float mProgress = 0.0f;
+ private @Status int mStatus = STATUS_PENDING;
+ private @Result int mResult = RESULT_NONE;
+
+ private TranscodingJob(long id, @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnTranscodingFinishedListener listener) {
+ mID = id;
+ mExecutor = executor;
+ mListener = listener;
+ }
+
+ /**
+ * Set a progress listener.
+ * @param listener The progress listener.
+ */
+ public void setOnProgressChangedListener(@NonNull @CallbackExecutor Executor executor,
+ @Nullable OnProgressChangedListener listener) {
+ mProgressChangedExecutor = executor;
+ mProgressChangedListener = listener;
+ }
+
+ /**
+ * Cancels the transcoding job and notify the listener. If the job happened to finish before
+ * being canceled this call is effectively a no-op and will not update the result in that
+ * case.
+ */
+ public void cancel() {
+ setJobFinished(RESULT_CANCELED);
+ sMediaTranscodeManager.native_cancelTranscodingRequest(mID);
+ }
+
+ /**
+ * Gets the progress of the transcoding job. The progress is between 0 and 1, where 0 means
+ * that the job has not yet started and 1 means that it is finished.
+ * @return The progress.
+ */
+ public float getProgress() {
+ return mProgress;
+ }
+
+ /**
+ * Gets the status of the transcoding job.
+ * @return The status.
+ */
+ public @Status int getStatus() {
+ return mStatus;
+ }
+
+ /**
+ * Gets the result of the transcoding job.
+ * @return The result.
+ */
+ public @Result int getResult() {
+ return mResult;
+ }
+
+ private void setJobStarted() {
+ mStatus = STATUS_RUNNING;
+ }
+
+ private void setJobProgress(float newProgress) {
+ mProgress = newProgress;
+
+ // Notify listener.
+ OnProgressChangedListener onProgressChangedListener = mProgressChangedListener;
+ if (onProgressChangedListener != null) {
+ mProgressChangedExecutor.execute(
+ () -> onProgressChangedListener.onProgressChanged(mProgress));
+ }
+ }
+
+ private void setJobFinished(int result) {
+ boolean doNotifyListener = false;
+
+ // Prevent conflicting simultaneous status updates from native (finished) and from the
+ // caller (cancel).
+ try {
+ mStatusChangeLock.lock();
+ if (mStatus != STATUS_FINISHED) {
+ mStatus = STATUS_FINISHED;
+ mResult = result;
+ doNotifyListener = true;
+ }
+ } finally {
+ mStatusChangeLock.unlock();
+ }
+
+ if (doNotifyListener) {
+ mExecutor.execute(() -> mListener.onTranscodingFinished(this));
+ }
+ }
+
+ private void processJobEvent(@Event int event, int arg) {
+ switch (event) {
+ case EVENT_JOB_STARTED:
+ setJobStarted();
+ break;
+ case EVENT_JOB_PROGRESSED:
+ setJobProgress((float) arg / 100);
+ break;
+ case EVENT_JOB_FINISHED:
+ setJobFinished(arg);
+ break;
+ default:
+ Log.e(TAG, "Unsupported event: " + event);
+ break;
+ }
+ }
+ }
+
+ // Initializes the native library.
+ private static native void native_init();
+ // Requests a new job ID from the native service.
+ private native long native_requestUniqueJobID();
+ // Enqueues a transcoding request to the native service.
+ private native boolean native_enqueueTranscodingRequest(
+ long id, @NonNull TranscodingRequest transcodingRequest, @NonNull Context context);
+ // Cancels an enqueued transcoding request.
+ private native void native_cancelTranscodingRequest(long id);
+
+ // Private constructor.
+ private MediaTranscodeManager(@NonNull Context context) {
+ mContext = context;
+ }
+
+ // Events posted from the native service.
+ @SuppressWarnings("unused")
+ private void postEventFromNative(@Event int event, long id, int arg) {
+ Log.d(TAG, String.format("postEventFromNative. Event %d, ID %d, arg %d", event, id, arg));
+
+ TranscodingJob transcodingJob = mPendingTranscodingJobs.get(id);
+
+ // Job IDs are added to the tracking set before the job is enqueued so it should never
+ // be null unless the service misbehaves.
+ if (transcodingJob == null) {
+ Log.e(TAG, "No matching transcode job found for id " + id);
+ return;
+ }
+
+ transcodingJob.processJobEvent(event, arg);
+ }
+
+ /**
+ * Gets the MediaTranscodeManager singleton instance.
+ * @param context The application context.
+ * @return the {@link MediaTranscodeManager} singleton instance.
+ */
+ public static MediaTranscodeManager getInstance(@NonNull Context context) {
+ Preconditions.checkNotNull(context);
+ synchronized (MediaTranscodeManager.class) {
+ if (sMediaTranscodeManager == null) {
+ sMediaTranscodeManager = new MediaTranscodeManager(context.getApplicationContext());
+ }
+ return sMediaTranscodeManager;
+ }
+ }
+
+ /**
+ * Enqueues a TranscodingRequest for execution.
+ * @param transcodingRequest The TranscodingRequest to enqueue.
+ * @param listenerExecutor Executor on which the listener is notified.
+ * @param listener Listener to get notified when the transcoding job is finished.
+ * @return A TranscodingJob for this operation.
+ */
+ public @Nullable TranscodingJob enqueueTranscodingRequest(
+ @NonNull TranscodingRequest transcodingRequest,
+ @NonNull @CallbackExecutor Executor listenerExecutor,
+ @NonNull OnTranscodingFinishedListener listener) {
+ Log.i(TAG, "enqueueTranscodingRequest called.");
+ Preconditions.checkNotNull(transcodingRequest);
+ Preconditions.checkNotNull(listenerExecutor);
+ Preconditions.checkNotNull(listener);
+
+ // Reserve a job ID.
+ long jobID = native_requestUniqueJobID();
+ if (jobID == ID_INVALID) {
+ return null;
+ }
+
+ // Add the job to the tracking set.
+ TranscodingJob transcodingJob = new TranscodingJob(jobID, listenerExecutor, listener);
+ mPendingTranscodingJobs.put(jobID, transcodingJob);
+
+ // Enqueue the request with the native service.
+ boolean enqueued = native_enqueueTranscodingRequest(jobID, transcodingRequest, mContext);
+ if (!enqueued) {
+ mPendingTranscodingJobs.remove(jobID);
+ return null;
+ }
+
+ return transcodingJob;
+ }
+
+ static {
+ System.loadLibrary("media_jni");
+ native_init();
+ }
+}
diff --git a/media/java/android/media/RouteSessionInfo.aidl b/media/java/android/media/RouteDiscoveryPreference.aidl
similarity index 94%
copy from media/java/android/media/RouteSessionInfo.aidl
copy to media/java/android/media/RouteDiscoveryPreference.aidl
index fb5d836..898eb39 100644
--- a/media/java/android/media/RouteSessionInfo.aidl
+++ b/media/java/android/media/RouteDiscoveryPreference.aidl
@@ -16,4 +16,4 @@
package android.media;
-parcelable RouteSessionInfo;
+parcelable RouteDiscoveryPreference;
diff --git a/media/java/android/media/RouteDiscoveryPreference.java b/media/java/android/media/RouteDiscoveryPreference.java
new file mode 100644
index 0000000..7ec1123
--- /dev/null
+++ b/media/java/android/media/RouteDiscoveryPreference.java
@@ -0,0 +1,215 @@
+/*
+ * 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.media;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * A media route discovery preference describing the kinds of routes that media router
+ * would like to discover and whether to perform active scanning.
+ *
+ * @see MediaRouter2#registerRouteCallback
+ */
+public final class RouteDiscoveryPreference implements Parcelable {
+ @NonNull
+ public static final Creator<RouteDiscoveryPreference> CREATOR =
+ new Creator<RouteDiscoveryPreference>() {
+ @Override
+ public RouteDiscoveryPreference createFromParcel(Parcel in) {
+ return new RouteDiscoveryPreference(in);
+ }
+
+ @Override
+ public RouteDiscoveryPreference[] newArray(int size) {
+ return new RouteDiscoveryPreference[size];
+ }
+ };
+
+ @NonNull
+ private final List<String> mPreferredFeatures;
+ private final boolean mActiveScan;
+ @Nullable
+ private final Bundle mExtras;
+
+ /**
+ * @hide
+ */
+ public static final RouteDiscoveryPreference EMPTY =
+ new Builder(Collections.emptyList(), false).build();
+
+ RouteDiscoveryPreference(@NonNull Builder builder) {
+ mPreferredFeatures = builder.mPreferredFeatures;
+ mActiveScan = builder.mActiveScan;
+ mExtras = builder.mExtras;
+ }
+
+ RouteDiscoveryPreference(@NonNull Parcel in) {
+ mPreferredFeatures = in.createStringArrayList();
+ mActiveScan = in.readBoolean();
+ mExtras = in.readBundle();
+ }
+
+ @NonNull
+ public List<String> getPreferredFeatures() {
+ return mPreferredFeatures;
+ }
+
+ public boolean isActiveScan() {
+ return mActiveScan;
+ }
+
+ /**
+ * @hide
+ */
+ public Bundle getExtras() {
+ return mExtras;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeStringList(mPreferredFeatures);
+ dest.writeBoolean(mActiveScan);
+ dest.writeBundle(mExtras);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder()
+ .append("RouteDiscoveryRequest{ ")
+ .append("preferredFeatures={")
+ .append(String.join(", ", mPreferredFeatures))
+ .append("}")
+ .append(", activeScan=")
+ .append(mActiveScan)
+ .append(" }");
+
+ return result.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof RouteDiscoveryPreference)) {
+ return false;
+ }
+ RouteDiscoveryPreference other = (RouteDiscoveryPreference) o;
+ return Objects.equals(mPreferredFeatures, other.mPreferredFeatures)
+ && mActiveScan == other.mActiveScan;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mPreferredFeatures, mActiveScan);
+ }
+
+ /**
+ * Builder for {@link RouteDiscoveryPreference}.
+ */
+ public static final class Builder {
+ List<String> mPreferredFeatures;
+ boolean mActiveScan;
+ Bundle mExtras;
+
+ public Builder(@NonNull List<String> preferredFeatures, boolean activeScan) {
+ mPreferredFeatures = new ArrayList<>(Objects.requireNonNull(preferredFeatures,
+ "preferredFeatures must not be null"));
+ mActiveScan = activeScan;
+ }
+
+ public Builder(@NonNull RouteDiscoveryPreference preference) {
+ Objects.requireNonNull(preference, "preference must not be null");
+
+ mPreferredFeatures = preference.getPreferredFeatures();
+ mActiveScan = preference.isActiveScan();
+ mExtras = preference.getExtras();
+ }
+
+ /**
+ * A constructor to combine all of the preferences into a single preference .
+ * It ignores extras of preferences.
+ *
+ * @hide
+ */
+ public Builder(@NonNull Collection<RouteDiscoveryPreference> preferences) {
+ Objects.requireNonNull(preferences, "preferences must not be null");
+
+ Set<String> routeFeatureSet = new HashSet<>();
+ mActiveScan = false;
+ for (RouteDiscoveryPreference preference : preferences) {
+ routeFeatureSet.addAll(preference.mPreferredFeatures);
+ mActiveScan |= preference.mActiveScan;
+ }
+ mPreferredFeatures = new ArrayList<>(routeFeatureSet);
+ }
+
+ /**
+ * Sets preferred route features to discover.
+ */
+ @NonNull
+ public Builder setPreferredFeatures(@NonNull List<String> preferredFeatures) {
+ mPreferredFeatures = new ArrayList<>(Objects.requireNonNull(preferredFeatures,
+ "preferredFeatures must not be null"));
+ return this;
+ }
+
+ /**
+ * Sets if active scanning should be performed.
+ */
+ @NonNull
+ public Builder setActiveScan(boolean activeScan) {
+ mActiveScan = activeScan;
+ return this;
+ }
+
+ /**
+ * Sets the extras of the route.
+ * @hide
+ */
+ @NonNull
+ public Builder setExtras(@Nullable Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ /**
+ * Builds the {@link RouteDiscoveryPreference}.
+ */
+ @NonNull
+ public RouteDiscoveryPreference build() {
+ return new RouteDiscoveryPreference(this);
+ }
+ }
+}
diff --git a/media/java/android/media/RouteSessionInfo.java b/media/java/android/media/RouteSessionInfo.java
deleted file mode 100644
index b9cf15e..0000000
--- a/media/java/android/media/RouteSessionInfo.java
+++ /dev/null
@@ -1,426 +0,0 @@
-/*
- * 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.media;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.text.TextUtils;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * Describes a route session that is made when a media route is selected.
- * @hide
- */
-public class RouteSessionInfo implements Parcelable {
- @NonNull
- public static final Creator<RouteSessionInfo> CREATOR =
- new Creator<RouteSessionInfo>() {
- @Override
- public RouteSessionInfo createFromParcel(Parcel in) {
- return new RouteSessionInfo(in);
- }
- @Override
- public RouteSessionInfo[] newArray(int size) {
- return new RouteSessionInfo[size];
- }
- };
-
- final int mSessionId;
- final String mPackageName;
- final String mControlCategory;
- @Nullable
- final String mProviderId;
- final List<String> mSelectedRoutes;
- final List<String> mSelectableRoutes;
- final List<String> mDeselectableRoutes;
- final List<String> mTransferrableRoutes;
- @Nullable
- final Bundle mControlHints;
-
- RouteSessionInfo(@NonNull Builder builder) {
- Objects.requireNonNull(builder, "builder must not be null.");
-
- mSessionId = builder.mSessionId;
- mPackageName = builder.mPackageName;
- mControlCategory = builder.mControlCategory;
- mProviderId = builder.mProviderId;
-
- mSelectedRoutes = Collections.unmodifiableList(builder.mSelectedRoutes);
- mSelectableRoutes = Collections.unmodifiableList(builder.mSelectableRoutes);
- mDeselectableRoutes = Collections.unmodifiableList(builder.mDeselectableRoutes);
- mTransferrableRoutes = Collections.unmodifiableList(builder.mTransferrableRoutes);
-
- mControlHints = builder.mControlHints;
- }
-
- RouteSessionInfo(@NonNull Parcel src) {
- Objects.requireNonNull(src, "src must not be null.");
-
- mSessionId = src.readInt();
- mPackageName = ensureString(src.readString());
- mControlCategory = ensureString(src.readString());
- mProviderId = src.readString();
-
- mSelectedRoutes = ensureList(src.createStringArrayList());
- mSelectableRoutes = ensureList(src.createStringArrayList());
- mDeselectableRoutes = ensureList(src.createStringArrayList());
- mTransferrableRoutes = ensureList(src.createStringArrayList());
-
- mControlHints = src.readBundle();
- }
-
- private static String ensureString(String str) {
- if (str != null) {
- return str;
- }
- return "";
- }
-
- private static <T> List<T> ensureList(List<? extends T> list) {
- if (list != null) {
- return Collections.unmodifiableList(list);
- }
- return Collections.emptyList();
- }
-
- /**
- * Gets non-unique session id (int) from unique session id (string).
- */
- public static int getSessionId(@NonNull String uniqueSessionId, @NonNull String providerId) {
- return Integer.parseInt(uniqueSessionId.substring(providerId.length() + 1));
- }
-
- /**
- * Returns whether the session info is valid or not
- */
- public boolean isValid() {
- return !TextUtils.isEmpty(mPackageName)
- && !TextUtils.isEmpty(mControlCategory)
- && mSelectedRoutes.size() > 0;
- }
-
- /**
- * Gets the id of the session
- */
- @NonNull
- public int getSessionId() {
- return mSessionId;
- }
-
- /**
- * Gets the client package name of the session
- */
- @NonNull
- public String getPackageName() {
- return mPackageName;
- }
-
- /**
- * Gets the control category of the session.
- * Routes that don't support the category can't be added to the session.
- */
- @NonNull
- public String getControlCategory() {
- return mControlCategory;
- }
-
- /**
- * Gets the provider id of the session.
- * @hide
- */
- @Nullable
- public String getProviderId() {
- return mProviderId;
- }
-
- /**
- * Gets the unique id of the session.
- * @hide
- */
- @NonNull
- public String getUniqueSessionId() {
- StringBuilder sessionIdBuilder = new StringBuilder()
- .append(mProviderId)
- .append("/")
- .append(mSessionId);
- return sessionIdBuilder.toString();
- }
-
- /**
- * Gets the list of ids of selected routes for the session. It shouldn't be empty.
- */
- @NonNull
- public List<String> getSelectedRoutes() {
- return mSelectedRoutes;
- }
-
- /**
- * Gets the list of ids of selectable routes for the session.
- */
- @NonNull
- public List<String> getSelectableRoutes() {
- return mSelectableRoutes;
- }
-
- /**
- * Gets the list of ids of deselectable routes for the session.
- */
- @NonNull
- public List<String> getDeselectableRoutes() {
- return mDeselectableRoutes;
- }
-
- /**
- * Gets the list of ids of transferrable routes for the session.
- */
- @NonNull
- public List<String> getTransferrableRoutes() {
- return mTransferrableRoutes;
- }
-
- /**
- * Gets the control hints
- */
- @Nullable
- public Bundle getControlHints() {
- return mControlHints;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(@NonNull Parcel dest, int flags) {
- dest.writeInt(mSessionId);
- dest.writeString(mPackageName);
- dest.writeString(mControlCategory);
- dest.writeString(mProviderId);
- dest.writeStringList(mSelectedRoutes);
- dest.writeStringList(mSelectableRoutes);
- dest.writeStringList(mDeselectableRoutes);
- dest.writeStringList(mTransferrableRoutes);
- dest.writeBundle(mControlHints);
- }
-
- @Override
- public String toString() {
- StringBuilder result = new StringBuilder()
- .append("RouteSessionInfo{ ")
- .append("sessionId=").append(mSessionId)
- .append(", controlCategory=").append(mControlCategory)
- .append(", selectedRoutes={")
- .append(String.join(",", mSelectedRoutes))
- .append("}")
- .append(", selectableRoutes={")
- .append(String.join(",", mSelectableRoutes))
- .append("}")
- .append(", deselectableRoutes={")
- .append(String.join(",", mDeselectableRoutes))
- .append("}")
- .append(", transferrableRoutes={")
- .append(String.join(",", mTransferrableRoutes))
- .append("}")
- .append(" }");
- return result.toString();
- }
-
- /**
- * Builder class for {@link RouteSessionInfo}.
- */
- public static final class Builder {
- final String mPackageName;
- final int mSessionId;
- final String mControlCategory;
- String mProviderId;
- final List<String> mSelectedRoutes;
- final List<String> mSelectableRoutes;
- final List<String> mDeselectableRoutes;
- final List<String> mTransferrableRoutes;
- Bundle mControlHints;
-
- public Builder(int sessionId, @NonNull String packageName,
- @NonNull String controlCategory) {
- mSessionId = sessionId;
- mPackageName = Objects.requireNonNull(packageName, "packageName must not be null");
- mControlCategory = Objects.requireNonNull(controlCategory,
- "controlCategory must not be null");
-
- mSelectedRoutes = new ArrayList<>();
- mSelectableRoutes = new ArrayList<>();
- mDeselectableRoutes = new ArrayList<>();
- mTransferrableRoutes = new ArrayList<>();
- }
-
- public Builder(RouteSessionInfo sessionInfo) {
- mSessionId = sessionInfo.mSessionId;
- mPackageName = sessionInfo.mPackageName;
- mControlCategory = sessionInfo.mControlCategory;
- mProviderId = sessionInfo.mProviderId;
-
- mSelectedRoutes = new ArrayList<>(sessionInfo.mSelectedRoutes);
- mSelectableRoutes = new ArrayList<>(sessionInfo.mSelectableRoutes);
- mDeselectableRoutes = new ArrayList<>(sessionInfo.mDeselectableRoutes);
- mTransferrableRoutes = new ArrayList<>(sessionInfo.mTransferrableRoutes);
-
- mControlHints = sessionInfo.mControlHints;
- }
-
- /**
- * Sets the provider id of the session.
- */
- @NonNull
- public Builder setProviderId(String providerId) {
- mProviderId = providerId;
- return this;
- }
-
- /**
- * Clears the selected routes.
- */
- @NonNull
- public Builder clearSelectedRoutes() {
- mSelectedRoutes.clear();
- return this;
- }
-
- /**
- * Adds a route to the selected routes.
- */
- @NonNull
- public Builder addSelectedRoute(@NonNull String routeId) {
- mSelectedRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null"));
- return this;
- }
-
- /**
- * Removes a route from the selected routes.
- */
- @NonNull
- public Builder removeSelectedRoute(@NonNull String routeId) {
- mSelectedRoutes.remove(Objects.requireNonNull(routeId, "routeId must not be null"));
- return this;
- }
-
- /**
- * Clears the selectable routes.
- */
- @NonNull
- public Builder clearSelectableRoutes() {
- mSelectableRoutes.clear();
- return this;
- }
-
- /**
- * Adds a route to the selectable routes.
- */
- @NonNull
- public Builder addSelectableRoute(@NonNull String routeId) {
- mSelectableRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null"));
- return this;
- }
-
- /**
- * Removes a route from the selectable routes.
- */
- @NonNull
- public Builder removeSelectableRoute(@NonNull String routeId) {
- mSelectableRoutes.remove(Objects.requireNonNull(routeId, "routeId must not be null"));
- return this;
- }
-
- /**
- * Clears the deselectable routes.
- */
- @NonNull
- public Builder clearDeselectableRoutes() {
- mDeselectableRoutes.clear();
- return this;
- }
-
- /**
- * Adds a route to the deselectable routes.
- */
- @NonNull
- public Builder addDeselectableRoute(@NonNull String routeId) {
- mDeselectableRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null"));
- return this;
- }
-
- /**
- * Removes a route from the deselectable routes.
- */
- @NonNull
- public Builder removeDeselectableRoute(@NonNull String routeId) {
- mDeselectableRoutes.remove(Objects.requireNonNull(routeId, "routeId must not be null"));
- return this;
- }
-
- /**
- * Clears the transferrable routes.
- */
- @NonNull
- public Builder clearTransferrableRoutes() {
- mTransferrableRoutes.clear();
- return this;
- }
-
- /**
- * Adds a route to the transferrable routes.
- */
- @NonNull
- public Builder addTransferrableRoute(@NonNull String routeId) {
- mTransferrableRoutes.add(Objects.requireNonNull(routeId, "routeId must not be null"));
- return this;
- }
-
- /**
- * Removes a route from the transferrable routes.
- */
- @NonNull
- public Builder removeTransferrableRoute(@NonNull String routeId) {
- mTransferrableRoutes.remove(
- Objects.requireNonNull(routeId, "routeId must not be null"));
- return this;
- }
-
- /**
- * Sets control hints.
- */
- @NonNull
- public Builder setControlHints(@Nullable Bundle controlHints) {
- mControlHints = controlHints;
- return this;
- }
-
- /**
- * Builds a route session info.
- */
- @NonNull
- public RouteSessionInfo build() {
- return new RouteSessionInfo(this);
- }
- }
-}
diff --git a/media/java/android/media/RouteSessionInfo.aidl b/media/java/android/media/RoutingSessionInfo.aidl
similarity index 95%
rename from media/java/android/media/RouteSessionInfo.aidl
rename to media/java/android/media/RoutingSessionInfo.aidl
index fb5d836..7b8e3d9 100644
--- a/media/java/android/media/RouteSessionInfo.aidl
+++ b/media/java/android/media/RoutingSessionInfo.aidl
@@ -16,4 +16,4 @@
package android.media;
-parcelable RouteSessionInfo;
+parcelable RoutingSessionInfo;
diff --git a/media/java/android/media/RoutingSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java
new file mode 100644
index 0000000..96acf6c
--- /dev/null
+++ b/media/java/android/media/RoutingSessionInfo.java
@@ -0,0 +1,529 @@
+/*
+ * 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.media;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Describes a routing session which is created when a media route is selected.
+ * @hide
+ */
+public final class RoutingSessionInfo implements Parcelable {
+ @NonNull
+ public static final Creator<RoutingSessionInfo> CREATOR =
+ new Creator<RoutingSessionInfo>() {
+ @Override
+ public RoutingSessionInfo createFromParcel(Parcel in) {
+ return new RoutingSessionInfo(in);
+ }
+ @Override
+ public RoutingSessionInfo[] newArray(int size) {
+ return new RoutingSessionInfo[size];
+ }
+ };
+
+ private static final String TAG = "RoutingSessionInfo";
+
+ final String mId;
+ final String mClientPackageName;
+ final String mRouteFeature;
+ @Nullable
+ final String mProviderId;
+ final List<String> mSelectedRoutes;
+ final List<String> mSelectableRoutes;
+ final List<String> mDeselectableRoutes;
+ final List<String> mTransferrableRoutes;
+ @Nullable
+ final Bundle mControlHints;
+
+ RoutingSessionInfo(@NonNull Builder builder) {
+ Objects.requireNonNull(builder, "builder must not be null.");
+
+ mId = builder.mId;
+ mClientPackageName = builder.mClientPackageName;
+ mRouteFeature = builder.mRouteFeature;
+ mProviderId = builder.mProviderId;
+
+ // TODO: Needs to check that the routes already have unique IDs.
+ mSelectedRoutes = Collections.unmodifiableList(
+ convertToUniqueRouteIds(builder.mSelectedRoutes));
+ mSelectableRoutes = Collections.unmodifiableList(
+ convertToUniqueRouteIds(builder.mSelectableRoutes));
+ mDeselectableRoutes = Collections.unmodifiableList(
+ convertToUniqueRouteIds(builder.mDeselectableRoutes));
+ mTransferrableRoutes = Collections.unmodifiableList(
+ convertToUniqueRouteIds(builder.mTransferrableRoutes));
+
+ mControlHints = builder.mControlHints;
+ }
+
+ RoutingSessionInfo(@NonNull Parcel src) {
+ Objects.requireNonNull(src, "src must not be null.");
+
+ mId = ensureString(src.readString());
+ mClientPackageName = ensureString(src.readString());
+ mRouteFeature = ensureString(src.readString());
+ mProviderId = src.readString();
+
+ mSelectedRoutes = ensureList(src.createStringArrayList());
+ mSelectableRoutes = ensureList(src.createStringArrayList());
+ mDeselectableRoutes = ensureList(src.createStringArrayList());
+ mTransferrableRoutes = ensureList(src.createStringArrayList());
+
+ mControlHints = src.readBundle();
+ }
+
+ private static String ensureString(String str) {
+ if (str != null) {
+ return str;
+ }
+ return "";
+ }
+
+ private static <T> List<T> ensureList(List<? extends T> list) {
+ if (list != null) {
+ return Collections.unmodifiableList(list);
+ }
+ return Collections.emptyList();
+ }
+
+ /**
+ * Gets the id of the session. The sessions which are given by {@link MediaRouter2} will have
+ * unique IDs.
+ * <p>
+ * In order to ensure uniqueness in {@link MediaRouter2} side, the value of this method
+ * can be different from what was set in {@link MediaRoute2ProviderService}.
+ *
+ * @see Builder#Builder(String, String, String)
+ */
+ @NonNull
+ public String getId() {
+ if (mProviderId != null) {
+ return MediaRouter2Utils.toUniqueId(mProviderId, mId);
+ } else {
+ return mId;
+ }
+ }
+
+ /**
+ * Gets the original id set by {@link Builder#Builder(String, String, String)}.
+ * @hide
+ */
+ @NonNull
+ public String getOriginalId() {
+ return mId;
+ }
+
+ /**
+ * Gets the client package name of the session
+ */
+ @NonNull
+ public String getClientPackageName() {
+ return mClientPackageName;
+ }
+
+ /**
+ * Gets the route feature of the session.
+ * Routes that don't have the feature can't be selected into the session.
+ */
+ @NonNull
+ public String getRouteFeature() {
+ return mRouteFeature;
+ }
+
+ /**
+ * Gets the provider id of the session.
+ * @hide
+ */
+ @Nullable
+ public String getProviderId() {
+ return mProviderId;
+ }
+
+ /**
+ * Gets the list of ids of selected routes for the session. It shouldn't be empty.
+ */
+ @NonNull
+ public List<String> getSelectedRoutes() {
+ return mSelectedRoutes;
+ }
+
+ /**
+ * Gets the list of ids of selectable routes for the session.
+ */
+ @NonNull
+ public List<String> getSelectableRoutes() {
+ return mSelectableRoutes;
+ }
+
+ /**
+ * Gets the list of ids of deselectable routes for the session.
+ */
+ @NonNull
+ public List<String> getDeselectableRoutes() {
+ return mDeselectableRoutes;
+ }
+
+ /**
+ * Gets the list of ids of transferrable routes for the session.
+ */
+ @NonNull
+ public List<String> getTransferrableRoutes() {
+ return mTransferrableRoutes;
+ }
+
+ /**
+ * Gets the control hints
+ */
+ @Nullable
+ public Bundle getControlHints() {
+ return mControlHints;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeString(mId);
+ dest.writeString(mClientPackageName);
+ dest.writeString(mRouteFeature);
+ dest.writeString(mProviderId);
+ dest.writeStringList(mSelectedRoutes);
+ dest.writeStringList(mSelectableRoutes);
+ dest.writeStringList(mDeselectableRoutes);
+ dest.writeStringList(mTransferrableRoutes);
+ dest.writeBundle(mControlHints);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (!(obj instanceof RoutingSessionInfo)) {
+ return false;
+ }
+
+ RoutingSessionInfo other = (RoutingSessionInfo) obj;
+ return Objects.equals(mId, other.mId)
+ && Objects.equals(mClientPackageName, other.mClientPackageName)
+ && Objects.equals(mRouteFeature, other.mRouteFeature)
+ && Objects.equals(mProviderId, other.mProviderId)
+ && Objects.equals(mSelectedRoutes, other.mSelectedRoutes)
+ && Objects.equals(mSelectableRoutes, other.mSelectableRoutes)
+ && Objects.equals(mDeselectableRoutes, other.mDeselectableRoutes)
+ && Objects.equals(mTransferrableRoutes, other.mTransferrableRoutes);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mId, mClientPackageName, mRouteFeature, mProviderId,
+ mSelectedRoutes, mSelectableRoutes, mDeselectableRoutes, mTransferrableRoutes);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder()
+ .append("RoutingSessionInfo{ ")
+ .append("sessionId=").append(mId)
+ .append(", routeFeature=").append(mRouteFeature)
+ .append(", selectedRoutes={")
+ .append(String.join(",", mSelectedRoutes))
+ .append("}")
+ .append(", selectableRoutes={")
+ .append(String.join(",", mSelectableRoutes))
+ .append("}")
+ .append(", deselectableRoutes={")
+ .append(String.join(",", mDeselectableRoutes))
+ .append("}")
+ .append(", transferrableRoutes={")
+ .append(String.join(",", mTransferrableRoutes))
+ .append("}")
+ .append(" }");
+ return result.toString();
+ }
+
+ private List<String> convertToUniqueRouteIds(@NonNull List<String> routeIds) {
+ if (routeIds == null) {
+ Log.w(TAG, "routeIds is null. Returning an empty list");
+ return Collections.emptyList();
+ }
+
+ // mProviderId can be null if not set. Return the original list for this case.
+ if (mProviderId == null) {
+ return routeIds;
+ }
+
+ List<String> result = new ArrayList<>();
+ for (String routeId : routeIds) {
+ result.add(MediaRouter2Utils.toUniqueId(mProviderId, routeId));
+ }
+ return result;
+ }
+
+ /**
+ * Builder class for {@link RoutingSessionInfo}.
+ */
+ public static final class Builder {
+ final String mId;
+ final String mClientPackageName;
+ final String mRouteFeature;
+ String mProviderId;
+ final List<String> mSelectedRoutes;
+ final List<String> mSelectableRoutes;
+ final List<String> mDeselectableRoutes;
+ final List<String> mTransferrableRoutes;
+ Bundle mControlHints;
+
+ /**
+ * Constructor for builder to create {@link RoutingSessionInfo}.
+ * <p>
+ * In order to ensure ID uniqueness in {@link MediaRouter2} side, the value of
+ * {@link RoutingSessionInfo#getId()} can be different from what was set in
+ * {@link MediaRoute2ProviderService}.
+ * </p>
+ *
+ * @param id ID of the session. Must not be empty.
+ * @param clientPackageName package name of the client app which uses this session.
+ * If is is unknown, then just use an empty string.
+ * @param routeFeature the route feature of session. Must not be empty.
+ * @see MediaRoute2Info#getId()
+ */
+ public Builder(@NonNull String id, @NonNull String clientPackageName,
+ @NonNull String routeFeature) {
+ if (TextUtils.isEmpty(id)) {
+ throw new IllegalArgumentException("id must not be empty");
+ }
+ Objects.requireNonNull(clientPackageName, "clientPackageName must not be null");
+ if (TextUtils.isEmpty(routeFeature)) {
+ throw new IllegalArgumentException("routeFeature must not be empty");
+ }
+
+ mId = id;
+ mClientPackageName = clientPackageName;
+ mRouteFeature = routeFeature;
+ mSelectedRoutes = new ArrayList<>();
+ mSelectableRoutes = new ArrayList<>();
+ mDeselectableRoutes = new ArrayList<>();
+ mTransferrableRoutes = new ArrayList<>();
+ }
+
+ /**
+ * Constructor for builder to create {@link RoutingSessionInfo} with
+ * existing {@link RoutingSessionInfo} instance.
+ *
+ * @param sessionInfo the existing instance to copy data from.
+ */
+ public Builder(@NonNull RoutingSessionInfo sessionInfo) {
+ Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
+
+ mId = sessionInfo.mId;
+ mClientPackageName = sessionInfo.mClientPackageName;
+ mRouteFeature = sessionInfo.mRouteFeature;
+ mProviderId = sessionInfo.mProviderId;
+
+ mSelectedRoutes = new ArrayList<>(sessionInfo.mSelectedRoutes);
+ mSelectableRoutes = new ArrayList<>(sessionInfo.mSelectableRoutes);
+ mDeselectableRoutes = new ArrayList<>(sessionInfo.mDeselectableRoutes);
+ mTransferrableRoutes = new ArrayList<>(sessionInfo.mTransferrableRoutes);
+
+ mControlHints = sessionInfo.mControlHints;
+ }
+
+ /**
+ * Sets the provider ID of the session.
+ *
+ * @hide
+ */
+ @NonNull
+ public Builder setProviderId(@NonNull String providerId) {
+ if (TextUtils.isEmpty(providerId)) {
+ throw new IllegalArgumentException("providerId must not be empty");
+ }
+ mProviderId = providerId;
+ return this;
+ }
+
+ /**
+ * Clears the selected routes.
+ */
+ @NonNull
+ public Builder clearSelectedRoutes() {
+ mSelectedRoutes.clear();
+ return this;
+ }
+
+ /**
+ * Adds a route to the selected routes. The {@code routeId} must not be empty.
+ */
+ @NonNull
+ public Builder addSelectedRoute(@NonNull String routeId) {
+ if (TextUtils.isEmpty(routeId)) {
+ throw new IllegalArgumentException("routeId must not be empty");
+ }
+ mSelectedRoutes.add(routeId);
+ return this;
+ }
+
+ /**
+ * Removes a route from the selected routes. The {@code routeId} must not be empty.
+ */
+ @NonNull
+ public Builder removeSelectedRoute(@NonNull String routeId) {
+ if (TextUtils.isEmpty(routeId)) {
+ throw new IllegalArgumentException("routeId must not be empty");
+ }
+ mSelectedRoutes.remove(routeId);
+ return this;
+ }
+
+ /**
+ * Clears the selectable routes.
+ */
+ @NonNull
+ public Builder clearSelectableRoutes() {
+ mSelectableRoutes.clear();
+ return this;
+ }
+
+ /**
+ * Adds a route to the selectable routes. The {@code routeId} must not be empty.
+ */
+ @NonNull
+ public Builder addSelectableRoute(@NonNull String routeId) {
+ if (TextUtils.isEmpty(routeId)) {
+ throw new IllegalArgumentException("routeId must not be empty");
+ }
+ mSelectableRoutes.add(routeId);
+ return this;
+ }
+
+ /**
+ * Removes a route from the selectable routes. The {@code routeId} must not be empty.
+ */
+ @NonNull
+ public Builder removeSelectableRoute(@NonNull String routeId) {
+ if (TextUtils.isEmpty(routeId)) {
+ throw new IllegalArgumentException("routeId must not be empty");
+ }
+ mSelectableRoutes.remove(routeId);
+ return this;
+ }
+
+ /**
+ * Clears the deselectable routes.
+ */
+ @NonNull
+ public Builder clearDeselectableRoutes() {
+ mDeselectableRoutes.clear();
+ return this;
+ }
+
+ /**
+ * Adds a route to the deselectable routes. The {@code routeId} must not be empty.
+ */
+ @NonNull
+ public Builder addDeselectableRoute(@NonNull String routeId) {
+ if (TextUtils.isEmpty(routeId)) {
+ throw new IllegalArgumentException("routeId must not be empty");
+ }
+ mDeselectableRoutes.add(routeId);
+ return this;
+ }
+
+ /**
+ * Removes a route from the deselectable routes. The {@code routeId} must not be empty.
+ */
+ @NonNull
+ public Builder removeDeselectableRoute(@NonNull String routeId) {
+ if (TextUtils.isEmpty(routeId)) {
+ throw new IllegalArgumentException("routeId must not be empty");
+ }
+ mDeselectableRoutes.remove(routeId);
+ return this;
+ }
+
+ /**
+ * Clears the transferrable routes.
+ */
+ @NonNull
+ public Builder clearTransferrableRoutes() {
+ mTransferrableRoutes.clear();
+ return this;
+ }
+
+ /**
+ * Adds a route to the transferrable routes. The {@code routeId} must not be empty.
+ */
+ @NonNull
+ public Builder addTransferrableRoute(@NonNull String routeId) {
+ if (TextUtils.isEmpty(routeId)) {
+ throw new IllegalArgumentException("routeId must not be empty");
+ }
+ mTransferrableRoutes.add(routeId);
+ return this;
+ }
+
+ /**
+ * Removes a route from the transferrable routes. The {@code routeId} must not be empty.
+ */
+ @NonNull
+ public Builder removeTransferrableRoute(@NonNull String routeId) {
+ if (TextUtils.isEmpty(routeId)) {
+ throw new IllegalArgumentException("routeId must not be empty");
+ }
+ mTransferrableRoutes.remove(routeId);
+ return this;
+ }
+
+ /**
+ * Sets control hints.
+ */
+ @NonNull
+ public Builder setControlHints(@Nullable Bundle controlHints) {
+ mControlHints = controlHints;
+ return this;
+ }
+
+ /**
+ * Builds a routing session info.
+ *
+ * @throws IllegalArgumentException if no selected routes are added.
+ */
+ @NonNull
+ public RoutingSessionInfo build() {
+ if (mSelectedRoutes.isEmpty()) {
+ throw new IllegalArgumentException("selectedRoutes must not be empty");
+ }
+ return new RoutingSessionInfo(this);
+ }
+ }
+}
diff --git a/media/java/android/media/VolumeProvider.java b/media/java/android/media/VolumeProvider.java
index 8f68cbd..ed272d5 100644
--- a/media/java/android/media/VolumeProvider.java
+++ b/media/java/android/media/VolumeProvider.java
@@ -16,6 +16,7 @@
package android.media;
import android.annotation.IntDef;
+import android.annotation.Nullable;
import android.media.session.MediaSession;
import java.lang.annotation.Retention;
@@ -60,6 +61,7 @@
private final int mControlType;
private final int mMaxVolume;
+ private final String mControlId;
private int mCurrentVolume;
private Callback mCallback;
@@ -73,10 +75,28 @@
* @param maxVolume The maximum allowed volume.
* @param currentVolume The current volume on the output.
*/
+
public VolumeProvider(@ControlType int volumeControl, int maxVolume, int currentVolume) {
+ this(volumeControl, maxVolume, currentVolume, null);
+ }
+
+ /**
+ * Create a new volume provider for handling volume events. You must specify
+ * the type of volume control, the maximum volume that can be used, and the
+ * current volume on the output.
+ *
+ * @param volumeControl The method for controlling volume that is used by
+ * this provider.
+ * @param maxVolume The maximum allowed volume.
+ * @param currentVolume The current volume on the output.
+ * @param volumeControlId The volume control id of this provider.
+ */
+ public VolumeProvider(@ControlType int volumeControl, int maxVolume, int currentVolume,
+ @Nullable String volumeControlId) {
mControlType = volumeControl;
mMaxVolume = maxVolume;
mCurrentVolume = currentVolume;
+ mControlId = volumeControlId;
}
/**
@@ -122,6 +142,17 @@
}
/**
+ * Gets the volume control id. It can be used to identify which volume provider is
+ * used by the session.
+ *
+ * @return the volume control id or {@code null} if it isn't set.
+ */
+ @Nullable
+ public final String getVolumeControlId() {
+ return mControlId;
+ }
+
+ /**
* Override to handle requests to set the volume of the current output.
* After the volume has been modified {@link #setCurrentVolume} must be
* called to notify the system.
diff --git a/media/java/android/media/audiofx/AudioEffect.java b/media/java/android/media/audiofx/AudioEffect.java
index 4a40c62..cad5aa6 100644
--- a/media/java/android/media/audiofx/AudioEffect.java
+++ b/media/java/android/media/audiofx/AudioEffect.java
@@ -16,11 +16,18 @@
package android.media.audiofx;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.ActivityThread;
import android.compat.annotation.UnsupportedAppUsage;
+import android.media.AudioDeviceAddress;
+import android.media.AudioDeviceInfo;
+import android.media.AudioSystem;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
@@ -448,12 +455,46 @@
public AudioEffect(UUID type, UUID uuid, int priority, int audioSession)
throws IllegalArgumentException, UnsupportedOperationException,
RuntimeException {
+ this(type, uuid, priority, audioSession, null);
+ }
+
+ /**
+ * Constructs an AudioEffect attached to a particular audio device.
+ * The device does not have to be attached when the effect is created. The effect will only
+ * be applied when the device is actually selected for playback or capture.
+ * @param uuid unique identifier of a particular effect implementation.
+ * @param device the device the effect must be attached to.
+ *
+ * @throws java.lang.IllegalArgumentException
+ * @throws java.lang.UnsupportedOperationException
+ * @throws java.lang.RuntimeException
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_DEFAULT_AUDIO_EFFECTS)
+ public AudioEffect(@NonNull UUID uuid, @NonNull AudioDeviceAddress device) {
+ this(EFFECT_TYPE_NULL, Objects.requireNonNull(uuid), 0, -2, Objects.requireNonNull(device));
+ }
+
+ private AudioEffect(UUID type, UUID uuid, int priority,
+ int audioSession, @Nullable AudioDeviceAddress device)
+ throws IllegalArgumentException, UnsupportedOperationException,
+ RuntimeException {
int[] id = new int[1];
Descriptor[] desc = new Descriptor[1];
+
+ int deviceType = AudioSystem.DEVICE_NONE;
+ String deviceAddress = "";
+ if (device != null) {
+ deviceType = AudioDeviceInfo.convertDeviceTypeToInternalDevice(device.getType());
+ deviceAddress = device.getAddress();
+ }
+
// native initialization
int initResult = native_setup(new WeakReference<AudioEffect>(this),
- type.toString(), uuid.toString(), priority, audioSession, id,
- desc, ActivityThread.currentOpPackageName());
+ type.toString(), uuid.toString(), priority, audioSession,
+ deviceType, deviceAddress,
+ id, desc, ActivityThread.currentOpPackageName());
if (initResult != SUCCESS && initResult != ALREADY_EXISTS) {
Log.e(TAG, "Error code " + initResult
+ " when initializing AudioEffect.");
@@ -1293,7 +1334,8 @@
private static native final void native_init();
private native final int native_setup(Object audioeffect_this, String type,
- String uuid, int priority, int audioSession, int[] id, Object[] desc,
+ String uuid, int priority, int audioSession,
+ int deviceType, String deviceAddress, int[] id, Object[] desc,
String opPackageName);
private native final void native_finalize();
diff --git a/media/java/android/media/audiopolicy/AudioPolicy.java b/media/java/android/media/audiopolicy/AudioPolicy.java
index 01f1250..27f02fe 100644
--- a/media/java/android/media/audiopolicy/AudioPolicy.java
+++ b/media/java/android/media/audiopolicy/AudioPolicy.java
@@ -719,9 +719,14 @@
if (track == null) {
break;
}
- // TODO: add synchronous versions
- track.stop();
- track.flush();
+ try {
+ // TODO: add synchronous versions
+ track.stop();
+ track.flush();
+ } catch (IllegalStateException e) {
+ // ignore exception, AudioTrack could have already been stopped or
+ // released by the user of the AudioPolicy
+ }
}
}
if (mCaptors != null) {
@@ -730,8 +735,13 @@
if (record == null) {
break;
}
- // TODO: if needed: implement an invalidate method
- record.stop();
+ try {
+ // TODO: if needed: implement an invalidate method
+ record.stop();
+ } catch (IllegalStateException e) {
+ // ignore exception, AudioRecord could have already been stopped or
+ // released by the user of the AudioPolicy
+ }
}
}
}
diff --git a/media/java/android/media/session/ISession.aidl b/media/java/android/media/session/ISession.aidl
index 4d68a6a..21378c8 100644
--- a/media/java/android/media/session/ISession.aidl
+++ b/media/java/android/media/session/ISession.aidl
@@ -48,6 +48,6 @@
// These commands relate to volume handling
void setPlaybackToLocal(in AudioAttributes attributes);
- void setPlaybackToRemote(int control, int max);
+ void setPlaybackToRemote(int control, int max, @nullable String controlId);
void setCurrentVolume(int currentVolume);
}
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index 1812d9c..c2f6206 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -964,16 +964,26 @@
private final int mMaxVolume;
private final int mCurrentVolume;
private final AudioAttributes mAudioAttrs;
+ private final String mVolumeControlId;
/**
* @hide
*/
public PlaybackInfo(int type, int control, int max, int current, AudioAttributes attrs) {
+ this(type, control, max, current, attrs, null);
+ }
+
+ /**
+ * @hide
+ */
+ public PlaybackInfo(int type, int control, int max, int current, AudioAttributes attrs,
+ String volumeControlId) {
mVolumeType = type;
mVolumeControl = control;
mMaxVolume = max;
mCurrentVolume = current;
mAudioAttrs = attrs;
+ mVolumeControlId = volumeControlId;
}
PlaybackInfo(Parcel in) {
@@ -982,6 +992,7 @@
mMaxVolume = in.readInt();
mCurrentVolume = in.readInt();
mAudioAttrs = in.readParcelable(null);
+ mVolumeControlId = in.readString();
}
/**
@@ -1042,11 +1053,24 @@
return mAudioAttrs;
}
+ /**
+ * Gets the volume control ID for this session. It can be used to identify which
+ * volume provider is used by the session.
+ *
+ * @return the volume control ID for this session or {@code null} if it's local playback
+ * or not set.
+ * @see VolumeProvider#getVolumeControlId()
+ */
+ @Nullable
+ public String getVolumeControlId() {
+ return mVolumeControlId;
+ }
+
@Override
public String toString() {
return "volumeType=" + mVolumeType + ", volumeControl=" + mVolumeControl
+ ", maxVolume=" + mMaxVolume + ", currentVolume=" + mCurrentVolume
- + ", audioAttrs=" + mAudioAttrs;
+ + ", audioAttrs=" + mAudioAttrs + ", volumeControlId=" + mVolumeControlId;
}
@Override
@@ -1061,6 +1085,7 @@
dest.writeInt(mMaxVolume);
dest.writeInt(mCurrentVolume);
dest.writeParcelable(mAudioAttrs, flags);
+ dest.writeString(mVolumeControlId);
}
public static final @android.annotation.NonNull Parcelable.Creator<PlaybackInfo> CREATOR =
diff --git a/media/java/android/media/session/MediaSession.java b/media/java/android/media/session/MediaSession.java
index 3adee59..9953626 100644
--- a/media/java/android/media/session/MediaSession.java
+++ b/media/java/android/media/session/MediaSession.java
@@ -335,7 +335,7 @@
try {
mBinder.setPlaybackToRemote(volumeProvider.getVolumeControl(),
- volumeProvider.getMaxVolume());
+ volumeProvider.getMaxVolume(), volumeProvider.getVolumeControlId());
mBinder.setCurrentVolume(volumeProvider.getCurrentVolume());
} catch (RemoteException e) {
Log.wtf(TAG, "Failure in setPlaybackToRemote.", e);
diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java
index aff7257..aece39d 100644
--- a/media/java/android/media/session/MediaSessionManager.java
+++ b/media/java/android/media/session/MediaSessionManager.java
@@ -940,16 +940,15 @@
/**
* Called when a media key event is dispatched through the media session service. The
* session token can be {@link null} if the framework has sent the media key event to the
- * media button receiver to revive the media app's playback.
- *
- * the session is dead when , but the framework sent
+ * media button receiver to revive the media app's playback after the corresponding session
+ * is released.
*
* @param event Dispatched media key event.
* @param packageName Package
* @param sessionToken The media session's token. Can be {@code null}.
*/
default void onMediaKeyEventDispatched(@NonNull KeyEvent event, @NonNull String packageName,
- @NonNull MediaSession.Token sessionToken) { }
+ @Nullable MediaSession.Token sessionToken) { }
}
/**
diff --git a/media/java/android/media/soundtrigger/SoundTriggerDetector.java b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
index 56e5566..118f65c 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerDetector.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerDetector.java
@@ -22,9 +22,10 @@
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.hardware.soundtrigger.IRecognitionStatusCallback;
import android.hardware.soundtrigger.SoundTrigger;
+import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
import android.media.AudioFormat;
import android.os.Handler;
@@ -75,7 +76,9 @@
value = {
RECOGNITION_FLAG_NONE,
RECOGNITION_FLAG_CAPTURE_TRIGGER_AUDIO,
- RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS
+ RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS,
+ RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION,
+ RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION,
})
public @interface RecognitionFlags {}
@@ -100,11 +103,34 @@
* triggers after a call to {@link #startRecognition(int)}, if the model
* triggers multiple times.
* When this isn't specified, the default behavior is to stop recognition once the
- * trigger happenss, till the caller starts recognition again.
+ * trigger happens, till the caller starts recognition again.
*/
public static final int RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS = 0x2;
/**
+ * Audio capabilities flag for {@link #startRecognition(int)} that indicates
+ * if the underlying recognition should use AEC.
+ * This capability may or may not be supported by the system, and support can be queried
+ * by calling {@link SoundTriggerManager#getModuleProperties()} and checking
+ * {@link ModuleProperties#audioCapabilities}. The corresponding capabilities field for
+ * this flag is {@link SoundTrigger.ModuleProperties#CAPABILITY_ECHO_CANCELLATION}.
+ * If this flag is passed without the audio capability supported, there will be no audio effect
+ * applied.
+ */
+ public static final int RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION = 0x4;
+
+ /**
+ * Audio capabilities flag for {@link #startRecognition(int)} that indicates
+ * if the underlying recognition should use noise suppression.
+ * This capability may or may not be supported by the system, and support can be queried
+ * by calling {@link SoundTriggerManager#getModuleProperties()} and checking
+ * {@link ModuleProperties#audioCapabilities}. The corresponding capabilities field for
+ * this flag is {@link SoundTrigger.ModuleProperties#CAPABILITY_NOISE_SUPPRESSION}. If this flag
+ * is passed without the audio capability supported, there will be no audio effect applied.
+ */
+ public static final int RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION = 0x8;
+
+ /**
* Additional payload for {@link Callback#onDetected}.
*/
public static class EventPayload {
@@ -267,11 +293,20 @@
boolean allowMultipleTriggers =
(recognitionFlags & RECOGNITION_FLAG_ALLOW_MULTIPLE_TRIGGERS) != 0;
- int status = STATUS_OK;
+
+ int audioCapabilities = 0;
+ if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_ECHO_CANCELLATION) != 0) {
+ audioCapabilities |= SoundTrigger.ModuleProperties.CAPABILITY_ECHO_CANCELLATION;
+ }
+ if ((recognitionFlags & RECOGNITION_FLAG_ENABLE_AUDIO_NOISE_SUPPRESSION) != 0) {
+ audioCapabilities |= SoundTrigger.ModuleProperties.CAPABILITY_NOISE_SUPPRESSION;
+ }
+
+ int status;
try {
status = mSoundTriggerService.startRecognition(new ParcelUuid(mSoundModelId),
mRecognitionCallback, new RecognitionConfig(captureTriggerAudio,
- allowMultipleTriggers, null, null));
+ allowMultipleTriggers, null, null, audioCapabilities));
} catch (RemoteException e) {
return false;
}
diff --git a/media/java/android/media/soundtrigger/SoundTriggerManager.java b/media/java/android/media/soundtrigger/SoundTriggerManager.java
index 61b3e76..dd4dac2 100644
--- a/media/java/android/media/soundtrigger/SoundTriggerManager.java
+++ b/media/java/android/media/soundtrigger/SoundTriggerManager.java
@@ -23,7 +23,7 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.hardware.soundtrigger.ModelParams;
@@ -44,6 +44,7 @@
import com.android.internal.util.Preconditions;
import java.util.HashMap;
+import java.util.Objects;
import java.util.UUID;
/**
@@ -175,19 +176,40 @@
* Factory constructor to create a SoundModel instance for use with methods in this
* class.
*/
- public static Model create(UUID modelUuid, UUID vendorUuid, byte[] data) {
- return new Model(new SoundTrigger.GenericSoundModel(modelUuid,
- vendorUuid, data));
+ @NonNull
+ public static Model create(@NonNull UUID modelUuid, @NonNull UUID vendorUuid,
+ @Nullable byte[] data, int version) {
+ Objects.requireNonNull(modelUuid);
+ Objects.requireNonNull(vendorUuid);
+ return new Model(new SoundTrigger.GenericSoundModel(modelUuid, vendorUuid, data,
+ version));
}
+ /**
+ * Factory constructor to create a SoundModel instance for use with methods in this
+ * class.
+ */
+ @NonNull
+ public static Model create(@NonNull UUID modelUuid, @NonNull UUID vendorUuid,
+ @Nullable byte[] data) {
+ return create(modelUuid, vendorUuid, data, -1);
+ }
+
+ @NonNull
public UUID getModelUuid() {
return mGenericSoundModel.uuid;
}
+ @NonNull
public UUID getVendorUuid() {
return mGenericSoundModel.vendorUuid;
}
+ public int getVersion() {
+ return mGenericSoundModel.version;
+ }
+
+ @Nullable
public byte[] getModelData() {
return mGenericSoundModel.data;
}
@@ -428,8 +450,7 @@
*/
@RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
public int setParameter(@Nullable UUID soundModelId,
- @ModelParams int modelParam, int value)
- throws UnsupportedOperationException, IllegalArgumentException {
+ @ModelParams int modelParam, int value) {
try {
return mSoundTriggerService.setParameter(new ParcelUuid(soundModelId), modelParam,
value);
@@ -449,15 +470,10 @@
* @param soundModelId UUID of model to get parameter
* @param modelParam {@link ModelParams}
* @return value of parameter
- * @throws UnsupportedOperationException if hal or model do not support this API.
- * {@link SoundTriggerManager#queryParameter} should be checked first.
- * @throws IllegalArgumentException if invalid model handle or parameter is passed.
- * {@link SoundTriggerManager#queryParameter} should be checked first.
*/
@RequiresPermission(android.Manifest.permission.MANAGE_SOUND_TRIGGER)
public int getParameter(@NonNull UUID soundModelId,
- @ModelParams int modelParam)
- throws UnsupportedOperationException, IllegalArgumentException {
+ @ModelParams int modelParam) {
try {
return mSoundTriggerService.getParameter(new ParcelUuid(soundModelId), modelParam);
} catch (RemoteException e) {
@@ -479,8 +495,7 @@
public ModelParamRange queryParameter(@Nullable UUID soundModelId,
@ModelParams int modelParam) {
try {
- return mSoundTriggerService.queryParameter(new ParcelUuid(soundModelId),
- modelParam);
+ return mSoundTriggerService.queryParameter(new ParcelUuid(soundModelId), modelParam);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/media/java/android/media/soundtrigger_middleware/AudioCapabilities.aidl b/media/java/android/media/soundtrigger_middleware/AudioCapabilities.aidl
new file mode 100644
index 0000000..97a8849
--- /dev/null
+++ b/media/java/android/media/soundtrigger_middleware/AudioCapabilities.aidl
@@ -0,0 +1,32 @@
+/*
+ * 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.media.soundtrigger_middleware;
+
+/**
+ * AudioCapabilities supported by the implemented HAL driver.
+ * @hide
+ */
+@Backing(type="int")
+enum AudioCapabilities {
+ /**
+ * If set the underlying module supports AEC.
+ */
+ ECHO_CANCELLATION = 1 << 0,
+ /**
+ * If set, the underlying module supports noise suppression.
+ */
+ NOISE_SUPPRESSION = 1 << 1,
+}
diff --git a/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl b/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
index 149c1cd..726af76 100644
--- a/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
+++ b/media/java/android/media/soundtrigger_middleware/ISoundTriggerCallback.aidl
@@ -44,4 +44,11 @@
* and this event will be sent in addition to the abort event.
*/
void onRecognitionAvailabilityChange(boolean available);
+ /**
+ * Notifies the client that the associated module has crashed and restarted. The module instance
+ * is no longer usable and will throw a ServiceSpecificException with a Status.DEAD_OBJECT code
+ * for every call. The client should detach, then re-attach to the module in order to get a new,
+ * usable instance. All state for this module has been lost.
+ */
+ void onModuleDied();
}
diff --git a/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl b/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl
index c7642e8..5c0eeb1 100644
--- a/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl
+++ b/media/java/android/media/soundtrigger_middleware/RecognitionConfig.aidl
@@ -28,6 +28,12 @@
/* Configuration for each key phrase. */
PhraseRecognitionExtra[] phraseRecognitionExtras;
+ /**
+ * Bit field encoding of the AudioCapabilities
+ * supported by the firmware.
+ */
+ int audioCapabilities;
+
/** Opaque capture configuration data. */
byte[] data;
}
diff --git a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl
index 1a3b402..9c56e7b 100644
--- a/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl
+++ b/media/java/android/media/soundtrigger_middleware/SoundTriggerModuleProperties.aidl
@@ -30,6 +30,14 @@
* Unique implementation ID. The UUID must change with each version of
the engine implementation */
String uuid;
+ /**
+ * String naming the architecture used for running the supported models.
+ * (eg. a platform running models on a DSP could implement this string to convey the DSP
+ * architecture used)
+ * This property is supported for soundtrigger HAL v2.3 and above.
+ * If running a previous version, the string will be empty.
+ */
+ String supportedModelArch;
/** Maximum number of concurrent sound models loaded */
int maxSoundModels;
/** Maximum number of key phrases */
@@ -50,4 +58,11 @@
* Rated power consumption when detection is active with TDB
* silence/sound/speech ratio */
int powerConsumptionMw;
+ /**
+ * Bit field encoding of the AudioCapabilities
+ * supported by the firmware.
+ * This property is supported for soundtrigger HAL v2.3 and above.
+ * If running a previous version, this value will be 0.
+ */
+ int audioCapabilities;
}
diff --git a/media/java/android/media/soundtrigger_middleware/Status.aidl b/media/java/android/media/soundtrigger_middleware/Status.aidl
index d8f9d8f..85ccacf 100644
--- a/media/java/android/media/soundtrigger_middleware/Status.aidl
+++ b/media/java/android/media/soundtrigger_middleware/Status.aidl
@@ -26,4 +26,8 @@
RESOURCE_CONTENTION = 1,
/** Operation is not supported in this implementation. This is a permanent condition. */
OPERATION_NOT_SUPPORTED = 2,
+ /** Temporary lack of permission. */
+ TEMPORARY_PERMISSION_DENIED = 3,
+ /** The object on which this method is called is dead and all of its state is lost. */
+ DEAD_OBJECT = 4,
}
diff --git a/media/java/android/media/tv/DvbDeviceInfo.java b/media/java/android/media/tv/DvbDeviceInfo.java
index a574fe1..96c8528 100644
--- a/media/java/android/media/tv/DvbDeviceInfo.java
+++ b/media/java/android/media/tv/DvbDeviceInfo.java
@@ -16,6 +16,8 @@
package android.media.tv;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
@@ -26,10 +28,11 @@
*
* @hide
*/
+@SystemApi
public final class DvbDeviceInfo implements Parcelable {
static final String TAG = "DvbDeviceInfo";
- public static final @android.annotation.NonNull Parcelable.Creator<DvbDeviceInfo> CREATOR =
+ public static final @NonNull Parcelable.Creator<DvbDeviceInfo> CREATOR =
new Parcelable.Creator<DvbDeviceInfo>() {
@Override
public DvbDeviceInfo createFromParcel(Parcel source) {
@@ -86,7 +89,7 @@
}
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mAdapterId);
dest.writeInt(mDeviceId);
}
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index b076bb6..b5e9d1b 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -60,6 +60,7 @@
void createSession(in ITvInputClient client, in String inputId, boolean isRecordingSession,
int seq, int userId);
void releaseSession(in IBinder sessionToken, int userId);
+ int getClientPid(in String sessionId);
void setMainSession(in IBinder sessionToken, int userId);
void setSurface(in IBinder sessionToken, in Surface surface, int userId);
diff --git a/media/java/android/media/tv/ITvInputService.aidl b/media/java/android/media/tv/ITvInputService.aidl
index f90c504..8ccf13a 100755
--- a/media/java/android/media/tv/ITvInputService.aidl
+++ b/media/java/android/media/tv/ITvInputService.aidl
@@ -30,8 +30,9 @@
void registerCallback(in ITvInputServiceCallback callback);
void unregisterCallback(in ITvInputServiceCallback callback);
void createSession(in InputChannel channel, in ITvInputSessionCallback callback,
- in String inputId);
- void createRecordingSession(in ITvInputSessionCallback callback, in String inputId);
+ in String inputId, in String sessionId);
+ void createRecordingSession(in ITvInputSessionCallback callback, in String inputId,
+ in String sessionId);
// For hardware TvInputService
void notifyHardwareAdded(in TvInputHardwareInfo hardwareInfo);
diff --git a/media/java/android/media/tv/TvInputInfo.java b/media/java/android/media/tv/TvInputInfo.java
index 1b9cac0..377b2bc 100644
--- a/media/java/android/media/tv/TvInputInfo.java
+++ b/media/java/android/media/tv/TvInputInfo.java
@@ -20,7 +20,7 @@
import android.annotation.NonNull;
import android.annotation.StringRes;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index d22a298..630d819 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -103,6 +103,12 @@
/** @hide */
@Retention(RetentionPolicy.SOURCE)
+ @IntDef({DVB_DEVICE_DEMUX, DVB_DEVICE_DVR, DVB_DEVICE_FRONTEND})
+ public @interface DvbDeviceType {}
+
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
@IntDef({VIDEO_UNAVAILABLE_REASON_UNKNOWN, VIDEO_UNAVAILABLE_REASON_TUNING,
VIDEO_UNAVAILABLE_REASON_WEAK_SIGNAL, VIDEO_UNAVAILABLE_REASON_BUFFERING,
VIDEO_UNAVAILABLE_REASON_AUDIO_ONLY})
@@ -260,6 +266,15 @@
public static final int INPUT_STATE_DISCONNECTED = 2;
/**
+ * An unknown state of the client pid gets from the TvInputManager. Client gets this value when
+ * query through {@link getClientPid(String sessionId)} fails.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int UNKNOWN_CLIENT_PID = -1;
+
+ /**
* Broadcast intent action when the user blocked content ratings change. For use with the
* {@link #isRatingBlocked}.
*/
@@ -1478,6 +1493,21 @@
}
/**
+ * Get a the client pid when creating the session with the session id provided.
+ *
+ * @param sessionId a String of session id that is used to query the client pid.
+ * @return the client pid when created the session. Returns {@link #UNKNOWN_CLIENT_PID}
+ * if the call fails.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS)
+ public int getClientPid(@NonNull String sessionId) {
+ return getClientPidInternal(sessionId);
+ };
+
+ /**
* Creates a recording {@link Session} for a given TV input.
*
* <p>The number of sessions that can be created at the same time is limited by the capability
@@ -1510,6 +1540,17 @@
}
}
+ private int getClientPidInternal(String sessionId) {
+ Preconditions.checkNotNull(sessionId);
+ int clientPid = UNKNOWN_CLIENT_PID;
+ try {
+ clientPid = mService.getClientPid(sessionId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ return clientPid;
+ }
+
/**
* Returns the TvStreamConfig list of the given TV input.
*
@@ -1663,6 +1704,9 @@
* @return the list of {@link DvbDeviceInfo} objects representing available DVB devices.
* @hide
*/
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.DVB_DEVICE)
+ @NonNull
public List<DvbDeviceInfo> getDvbDeviceList() {
try {
return mService.getDvbDeviceList();
@@ -1676,19 +1720,24 @@
* {@link DvbDeviceInfo}
*
* @param info A {@link DvbDeviceInfo} to open a DVB device.
- * @param device A DVB device. The DVB device can be {@link #DVB_DEVICE_DEMUX},
+ * @param deviceType A DVB device type. The type can be {@link #DVB_DEVICE_DEMUX},
* {@link #DVB_DEVICE_DVR} or {@link #DVB_DEVICE_FRONTEND}.
* @return a {@link ParcelFileDescriptor} of a specified DVB device for a given
- * {@link DvbDeviceInfo}, or {@code null} if the given {@link DvbDeviceInfo} was invalid
- * or the specified DVB device was busy with a previous request.
+ * {@link DvbDeviceInfo}, or {@code null} if the given {@link DvbDeviceInfo}
+ * failed to open.
+ * @throws IllegalArgumentException if {@code deviceType} is invalid or the device is not found.
* @hide
*/
- public ParcelFileDescriptor openDvbDevice(DvbDeviceInfo info, int device) {
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.DVB_DEVICE)
+ @Nullable
+ public ParcelFileDescriptor openDvbDevice(@NonNull DvbDeviceInfo info,
+ @DvbDeviceType int deviceType) {
try {
- if (DVB_DEVICE_START > device || DVB_DEVICE_END < device) {
- throw new IllegalArgumentException("Invalid DVB device: " + device);
+ if (DVB_DEVICE_START > deviceType || DVB_DEVICE_END < deviceType) {
+ throw new IllegalArgumentException("Invalid DVB device: " + deviceType);
}
- return mService.openDvbDevice(info, device);
+ return mService.openDvbDevice(info, deviceType);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 5c11ed9b..629dc7c 100755
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -22,9 +22,9 @@
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityManager;
import android.app.Service;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
import android.graphics.PixelFormat;
@@ -124,7 +124,7 @@
@Override
public void createSession(InputChannel channel, ITvInputSessionCallback cb,
- String inputId) {
+ String inputId, String sessionId) {
if (channel == null) {
Log.w(TAG, "Creating session without input channel");
}
@@ -135,17 +135,20 @@
args.arg1 = channel;
args.arg2 = cb;
args.arg3 = inputId;
+ args.arg4 = sessionId;
mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_SESSION, args).sendToTarget();
}
@Override
- public void createRecordingSession(ITvInputSessionCallback cb, String inputId) {
+ public void createRecordingSession(ITvInputSessionCallback cb, String inputId,
+ String sessionId) {
if (cb == null) {
return;
}
SomeArgs args = SomeArgs.obtain();
args.arg1 = cb;
args.arg2 = inputId;
+ args.arg3 = sessionId;
mServiceHandler.obtainMessage(ServiceHandler.DO_CREATE_RECORDING_SESSION, args)
.sendToTarget();
}
@@ -208,6 +211,37 @@
}
/**
+ * Returns a concrete implementation of {@link Session}.
+ *
+ * <p>For any apps that needs sessionId to request tuner resources from TunerResourceManager,
+ * it needs to override this method to get the sessionId passed. When no overriding, this method
+ * calls {@link #onCreateSession(String)} defaultly.
+ *
+ * @param inputId The ID of the TV input associated with the session.
+ * @param sessionId the unique sessionId created by TIF when session is created.
+ */
+ @Nullable
+ public Session onCreateSession(@NonNull String inputId, @NonNull String sessionId) {
+ return onCreateSession(inputId);
+ }
+
+ /**
+ * Returns a concrete implementation of {@link RecordingSession}.
+ *
+ * <p>For any apps that needs sessionId to request tuner resources from TunerResourceManager,
+ * it needs to override this method to get the sessionId passed. When no overriding, this method
+ * calls {@link #onCreateRecordingSession(String)} defaultly.
+ *
+ * @param inputId The ID of the TV input associated with the recording session.
+ * @param sessionId the unique sessionId created by TIF when session is created.
+ */
+ @Nullable
+ public RecordingSession onCreateRecordingSession(
+ @NonNull String inputId, @NonNull String sessionId) {
+ return onCreateRecordingSession(inputId);
+ }
+
+ /**
* Returns a new {@link TvInputInfo} object if this service is responsible for
* {@code hardwareInfo}; otherwise, return {@code null}. Override to modify default behavior of
* ignoring all hardware input.
@@ -2032,8 +2066,9 @@
InputChannel channel = (InputChannel) args.arg1;
ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg2;
String inputId = (String) args.arg3;
+ String sessionId = (String) args.arg4;
args.recycle();
- Session sessionImpl = onCreateSession(inputId);
+ Session sessionImpl = onCreateSession(inputId, sessionId);
if (sessionImpl == null) {
try {
// Failed to create a session.
@@ -2103,8 +2138,10 @@
SomeArgs args = (SomeArgs) msg.obj;
ITvInputSessionCallback cb = (ITvInputSessionCallback) args.arg1;
String inputId = (String) args.arg2;
+ String sessionId = (String) args.arg3;
args.recycle();
- RecordingSession recordingSessionImpl = onCreateRecordingSession(inputId);
+ RecordingSession recordingSessionImpl =
+ onCreateRecordingSession(inputId, sessionId);
if (recordingSessionImpl == null) {
try {
// Failed to create a recording session.
diff --git a/media/java/android/media/tv/TvTrackInfo.java b/media/java/android/media/tv/TvTrackInfo.java
index ea00d6e..5e0b1ea 100644
--- a/media/java/android/media/tv/TvTrackInfo.java
+++ b/media/java/android/media/tv/TvTrackInfo.java
@@ -61,6 +61,9 @@
private final boolean mEncrypted;
private final int mAudioChannelCount;
private final int mAudioSampleRate;
+ private final boolean mAudioDescription;
+ private final boolean mHardOfHearing;
+ private final boolean mSpokenSubtitle;
private final int mVideoWidth;
private final int mVideoHeight;
private final float mVideoFrameRate;
@@ -70,9 +73,10 @@
private final Bundle mExtra;
private TvTrackInfo(int type, String id, String language, CharSequence description,
- boolean encrypted, int audioChannelCount, int audioSampleRate, int videoWidth,
- int videoHeight, float videoFrameRate, float videoPixelAspectRatio,
- byte videoActiveFormatDescription, Bundle extra) {
+ boolean encrypted, int audioChannelCount, int audioSampleRate, boolean audioDescription,
+ boolean hardOfHearing, boolean spokenSubtitle, int videoWidth, int videoHeight,
+ float videoFrameRate, float videoPixelAspectRatio, byte videoActiveFormatDescription,
+ Bundle extra) {
mType = type;
mId = id;
mLanguage = language;
@@ -80,6 +84,9 @@
mEncrypted = encrypted;
mAudioChannelCount = audioChannelCount;
mAudioSampleRate = audioSampleRate;
+ mAudioDescription = audioDescription;
+ mHardOfHearing = hardOfHearing;
+ mSpokenSubtitle = spokenSubtitle;
mVideoWidth = videoWidth;
mVideoHeight = videoHeight;
mVideoFrameRate = videoFrameRate;
@@ -96,6 +103,9 @@
mEncrypted = in.readInt() != 0;
mAudioChannelCount = in.readInt();
mAudioSampleRate = in.readInt();
+ mAudioDescription = in.readInt() != 0;
+ mHardOfHearing = in.readInt() != 0;
+ mSpokenSubtitle = in.readInt() != 0;
mVideoWidth = in.readInt();
mVideoHeight = in.readInt();
mVideoFrameRate = in.readFloat();
@@ -172,6 +182,56 @@
}
/**
+ * Returns {@code true} if the track is an audio description intended for people with visual
+ * impairment, {@code false} otherwise. Valid only for {@link #TYPE_AUDIO} tracks.
+ *
+ * <p>For example of broadcast, audio description information may be referred to broadcast
+ * standard (e.g. ISO 639 Language Descriptor of ISO/IEC 13818-1, Supplementary Audio Language
+ * Descriptor, AC-3 Descriptor, Enhanced AC-3 Descriptor, AAC Descriptor of ETSI EN 300 468).
+ *
+ * @throws IllegalStateException if not called on an audio track
+ */
+ public boolean isAudioDescription() {
+ if (mType != TYPE_AUDIO) {
+ throw new IllegalStateException("Not an audio track");
+ }
+ return mAudioDescription;
+ }
+
+ /**
+ * Returns {@code true} if the track is intended for people with hearing impairment, {@code
+ * false} otherwise. Valid only for {@link #TYPE_AUDIO} and {@link #TYPE_SUBTITLE} tracks.
+ *
+ * <p>For example of broadcast, hard of hearing information may be referred to broadcast
+ * standard (e.g. ISO 639 Language Descriptor of ISO/IEC 13818-1, Supplementary Audio Language
+ * Descriptor, AC-3 Descriptor, Enhanced AC-3 Descriptor, AAC Descriptor of ETSI EN 300 468).
+ *
+ * @throws IllegalStateException if not called on an audio track or a subtitle track
+ */
+ public boolean isHardOfHearing() {
+ if (mType != TYPE_AUDIO && mType != TYPE_SUBTITLE) {
+ throw new IllegalStateException("Not an audio or a subtitle track");
+ }
+ return mHardOfHearing;
+ }
+
+ /**
+ * Returns {@code true} if the track is a spoken subtitle for people with visual impairment,
+ * {@code false} otherwise. Valid only for {@link #TYPE_AUDIO} tracks.
+ *
+ * <p>For example of broadcast, spoken subtitle information may be referred to broadcast
+ * standard (e.g. Supplementary Audio Language Descriptor of ETSI EN 300 468).
+ *
+ * @throws IllegalStateException if not called on an audio track
+ */
+ public boolean isSpokenSubtitle() {
+ if (mType != TYPE_AUDIO) {
+ throw new IllegalStateException("Not an audio track");
+ }
+ return mSpokenSubtitle;
+ }
+
+ /**
* Returns the width of the video, in the unit of pixels. Valid only for {@link #TYPE_VIDEO}
* tracks.
*
@@ -258,7 +318,8 @@
* @param flags The flags used for parceling.
*/
@Override
- public void writeToParcel(Parcel dest, int flags) {
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ Preconditions.checkNotNull(dest);
dest.writeInt(mType);
dest.writeString(mId);
dest.writeString(mLanguage);
@@ -266,6 +327,9 @@
dest.writeInt(mEncrypted ? 1 : 0);
dest.writeInt(mAudioChannelCount);
dest.writeInt(mAudioSampleRate);
+ dest.writeInt(mAudioDescription ? 1 : 0);
+ dest.writeInt(mHardOfHearing ? 1 : 0);
+ dest.writeInt(mSpokenSubtitle ? 1 : 0);
dest.writeInt(mVideoWidth);
dest.writeInt(mVideoHeight);
dest.writeFloat(mVideoFrameRate);
@@ -289,6 +353,7 @@
if (!TextUtils.equals(mId, obj.mId) || mType != obj.mType
|| !TextUtils.equals(mLanguage, obj.mLanguage)
|| !TextUtils.equals(mDescription, obj.mDescription)
+ || mEncrypted != obj.mEncrypted
|| !Objects.equals(mExtra, obj.mExtra)) {
return false;
}
@@ -296,7 +361,10 @@
switch (mType) {
case TYPE_AUDIO:
return mAudioChannelCount == obj.mAudioChannelCount
- && mAudioSampleRate == obj.mAudioSampleRate;
+ && mAudioSampleRate == obj.mAudioSampleRate
+ && mAudioDescription == obj.mAudioDescription
+ && mHardOfHearing == obj.mHardOfHearing
+ && mSpokenSubtitle == obj.mSpokenSubtitle;
case TYPE_VIDEO:
return mVideoWidth == obj.mVideoWidth
@@ -304,6 +372,9 @@
&& mVideoFrameRate == obj.mVideoFrameRate
&& mVideoPixelAspectRatio == obj.mVideoPixelAspectRatio
&& mVideoActiveFormatDescription == obj.mVideoActiveFormatDescription;
+
+ case TYPE_SUBTITLE:
+ return mHardOfHearing == obj.mHardOfHearing;
}
return true;
@@ -317,11 +388,13 @@
public static final @android.annotation.NonNull Parcelable.Creator<TvTrackInfo> CREATOR =
new Parcelable.Creator<TvTrackInfo>() {
@Override
+ @NonNull
public TvTrackInfo createFromParcel(Parcel in) {
return new TvTrackInfo(in);
}
@Override
+ @NonNull
public TvTrackInfo[] newArray(int size) {
return new TvTrackInfo[size];
}
@@ -338,6 +411,9 @@
private boolean mEncrypted;
private int mAudioChannelCount;
private int mAudioSampleRate;
+ private boolean mAudioDescription;
+ private boolean mHardOfHearing;
+ private boolean mSpokenSubtitle;
private int mVideoWidth;
private int mVideoHeight;
private float mVideoFrameRate;
@@ -371,7 +447,9 @@
*
* @param language The language string encoded by either ISO 639-1 or ISO 639-2/T.
*/
- public final Builder setLanguage(String language) {
+ @NonNull
+ public Builder setLanguage(@NonNull String language) {
+ Preconditions.checkNotNull(language);
mLanguage = language;
return this;
}
@@ -381,7 +459,9 @@
*
* @param description The user readable description.
*/
- public final Builder setDescription(CharSequence description) {
+ @NonNull
+ public Builder setDescription(@NonNull CharSequence description) {
+ Preconditions.checkNotNull(description);
mDescription = description;
return this;
}
@@ -406,7 +486,8 @@
* @param audioChannelCount The audio channel count.
* @throws IllegalStateException if not called on an audio track
*/
- public final Builder setAudioChannelCount(int audioChannelCount) {
+ @NonNull
+ public Builder setAudioChannelCount(int audioChannelCount) {
if (mType != TYPE_AUDIO) {
throw new IllegalStateException("Not an audio track");
}
@@ -421,7 +502,8 @@
* @param audioSampleRate The audio sample rate.
* @throws IllegalStateException if not called on an audio track
*/
- public final Builder setAudioSampleRate(int audioSampleRate) {
+ @NonNull
+ public Builder setAudioSampleRate(int audioSampleRate) {
if (mType != TYPE_AUDIO) {
throw new IllegalStateException("Not an audio track");
}
@@ -430,13 +512,75 @@
}
/**
+ * Sets the audio description attribute of the audio. Valid only for {@link #TYPE_AUDIO}
+ * tracks.
+ *
+ * <p>For example of broadcast, audio description information may be referred to broadcast
+ * standard (e.g. ISO 639 Language Descriptor of ISO/IEC 13818-1, Supplementary Audio
+ * Language Descriptor, AC-3 Descriptor, Enhanced AC-3 Descriptor, AAC Descriptor of ETSI EN
+ * 300 468).
+ *
+ * @param audioDescription The audio description attribute of the audio.
+ * @throws IllegalStateException if not called on an audio track
+ */
+ @NonNull
+ public Builder setAudioDescription(boolean audioDescription) {
+ if (mType != TYPE_AUDIO) {
+ throw new IllegalStateException("Not an audio track");
+ }
+ mAudioDescription = audioDescription;
+ return this;
+ }
+
+ /**
+ * Sets the hard of hearing attribute of the track. Valid only for {@link #TYPE_AUDIO} and
+ * {@link #TYPE_SUBTITLE} tracks.
+ *
+ * <p>For example of broadcast, hard of hearing information may be referred to broadcast
+ * standard (e.g. ISO 639 Language Descriptor of ISO/IEC 13818-1, Supplementary Audio
+ * Language Descriptor, AC-3 Descriptor, Enhanced AC-3 Descriptor, AAC Descriptor of ETSI EN
+ * 300 468).
+ *
+ * @param hardOfHearing The hard of hearing attribute of the track.
+ * @throws IllegalStateException if not called on an audio track or a subtitle track
+ */
+ @NonNull
+ public Builder setHardOfHearing(boolean hardOfHearing) {
+ if (mType != TYPE_AUDIO && mType != TYPE_SUBTITLE) {
+ throw new IllegalStateException("Not an audio track or a subtitle track");
+ }
+ mHardOfHearing = hardOfHearing;
+ return this;
+ }
+
+ /**
+ * Sets the spoken subtitle attribute of the audio. Valid only for {@link #TYPE_AUDIO}
+ * tracks.
+ *
+ * <p>For example of broadcast, spoken subtitle information may be referred to broadcast
+ * standard (e.g. Supplementary Audio Language Descriptor of ETSI EN 300 468).
+ *
+ * @param spokenSubtitle The spoken subtitle attribute of the audio.
+ * @throws IllegalStateException if not called on an audio track
+ */
+ @NonNull
+ public Builder setSpokenSubtitle(boolean spokenSubtitle) {
+ if (mType != TYPE_AUDIO) {
+ throw new IllegalStateException("Not an audio track");
+ }
+ mSpokenSubtitle = spokenSubtitle;
+ return this;
+ }
+
+ /**
* Sets the width of the video, in the unit of pixels. Valid only for {@link #TYPE_VIDEO}
* tracks.
*
* @param videoWidth The width of the video.
* @throws IllegalStateException if not called on a video track
*/
- public final Builder setVideoWidth(int videoWidth) {
+ @NonNull
+ public Builder setVideoWidth(int videoWidth) {
if (mType != TYPE_VIDEO) {
throw new IllegalStateException("Not a video track");
}
@@ -451,7 +595,8 @@
* @param videoHeight The height of the video.
* @throws IllegalStateException if not called on a video track
*/
- public final Builder setVideoHeight(int videoHeight) {
+ @NonNull
+ public Builder setVideoHeight(int videoHeight) {
if (mType != TYPE_VIDEO) {
throw new IllegalStateException("Not a video track");
}
@@ -466,7 +611,8 @@
* @param videoFrameRate The frame rate of the video.
* @throws IllegalStateException if not called on a video track
*/
- public final Builder setVideoFrameRate(float videoFrameRate) {
+ @NonNull
+ public Builder setVideoFrameRate(float videoFrameRate) {
if (mType != TYPE_VIDEO) {
throw new IllegalStateException("Not a video track");
}
@@ -486,7 +632,8 @@
* @param videoPixelAspectRatio The pixel aspect ratio of the video.
* @throws IllegalStateException if not called on a video track
*/
- public final Builder setVideoPixelAspectRatio(float videoPixelAspectRatio) {
+ @NonNull
+ public Builder setVideoPixelAspectRatio(float videoPixelAspectRatio) {
if (mType != TYPE_VIDEO) {
throw new IllegalStateException("Not a video track");
}
@@ -506,7 +653,8 @@
* @param videoActiveFormatDescription The AFD code of the video.
* @throws IllegalStateException if not called on a video track
*/
- public final Builder setVideoActiveFormatDescription(byte videoActiveFormatDescription) {
+ @NonNull
+ public Builder setVideoActiveFormatDescription(byte videoActiveFormatDescription) {
if (mType != TYPE_VIDEO) {
throw new IllegalStateException("Not a video track");
}
@@ -519,7 +667,9 @@
*
* @param extra The extra information.
*/
- public final Builder setExtra(Bundle extra) {
+ @NonNull
+ public Builder setExtra(@NonNull Bundle extra) {
+ Preconditions.checkNotNull(extra);
mExtra = new Bundle(extra);
return this;
}
@@ -529,10 +679,12 @@
*
* @return The new {@link TvTrackInfo} instance
*/
+ @NonNull
public TvTrackInfo build() {
return new TvTrackInfo(mType, mId, mLanguage, mDescription, mEncrypted,
- mAudioChannelCount, mAudioSampleRate, mVideoWidth, mVideoHeight,
- mVideoFrameRate, mVideoPixelAspectRatio, mVideoActiveFormatDescription, mExtra);
+ mAudioChannelCount, mAudioSampleRate, mAudioDescription, mHardOfHearing,
+ mSpokenSubtitle, mVideoWidth, mVideoHeight, mVideoFrameRate,
+ mVideoPixelAspectRatio, mVideoActiveFormatDescription, mExtra);
}
}
}
diff --git a/media/java/android/media/tv/tuner/DemuxCapabilities.java b/media/java/android/media/tv/tuner/DemuxCapabilities.java
new file mode 100644
index 0000000..83abf86
--- /dev/null
+++ b/media/java/android/media/tv/tuner/DemuxCapabilities.java
@@ -0,0 +1,159 @@
+/*
+ * 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.media.tv.tuner;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.annotation.Size;
+import android.media.tv.tuner.filter.FilterConfiguration;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Capabilities info for Demux.
+ *
+ * @hide
+ */
+public class DemuxCapabilities {
+
+ /** @hide */
+ @IntDef(flag = true, value = {
+ FilterConfiguration.FILTER_TYPE_TS,
+ FilterConfiguration.FILTER_TYPE_MMTP,
+ FilterConfiguration.FILTER_TYPE_IP,
+ FilterConfiguration.FILTER_TYPE_TLV,
+ FilterConfiguration.FILTER_TYPE_ALP
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FilterCapabilities {}
+
+ private final int mNumDemux;
+ private final int mNumRecord;
+ private final int mNumPlayback;
+ private final int mNumTsFilter;
+ private final int mNumSectionFilter;
+ private final int mNumAudioFilter;
+ private final int mNumVideoFilter;
+ private final int mNumPesFilter;
+ private final int mNumPcrFilter;
+ private final int mNumBytesInSectionFilter;
+ private final int mFilterCaps;
+ private final int[] mLinkCaps;
+
+ // Used by JNI
+ private DemuxCapabilities(int numDemux, int numRecord, int numPlayback, int numTsFilter,
+ int numSectionFilter, int numAudioFilter, int numVideoFilter, int numPesFilter,
+ int numPcrFilter, int numBytesInSectionFilter, int filterCaps, int[] linkCaps) {
+ mNumDemux = numDemux;
+ mNumRecord = numRecord;
+ mNumPlayback = numPlayback;
+ mNumTsFilter = numTsFilter;
+ mNumSectionFilter = numSectionFilter;
+ mNumAudioFilter = numAudioFilter;
+ mNumVideoFilter = numVideoFilter;
+ mNumPesFilter = numPesFilter;
+ mNumPcrFilter = numPcrFilter;
+ mNumBytesInSectionFilter = numBytesInSectionFilter;
+ mFilterCaps = filterCaps;
+ mLinkCaps = linkCaps;
+ }
+
+ /**
+ * Gets total number of demuxes.
+ */
+ public int getNumDemux() {
+ return mNumDemux;
+ }
+ /**
+ * Gets max number of recordings at a time.
+ */
+ public int getNumRecord() {
+ return mNumRecord;
+ }
+ /**
+ * Gets max number of playbacks at a time.
+ */
+ public int getNumPlayback() {
+ return mNumPlayback;
+ }
+ /**
+ * Gets number of TS filters.
+ */
+ public int getNumTsFilter() {
+ return mNumTsFilter;
+ }
+ /**
+ * Gets number of section filters.
+ */
+ public int getNumSectionFilter() {
+ return mNumSectionFilter;
+ }
+ /**
+ * Gets number of audio filters.
+ */
+ public int getNumAudioFilter() {
+ return mNumAudioFilter;
+ }
+ /**
+ * Gets number of video filters.
+ */
+ public int getNumVideoFilter() {
+ return mNumVideoFilter;
+ }
+ /**
+ * Gets number of PES filters.
+ */
+ public int getNumPesFilter() {
+ return mNumPesFilter;
+ }
+ /**
+ * Gets number of PCR filters.
+ */
+ public int getNumPcrFilter() {
+ return mNumPcrFilter;
+ }
+ /**
+ * Gets number of bytes in the mask of a section filter.
+ */
+ public int getNumBytesInSectionFilter() {
+ return mNumBytesInSectionFilter;
+ }
+ /**
+ * Gets filter capabilities in bit field.
+ *
+ * <p>The bits of the returned value is corresponding to the types in
+ * {@link FilterConfiguration}.
+ */
+ @FilterCapabilities
+ public int getFilterCapabilities() {
+ return mFilterCaps;
+ }
+
+ /**
+ * Gets link capabilities.
+ *
+ * <p>The returned array contains the same elements as the number of types in
+ * {@link FilterConfiguration}.
+ * <p>The ith element represents the filter's capability as the source for the ith type.
+ */
+ @Nullable
+ @Size(5)
+ public int[] getLinkCapabilities() {
+ return mLinkCaps;
+ }
+}
diff --git a/media/java/android/media/tv/tuner/Descrambler.java b/media/java/android/media/tv/tuner/Descrambler.java
new file mode 100644
index 0000000..0143582
--- /dev/null
+++ b/media/java/android/media/tv/tuner/Descrambler.java
@@ -0,0 +1,114 @@
+/*
+ * 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.media.tv.tuner;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.media.tv.tuner.Tuner.Filter;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This class is used to interact with descramblers.
+ *
+ * <p> Descrambler is a hardware component used to descramble data.
+ *
+ * <p> This class controls the TIS interaction with Tuner HAL.
+ *
+ * @hide
+ */
+public class Descrambler implements AutoCloseable {
+ /** @hide */
+ @IntDef(prefix = "PID_TYPE_", value = {PID_TYPE_T, PID_TYPE_MMPT})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PidType {}
+
+ /**
+ * Packet ID is used to specify packets in transport stream.
+ */
+ public static final int PID_TYPE_T = 1;
+ /**
+ * Packet ID is used to specify packets in MMTP.
+ */
+ public static final int PID_TYPE_MMPT = 2;
+
+
+ private long mNativeContext;
+
+ private native int nativeAddPid(int pidType, int pid, Filter filter);
+ private native int nativeRemovePid(int pidType, int pid, Filter filter);
+ private native int nativeSetKeyToken(byte[] keyToken);
+ private native int nativeClose();
+
+ private Descrambler() {}
+
+ /**
+ * Add packets' PID to the descrambler for descrambling.
+ *
+ * The descrambler will start descrambling packets with this PID. Multiple PIDs can be added
+ * into one descrambler instance because descambling can happen simultaneously on packets
+ * from different PIDs.
+ *
+ * If the Descrambler previously contained a filter for the PID, the old filter is replaced
+ * by the specified filter.
+ *
+ * @param pidType the type of the PID.
+ * @param pid the PID of packets to start to be descrambled.
+ * @param filter an optional filter instance to identify upper stream.
+ * @return result status of the operation.
+ */
+ public int addPid(@PidType int pidType, int pid, @Nullable Filter filter) {
+ return nativeAddPid(pidType, pid, filter);
+ }
+
+ /**
+ * Remove packets' PID from the descrambler
+ *
+ * The descrambler will stop descrambling packets with this PID.
+ *
+ * @param pidType the type of the PID.
+ * @param pid the PID of packets to stop to be descrambled.
+ * @param filter an optional filter instance to identify upper stream.
+ * @return result status of the operation.
+ */
+ public int removePid(@PidType int pidType, int pid, @Nullable Filter filter) {
+ return nativeRemovePid(pidType, pid, filter);
+ }
+
+ /**
+ * Set a key token to link descrambler to a key slot
+ *
+ * A descrambler instance can have only one key slot to link, but a key slot can hold a few
+ * keys for different purposes.
+ *
+ * @param keyToken the token to be used to link the key slot.
+ * @return result status of the operation.
+ */
+ public int setKeyToken(@Nullable byte[] keyToken) {
+ return nativeSetKeyToken(keyToken);
+ }
+
+ /**
+ * Release the descrambler instance.
+ */
+ @Override
+ public void close() {
+ nativeClose();
+ }
+
+}
diff --git a/media/java/android/media/tv/tuner/DvrSettings.java b/media/java/android/media/tv/tuner/DvrSettings.java
deleted file mode 100644
index 76160dc..0000000
--- a/media/java/android/media/tv/tuner/DvrSettings.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * 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.media.tv.tuner;
-
-import android.media.tv.tuner.TunerConstants.DataFormat;
-import android.media.tv.tuner.TunerConstants.DvrSettingsType;
-
-/**
- * DVR settings.
- *
- * @hide
- */
-public class DvrSettings {
- private int mStatusMask;
- private int mLowThreshold;
- private int mHighThreshold;
- private int mPacketSize;
-
- @DataFormat
- private int mDataFormat;
- @DvrSettingsType
- private int mType;
-
- private DvrSettings(int statusMask, int lowThreshold, int highThreshold, int packetSize,
- @DataFormat int dataFormat, @DvrSettingsType int type) {
- mStatusMask = statusMask;
- mLowThreshold = lowThreshold;
- mHighThreshold = highThreshold;
- mPacketSize = packetSize;
- mDataFormat = dataFormat;
- mType = type;
- }
-
- /**
- * Creates a new builder.
- */
- public static Builder newBuilder() {
- return new Builder();
- }
-
- /**
- * Builder for DvrSettings.
- */
- public static final class Builder {
- private int mStatusMask;
- private int mLowThreshold;
- private int mHighThreshold;
- private int mPacketSize;
- @DataFormat
- private int mDataFormat;
- @DvrSettingsType
- private int mType;
-
- /**
- * Sets status mask.
- */
- public Builder setStatusMask(int statusMask) {
- this.mStatusMask = statusMask;
- return this;
- }
-
- /**
- * Sets low threshold.
- */
- public Builder setLowThreshold(int lowThreshold) {
- this.mLowThreshold = lowThreshold;
- return this;
- }
-
- /**
- * Sets high threshold.
- */
- public Builder setHighThreshold(int highThreshold) {
- this.mHighThreshold = highThreshold;
- return this;
- }
-
- /**
- * Sets packet size.
- */
- public Builder setPacketSize(int packetSize) {
- this.mPacketSize = packetSize;
- return this;
- }
-
- /**
- * Sets data format.
- */
- public Builder setDataFormat(@DataFormat int dataFormat) {
- this.mDataFormat = dataFormat;
- return this;
- }
-
- /**
- * Sets settings type.
- */
- public Builder setType(@DvrSettingsType int type) {
- this.mType = type;
- return this;
- }
-
- /**
- * Builds a DvrSettings instance.
- */
- public DvrSettings build() {
- return new DvrSettings(
- mStatusMask, mLowThreshold, mHighThreshold, mPacketSize, mDataFormat, mType);
- }
- }
-}
diff --git a/media/java/android/media/tv/tuner/FilterEvent.java b/media/java/android/media/tv/tuner/FilterEvent.java
deleted file mode 100644
index 7c165ce..0000000
--- a/media/java/android/media/tv/tuner/FilterEvent.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * 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.media.tv.tuner;
-
-import android.os.NativeHandle;
-
-/**
- * Demux filter event.
- *
- * @hide
- */
-public abstract class FilterEvent {
-
- /**
- * Section event.
- */
- public static class SectionEvent extends FilterEvent {
- private int mTableId;
- private int mVersion;
- private int mSectionNum;
- private int mDataLength;
- }
-
- /**
- * Media event.
- */
- public static class MediaEvent extends FilterEvent {
- private int mStreamId;
- private boolean mIsPtsPresent;
- private long mPts;
- private int mDataLength;
- private NativeHandle mHandle;
- private boolean mIsSecureMemory;
- private int mMpuSequenceNumber;
- private boolean mIsPrivateData;
- private AudioExtraMetaData mExtraMetaData;
- }
-
- /**
- * PES event.
- */
- public static class PesEvent extends FilterEvent {
- private int mStreamId;
- private int mDataLength;
- private int mMpuSequenceNumber;
- }
-
- /**
- * TS record event.
- */
- public static class TsRecordEvent extends FilterEvent {
- private int mTpid;
- private int mIndexMask;
- private long mByteNumber;
- }
-
- /**
- * MMPT record event.
- */
- public static class MmtpRecordEvent extends FilterEvent {
- private int mScHevcIndexMask;
- private long mByteNumber;
- }
-
- /**
- * Download event.
- */
- public static class DownloadEvent extends FilterEvent {
- private int mItemId;
- private int mMpuSequenceNumber;
- private int mItemFragmentIndex;
- private int mLastItemFragmentIndex;
- private int mDataLength;
- }
-
- /**
- * IP payload event.
- */
- public static class IpPayloadEvent extends FilterEvent {
- private int mDataLength;
- }
-
- /**
- * TEMI event.
- */
- public static class TemiEvent extends FilterEvent {
- private long mPts;
- private byte mDescrTag;
- private byte[] mDescrData;
- }
-
- /**
- * Extra Meta Data from AD (Audio Descriptor) according to
- * ETSI TS 101 154 V2.1.1.
- */
- public static class AudioExtraMetaData {
- private byte mAdFade;
- private byte mAdPan;
- private byte mVersionTextTag;
- private byte mAdGainCenter;
- private byte mAdGainFront;
- private byte mAdGainSurround;
- }
-}
diff --git a/media/java/android/media/tv/tuner/FilterSettings.java b/media/java/android/media/tv/tuner/FilterSettings.java
deleted file mode 100644
index 673b3d9..0000000
--- a/media/java/android/media/tv/tuner/FilterSettings.java
+++ /dev/null
@@ -1,325 +0,0 @@
-/*
- * 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.media.tv.tuner;
-
-import android.annotation.Nullable;
-import android.media.tv.tuner.TunerConstants.FilterType;
-
-import java.util.List;
-
-/**
- * Demux Filter settings.
- *
- * @hide
- */
-public abstract class FilterSettings {
- @Nullable
- protected final Settings mSettings;
-
- protected FilterSettings(Settings settings) {
- mSettings = settings;
- }
-
- /**
- * Gets filter settings type
- */
- @FilterType
- public abstract int getType();
-
- // TODO: more builders and getters
-
- /**
- * Filter Settings for a TS filter.
- */
- public static class TsFilterSettings extends FilterSettings {
- private int mTpid;
-
- private TsFilterSettings(Settings settings, int tpid) {
- super(settings);
- mTpid = tpid;
- }
-
- @Override
- public int getType() {
- return TunerConstants.FILTER_TYPE_TS;
- }
-
- /**
- * Creates a new builder.
- */
- public static Builder newBuilder() {
- return new Builder();
- }
-
- /**
- * Builder for TsFilterSettings.
- */
- public static class Builder {
- private Settings mSettings;
- private int mTpid;
-
- /**
- * Sets settings.
- */
- public Builder setSettings(Settings settings) {
- mSettings = settings;
- return this;
- }
-
- /**
- * Sets TPID.
- */
- public Builder setTpid(int tpid) {
- mTpid = tpid;
- return this;
- }
-
- /**
- * Builds a TsFilterSettings instance.
- */
- public TsFilterSettings build() {
- return new TsFilterSettings(mSettings, mTpid);
- }
- }
- }
-
- /**
- * Filter Settings for a MMTP filter.
- */
- public static class MmtpFilterSettings extends FilterSettings {
- private int mMmtpPid;
-
- public MmtpFilterSettings(Settings settings) {
- super(settings);
- }
-
- @Override
- public int getType() {
- return TunerConstants.FILTER_TYPE_MMTP;
- }
- }
-
-
- /**
- * Filter Settings for a IP filter.
- */
- public static class IpFilterSettings extends FilterSettings {
- private byte[] mSrcIpAddress;
- private byte[] mDstIpAddress;
- private int mSrcPort;
- private int mDstPort;
- private boolean mPassthrough;
-
- public IpFilterSettings(Settings settings) {
- super(settings);
- }
-
- @Override
- public int getType() {
- return TunerConstants.FILTER_TYPE_IP;
- }
- }
-
-
- /**
- * Filter Settings for a TLV filter.
- */
- public static class TlvFilterSettings extends FilterSettings {
- private int mPacketType;
- private boolean mIsCompressedIpPacket;
- private boolean mPassthrough;
-
- public TlvFilterSettings(Settings settings) {
- super(settings);
- }
-
- @Override
- public int getType() {
- return TunerConstants.FILTER_TYPE_TLV;
- }
- }
-
-
- /**
- * Filter Settings for a ALP filter.
- */
- public static class AlpFilterSettings extends FilterSettings {
- private int mPacketType;
- private int mLengthType;
-
- public AlpFilterSettings(Settings settings) {
- super(settings);
- }
-
- @Override
- public int getType() {
- return TunerConstants.FILTER_TYPE_ALP;
- }
- }
-
-
- /**
- * Settings for filters of different subtypes.
- */
- public abstract static class Settings {
- protected final int mType;
-
- protected Settings(int type) {
- mType = type;
- }
-
- /**
- * Gets filter settings type.
- * @return
- */
- int getType() {
- return mType;
- }
- }
-
- /**
- * Filter Settings for Section data according to ISO/IEC 13818-1.
- */
- public static class SectionSettings extends Settings {
-
- private SectionSettings(int mainType) {
- super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_SECTION));
- }
- }
-
- /**
- * Bits Settings for Section Filter.
- */
- public static class SectionSettingsWithSectionBits extends SectionSettings {
- private List<Byte> mFilter;
- private List<Byte> mMask;
- private List<Byte> mMode;
-
- private SectionSettingsWithSectionBits(int mainType) {
- super(mainType);
- }
- }
-
- /**
- * Table information for Section Filter.
- */
- public static class SectionSettingsWithTableInfo extends SectionSettings {
- private int mTableId;
- private int mVersion;
-
- private SectionSettingsWithTableInfo(int mainType) {
- super(mainType);
- }
- }
-
- /**
- * Filter Settings for a PES Data.
- */
- public static class PesSettings extends Settings {
- private int mStreamId;
- private boolean mIsRaw;
-
- private PesSettings(int mainType, int streamId, boolean isRaw) {
- super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_PES));
- mStreamId = streamId;
- mIsRaw = isRaw;
- }
-
- /**
- * Creates a builder for PesSettings.
- */
- public static Builder newBuilder(int mainType) {
- return new Builder(mainType);
- }
-
- /**
- * Builder for PesSettings.
- */
- public static class Builder {
- private final int mMainType;
- private int mStreamId;
- private boolean mIsRaw;
-
- public Builder(int mainType) {
- mMainType = mainType;
- }
-
- /**
- * Sets stream ID.
- */
- public Builder setStreamId(int streamId) {
- mStreamId = streamId;
- return this;
- }
-
- /**
- * Sets whether it's raw.
- * true if the filter send onFilterStatus instead of onFilterEvent.
- */
- public Builder setIsRaw(boolean isRaw) {
- mIsRaw = isRaw;
- return this;
- }
-
- /**
- * Builds a PesSettings instance.
- */
- public PesSettings build() {
- return new PesSettings(mMainType, mStreamId, mIsRaw);
- }
- }
- }
-
- /**
- * Filter Settings for a Video and Audio.
- */
- public static class AvSettings extends Settings {
- private boolean mIsPassthrough;
-
- private AvSettings(int mainType, boolean isAudio) {
- super(TunerUtils.getFilterSubtype(
- mainType,
- isAudio
- ? TunerConstants.FILTER_SUBTYPE_AUDIO
- : TunerConstants.FILTER_SUBTYPE_VIDEO));
- }
- }
-
- /**
- * Filter Settings for a Download.
- */
- public static class DownloadSettings extends Settings {
- private int mDownloadId;
-
- public DownloadSettings(int mainType) {
- super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_DOWNLOAD));
- }
- }
-
- /**
- * The Settings for the record in DVR.
- */
- public static class RecordSettings extends Settings {
- private int mIndexType;
- private int mIndexMask;
-
- public RecordSettings(int mainType) {
- super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_RECORD));
- }
- }
-
-}
diff --git a/media/java/android/media/tv/tuner/FrontendSettings.java b/media/java/android/media/tv/tuner/FrontendSettings.java
index 531e210..ad8422c 100644
--- a/media/java/android/media/tv/tuner/FrontendSettings.java
+++ b/media/java/android/media/tv/tuner/FrontendSettings.java
@@ -17,9 +17,6 @@
package android.media.tv.tuner;
import android.annotation.SystemApi;
-import android.media.tv.tuner.TunerConstants.FrontendSettingsType;
-
-import java.util.List;
/**
* Frontend settings for tune and scan operations.
@@ -29,14 +26,14 @@
public abstract class FrontendSettings {
private final int mFrequency;
- FrontendSettings(int frequency) {
+ /** @hide */
+ public FrontendSettings(int frequency) {
mFrequency = frequency;
}
/**
* Returns the frontend type.
*/
- @FrontendSettingsType
public abstract int getType();
/**
@@ -48,282 +45,4 @@
return mFrequency;
}
- // TODO: use hal constants for enum fields
- // TODO: javaDoc
- // TODO: add builders and getters for other settings type
-
- /**
- * Frontend settings for analog.
- * @hide
- */
- public static class FrontendAnalogSettings extends FrontendSettings {
- private int mAnalogType;
- private int mSifStandard;
-
- @Override
- public int getType() {
- return TunerConstants.FRONTEND_TYPE_ANALOG;
- }
-
- public int getAnalogType() {
- return mAnalogType;
- }
-
- public int getSifStandard() {
- return mSifStandard;
- }
-
- /**
- * Creates a new builder object.
- */
- public static Builder newBuilder() {
- return new Builder();
- }
-
- private FrontendAnalogSettings(int frequency, int analogType, int sifStandard) {
- super(frequency);
- mAnalogType = analogType;
- mSifStandard = sifStandard;
- }
-
- /**
- * Builder for FrontendAnalogSettings.
- */
- public static class Builder {
- private int mFrequency;
- private int mAnalogType;
- private int mSifStandard;
-
- private Builder() {}
-
- /**
- * Sets frequency.
- */
- public Builder setFrequency(int frequency) {
- mFrequency = frequency;
- return this;
- }
-
- /**
- * Sets analog type.
- */
- public Builder setAnalogType(int analogType) {
- mAnalogType = analogType;
- return this;
- }
-
- /**
- * Sets sif standard.
- */
- public Builder setSifStandard(int sifStandard) {
- mSifStandard = sifStandard;
- return this;
- }
-
- /**
- * Builds a FrontendAnalogSettings instance.
- */
- public FrontendAnalogSettings build() {
- return new FrontendAnalogSettings(mFrequency, mAnalogType, mSifStandard);
- }
- }
- }
-
- /**
- * Frontend settings for ATSC.
- * @hide
- */
- public static class FrontendAtscSettings extends FrontendSettings {
- public int modulation;
-
- FrontendAtscSettings(int frequency) {
- super(frequency);
- }
-
- @Override
- public int getType() {
- return TunerConstants.FRONTEND_TYPE_ATSC;
- }
- }
-
- /**
- * Frontend settings for ATSC-3.
- * @hide
- */
- public static class FrontendAtsc3Settings extends FrontendSettings {
- public int bandwidth;
- public byte demodOutputFormat;
- public List<FrontendAtsc3PlpSettings> plpSettings;
-
- FrontendAtsc3Settings(int frequency) {
- super(frequency);
- }
-
- @Override
- public int getType() {
- return TunerConstants.FRONTEND_TYPE_ATSC3;
- }
- }
-
- /**
- * Frontend settings for DVBS.
- * @hide
- */
- public static class FrontendDvbsSettings extends FrontendSettings {
- public int modulation;
- public FrontendDvbsCodeRate coderate;
- public int symbolRate;
- public int rolloff;
- public int pilot;
- public int inputStreamId;
- public byte standard;
-
- FrontendDvbsSettings(int frequency) {
- super(frequency);
- }
-
- @Override
- public int getType() {
- return TunerConstants.FRONTEND_TYPE_DVBS;
- }
- }
-
- /**
- * Frontend settings for DVBC.
- * @hide
- */
- public static class FrontendDvbcSettings extends FrontendSettings {
- public int modulation;
- public long fec;
- public int symbolRate;
- public int outerFec;
- public byte annex;
- public int spectralInversion;
-
- FrontendDvbcSettings(int frequency) {
- super(frequency);
- }
-
- @Override
- public int getType() {
- return TunerConstants.FRONTEND_TYPE_DVBC;
- }
- }
-
- /**
- * Frontend settings for DVBT.
- * @hide
- */
- public static class FrontendDvbtSettings extends FrontendSettings {
- public int transmissionMode;
- public int bandwidth;
- public int constellation;
- public int hierarchy;
- public int hpCoderate;
- public int lpCoderate;
- public int guardInterval;
- public boolean isHighPriority;
- public byte standard;
- public boolean isMiso;
- public int plpMode;
- public byte plpId;
- public byte plpGroupId;
-
- FrontendDvbtSettings(int frequency) {
- super(frequency);
- }
-
- @Override
- public int getType() {
- return TunerConstants.FRONTEND_TYPE_DVBT;
- }
- }
-
- /**
- * Frontend settings for ISDBS.
- * @hide
- */
- public static class FrontendIsdbsSettings extends FrontendSettings {
- public int streamId;
- public int streamIdType;
- public int modulation;
- public int coderate;
- public int symbolRate;
- public int rolloff;
-
- FrontendIsdbsSettings(int frequency) {
- super(frequency);
- }
-
- @Override
- public int getType() {
- return TunerConstants.FRONTEND_TYPE_ISDBS;
- }
- }
-
- /**
- * Frontend settings for ISDBS-3.
- * @hide
- */
- public static class FrontendIsdbs3Settings extends FrontendSettings {
- public int streamId;
- public int streamIdType;
- public int modulation;
- public int coderate;
- public int symbolRate;
- public int rolloff;
-
- FrontendIsdbs3Settings(int frequency) {
- super(frequency);
- }
-
- @Override
- public int getType() {
- return TunerConstants.FRONTEND_TYPE_ISDBS3;
- }
- }
-
- /**
- * Frontend settings for ISDBT.
- * @hide
- */
- public static class FrontendIsdbtSettings extends FrontendSettings {
- public int modulation;
- public int bandwidth;
- public int coderate;
- public int guardInterval;
- public int serviceAreaId;
-
- FrontendIsdbtSettings(int frequency) {
- super(frequency);
- }
-
- @Override
- public int getType() {
- return TunerConstants.FRONTEND_TYPE_ISDBT;
- }
- }
-
- /**
- * PLP settings for ATSC-3.
- * @hide
- */
- public static class FrontendAtsc3PlpSettings {
- public byte plpId;
- public int modulation;
- public int interleaveMode;
- public int codeRate;
- public int fec;
- }
-
- /**
- * Code rate for DVBS.
- * @hide
- */
- public static class FrontendDvbsCodeRate {
- public long fec;
- public boolean isLinear;
- public boolean isShortFrames;
- public int bitsPer1000Symbol;
- }
}
diff --git a/media/java/android/media/tv/tuner/FrontendStatus.java b/media/java/android/media/tv/tuner/FrontendStatus.java
deleted file mode 100644
index f8b2d12..0000000
--- a/media/java/android/media/tv/tuner/FrontendStatus.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * 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.media.tv.tuner;
-
-import android.media.tv.tuner.TunerConstants.FrontendDvbcSpectralInversion;
-import android.media.tv.tuner.TunerConstants.FrontendDvbtHierarchy;
-import android.media.tv.tuner.TunerConstants.FrontendInnerFec;
-import android.media.tv.tuner.TunerConstants.FrontendModulation;
-import android.media.tv.tuner.TunerConstants.FrontendStatusType;
-import android.media.tv.tuner.TunerConstants.LnbVoltage;
-
-/**
- * Frontend status
- *
- * @hide
- */
-public class FrontendStatus {
-
- private final int mType;
- private final Object mValue;
-
- private FrontendStatus(int type, Object value) {
- mType = type;
- mValue = value;
- }
-
- /** Gets frontend status type. */
- @FrontendStatusType
- public int getStatusType() {
- return mType;
- }
- /** Lock status for Demod in True/False. */
- public boolean getIsDemodLocked() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_DEMOD_LOCK) {
- throw new IllegalStateException();
- }
- return (Boolean) mValue;
- }
- /** SNR value measured by 0.001 dB. */
- public int getSnr() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_SNR) {
- throw new IllegalStateException();
- }
- return (int) mValue;
- }
- /** The number of error bit per 1 billion bits. */
- public int getBer() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_BER) {
- throw new IllegalStateException();
- }
- return (int) mValue;
- }
- /** The number of error package per 1 billion packages. */
- public int getPer() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_PER) {
- throw new IllegalStateException();
- }
- return (int) mValue;
- }
- /** The number of error bit per 1 billion bits before FEC. */
- public int getPerBer() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_PRE_BER) {
- throw new IllegalStateException();
- }
- return (int) mValue;
- }
- /** Signal Quality in percent. */
- public int getSignalQuality() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_SIGNAL_QUALITY) {
- throw new IllegalStateException();
- }
- return (int) mValue;
- }
- /** Signal Strength measured by 0.001 dBm. */
- public int getSignalStrength() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_SIGNAL_STRENGTH) {
- throw new IllegalStateException();
- }
- return (int) mValue;
- }
- /** Symbols per second. */
- public int getSymbolRate() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_SYMBOL_RATE) {
- throw new IllegalStateException();
- }
- return (int) mValue;
- }
- /**
- * Inner Forward Error Correction type as specified in ETSI EN 300 468 V1.15.1
- * and ETSI EN 302 307-2 V1.1.1.
- */
- @FrontendInnerFec
- public long getFec() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_FEC) {
- throw new IllegalStateException();
- }
- return (long) mValue;
- }
- /** Modulation */
- @FrontendModulation
- public int getModulation() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_MODULATION) {
- throw new IllegalStateException();
- }
- return (int) mValue;
- }
- /** Spectral Inversion for DVBC. */
- @FrontendDvbcSpectralInversion
- public int getSpectralInversion() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_SPECTRAL) {
- throw new IllegalStateException();
- }
- return (int) mValue;
- }
- /** Power Voltage Type for LNB. */
- @LnbVoltage
- public int getLnbVoltage() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_LNB_VOLTAGE) {
- throw new IllegalStateException();
- }
- return (int) mValue;
- }
- /** PLP ID */
- public byte getPlpId() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_PLP_ID) {
- throw new IllegalStateException();
- }
- return (byte) mValue;
- }
- /** Emergency Warning Broadcasting System */
- public boolean getIsEwbs() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_EWBS) {
- throw new IllegalStateException();
- }
- return (Boolean) mValue;
- }
- /** AGC value is normalized from 0 to 255. */
- public byte getAgc() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_AGC) {
- throw new IllegalStateException();
- }
- return (byte) mValue;
- }
- /** LNA(Low Noise Amplifier) is on or not. */
- public boolean getLnaOn() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_LNA) {
- throw new IllegalStateException();
- }
- return (Boolean) mValue;
- }
- /** Error status by layer. */
- public boolean[] getIsLayerError() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_LAYER_ERROR) {
- throw new IllegalStateException();
- }
- return (boolean[]) mValue;
- }
- /** CN value by VBER measured by 0.001 dB. */
- public int getVberCn() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_VBER_CN) {
- throw new IllegalStateException();
- }
- return (int) mValue;
- }
- /** CN value by LBER measured by 0.001 dB. */
- public int getLberCn() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_LBER_CN) {
- throw new IllegalStateException();
- }
- return (int) mValue;
- }
- /** CN value by XER measured by 0.001 dB. */
- public int getXerCn() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_XER_CN) {
- throw new IllegalStateException();
- }
- return (int) mValue;
- }
- /** MER value measured by 0.001 dB. */
- public int getMer() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_MER) {
- throw new IllegalStateException();
- }
- return (int) mValue;
- }
- /** Frequency difference in Hertz. */
- public int getFreqOffset() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_FREQ_OFFSET) {
- throw new IllegalStateException();
- }
- return (int) mValue;
- }
- /** Hierarchy Type for DVBT. */
- @FrontendDvbtHierarchy
- public int getHierarchy() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_HIERARCHY) {
- throw new IllegalStateException();
- }
- return (int) mValue;
- }
- /** Lock status for RF. */
- public boolean getIsRfLock() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_RF_LOCK) {
- throw new IllegalStateException();
- }
- return (Boolean) mValue;
- }
- /** A list of PLP status for tuned PLPs for ATSC3 frontend. */
- public Atsc3PlpInfo[] getAtsc3PlpInfo() {
- if (mType != TunerConstants.FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO) {
- throw new IllegalStateException();
- }
- return (Atsc3PlpInfo[]) mValue;
- }
-
- /** Status for each tuning PLPs. */
- public static class Atsc3PlpInfo {
- private final int mPlpId;
- private final boolean mIsLock;
- private final int mUec;
-
- private Atsc3PlpInfo(int plpId, boolean isLock, int uec) {
- mPlpId = plpId;
- mIsLock = isLock;
- mUec = uec;
- }
-
- /** Gets PLP IDs. */
- public int getPlpId() {
- return mPlpId;
- }
- /** Gets Demod Lock/Unlock status of this particular PLP. */
- public boolean getIsLock() {
- return mIsLock;
- }
- /**
- * Gets Uncorrectable Error Counts (UEC) of this particular PLP since last tune
- * operation.
- */
- public int getUec() {
- return mUec;
- }
- }
-}
diff --git a/media/java/android/media/tv/tuner/Lnb.java b/media/java/android/media/tv/tuner/Lnb.java
new file mode 100644
index 0000000..8e579bf
--- /dev/null
+++ b/media/java/android/media/tv/tuner/Lnb.java
@@ -0,0 +1,203 @@
+/*
+ * 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.media.tv.tuner;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerConstants.Result;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * LNB (low-noise block downconverter) for satellite tuner.
+ *
+ * A Tuner LNB (low-noise block downconverter) is used by satellite frontend to receive the
+ * microwave signal from the satellite, amplify it, and downconvert the frequency to a lower
+ * frequency.
+ *
+ * @hide
+ */
+@SystemApi
+public class Lnb implements AutoCloseable {
+ /** @hide */
+ @IntDef({VOLTAGE_NONE, VOLTAGE_5V, VOLTAGE_11V, VOLTAGE_12V, VOLTAGE_13V, VOLTAGE_14V,
+ VOLTAGE_15V, VOLTAGE_18V, VOLTAGE_19V})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Voltage {}
+
+ /**
+ * LNB power voltage not set.
+ */
+ public static final int VOLTAGE_NONE = Constants.LnbVoltage.NONE;
+ /**
+ * LNB power voltage 5V.
+ */
+ public static final int VOLTAGE_5V = Constants.LnbVoltage.VOLTAGE_5V;
+ /**
+ * LNB power voltage 11V.
+ */
+ public static final int VOLTAGE_11V = Constants.LnbVoltage.VOLTAGE_11V;
+ /**
+ * LNB power voltage 12V.
+ */
+ public static final int VOLTAGE_12V = Constants.LnbVoltage.VOLTAGE_12V;
+ /**
+ * LNB power voltage 13V.
+ */
+ public static final int VOLTAGE_13V = Constants.LnbVoltage.VOLTAGE_13V;
+ /**
+ * LNB power voltage 14V.
+ */
+ public static final int VOLTAGE_14V = Constants.LnbVoltage.VOLTAGE_14V;
+ /**
+ * LNB power voltage 15V.
+ */
+ public static final int VOLTAGE_15V = Constants.LnbVoltage.VOLTAGE_15V;
+ /**
+ * LNB power voltage 18V.
+ */
+ public static final int VOLTAGE_18V = Constants.LnbVoltage.VOLTAGE_18V;
+ /**
+ * LNB power voltage 19V.
+ */
+ public static final int VOLTAGE_19V = Constants.LnbVoltage.VOLTAGE_19V;
+
+ /** @hide */
+ @IntDef({TONE_NONE, TONE_CONTINUOUS})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Tone {}
+
+ /**
+ * LNB tone mode not set.
+ */
+ public static final int TONE_NONE = Constants.LnbTone.NONE;
+ /**
+ * LNB continuous tone mode.
+ */
+ public static final int TONE_CONTINUOUS = Constants.LnbTone.CONTINUOUS;
+
+ /** @hide */
+ @IntDef({POSITION_UNDEFINED, POSITION_A, POSITION_B})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Position {}
+
+ /**
+ * LNB position is not defined.
+ */
+ public static final int POSITION_UNDEFINED = Constants.LnbPosition.UNDEFINED;
+ /**
+ * Position A of two-band LNBs
+ */
+ public static final int POSITION_A = Constants.LnbPosition.POSITION_A;
+ /**
+ * Position B of two-band LNBs
+ */
+ public static final int POSITION_B = Constants.LnbPosition.POSITION_B;
+
+ int mId;
+ LnbCallback mCallback;
+ Context mContext;
+
+ private native int nativeSetVoltage(int voltage);
+ private native int nativeSetTone(int tone);
+ private native int nativeSetSatellitePosition(int position);
+ private native int nativeSendDiseqcMessage(byte[] message);
+ private native int nativeClose();
+
+ Lnb(int id) {
+ mId = id;
+ }
+
+ /** @hide */
+ public void setCallback(@Nullable LnbCallback callback) {
+ mCallback = callback;
+ if (mCallback == null) {
+ return;
+ }
+ }
+
+ /**
+ * Sets the LNB's power voltage.
+ *
+ * @param voltage the power voltage constant the Lnb to use.
+ * @return result status of the operation.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @Result
+ public int setVoltage(@Voltage int voltage) {
+ TunerUtils.checkTunerPermission(mContext);
+ return nativeSetVoltage(voltage);
+ }
+
+ /**
+ * Sets the LNB's tone mode.
+ *
+ * @param tone the tone mode the Lnb to use.
+ * @return result status of the operation.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @Result
+ public int setTone(@Tone int tone) {
+ TunerUtils.checkTunerPermission(mContext);
+ return nativeSetTone(tone);
+ }
+
+ /**
+ * Selects the LNB's position.
+ *
+ * @param position the position the Lnb to use.
+ * @return result status of the operation.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @Result
+ public int setSatellitePosition(@Position int position) {
+ TunerUtils.checkTunerPermission(mContext);
+ return nativeSetSatellitePosition(position);
+ }
+
+ /**
+ * Sends DiSEqC (Digital Satellite Equipment Control) message.
+ *
+ * The response message from the device comes back through callback onDiseqcMessage.
+ *
+ * @param message a byte array of data for DiSEqC message which is specified by EUTELSAT Bus
+ * Functional Specification Version 4.2.
+ *
+ * @return result status of the operation.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @Result
+ public int sendDiseqcMessage(@NonNull byte[] message) {
+ TunerUtils.checkTunerPermission(mContext);
+ return nativeSendDiseqcMessage(message);
+ }
+
+ /**
+ * Releases the LNB instance.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ public void close() {
+ TunerUtils.checkTunerPermission(mContext);
+ nativeClose();
+ }
+}
diff --git a/media/java/android/media/tv/tuner/LnbCallback.java b/media/java/android/media/tv/tuner/LnbCallback.java
new file mode 100644
index 0000000..99bbf86
--- /dev/null
+++ b/media/java/android/media/tv/tuner/LnbCallback.java
@@ -0,0 +1,39 @@
+/*
+ * 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.media.tv.tuner;
+
+
+/**
+ * Callback interface for receiving information from LNBs.
+ *
+ * @hide
+ */
+public interface LnbCallback {
+ /**
+ * Invoked when there is a LNB event.
+ */
+ void onEvent(int lnbEventType);
+
+ /**
+ * Invoked when there is a new DiSEqC message.
+ *
+ * @param diseqcMessage a byte array of data for DiSEqC (Digital Satellite
+ * Equipment Control) message which is specified by EUTELSAT Bus Functional
+ * Specification Version 4.2.
+ */
+ void onDiseqcMessage(byte[] diseqcMessage);
+}
diff --git a/media/java/android/media/tv/tuner/ScanMessage.java b/media/java/android/media/tv/tuner/ScanMessage.java
deleted file mode 100644
index 35f54f8..0000000
--- a/media/java/android/media/tv/tuner/ScanMessage.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * 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.media.tv.tuner;
-
-import android.media.tv.tuner.TunerConstants.ScanMessageType;
-
-/**
- * Message from frontend during scan operations.
- *
- * @hide
- */
-public class ScanMessage {
- private final int mType;
- private final Object mValue;
-
- private ScanMessage(int type, Object value) {
- mType = type;
- mValue = value;
- }
-
- /** Gets scan message type. */
- @ScanMessageType
- public int getMessageType() {
- return mType;
- }
- /** Message indicates whether frontend is locked or not. */
- public boolean getIsLocked() {
- if (mType != TunerConstants.SCAN_MESSAGE_TYPE_LOCKED) {
- throw new IllegalStateException();
- }
- return (Boolean) mValue;
- }
- /** Message indicates whether the scan has reached the end or not. */
- public boolean getIsEnd() {
- if (mType != TunerConstants.SCAN_MESSAGE_TYPE_END) {
- throw new IllegalStateException();
- }
- return (Boolean) mValue;
- }
- /** Progress message in percent. */
- public int getProgressPercent() {
- if (mType != TunerConstants.SCAN_MESSAGE_TYPE_PROGRESS_PERCENT) {
- throw new IllegalStateException();
- }
- return (Integer) mValue;
- }
- /** Gets frequency. */
- public int getFrequency() {
- if (mType != TunerConstants.SCAN_MESSAGE_TYPE_FREQUENCY) {
- throw new IllegalStateException();
- }
- return (Integer) mValue;
- }
- /** Gets symbol rate. */
- public int getSymbolRate() {
- if (mType != TunerConstants.SCAN_MESSAGE_TYPE_SYMBOL_RATE) {
- throw new IllegalStateException();
- }
- return (Integer) mValue;
- }
- /** Gets PLP IDs. */
- public int[] getPlpIds() {
- if (mType != TunerConstants.SCAN_MESSAGE_TYPE_PLP_IDS) {
- throw new IllegalStateException();
- }
- return (int[]) mValue;
- }
- /** Gets group IDs. */
- public int[] getGroupIds() {
- if (mType != TunerConstants.SCAN_MESSAGE_TYPE_GROUP_IDS) {
- throw new IllegalStateException();
- }
- return (int[]) mValue;
- }
- /** Gets Input stream IDs. */
- public int[] getInputStreamIds() {
- if (mType != TunerConstants.SCAN_MESSAGE_TYPE_INPUT_STREAM_IDS) {
- throw new IllegalStateException();
- }
- return (int[]) mValue;
- }
- /** Gets the DVB-T or DVB-S standard. */
- public int getStandard() {
- if (mType != TunerConstants.SCAN_MESSAGE_TYPE_STANDARD) {
- throw new IllegalStateException();
- }
- return (int) mValue;
- }
-
- /** Gets PLP information for ATSC3. */
- public Atsc3PlpInfo[] getAtsc3PlpInfos() {
- if (mType != TunerConstants.SCAN_MESSAGE_TYPE_ATSC3_PLP_INFO) {
- throw new IllegalStateException();
- }
- return (Atsc3PlpInfo[]) mValue;
- }
-
- /** PLP information for ATSC3. */
- public static class Atsc3PlpInfo {
- private final int mPlpId;
- private final boolean mLlsFlag;
-
- private Atsc3PlpInfo(int plpId, boolean llsFlag) {
- mPlpId = plpId;
- mLlsFlag = llsFlag;
- }
-
- /** Gets PLP IDs. */
- public int getPlpId() {
- return mPlpId;
- }
- /** Gets LLS flag. */
- public boolean getLlsFlag() {
- return mLlsFlag;
- }
- }
-}
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index b8ab7ee..18969ae 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -16,24 +16,33 @@
package android.media.tv.tuner;
+import android.annotation.BytesLong;
+import android.annotation.CallbackExecutor;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.content.Context;
-import android.media.tv.tuner.TunerConstants.DemuxPidType;
+import android.media.tv.tuner.TunerConstants.FilterStatus;
import android.media.tv.tuner.TunerConstants.FilterSubtype;
-import android.media.tv.tuner.TunerConstants.FilterType;
import android.media.tv.tuner.TunerConstants.FrontendScanType;
-import android.media.tv.tuner.TunerConstants.LnbPosition;
-import android.media.tv.tuner.TunerConstants.LnbTone;
-import android.media.tv.tuner.TunerConstants.LnbVoltage;
import android.media.tv.tuner.TunerConstants.Result;
+import android.media.tv.tuner.dvr.Dvr;
+import android.media.tv.tuner.dvr.DvrCallback;
+import android.media.tv.tuner.dvr.DvrSettings;
+import android.media.tv.tuner.filter.FilterConfiguration.FilterType;
+import android.media.tv.tuner.filter.FilterEvent;
+import android.media.tv.tuner.filter.TimeFilter;
+import android.media.tv.tuner.frontend.FrontendCallback;
+import android.media.tv.tuner.frontend.FrontendInfo;
+import android.media.tv.tuner.frontend.FrontendStatus;
+import android.media.tv.tuner.frontend.ScanCallback;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import java.util.List;
+import java.util.concurrent.Executor;
/**
* This class is used to interact with hardware tuners devices.
@@ -67,6 +76,10 @@
private List<Integer> mLnbIds;
private Lnb mLnb;
+ @Nullable
+ private ScanCallback mScanCallback;
+ @Nullable
+ private Executor mScanCallbackExecutor;
/**
* Constructs a Tuner instance.
@@ -78,6 +91,31 @@
nativeSetup();
}
+ /**
+ * Constructs a Tuner instance.
+ *
+ * @param context the context of the caller.
+ * @param tvInputSessionId the session ID of the TV input.
+ * @param useCase the use case of this Tuner instance.
+ *
+ * @hide
+ * TODO: replace the other constructor
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ public Tuner(@NonNull Context context, @NonNull String tvInputSessionId, int useCase) {
+ mContext = context;
+ }
+
+ /**
+ * Shares the frontend resource with another Tuner instance
+ *
+ * @param tuner the Tuner instance to share frontend resource with.
+ *
+ * @hide
+ */
+ public void shareFrontend(@NonNull Tuner tuner) { }
+
+
private long mNativeContext; // used by native jMediaTuner
/** @hide */
@@ -109,86 +147,59 @@
private native int nativeStopScan();
private native int nativeSetLnb(int lnbId);
private native int nativeSetLna(boolean enable);
- private native FrontendStatus[] nativeGetFrontendStatus(int[] statusTypes);
- private native Filter nativeOpenFilter(int type, int subType, int bufferSize);
+ private native FrontendStatus nativeGetFrontendStatus(int[] statusTypes);
+ private native int nativeGetAvSyncHwId(Filter filter);
+ private native long nativeGetAvSyncTime(int avSyncId);
+ private native int nativeConnectCiCam(int ciCamId);
+ private native int nativeDisconnectCiCam();
+ private native FrontendInfo nativeGetFrontendInfo(int id);
+ private native Filter nativeOpenFilter(int type, int subType, long bufferSize);
+ private native TimeFilter nativeOpenTimeFilter();
private native List<Integer> nativeGetLnbIds();
private native Lnb nativeOpenLnbById(int id);
private native Descrambler nativeOpenDescrambler();
- private native Dvr nativeOpenDvr(int type, int bufferSize);
+ private native Dvr nativeOpenDvr(int type, long bufferSize);
+
+ private static native DemuxCapabilities nativeGetDemuxCapabilities();
+
/**
- * Frontend Callback.
- *
- * @hide
- */
- public interface FrontendCallback {
-
- /**
- * Invoked when there is a frontend event.
- */
- void onEvent(int frontendEventType);
-
- /**
- * Invoked when there is a scan message.
- * @param msg
- */
- void onScanMessage(ScanMessage msg);
- }
-
- /**
- * LNB Callback.
- *
- * @hide
- */
- public interface LnbCallback {
- /**
- * Invoked when there is a LNB event.
- */
- void onEvent(int lnbEventType);
-
- /**
- * Invoked when there is a new DiSEqC message.
- *
- * @param diseqcMessage a byte array of data for DiSEqC (Digital Satellite
- * Equipment Control) message which is specified by EUTELSAT Bus Functional
- * Specification Version 4.2.
- */
- void onDiseqcMessage(byte[] diseqcMessage);
- }
-
- /**
- * Frontend Callback.
- *
- * @hide
+ * Callback interface for receiving information from the corresponding filters.
+ * TODO: remove
*/
public interface FilterCallback {
/**
* Invoked when there are filter events.
+ *
+ * @param filter the corresponding filter which sent the events.
+ * @param events the filter events sent from the filter.
*/
- void onFilterEvent(FilterEvent[] events);
+ void onFilterEvent(@NonNull Filter filter, @NonNull FilterEvent[] events);
/**
* Invoked when filter status changed.
+ *
+ * @param filter the corresponding filter whose status is changed.
+ * @param status the new status of the filter.
*/
- void onFilterStatus(int status);
+ void onFilterStatusChanged(@NonNull Filter filter, @FilterStatus int status);
}
+
/**
- * DVR Callback.
+ * Listener for resource lost.
*
* @hide
*/
- public interface DvrCallback {
+ public interface OnResourceLostListener {
/**
- * Invoked when record status changed.
+ * Invoked when resource lost.
+ *
+ * @param tuner the tuner instance whose resource is being reclaimed.
*/
- void onRecordStatus(int status);
- /**
- * Invoked when playback status changed.
- */
- void onPlaybackStatus(int status);
+ void onResourceLost(@NonNull Tuner tuner);
}
@Nullable
@@ -218,15 +229,10 @@
case MSG_ON_FILTER_STATUS: {
Filter filter = (Filter) msg.obj;
if (filter.mCallback != null) {
- filter.mCallback.onFilterStatus(msg.arg1);
+ filter.mCallback.onFilterStatusChanged(filter, msg.arg1);
}
break;
}
- case MSG_ON_LNB_EVENT: {
- if (mLnb != null && mLnb.mCallback != null) {
- mLnb.mCallback.onEvent(msg.arg1);
- }
- }
default:
// fall through
}
@@ -240,29 +246,6 @@
private Frontend(int id) {
mId = id;
}
-
- public void setCallback(@Nullable FrontendCallback callback, @Nullable Handler handler) {
- mCallback = callback;
-
- if (mCallback == null) {
- return;
- }
-
- if (handler == null) {
- // use default looper if handler is null
- if (mHandler == null) {
- mHandler = createEventHandler();
- }
- return;
- }
-
- Looper looper = handler.getLooper();
- if (mHandler != null && mHandler.getLooper() == looper) {
- // the same looper. reuse mHandler
- return;
- }
- mHandler = new EventHandler(looper);
- }
}
/**
@@ -281,76 +264,215 @@
/**
* Stops a previous tuning.
*
- * If the method completes successfully the frontend is no longer tuned and no data
+ * <p>If the method completes successfully, the frontend is no longer tuned and no data
* will be sent to attached filters.
*
* @return result status of the operation.
+ *
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @Result
public int stopTune() {
+ TunerUtils.checkTunerPermission(mContext);
return nativeStopTune();
}
/**
* Scan channels.
+ *
+ * @param settings A {@link FrontendSettings} to configure the frontend.
+ * @param scanType The scan type.
+ *
* @hide
*/
- public int scan(@NonNull FrontendSettings settings, @FrontendScanType int scanType) {
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ public int scan(@NonNull FrontendSettings settings, @FrontendScanType int scanType,
+ @NonNull @CallbackExecutor Executor executor, @NonNull ScanCallback scanCallback) {
+ TunerUtils.checkTunerPermission(mContext);
+ mScanCallback = scanCallback;
+ mScanCallbackExecutor = executor;
return nativeScan(settings.getType(), settings, scanType);
}
/**
* Stops a previous scanning.
*
- * If the method completes successfully, the frontend stop previous scanning.
+ * <p>
+ * The {@link ScanCallback} and it's {@link Executor} will be removed.
+ *
+ * <p>
+ * If the method completes successfully, the frontend stopped previous scanning.
+ *
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @Result
public int stopScan() {
- return nativeStopScan();
+ TunerUtils.checkTunerPermission(mContext);
+ int retVal = nativeStopScan();
+ mScanCallback = null;
+ mScanCallbackExecutor = null;
+ return retVal;
}
/**
* Sets Low-Noise Block downconverter (LNB) for satellite frontend.
*
- * This assigns a hardware LNB resource to the satellite tuner. It can be
+ * <p>This assigns a hardware LNB resource to the satellite tuner. It can be
* called multiple times to update LNB assignment.
*
* @param lnb the LNB instance.
*
* @return result status of the operation.
+ *
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @Result
public int setLnb(@NonNull Lnb lnb) {
+ TunerUtils.checkTunerPermission(mContext);
return nativeSetLnb(lnb.mId);
}
/**
* Enable or Disable Low Noise Amplifier (LNA).
*
- * @param enable true to activate LNA module; false to deactivate LNA
+ * @param enable {@code true} to activate LNA module; {@code false} to deactivate LNA.
*
* @return result status of the operation.
+ *
* @hide
*/
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @Result
public int setLna(boolean enable) {
+ TunerUtils.checkTunerPermission(mContext);
return nativeSetLna(enable);
}
/**
* Gets the statuses of the frontend.
*
- * This retrieve the statuses of the frontend for given status types.
+ * <p>This retrieve the statuses of the frontend for given status types.
*
- * @param statusTypes an array of status type which the caller request.
- *
- * @return statuses an array of statuses which response the caller's
- * request.
+ * @param statusTypes an array of status types which the caller requests.
+ * @return statuses which response the caller's requests.
* @hide
*/
- public FrontendStatus[] getFrontendStatus(int[] statusTypes) {
+ @Nullable
+ public FrontendStatus getFrontendStatus(int[] statusTypes) {
return nativeGetFrontendStatus(statusTypes);
}
+ /**
+ * Gets hardware sync ID for audio and video.
+ *
+ * @param filter the filter instance for the hardware sync ID.
+ * @return the id of hardware A/V sync.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ public int getAvSyncHwId(@NonNull Filter filter) {
+ TunerUtils.checkTunerPermission(mContext);
+ return nativeGetAvSyncHwId(filter);
+ }
+
+ /**
+ * Gets the current timestamp for Audio/Video sync
+ *
+ * <p>The timestamp is maintained by hardware. The timestamp based on 90KHz, and it's format is
+ * the same as PTS (Presentation Time Stamp).
+ *
+ * @param avSyncHwId the hardware id of A/V sync.
+ * @return the current timestamp of hardware A/V sync.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ public long getAvSyncTime(int avSyncHwId) {
+ TunerUtils.checkTunerPermission(mContext);
+ return nativeGetAvSyncTime(avSyncHwId);
+ }
+
+ /**
+ * Connects Conditional Access Modules (CAM) through Common Interface (CI)
+ *
+ * <p>The demux uses the output from the frontend as the input by default, and must change to
+ * use the output from CI-CAM as the input after this call.
+ *
+ * @param ciCamId specify CI-CAM Id to connect.
+ * @return result status of the operation.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @Result
+ public int connectCiCam(int ciCamId) {
+ TunerUtils.checkTunerPermission(mContext);
+ return nativeConnectCiCam(ciCamId);
+ }
+
+ /**
+ * Disconnects Conditional Access Modules (CAM)
+ *
+ * <p>The demux will use the output from the frontend as the input after this call.
+ *
+ * @return result status of the operation.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @Result
+ public int disconnectCiCam() {
+ TunerUtils.checkTunerPermission(mContext);
+ return nativeDisconnectCiCam();
+ }
+
+ /**
+ * Gets the frontend information.
+ *
+ * @return The frontend information. {@code null} if the operation failed.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @Nullable
+ public FrontendInfo getFrontendInfo() {
+ TunerUtils.checkTunerPermission(mContext);
+ if (mFrontend == null) {
+ throw new IllegalStateException("frontend is not initialized");
+ }
+ return nativeGetFrontendInfo(mFrontend.mId);
+ }
+
+ /**
+ * Gets the frontend ID.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ public int getFrontendId() {
+ TunerUtils.checkTunerPermission(mContext);
+ if (mFrontend == null) {
+ throw new IllegalStateException("frontend is not initialized");
+ }
+ return mFrontend.mId;
+ }
+
+ /**
+ * Gets Demux capabilities.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @Nullable
+ public static DemuxCapabilities getDemuxCapabilities(@NonNull Context context) {
+ TunerUtils.checkTunerPermission(context);
+ return nativeGetDemuxCapabilities();
+ }
+
private List<Integer> getFrontendIds() {
mFrontendIds = nativeGetFrontendIds();
return mFrontendIds;
@@ -373,116 +495,37 @@
}
}
- /** @hide */
+ /**
+ * Tuner data filter.
+ *
+ * <p> This class is used to filter wanted data according to the filter's configuration.
+ * TODO: remove
+ */
public class Filter {
- private long mNativeContext;
- private FilterCallback mCallback;
- int mId;
-
- private native int nativeConfigureFilter(int type, int subType, FilterSettings settings);
- private native int nativeGetId();
- private native int nativeSetDataSource(Filter source);
- private native int nativeStartFilter();
- private native int nativeStopFilter();
- private native int nativeFlushFilter();
- private native int nativeRead(byte[] buffer, int offset, int size);
- private native int nativeClose();
-
- private Filter(int id) {
- mId = id;
- }
-
- private void onFilterStatus(int status) {
- if (mHandler != null) {
- mHandler.sendMessage(
- mHandler.obtainMessage(MSG_ON_FILTER_STATUS, status, 0, this));
- }
- }
-
- /**
- * Configures the filter.
- *
- * @param settings the settings of the filter.
- * @return result status of the operation.
- */
- public int configure(FilterSettings settings) {
- int subType = -1;
- if (settings.mSettings != null) {
- subType = settings.mSettings.getType();
- }
- return nativeConfigureFilter(settings.getType(), subType, settings);
- }
-
- /**
- * Gets the filter Id.
- *
- * @return the hardware resource Id for the filter.
- */
- public int getId() {
- return nativeGetId();
- }
-
- /**
- * Sets the filter's data source.
- *
- * A filter uses demux as data source by default. If the data was packetized
- * by multiple protocols, multiple filters may need to work together to
- * extract all protocols' header. Then a filter's data source can be output
- * from another filter.
- *
- * @param source the filter instance which provides data input. Switch to
- * use demux as data source if the filter instance is NULL.
- * @return result status of the operation.
- */
- public int setDataSource(@Nullable Filter source) {
- return nativeSetDataSource(source);
- }
-
- /**
- * Starts the filter.
- *
- * @return result status of the operation.
- */
- public int start() {
- return nativeStartFilter();
- }
-
-
- /**
- * Stops the filter.
- *
- * @return result status of the operation.
- */
- public int stop() {
- return nativeStopFilter();
- }
-
- /**
- * Flushes the filter.
- *
- * @return result status of the operation.
- */
- public int flush() {
- return nativeFlushFilter();
- }
-
- public int read(@NonNull byte[] buffer, int offset, int size) {
- size = Math.min(size, buffer.length - offset);
- return nativeRead(buffer, offset, size);
- }
-
- /**
- * Release the Filter instance.
- *
- * @return result status of the operation.
- */
- public int close() {
- return nativeClose();
- }
+ FilterCallback mCallback;
+ private Filter() {}
}
- private Filter openFilter(@FilterType int mainType, @FilterSubtype int subType, int bufferSize,
- FilterCallback cb) {
+ /**
+ * Opens a filter object based on the given types and buffer size.
+ *
+ * @param mainType the main type of the filter.
+ * @param subType the subtype of the filter.
+ * @param bufferSize the buffer size of the filter to be opened in bytes. The buffer holds the
+ * data output from the filter.
+ * @param cb the callback to receive notifications from filter.
+ * @param executor the executor on which callback will be invoked. The default event handler
+ * executor is used if it's {@code null}.
+ * @return the opened filter. {@code null} if the operation failed.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @Nullable
+ public Filter openFilter(@FilterType int mainType, @FilterSubtype int subType,
+ @BytesLong long bufferSize, @Nullable FilterCallback cb,
+ @CallbackExecutor @Nullable Executor executor) {
+ TunerUtils.checkTunerPermission(mContext);
Filter filter = nativeOpenFilter(
mainType, TunerUtils.getFilterSubtype(mainType, subType), bufferSize);
if (filter != null) {
@@ -494,88 +537,22 @@
return filter;
}
- /** @hide */
- public class Lnb {
- private int mId;
- private LnbCallback mCallback;
-
- private native int nativeSetVoltage(int voltage);
- private native int nativeSetTone(int tone);
- private native int nativeSetSatellitePosition(int position);
- private native int nativeSendDiseqcMessage(byte[] message);
- private native int nativeClose();
-
- private Lnb(int id) {
- mId = id;
- }
-
- public void setCallback(@Nullable LnbCallback callback) {
- mCallback = callback;
- if (mCallback == null) {
- return;
- }
- if (mHandler == null) {
- mHandler = createEventHandler();
- }
- }
-
- /**
- * Sets the LNB's power voltage.
- *
- * @param voltage the power voltage the Lnb to use.
- * @return result status of the operation.
- */
- @Result
- public int setVoltage(@LnbVoltage int voltage) {
- return nativeSetVoltage(voltage);
- }
-
- /**
- * Sets the LNB's tone mode.
- *
- * @param tone the tone mode the Lnb to use.
- * @return result status of the operation.
- */
- @Result
- public int setTone(@LnbTone int tone) {
- return nativeSetTone(tone);
- }
-
- /**
- * Selects the LNB's position.
- *
- * @param position the position the Lnb to use.
- * @return result status of the operation.
- */
- @Result
- public int setSatellitePosition(@LnbPosition int position) {
- return nativeSetSatellitePosition(position);
- }
-
- /**
- * Sends DiSEqC (Digital Satellite Equipment Control) message.
- *
- * The response message from the device comes back through callback onDiseqcMessage.
- *
- * @param message a byte array of data for DiSEqC message which is specified by EUTELSAT Bus
- * Functional Specification Version 4.2.
- *
- * @return result status of the operation.
- */
- @Result
- public int sendDiseqcMessage(byte[] message) {
- return nativeSendDiseqcMessage(message);
- }
-
- /**
- * Releases the LNB instance
- *
- * @return result status of the operation.
- */
- @Result
- public int close() {
- return nativeClose();
- }
+ /**
+ * Opens an LNB (low-noise block downconverter) object.
+ *
+ * @param cb the callback to receive notifications from LNB.
+ * @param executor the executor on which callback will be invoked. The default event handler
+ * executor is used if it's {@code null}.
+ * @return the opened LNB object. {@code null} if the operation failed.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @Nullable
+ public Lnb openLnb(LnbCallback cb, @CallbackExecutor @Nullable Executor executor) {
+ TunerUtils.checkTunerPermission(mContext);
+ // TODO: use resource manager to get LNB ID.
+ return new Lnb(0);
}
private List<Integer> getLnbIds() {
@@ -606,81 +583,11 @@
* <p> Descrambler is a hardware component used to descramble data.
*
* <p> This class controls the TIS interaction with Tuner HAL.
- * TODO: make it static and extends Closable.
+ * TODO: Remove
*/
public class Descrambler {
- private long mNativeContext;
-
- private native int nativeAddPid(int pidType, int pid, Filter filter);
- private native int nativeRemovePid(int pidType, int pid, Filter filter);
- private native int nativeSetKeyToken(byte[] keyToken);
- private native int nativeClose();
-
- private Descrambler() {}
-
- /**
- * Add packets' PID to the descrambler for descrambling.
- *
- * The descrambler will start descrambling packets with this PID. Multiple PIDs can be added
- * into one descrambler instance because descambling can happen simultaneously on packets
- * from different PIDs.
- *
- * If the Descrambler previously contained a filter for the PID, the old filter is replaced
- * by the specified filter.
- *
- * @param pidType the type of the PID.
- * @param pid the PID of packets to start to be descrambled.
- * @param filter an optional filter instance to identify upper stream.
- * @return result status of the operation.
- *
- * @hide
- */
- public int addPid(@DemuxPidType int pidType, int pid, @Nullable Filter filter) {
- return nativeAddPid(pidType, pid, filter);
+ private Descrambler() {
}
-
- /**
- * Remove packets' PID from the descrambler
- *
- * The descrambler will stop descrambling packets with this PID.
- *
- * @param pidType the type of the PID.
- * @param pid the PID of packets to stop to be descrambled.
- * @param filter an optional filter instance to identify upper stream.
- * @return result status of the operation.
- *
- * @hide
- */
- public int removePid(@DemuxPidType int pidType, int pid, @Nullable Filter filter) {
- return nativeRemovePid(pidType, pid, filter);
- }
-
- /**
- * Set a key token to link descrambler to a key slot
- *
- * A descrambler instance can have only one key slot to link, but a key slot can hold a few
- * keys for different purposes.
- *
- * @param keyToken the token to be used to link the key slot.
- * @return result status of the operation.
- *
- * @hide
- */
- public int setKeyToken(byte[] keyToken) {
- return nativeSetKeyToken(keyToken);
- }
-
- /**
- * Release the descrambler instance.
- *
- * @return result status of the operation.
- *
- * @hide
- */
- public int close() {
- return nativeClose();
- }
-
}
/**
@@ -695,94 +602,24 @@
return nativeOpenDescrambler();
}
- // TODO: consider splitting Dvr to Playback and Recording
- /** @hide */
- public class Dvr {
- private long mNativeContext;
- private DvrCallback mCallback;
-
- private native int nativeAttachFilter(Filter filter);
- private native int nativeDetachFilter(Filter filter);
- private native int nativeConfigureDvr(DvrSettings settings);
- private native int nativeStartDvr();
- private native int nativeStopDvr();
- private native int nativeFlushDvr();
- private native int nativeClose();
-
- private Dvr() {}
-
- /**
- * Attaches a filter to DVR interface for recording.
- *
- * @param filter the filter to be attached.
- * @return result status of the operation.
- */
- public int attachFilter(Filter filter) {
- return nativeAttachFilter(filter);
- }
-
- /**
- * Detaches a filter from DVR interface.
- *
- * @param filter the filter to be detached.
- * @return result status of the operation.
- */
- public int detachFilter(Filter filter) {
- return nativeDetachFilter(filter);
- }
-
- /**
- * Configures the DVR.
- *
- * @param settings the settings of the DVR interface.
- * @return result status of the operation.
- */
- public int configure(DvrSettings settings) {
- return nativeConfigureDvr(settings);
- }
-
- /**
- * Starts DVR.
- *
- * Starts consuming playback data or producing data for recording.
- *
- * @return result status of the operation.
- */
- public int start() {
- return nativeStartDvr();
- }
-
- /**
- * Stops DVR.
- *
- * Stops consuming playback data or producing data for recording.
- *
- * @return result status of the operation.
- */
- public int stop() {
- return nativeStopDvr();
- }
-
- /**
- * Flushed DVR data.
- *
- * @return result status of the operation.
- */
- public int flush() {
- return nativeFlushDvr();
- }
-
- /**
- * closes the DVR instance to release resources.
- *
- * @return result status of the operation.
- */
- public int close() {
- return nativeClose();
- }
- }
-
- private Dvr openDvr(int type, int bufferSize) {
+ /**
+ * Open a DVR (Digital Video Record) instance.
+ *
+ * @param type the DVR type to be opened.
+ * @param bufferSize the buffer size of the output in bytes. It's used to hold output data of
+ * the attached filters.
+ * @param cb the callback to receive notifications from DVR.
+ * @param executor the executor on which callback will be invoked. The default event handler
+ * executor is used if it's {@code null}.
+ * @return the opened DVR object. {@code null} if the operation failed.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @Nullable
+ public Dvr openDvr(@DvrSettings.Type int type, @BytesLong long bufferSize, DvrCallback cb,
+ @CallbackExecutor @Nullable Executor executor) {
+ TunerUtils.checkTunerPermission(mContext);
Dvr dvr = nativeOpenDvr(type, bufferSize);
return dvr;
}
diff --git a/media/java/android/media/tv/tuner/TunerConstants.java b/media/java/android/media/tv/tuner/TunerConstants.java
index e611431..fa8f550 100644
--- a/media/java/android/media/tv/tuner/TunerConstants.java
+++ b/media/java/android/media/tv/tuner/TunerConstants.java
@@ -18,609 +18,539 @@
import android.annotation.IntDef;
import android.annotation.LongDef;
+import android.annotation.SystemApi;
import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.frontend.DvbcFrontendSettings;
+import android.media.tv.tuner.frontend.DvbsFrontendSettings;
+import android.media.tv.tuner.frontend.Isdbs3FrontendSettings;
+import android.media.tv.tuner.frontend.IsdbsFrontendSettings;
+import android.media.tv.tuner.frontend.IsdbtFrontendSettings;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
+ * Constants for tuner framework.
+ *
* @hide
*/
-final class TunerConstants {
+@SystemApi
+public final class TunerConstants {
+ /** @hide */
public static final int INVALID_TS_PID = Constants.Constant.INVALID_TS_PID;
+ /** @hide */
public static final int INVALID_STREAM_ID = Constants.Constant.INVALID_STREAM_ID;
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({FRONTEND_TYPE_UNDEFINED, FRONTEND_TYPE_ANALOG, FRONTEND_TYPE_ATSC, FRONTEND_TYPE_ATSC3,
- FRONTEND_TYPE_DVBC, FRONTEND_TYPE_DVBS, FRONTEND_TYPE_DVBT, FRONTEND_TYPE_ISDBS,
- FRONTEND_TYPE_ISDBS3, FRONTEND_TYPE_ISDBT})
- public @interface FrontendType {}
-
- public static final int FRONTEND_TYPE_UNDEFINED = Constants.FrontendType.UNDEFINED;
- public static final int FRONTEND_TYPE_ANALOG = Constants.FrontendType.ANALOG;
- public static final int FRONTEND_TYPE_ATSC = Constants.FrontendType.ATSC;
- public static final int FRONTEND_TYPE_ATSC3 = Constants.FrontendType.ATSC3;
- public static final int FRONTEND_TYPE_DVBC = Constants.FrontendType.DVBC;
- public static final int FRONTEND_TYPE_DVBS = Constants.FrontendType.DVBS;
- public static final int FRONTEND_TYPE_DVBT = Constants.FrontendType.DVBT;
- public static final int FRONTEND_TYPE_ISDBS = Constants.FrontendType.ISDBS;
- public static final int FRONTEND_TYPE_ISDBS3 = Constants.FrontendType.ISDBS3;
- public static final int FRONTEND_TYPE_ISDBT = Constants.FrontendType.ISDBT;
-
-
- @Retention(RetentionPolicy.SOURCE)
+ /** @hide */
@IntDef({FRONTEND_EVENT_TYPE_LOCKED, FRONTEND_EVENT_TYPE_NO_SIGNAL,
FRONTEND_EVENT_TYPE_LOST_LOCK})
+ @Retention(RetentionPolicy.SOURCE)
public @interface FrontendEventType {}
-
+ /** @hide */
public static final int FRONTEND_EVENT_TYPE_LOCKED = Constants.FrontendEventType.LOCKED;
+ /** @hide */
public static final int FRONTEND_EVENT_TYPE_NO_SIGNAL = Constants.FrontendEventType.NO_SIGNAL;
+ /** @hide */
public static final int FRONTEND_EVENT_TYPE_LOST_LOCK = Constants.FrontendEventType.LOST_LOCK;
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({DATA_FORMAT_TS, DATA_FORMAT_PES, DATA_FORMAT_ES, DATA_FORMAT_SHV_TLV})
- public @interface DataFormat {}
-
- public static final int DATA_FORMAT_TS = Constants.DataFormat.TS;
- public static final int DATA_FORMAT_PES = Constants.DataFormat.PES;
- public static final int DATA_FORMAT_ES = Constants.DataFormat.ES;
- public static final int DATA_FORMAT_SHV_TLV = Constants.DataFormat.SHV_TLV;
-
-
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({DEMUX_T_PID, DEMUX_MMPT_PID})
- public @interface DemuxPidType {}
-
- public static final int DEMUX_T_PID = 1;
- public static final int DEMUX_MMPT_PID = 2;
-
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({FRONTEND_SETTINGS_ANALOG, FRONTEND_SETTINGS_ATSC, FRONTEND_SETTINGS_ATSC3,
- FRONTEND_SETTINGS_DVBS, FRONTEND_SETTINGS_DVBC, FRONTEND_SETTINGS_DVBT,
- FRONTEND_SETTINGS_ISDBS, FRONTEND_SETTINGS_ISDBS3, FRONTEND_SETTINGS_ISDBT})
- public @interface FrontendSettingsType {}
-
- public static final int FRONTEND_SETTINGS_ANALOG = 1;
- public static final int FRONTEND_SETTINGS_ATSC = 2;
- public static final int FRONTEND_SETTINGS_ATSC3 = 3;
- public static final int FRONTEND_SETTINGS_DVBS = 4;
- public static final int FRONTEND_SETTINGS_DVBC = 5;
- public static final int FRONTEND_SETTINGS_DVBT = 6;
- public static final int FRONTEND_SETTINGS_ISDBS = 7;
- public static final int FRONTEND_SETTINGS_ISDBS3 = 8;
- public static final int FRONTEND_SETTINGS_ISDBT = 9;
-
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({FILTER_TYPE_TS, FILTER_TYPE_MMTP, FILTER_TYPE_IP, FILTER_TYPE_TLV, FILTER_TYPE_ALP})
- public @interface FilterType {}
-
- public static final int FILTER_TYPE_TS = Constants.DemuxFilterMainType.TS;
- public static final int FILTER_TYPE_MMTP = Constants.DemuxFilterMainType.MMTP;
- public static final int FILTER_TYPE_IP = Constants.DemuxFilterMainType.IP;
- public static final int FILTER_TYPE_TLV = Constants.DemuxFilterMainType.TLV;
- public static final int FILTER_TYPE_ALP = Constants.DemuxFilterMainType.ALP;
-
- @Retention(RetentionPolicy.SOURCE)
+ /** @hide */
@IntDef({FILTER_SUBTYPE_UNDEFINED, FILTER_SUBTYPE_SECTION, FILTER_SUBTYPE_PES,
FILTER_SUBTYPE_AUDIO, FILTER_SUBTYPE_VIDEO, FILTER_SUBTYPE_DOWNLOAD,
FILTER_SUBTYPE_RECORD, FILTER_SUBTYPE_TS, FILTER_SUBTYPE_PCR, FILTER_SUBTYPE_TEMI,
FILTER_SUBTYPE_MMPT, FILTER_SUBTYPE_NTP, FILTER_SUBTYPE_IP_PAYLOAD, FILTER_SUBTYPE_IP,
FILTER_SUBTYPE_PAYLOAD_THROUGH, FILTER_SUBTYPE_TLV, FILTER_SUBTYPE_PTP, })
+ @Retention(RetentionPolicy.SOURCE)
public @interface FilterSubtype {}
-
+ /** @hide */
public static final int FILTER_SUBTYPE_UNDEFINED = 0;
+ /** @hide */
public static final int FILTER_SUBTYPE_SECTION = 1;
+ /** @hide */
public static final int FILTER_SUBTYPE_PES = 2;
+ /** @hide */
public static final int FILTER_SUBTYPE_AUDIO = 3;
+ /** @hide */
public static final int FILTER_SUBTYPE_VIDEO = 4;
+ /** @hide */
public static final int FILTER_SUBTYPE_DOWNLOAD = 5;
+ /** @hide */
public static final int FILTER_SUBTYPE_RECORD = 6;
+ /** @hide */
public static final int FILTER_SUBTYPE_TS = 7;
+ /** @hide */
public static final int FILTER_SUBTYPE_PCR = 8;
+ /** @hide */
public static final int FILTER_SUBTYPE_TEMI = 9;
+ /** @hide */
public static final int FILTER_SUBTYPE_MMPT = 10;
+ /** @hide */
public static final int FILTER_SUBTYPE_NTP = 11;
+ /** @hide */
public static final int FILTER_SUBTYPE_IP_PAYLOAD = 12;
+ /** @hide */
public static final int FILTER_SUBTYPE_IP = 13;
+ /** @hide */
public static final int FILTER_SUBTYPE_PAYLOAD_THROUGH = 14;
+ /** @hide */
public static final int FILTER_SUBTYPE_TLV = 15;
+ /** @hide */
public static final int FILTER_SUBTYPE_PTP = 16;
+ /** @hide */
+ @IntDef(flag = true, prefix = "FILTER_STATUS_", value = {FILTER_STATUS_DATA_READY,
+ FILTER_STATUS_LOW_WATER, FILTER_STATUS_HIGH_WATER, FILTER_STATUS_OVERFLOW})
@Retention(RetentionPolicy.SOURCE)
- @IntDef({FRONTEND_SCAN_UNDEFINED, FRONTEND_SCAN_AUTO, FRONTEND_SCAN_BLIND})
- public @interface FrontendScanType {}
+ public @interface FilterStatus {}
+ /**
+ * The status of a filter that the data in the filter buffer is ready to be read.
+ */
+ public static final int FILTER_STATUS_DATA_READY = Constants.DemuxFilterStatus.DATA_READY;
+ /**
+ * The status of a filter that the amount of available data in the filter buffer is at low
+ * level.
+ *
+ * The value is set to 25 percent of the buffer size by default. It can be changed when
+ * configuring the filter.
+ */
+ public static final int FILTER_STATUS_LOW_WATER = Constants.DemuxFilterStatus.LOW_WATER;
+ /**
+ * The status of a filter that the amount of available data in the filter buffer is at high
+ * level.
+ * The value is set to 75 percent of the buffer size by default. It can be changed when
+ * configuring the filter.
+ */
+ public static final int FILTER_STATUS_HIGH_WATER = Constants.DemuxFilterStatus.HIGH_WATER;
+ /**
+ * The status of a filter that the filter buffer is full and newly filtered data is being
+ * discarded.
+ */
+ public static final int FILTER_STATUS_OVERFLOW = Constants.DemuxFilterStatus.OVERFLOW;
+
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "INDEX_TYPE_", value =
+ {INDEX_TYPE_NONE, INDEX_TYPE_SC, INDEX_TYPE_SC_HEVC})
+ public @interface ScIndexType {}
+
+ /**
+ * Start Code Index is not used.
+ * @hide
+ */
+ public static final int INDEX_TYPE_NONE = Constants.DemuxRecordScIndexType.NONE;
+ /**
+ * Start Code index.
+ * @hide
+ */
+ public static final int INDEX_TYPE_SC = Constants.DemuxRecordScIndexType.SC;
+ /**
+ * Start Code index for HEVC.
+ * @hide
+ */
+ public static final int INDEX_TYPE_SC_HEVC = Constants.DemuxRecordScIndexType.SC_HEVC;
+
+
+ /**
+ * Indexes can be tagged by Start Code in PES (Packetized Elementary Stream)
+ * according to ISO/IEC 13818-1.
+ * @hide
+ */
+ @IntDef(flag = true, value = {SC_INDEX_I_FRAME, SC_INDEX_P_FRAME, SC_INDEX_B_FRAME,
+ SC_INDEX_SEQUENCE})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ScIndex {}
+
+ /**
+ * SC index for a new I-frame.
+ * @hide
+ */
+ public static final int SC_INDEX_I_FRAME = Constants.DemuxScIndex.I_FRAME;
+ /**
+ * SC index for a new P-frame.
+ * @hide
+ */
+ public static final int SC_INDEX_P_FRAME = Constants.DemuxScIndex.P_FRAME;
+ /**
+ * SC index for a new B-frame.
+ * @hide
+ */
+ public static final int SC_INDEX_B_FRAME = Constants.DemuxScIndex.B_FRAME;
+ /**
+ * SC index for a new sequence.
+ * @hide
+ */
+ public static final int SC_INDEX_SEQUENCE = Constants.DemuxScIndex.SEQUENCE;
+
+
+ /**
+ * Indexes can be tagged by NAL unit group in HEVC according to ISO/IEC 23008-2.
+ *
+ * @hide
+ */
+ @IntDef(flag = true,
+ value = {SC_HEVC_INDEX_SPS, SC_HEVC_INDEX_AUD, SC_HEVC_INDEX_SLICE_CE_BLA_W_LP,
+ SC_HEVC_INDEX_SLICE_BLA_W_RADL, SC_HEVC_INDEX_SLICE_BLA_N_LP,
+ SC_HEVC_INDEX_SLICE_IDR_W_RADL, SC_HEVC_INDEX_SLICE_IDR_N_LP,
+ SC_HEVC_INDEX_SLICE_TRAIL_CRA})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ScHevcIndex {}
+
+ /**
+ * SC HEVC index SPS.
+ * @hide
+ */
+ public static final int SC_HEVC_INDEX_SPS = Constants.DemuxScHevcIndex.SPS;
+ /**
+ * SC HEVC index AUD.
+ * @hide
+ */
+ public static final int SC_HEVC_INDEX_AUD = Constants.DemuxScHevcIndex.AUD;
+ /**
+ * SC HEVC index SLICE_CE_BLA_W_LP.
+ * @hide
+ */
+ public static final int SC_HEVC_INDEX_SLICE_CE_BLA_W_LP =
+ Constants.DemuxScHevcIndex.SLICE_CE_BLA_W_LP;
+ /**
+ * SC HEVC index SLICE_BLA_W_RADL.
+ * @hide
+ */
+ public static final int SC_HEVC_INDEX_SLICE_BLA_W_RADL =
+ Constants.DemuxScHevcIndex.SLICE_BLA_W_RADL;
+ /**
+ * SC HEVC index SLICE_BLA_N_LP.
+ * @hide
+ */
+ public static final int SC_HEVC_INDEX_SLICE_BLA_N_LP =
+ Constants.DemuxScHevcIndex.SLICE_BLA_N_LP;
+ /**
+ * SC HEVC index SLICE_IDR_W_RADL.
+ * @hide
+ */
+ public static final int SC_HEVC_INDEX_SLICE_IDR_W_RADL =
+ Constants.DemuxScHevcIndex.SLICE_IDR_W_RADL;
+ /**
+ * SC HEVC index SLICE_IDR_N_LP.
+ * @hide
+ */
+ public static final int SC_HEVC_INDEX_SLICE_IDR_N_LP =
+ Constants.DemuxScHevcIndex.SLICE_IDR_N_LP;
+ /**
+ * SC HEVC index SLICE_TRAIL_CRA.
+ * @hide
+ */
+ public static final int SC_HEVC_INDEX_SLICE_TRAIL_CRA =
+ Constants.DemuxScHevcIndex.SLICE_TRAIL_CRA;
+
+
+ /** @hide */
+ @IntDef({FRONTEND_SCAN_UNDEFINED, FRONTEND_SCAN_AUTO, FRONTEND_SCAN_BLIND})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FrontendScanType {}
+ /** @hide */
public static final int FRONTEND_SCAN_UNDEFINED = Constants.FrontendScanType.SCAN_UNDEFINED;
+ /** @hide */
public static final int FRONTEND_SCAN_AUTO = Constants.FrontendScanType.SCAN_AUTO;
+ /** @hide */
public static final int FRONTEND_SCAN_BLIND = Constants.FrontendScanType.SCAN_BLIND;
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({SCAN_MESSAGE_TYPE_LOCKED, SCAN_MESSAGE_TYPE_END, SCAN_MESSAGE_TYPE_PROGRESS_PERCENT,
- SCAN_MESSAGE_TYPE_FREQUENCY, SCAN_MESSAGE_TYPE_SYMBOL_RATE, SCAN_MESSAGE_TYPE_PLP_IDS,
- SCAN_MESSAGE_TYPE_GROUP_IDS, SCAN_MESSAGE_TYPE_INPUT_STREAM_IDS,
- SCAN_MESSAGE_TYPE_STANDARD, SCAN_MESSAGE_TYPE_ATSC3_PLP_INFO})
- public @interface ScanMessageType {}
-
- public static final int SCAN_MESSAGE_TYPE_LOCKED = Constants.FrontendScanMessageType.LOCKED;
- public static final int SCAN_MESSAGE_TYPE_END = Constants.FrontendScanMessageType.END;
- public static final int SCAN_MESSAGE_TYPE_PROGRESS_PERCENT =
- Constants.FrontendScanMessageType.PROGRESS_PERCENT;
- public static final int SCAN_MESSAGE_TYPE_FREQUENCY =
- Constants.FrontendScanMessageType.FREQUENCY;
- public static final int SCAN_MESSAGE_TYPE_SYMBOL_RATE =
- Constants.FrontendScanMessageType.SYMBOL_RATE;
- public static final int SCAN_MESSAGE_TYPE_PLP_IDS = Constants.FrontendScanMessageType.PLP_IDS;
- public static final int SCAN_MESSAGE_TYPE_GROUP_IDS =
- Constants.FrontendScanMessageType.GROUP_IDS;
- public static final int SCAN_MESSAGE_TYPE_INPUT_STREAM_IDS =
- Constants.FrontendScanMessageType.INPUT_STREAM_IDS;
- public static final int SCAN_MESSAGE_TYPE_STANDARD =
- Constants.FrontendScanMessageType.STANDARD;
- public static final int SCAN_MESSAGE_TYPE_ATSC3_PLP_INFO =
- Constants.FrontendScanMessageType.ATSC3_PLP_INFO;
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({FRONTEND_STATUS_TYPE_DEMOD_LOCK, FRONTEND_STATUS_TYPE_SNR, FRONTEND_STATUS_TYPE_BER,
- FRONTEND_STATUS_TYPE_PER, FRONTEND_STATUS_TYPE_PRE_BER,
- FRONTEND_STATUS_TYPE_SIGNAL_QUALITY, FRONTEND_STATUS_TYPE_SIGNAL_STRENGTH,
- FRONTEND_STATUS_TYPE_SYMBOL_RATE, FRONTEND_STATUS_TYPE_FEC,
- FRONTEND_STATUS_TYPE_MODULATION, FRONTEND_STATUS_TYPE_SPECTRAL,
- FRONTEND_STATUS_TYPE_LNB_VOLTAGE, FRONTEND_STATUS_TYPE_PLP_ID,
- FRONTEND_STATUS_TYPE_EWBS, FRONTEND_STATUS_TYPE_AGC, FRONTEND_STATUS_TYPE_LNA,
- FRONTEND_STATUS_TYPE_LAYER_ERROR, FRONTEND_STATUS_TYPE_VBER_CN,
- FRONTEND_STATUS_TYPE_LBER_CN, FRONTEND_STATUS_TYPE_XER_CN, FRONTEND_STATUS_TYPE_MER,
- FRONTEND_STATUS_TYPE_FREQ_OFFSET, FRONTEND_STATUS_TYPE_HIERARCHY,
- FRONTEND_STATUS_TYPE_RF_LOCK, FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO})
- public @interface FrontendStatusType {}
-
- /**
- * Lock status for Demod.
- */
- public static final int FRONTEND_STATUS_TYPE_DEMOD_LOCK =
- Constants.FrontendStatusType.DEMOD_LOCK;
- /**
- * Signal to Noise Ratio.
- */
- public static final int FRONTEND_STATUS_TYPE_SNR = Constants.FrontendStatusType.SNR;
- /**
- * Bit Error Ratio.
- */
- public static final int FRONTEND_STATUS_TYPE_BER = Constants.FrontendStatusType.BER;
- /**
- * Packages Error Ratio.
- */
- public static final int FRONTEND_STATUS_TYPE_PER = Constants.FrontendStatusType.PER;
- /**
- * Bit Error Ratio before FEC.
- */
- public static final int FRONTEND_STATUS_TYPE_PRE_BER = Constants.FrontendStatusType.PRE_BER;
- /**
- * Signal Quality (0..100). Good data over total data in percent can be
- * used as a way to present Signal Quality.
- */
- public static final int FRONTEND_STATUS_TYPE_SIGNAL_QUALITY =
- Constants.FrontendStatusType.SIGNAL_QUALITY;
- /**
- * Signal Strength.
- */
- public static final int FRONTEND_STATUS_TYPE_SIGNAL_STRENGTH =
- Constants.FrontendStatusType.SIGNAL_STRENGTH;
- /**
- * Symbol Rate.
- */
- public static final int FRONTEND_STATUS_TYPE_SYMBOL_RATE =
- Constants.FrontendStatusType.SYMBOL_RATE;
- /**
- * Forward Error Correction Type.
- */
- public static final int FRONTEND_STATUS_TYPE_FEC = Constants.FrontendStatusType.FEC;
- /**
- * Modulation Type.
- */
- public static final int FRONTEND_STATUS_TYPE_MODULATION =
- Constants.FrontendStatusType.MODULATION;
- /**
- * Spectral Inversion Type.
- */
- public static final int FRONTEND_STATUS_TYPE_SPECTRAL = Constants.FrontendStatusType.SPECTRAL;
- /**
- * LNB Voltage.
- */
- public static final int FRONTEND_STATUS_TYPE_LNB_VOLTAGE =
- Constants.FrontendStatusType.LNB_VOLTAGE;
- /**
- * Physical Layer Pipe ID.
- */
- public static final int FRONTEND_STATUS_TYPE_PLP_ID = Constants.FrontendStatusType.PLP_ID;
- /**
- * Status for Emergency Warning Broadcasting System.
- */
- public static final int FRONTEND_STATUS_TYPE_EWBS = Constants.FrontendStatusType.EWBS;
- /**
- * Automatic Gain Control.
- */
- public static final int FRONTEND_STATUS_TYPE_AGC = Constants.FrontendStatusType.AGC;
- /**
- * Low Noise Amplifier.
- */
- public static final int FRONTEND_STATUS_TYPE_LNA = Constants.FrontendStatusType.LNA;
- /**
- * Error status by layer.
- */
- public static final int FRONTEND_STATUS_TYPE_LAYER_ERROR =
- Constants.FrontendStatusType.LAYER_ERROR;
- /**
- * CN value by VBER.
- */
- public static final int FRONTEND_STATUS_TYPE_VBER_CN = Constants.FrontendStatusType.VBER_CN;
- /**
- * CN value by LBER.
- */
- public static final int FRONTEND_STATUS_TYPE_LBER_CN = Constants.FrontendStatusType.LBER_CN;
- /**
- * CN value by XER.
- */
- public static final int FRONTEND_STATUS_TYPE_XER_CN = Constants.FrontendStatusType.XER_CN;
- /**
- * Moduration Error Ratio.
- */
- public static final int FRONTEND_STATUS_TYPE_MER = Constants.FrontendStatusType.MER;
- /**
- * Difference between tuning frequency and actual locked frequency.
- */
- public static final int FRONTEND_STATUS_TYPE_FREQ_OFFSET =
- Constants.FrontendStatusType.FREQ_OFFSET;
- /**
- * Hierarchy for DVBT.
- */
- public static final int FRONTEND_STATUS_TYPE_HIERARCHY = Constants.FrontendStatusType.HIERARCHY;
- /**
- * Lock status for RF.
- */
- public static final int FRONTEND_STATUS_TYPE_RF_LOCK = Constants.FrontendStatusType.RF_LOCK;
- /**
- * PLP information in a frequency band for ATSC3.0 frontend.
- */
- public static final int FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO =
- Constants.FrontendStatusType.ATSC3_PLP_INFO;
-
-
- @Retention(RetentionPolicy.SOURCE)
+ /** @hide */
@LongDef({FEC_UNDEFINED, FEC_AUTO, FEC_1_2, FEC_1_3, FEC_1_4, FEC_1_5, FEC_2_3, FEC_2_5,
FEC_2_9, FEC_3_4, FEC_3_5, FEC_4_5, FEC_4_15, FEC_5_6, FEC_5_9, FEC_6_7, FEC_7_8,
FEC_7_9, FEC_7_15, FEC_8_9, FEC_8_15, FEC_9_10, FEC_9_20, FEC_11_15, FEC_11_20,
FEC_11_45, FEC_13_18, FEC_13_45, FEC_14_45, FEC_23_36, FEC_25_36, FEC_26_45, FEC_28_45,
FEC_29_45, FEC_31_45, FEC_32_45, FEC_77_90})
+ @Retention(RetentionPolicy.SOURCE)
public @interface FrontendInnerFec {}
/**
* FEC not defined
+ * @hide
*/
public static final long FEC_UNDEFINED = Constants.FrontendInnerFec.FEC_UNDEFINED;
/**
* hardware is able to detect and set FEC automatically
+ * @hide
*/
public static final long FEC_AUTO = Constants.FrontendInnerFec.AUTO;
/**
* 1/2 conv. code rate
+ * @hide
*/
public static final long FEC_1_2 = Constants.FrontendInnerFec.FEC_1_2;
/**
* 1/3 conv. code rate
+ * @hide
*/
public static final long FEC_1_3 = Constants.FrontendInnerFec.FEC_1_3;
/**
* 1/4 conv. code rate
+ * @hide
*/
public static final long FEC_1_4 = Constants.FrontendInnerFec.FEC_1_4;
/**
* 1/5 conv. code rate
+ * @hide
*/
public static final long FEC_1_5 = Constants.FrontendInnerFec.FEC_1_5;
/**
* 2/3 conv. code rate
+ * @hide
*/
public static final long FEC_2_3 = Constants.FrontendInnerFec.FEC_2_3;
/**
* 2/5 conv. code rate
+ * @hide
*/
public static final long FEC_2_5 = Constants.FrontendInnerFec.FEC_2_5;
/**
* 2/9 conv. code rate
+ * @hide
*/
public static final long FEC_2_9 = Constants.FrontendInnerFec.FEC_2_9;
/**
* 3/4 conv. code rate
+ * @hide
*/
public static final long FEC_3_4 = Constants.FrontendInnerFec.FEC_3_4;
/**
* 3/5 conv. code rate
+ * @hide
*/
public static final long FEC_3_5 = Constants.FrontendInnerFec.FEC_3_5;
/**
* 4/5 conv. code rate
+ * @hide
*/
public static final long FEC_4_5 = Constants.FrontendInnerFec.FEC_4_5;
/**
* 4/15 conv. code rate
+ * @hide
*/
public static final long FEC_4_15 = Constants.FrontendInnerFec.FEC_4_15;
/**
* 5/6 conv. code rate
+ * @hide
*/
public static final long FEC_5_6 = Constants.FrontendInnerFec.FEC_5_6;
/**
* 5/9 conv. code rate
+ * @hide
*/
public static final long FEC_5_9 = Constants.FrontendInnerFec.FEC_5_9;
/**
* 6/7 conv. code rate
+ * @hide
*/
public static final long FEC_6_7 = Constants.FrontendInnerFec.FEC_6_7;
/**
* 7/8 conv. code rate
+ * @hide
*/
public static final long FEC_7_8 = Constants.FrontendInnerFec.FEC_7_8;
/**
* 7/9 conv. code rate
+ * @hide
*/
public static final long FEC_7_9 = Constants.FrontendInnerFec.FEC_7_9;
/**
* 7/15 conv. code rate
+ * @hide
*/
public static final long FEC_7_15 = Constants.FrontendInnerFec.FEC_7_15;
/**
* 8/9 conv. code rate
+ * @hide
*/
public static final long FEC_8_9 = Constants.FrontendInnerFec.FEC_8_9;
/**
* 8/15 conv. code rate
+ * @hide
*/
public static final long FEC_8_15 = Constants.FrontendInnerFec.FEC_8_15;
/**
* 9/10 conv. code rate
+ * @hide
*/
public static final long FEC_9_10 = Constants.FrontendInnerFec.FEC_9_10;
/**
* 9/20 conv. code rate
+ * @hide
*/
public static final long FEC_9_20 = Constants.FrontendInnerFec.FEC_9_20;
/**
* 11/15 conv. code rate
+ * @hide
*/
public static final long FEC_11_15 = Constants.FrontendInnerFec.FEC_11_15;
/**
* 11/20 conv. code rate
+ * @hide
*/
public static final long FEC_11_20 = Constants.FrontendInnerFec.FEC_11_20;
/**
* 11/45 conv. code rate
+ * @hide
*/
public static final long FEC_11_45 = Constants.FrontendInnerFec.FEC_11_45;
/**
* 13/18 conv. code rate
+ * @hide
*/
public static final long FEC_13_18 = Constants.FrontendInnerFec.FEC_13_18;
/**
* 13/45 conv. code rate
+ * @hide
*/
public static final long FEC_13_45 = Constants.FrontendInnerFec.FEC_13_45;
/**
* 14/45 conv. code rate
+ * @hide
*/
public static final long FEC_14_45 = Constants.FrontendInnerFec.FEC_14_45;
/**
* 23/36 conv. code rate
+ * @hide
*/
public static final long FEC_23_36 = Constants.FrontendInnerFec.FEC_23_36;
/**
* 25/36 conv. code rate
+ * @hide
*/
public static final long FEC_25_36 = Constants.FrontendInnerFec.FEC_25_36;
/**
* 26/45 conv. code rate
+ * @hide
*/
public static final long FEC_26_45 = Constants.FrontendInnerFec.FEC_26_45;
/**
* 28/45 conv. code rate
+ * @hide
*/
public static final long FEC_28_45 = Constants.FrontendInnerFec.FEC_28_45;
/**
* 29/45 conv. code rate
+ * @hide
*/
public static final long FEC_29_45 = Constants.FrontendInnerFec.FEC_29_45;
/**
* 31/45 conv. code rate
+ * @hide
*/
public static final long FEC_31_45 = Constants.FrontendInnerFec.FEC_31_45;
/**
* 32/45 conv. code rate
+ * @hide
*/
public static final long FEC_32_45 = Constants.FrontendInnerFec.FEC_32_45;
/**
* 77/90 conv. code rate
+ * @hide
*/
public static final long FEC_77_90 = Constants.FrontendInnerFec.FEC_77_90;
+ /** @hide */
+ @IntDef(value = {
+ DvbcFrontendSettings.MODULATION_UNDEFINED,
+ DvbcFrontendSettings.MODULATION_AUTO,
+ DvbcFrontendSettings.MODULATION_MOD_16QAM,
+ DvbcFrontendSettings.MODULATION_MOD_32QAM,
+ DvbcFrontendSettings.MODULATION_MOD_64QAM,
+ DvbcFrontendSettings.MODULATION_MOD_128QAM,
+ DvbcFrontendSettings.MODULATION_MOD_256QAM,
+ DvbsFrontendSettings.MODULATION_UNDEFINED,
+ DvbsFrontendSettings.MODULATION_AUTO,
+ DvbsFrontendSettings.MODULATION_MOD_QPSK,
+ DvbsFrontendSettings.MODULATION_MOD_8PSK,
+ DvbsFrontendSettings.MODULATION_MOD_16QAM,
+ DvbsFrontendSettings.MODULATION_MOD_16PSK,
+ DvbsFrontendSettings.MODULATION_MOD_32PSK,
+ DvbsFrontendSettings.MODULATION_MOD_ACM,
+ DvbsFrontendSettings.MODULATION_MOD_8APSK,
+ DvbsFrontendSettings.MODULATION_MOD_16APSK,
+ DvbsFrontendSettings.MODULATION_MOD_32APSK,
+ DvbsFrontendSettings.MODULATION_MOD_64APSK,
+ DvbsFrontendSettings.MODULATION_MOD_128APSK,
+ DvbsFrontendSettings.MODULATION_MOD_256APSK,
+ DvbsFrontendSettings.MODULATION_MOD_RESERVED,
+ IsdbsFrontendSettings.MODULATION_UNDEFINED,
+ IsdbsFrontendSettings.MODULATION_AUTO,
+ IsdbsFrontendSettings.MODULATION_MOD_BPSK,
+ IsdbsFrontendSettings.MODULATION_MOD_QPSK,
+ IsdbsFrontendSettings.MODULATION_MOD_TC8PSK,
+ Isdbs3FrontendSettings.MODULATION_UNDEFINED,
+ Isdbs3FrontendSettings.MODULATION_AUTO,
+ Isdbs3FrontendSettings.MODULATION_MOD_BPSK,
+ Isdbs3FrontendSettings.MODULATION_MOD_QPSK,
+ Isdbs3FrontendSettings.MODULATION_MOD_8PSK,
+ Isdbs3FrontendSettings.MODULATION_MOD_16APSK,
+ Isdbs3FrontendSettings.MODULATION_MOD_32APSK,
+ IsdbtFrontendSettings.MODULATION_UNDEFINED,
+ IsdbtFrontendSettings.MODULATION_AUTO,
+ IsdbtFrontendSettings.MODULATION_MOD_DQPSK,
+ IsdbtFrontendSettings.MODULATION_MOD_QPSK,
+ IsdbtFrontendSettings.MODULATION_MOD_16QAM,
+ IsdbtFrontendSettings.MODULATION_MOD_64QAM})
@Retention(RetentionPolicy.SOURCE)
- @IntDef({DVBC_MODULATION_UNDEFINED, DVBC_MODULATION_AUTO, DVBC_MODULATION_MOD_16QAM,
- DVBC_MODULATION_MOD_32QAM, DVBC_MODULATION_MOD_64QAM, DVBC_MODULATION_MOD_128QAM,
- DVBC_MODULATION_MOD_256QAM, DVBS_MODULATION_UNDEFINED, DVBS_MODULATION_AUTO,
- DVBS_MODULATION_MOD_QPSK, DVBS_MODULATION_MOD_8PSK, DVBS_MODULATION_MOD_16QAM,
- DVBS_MODULATION_MOD_16PSK, DVBS_MODULATION_MOD_32PSK, DVBS_MODULATION_MOD_ACM,
- DVBS_MODULATION_MOD_8APSK, DVBS_MODULATION_MOD_16APSK, DVBS_MODULATION_MOD_32APSK,
- DVBS_MODULATION_MOD_64APSK, DVBS_MODULATION_MOD_128APSK, DVBS_MODULATION_MOD_256APSK,
- DVBS_MODULATION_MOD_RESERVED, ISDBS_MODULATION_UNDEFINED, ISDBS_MODULATION_AUTO,
- ISDBS_MODULATION_MOD_BPSK, ISDBS_MODULATION_MOD_QPSK, ISDBS_MODULATION_MOD_TC8PSK,
- ISDBS3_MODULATION_UNDEFINED, ISDBS3_MODULATION_AUTO, ISDBS3_MODULATION_MOD_BPSK,
- ISDBS3_MODULATION_MOD_QPSK, ISDBS3_MODULATION_MOD_8PSK, ISDBS3_MODULATION_MOD_16APSK,
- ISDBS3_MODULATION_MOD_32APSK, ISDBT_MODULATION_UNDEFINED, ISDBT_MODULATION_AUTO,
- ISDBT_MODULATION_MOD_DQPSK, ISDBT_MODULATION_MOD_QPSK, ISDBT_MODULATION_MOD_16QAM,
- ISDBT_MODULATION_MOD_64QAM})
public @interface FrontendModulation {}
- public static final int DVBC_MODULATION_UNDEFINED = Constants.FrontendDvbcModulation.UNDEFINED;
- public static final int DVBC_MODULATION_AUTO = Constants.FrontendDvbcModulation.AUTO;
- public static final int DVBC_MODULATION_MOD_16QAM = Constants.FrontendDvbcModulation.MOD_16QAM;
- public static final int DVBC_MODULATION_MOD_32QAM = Constants.FrontendDvbcModulation.MOD_32QAM;
- public static final int DVBC_MODULATION_MOD_64QAM = Constants.FrontendDvbcModulation.MOD_64QAM;
- public static final int DVBC_MODULATION_MOD_128QAM =
- Constants.FrontendDvbcModulation.MOD_128QAM;
- public static final int DVBC_MODULATION_MOD_256QAM =
- Constants.FrontendDvbcModulation.MOD_256QAM;
- public static final int DVBS_MODULATION_UNDEFINED = Constants.FrontendDvbsModulation.UNDEFINED;
- public static final int DVBS_MODULATION_AUTO = Constants.FrontendDvbsModulation.AUTO;
- public static final int DVBS_MODULATION_MOD_QPSK = Constants.FrontendDvbsModulation.MOD_QPSK;
- public static final int DVBS_MODULATION_MOD_8PSK = Constants.FrontendDvbsModulation.MOD_8PSK;
- public static final int DVBS_MODULATION_MOD_16QAM = Constants.FrontendDvbsModulation.MOD_16QAM;
- public static final int DVBS_MODULATION_MOD_16PSK = Constants.FrontendDvbsModulation.MOD_16PSK;
- public static final int DVBS_MODULATION_MOD_32PSK = Constants.FrontendDvbsModulation.MOD_32PSK;
- public static final int DVBS_MODULATION_MOD_ACM = Constants.FrontendDvbsModulation.MOD_ACM;
- public static final int DVBS_MODULATION_MOD_8APSK = Constants.FrontendDvbsModulation.MOD_8APSK;
- public static final int DVBS_MODULATION_MOD_16APSK =
- Constants.FrontendDvbsModulation.MOD_16APSK;
- public static final int DVBS_MODULATION_MOD_32APSK =
- Constants.FrontendDvbsModulation.MOD_32APSK;
- public static final int DVBS_MODULATION_MOD_64APSK =
- Constants.FrontendDvbsModulation.MOD_64APSK;
- public static final int DVBS_MODULATION_MOD_128APSK =
- Constants.FrontendDvbsModulation.MOD_128APSK;
- public static final int DVBS_MODULATION_MOD_256APSK =
- Constants.FrontendDvbsModulation.MOD_256APSK;
- public static final int DVBS_MODULATION_MOD_RESERVED =
- Constants.FrontendDvbsModulation.MOD_RESERVED;
- public static final int ISDBS_MODULATION_UNDEFINED =
- Constants.FrontendIsdbsModulation.UNDEFINED;
- public static final int ISDBS_MODULATION_AUTO = Constants.FrontendIsdbsModulation.AUTO;
- public static final int ISDBS_MODULATION_MOD_BPSK = Constants.FrontendIsdbsModulation.MOD_BPSK;
- public static final int ISDBS_MODULATION_MOD_QPSK = Constants.FrontendIsdbsModulation.MOD_QPSK;
- public static final int ISDBS_MODULATION_MOD_TC8PSK =
- Constants.FrontendIsdbsModulation.MOD_TC8PSK;
- public static final int ISDBS3_MODULATION_UNDEFINED =
- Constants.FrontendIsdbs3Modulation.UNDEFINED;
- public static final int ISDBS3_MODULATION_AUTO = Constants.FrontendIsdbs3Modulation.AUTO;
- public static final int ISDBS3_MODULATION_MOD_BPSK =
- Constants.FrontendIsdbs3Modulation.MOD_BPSK;
- public static final int ISDBS3_MODULATION_MOD_QPSK =
- Constants.FrontendIsdbs3Modulation.MOD_QPSK;
- public static final int ISDBS3_MODULATION_MOD_8PSK =
- Constants.FrontendIsdbs3Modulation.MOD_8PSK;
- public static final int ISDBS3_MODULATION_MOD_16APSK =
- Constants.FrontendIsdbs3Modulation.MOD_16APSK;
- public static final int ISDBS3_MODULATION_MOD_32APSK =
- Constants.FrontendIsdbs3Modulation.MOD_32APSK;
- public static final int ISDBT_MODULATION_UNDEFINED =
- Constants.FrontendIsdbtModulation.UNDEFINED;
- public static final int ISDBT_MODULATION_AUTO = Constants.FrontendIsdbtModulation.AUTO;
- public static final int ISDBT_MODULATION_MOD_DQPSK =
- Constants.FrontendIsdbtModulation.MOD_DQPSK;
- public static final int ISDBT_MODULATION_MOD_QPSK = Constants.FrontendIsdbtModulation.MOD_QPSK;
- public static final int ISDBT_MODULATION_MOD_16QAM =
- Constants.FrontendIsdbtModulation.MOD_16QAM;
- public static final int ISDBT_MODULATION_MOD_64QAM =
- Constants.FrontendIsdbtModulation.MOD_64QAM;
-
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({SPECTRAL_INVERSION_UNDEFINED, SPECTRAL_INVERSION_NORMAL, SPECTRAL_INVERSION_INVERTED})
- public @interface FrontendDvbcSpectralInversion {}
-
- public static final int SPECTRAL_INVERSION_UNDEFINED =
- Constants.FrontendDvbcSpectralInversion.UNDEFINED;
- public static final int SPECTRAL_INVERSION_NORMAL =
- Constants.FrontendDvbcSpectralInversion.NORMAL;
- public static final int SPECTRAL_INVERSION_INVERTED =
- Constants.FrontendDvbcSpectralInversion.INVERTED;
-
-
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({HIERARCHY_UNDEFINED, HIERARCHY_AUTO, HIERARCHY_NON_NATIVE, HIERARCHY_1_NATIVE,
- HIERARCHY_2_NATIVE, HIERARCHY_4_NATIVE, HIERARCHY_NON_INDEPTH, HIERARCHY_1_INDEPTH,
- HIERARCHY_2_INDEPTH, HIERARCHY_4_INDEPTH})
- public @interface FrontendDvbtHierarchy {}
-
- public static final int HIERARCHY_UNDEFINED = Constants.FrontendDvbtHierarchy.UNDEFINED;
- public static final int HIERARCHY_AUTO = Constants.FrontendDvbtHierarchy.AUTO;
- public static final int HIERARCHY_NON_NATIVE =
- Constants.FrontendDvbtHierarchy.HIERARCHY_NON_NATIVE;
- public static final int HIERARCHY_1_NATIVE = Constants.FrontendDvbtHierarchy.HIERARCHY_1_NATIVE;
- public static final int HIERARCHY_2_NATIVE = Constants.FrontendDvbtHierarchy.HIERARCHY_2_NATIVE;
- public static final int HIERARCHY_4_NATIVE = Constants.FrontendDvbtHierarchy.HIERARCHY_4_NATIVE;
- public static final int HIERARCHY_NON_INDEPTH =
- Constants.FrontendDvbtHierarchy.HIERARCHY_NON_INDEPTH;
- public static final int HIERARCHY_1_INDEPTH =
- Constants.FrontendDvbtHierarchy.HIERARCHY_1_INDEPTH;
- public static final int HIERARCHY_2_INDEPTH =
- Constants.FrontendDvbtHierarchy.HIERARCHY_2_INDEPTH;
- public static final int HIERARCHY_4_INDEPTH =
- Constants.FrontendDvbtHierarchy.HIERARCHY_4_INDEPTH;
-
-
- @Retention(RetentionPolicy.SOURCE)
+ /** @hide */
@IntDef({FILTER_SETTINGS_TS, FILTER_SETTINGS_MMTP, FILTER_SETTINGS_IP, FILTER_SETTINGS_TLV,
FILTER_SETTINGS_ALP})
+ @Retention(RetentionPolicy.SOURCE)
public @interface FilterSettingsType {}
-
+ /** @hide */
public static final int FILTER_SETTINGS_TS = Constants.DemuxFilterMainType.TS;
+ /** @hide */
public static final int FILTER_SETTINGS_MMTP = Constants.DemuxFilterMainType.MMTP;
+ /** @hide */
public static final int FILTER_SETTINGS_IP = Constants.DemuxFilterMainType.IP;
+ /** @hide */
public static final int FILTER_SETTINGS_TLV = Constants.DemuxFilterMainType.TLV;
+ /** @hide */
public static final int FILTER_SETTINGS_ALP = Constants.DemuxFilterMainType.ALP;
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({DVR_SETTINGS_RECORD, DVR_SETTINGS_PLAYBACK})
- public @interface DvrSettingsType {}
- public static final int DVR_SETTINGS_RECORD = Constants.DvrType.RECORD;
- public static final int DVR_SETTINGS_PLAYBACK = Constants.DvrType.PLAYBACK;
-
-
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({LNB_VOLTAGE_NONE, LNB_VOLTAGE_5V, LNB_VOLTAGE_11V, LNB_VOLTAGE_12V, LNB_VOLTAGE_13V,
- LNB_VOLTAGE_14V, LNB_VOLTAGE_15V, LNB_VOLTAGE_18V, LNB_VOLTAGE_19V})
- public @interface LnbVoltage {}
-
- public static final int LNB_VOLTAGE_NONE = Constants.LnbVoltage.NONE;
- public static final int LNB_VOLTAGE_5V = Constants.LnbVoltage.VOLTAGE_5V;
- public static final int LNB_VOLTAGE_11V = Constants.LnbVoltage.VOLTAGE_11V;
- public static final int LNB_VOLTAGE_12V = Constants.LnbVoltage.VOLTAGE_12V;
- public static final int LNB_VOLTAGE_13V = Constants.LnbVoltage.VOLTAGE_13V;
- public static final int LNB_VOLTAGE_14V = Constants.LnbVoltage.VOLTAGE_14V;
- public static final int LNB_VOLTAGE_15V = Constants.LnbVoltage.VOLTAGE_15V;
- public static final int LNB_VOLTAGE_18V = Constants.LnbVoltage.VOLTAGE_18V;
- public static final int LNB_VOLTAGE_19V = Constants.LnbVoltage.VOLTAGE_19V;
-
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({LNB_TONE_NONE, LNB_TONE_CONTINUOUS})
- public @interface LnbTone {}
-
- public static final int LNB_TONE_NONE = Constants.LnbTone.NONE;
- public static final int LNB_TONE_CONTINUOUS = Constants.LnbTone.CONTINUOUS;
-
- @Retention(RetentionPolicy.SOURCE)
- @IntDef({LNB_POSITION_UNDEFINED, LNB_POSITION_A, LNB_POSITION_B})
- public @interface LnbPosition {}
-
- public static final int LNB_POSITION_UNDEFINED = Constants.LnbPosition.UNDEFINED;
- public static final int LNB_POSITION_A = Constants.LnbPosition.POSITION_A;
- public static final int LNB_POSITION_B = Constants.LnbPosition.POSITION_B;
-
-
- @Retention(RetentionPolicy.SOURCE)
+ /** @hide */
@IntDef({RESULT_SUCCESS, RESULT_UNAVAILABLE, RESULT_NOT_INITIALIZED, RESULT_INVALID_STATE,
RESULT_INVALID_ARGUMENT, RESULT_OUT_OF_MEMORY, RESULT_UNKNOWN_ERROR})
+ @Retention(RetentionPolicy.SOURCE)
public @interface Result {}
+ /**
+ * Operation succeeded.
+ */
public static final int RESULT_SUCCESS = Constants.Result.SUCCESS;
+ /**
+ * Operation failed because the corresponding resources are not available.
+ */
public static final int RESULT_UNAVAILABLE = Constants.Result.UNAVAILABLE;
+ /**
+ * Operation failed because the corresponding resources are not initialized.
+ */
public static final int RESULT_NOT_INITIALIZED = Constants.Result.NOT_INITIALIZED;
+ /**
+ * Operation failed because it's not in a valid state.
+ */
public static final int RESULT_INVALID_STATE = Constants.Result.INVALID_STATE;
+ /**
+ * Operation failed because there are invalid arguments.
+ */
public static final int RESULT_INVALID_ARGUMENT = Constants.Result.INVALID_ARGUMENT;
+ /**
+ * Memory allocation failed.
+ */
public static final int RESULT_OUT_OF_MEMORY = Constants.Result.OUT_OF_MEMORY;
+ /**
+ * Operation failed due to unknown errors.
+ */
public static final int RESULT_UNKNOWN_ERROR = Constants.Result.UNKNOWN_ERROR;
private TunerConstants() {
diff --git a/media/java/android/media/tv/tuner/TunerUtils.java b/media/java/android/media/tv/tuner/TunerUtils.java
index b3bd780..8780b72 100644
--- a/media/java/android/media/tv/tuner/TunerUtils.java
+++ b/media/java/android/media/tv/tuner/TunerUtils.java
@@ -20,7 +20,8 @@
import android.content.pm.PackageManager;
import android.hardware.tv.tuner.V1_0.Constants;
import android.media.tv.tuner.TunerConstants.FilterSubtype;
-import android.media.tv.tuner.TunerConstants.FilterType;
+import android.media.tv.tuner.filter.FilterConfiguration;
+import android.media.tv.tuner.filter.FilterConfiguration.FilterType;
/**
* Utility class for tuner framework.
@@ -30,15 +31,27 @@
public final class TunerUtils {
private static final String PERMISSION = android.Manifest.permission.ACCESS_TV_TUNER;
- static void checkTunerPermission(Context context) {
+ /**
+ * Checks whether the caller has permission to access tuner.
+ *
+ * @param context context of the caller.
+ * @throws SecurityException if the caller doesn't have the permission.
+ */
+ public static void checkTunerPermission(Context context) {
if (context.checkCallingOrSelfPermission(PERMISSION)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Caller must have " + PERMISSION + " permission.");
}
}
- static int getFilterSubtype(@FilterType int mainType, @FilterSubtype int subtype) {
- if (mainType == TunerConstants.FILTER_TYPE_TS) {
+ /**
+ * Gets the corresponding filter subtype constant defined in tuner HAL.
+ *
+ * @param mainType filter main type.
+ * @param subtype filter subtype.
+ */
+ public static int getFilterSubtype(@FilterType int mainType, @FilterSubtype int subtype) {
+ if (mainType == FilterConfiguration.FILTER_TYPE_TS) {
switch (subtype) {
case TunerConstants.FILTER_SUBTYPE_UNDEFINED:
return Constants.DemuxTsFilterType.UNDEFINED;
@@ -61,7 +74,7 @@
default:
break;
}
- } else if (mainType == TunerConstants.FILTER_TYPE_MMTP) {
+ } else if (mainType == FilterConfiguration.FILTER_TYPE_MMTP) {
switch (subtype) {
case TunerConstants.FILTER_SUBTYPE_UNDEFINED:
return Constants.DemuxMmtpFilterType.UNDEFINED;
@@ -83,7 +96,7 @@
break;
}
- } else if (mainType == TunerConstants.FILTER_TYPE_IP) {
+ } else if (mainType == FilterConfiguration.FILTER_TYPE_IP) {
switch (subtype) {
case TunerConstants.FILTER_SUBTYPE_UNDEFINED:
return Constants.DemuxIpFilterType.UNDEFINED;
@@ -100,7 +113,7 @@
default:
break;
}
- } else if (mainType == TunerConstants.FILTER_TYPE_TLV) {
+ } else if (mainType == FilterConfiguration.FILTER_TYPE_TLV) {
switch (subtype) {
case TunerConstants.FILTER_SUBTYPE_UNDEFINED:
return Constants.DemuxTlvFilterType.UNDEFINED;
@@ -113,7 +126,7 @@
default:
break;
}
- } else if (mainType == TunerConstants.FILTER_TYPE_ALP) {
+ } else if (mainType == FilterConfiguration.FILTER_TYPE_ALP) {
switch (subtype) {
case TunerConstants.FILTER_SUBTYPE_UNDEFINED:
return Constants.DemuxAlpFilterType.UNDEFINED;
diff --git a/media/java/android/media/tv/tuner/dvr/Dvr.java b/media/java/android/media/tv/tuner/dvr/Dvr.java
new file mode 100644
index 0000000..95508d3
--- /dev/null
+++ b/media/java/android/media/tv/tuner/dvr/Dvr.java
@@ -0,0 +1,185 @@
+/*
+ * 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.media.tv.tuner.dvr;
+
+import android.annotation.BytesLong;
+import android.annotation.NonNull;
+import android.media.tv.tuner.Tuner.Filter;
+import android.media.tv.tuner.TunerConstants.Result;
+import android.os.ParcelFileDescriptor;
+
+/**
+ * Digital Video Record (DVR) interface provides record control on Demux's output buffer and
+ * playback control on Demux's input buffer.
+ *
+ * @hide
+ */
+public class Dvr {
+ private long mNativeContext;
+ private DvrCallback mCallback;
+
+ private native int nativeAttachFilter(Filter filter);
+ private native int nativeDetachFilter(Filter filter);
+ private native int nativeConfigureDvr(DvrSettings settings);
+ private native int nativeStartDvr();
+ private native int nativeStopDvr();
+ private native int nativeFlushDvr();
+ private native int nativeClose();
+ private native void nativeSetFileDescriptor(int fd);
+ private native int nativeRead(long size);
+ private native int nativeRead(byte[] bytes, long offset, long size);
+ private native int nativeWrite(long size);
+ private native int nativeWrite(byte[] bytes, long offset, long size);
+
+ private Dvr() {}
+
+ /**
+ * Attaches a filter to DVR interface for recording.
+ *
+ * @param filter the filter to be attached.
+ * @return result status of the operation.
+ */
+ @Result
+ public int attachFilter(@NonNull Filter filter) {
+ return nativeAttachFilter(filter);
+ }
+
+ /**
+ * Detaches a filter from DVR interface.
+ *
+ * @param filter the filter to be detached.
+ * @return result status of the operation.
+ */
+ @Result
+ public int detachFilter(@NonNull Filter filter) {
+ return nativeDetachFilter(filter);
+ }
+
+ /**
+ * Configures the DVR.
+ *
+ * @param settings the settings of the DVR interface.
+ * @return result status of the operation.
+ */
+ @Result
+ public int configure(@NonNull DvrSettings settings) {
+ return nativeConfigureDvr(settings);
+ }
+
+ /**
+ * Starts DVR.
+ *
+ * <p>Starts consuming playback data or producing data for recording.
+ *
+ * @return result status of the operation.
+ */
+ @Result
+ public int start() {
+ return nativeStartDvr();
+ }
+
+ /**
+ * Stops DVR.
+ *
+ * <p>Stops consuming playback data or producing data for recording.
+ *
+ * @return result status of the operation.
+ */
+ @Result
+ public int stop() {
+ return nativeStopDvr();
+ }
+
+ /**
+ * Flushed DVR data.
+ *
+ * <p>The data in DVR buffer is cleared.
+ *
+ * @return result status of the operation.
+ */
+ @Result
+ public int flush() {
+ return nativeFlushDvr();
+ }
+
+ /**
+ * Closes the DVR instance to release resources.
+ *
+ * @return result status of the operation.
+ */
+ @Result
+ public int close() {
+ return nativeClose();
+ }
+
+ /**
+ * Sets file descriptor to read/write data.
+ *
+ * @param fd the file descriptor to read/write data.
+ */
+ public void setFileDescriptor(@NonNull ParcelFileDescriptor fd) {
+ nativeSetFileDescriptor(fd.getFd());
+ }
+
+ /**
+ * Reads data from the file for DVR playback.
+ *
+ * @param size the maximum number of bytes to read.
+ * @return the number of bytes read.
+ */
+ public int read(@BytesLong long size) {
+ return nativeRead(size);
+ }
+
+ /**
+ * Reads data from the buffer for DVR playback and copies to the given byte array.
+ *
+ * @param bytes the byte array to store the data.
+ * @param offset the index of the first byte in {@code bytes} to copy to.
+ * @param size the maximum number of bytes to read.
+ * @return the number of bytes read.
+ */
+ public int read(@NonNull byte[] bytes, @BytesLong long offset, @BytesLong long size) {
+ if (size + offset > bytes.length) {
+ throw new ArrayIndexOutOfBoundsException(
+ "Array length=" + bytes.length + ", offset=" + offset + ", size=" + size);
+ }
+ return nativeRead(bytes, offset, size);
+ }
+
+ /**
+ * Writes recording data to file.
+ *
+ * @param size the maximum number of bytes to write.
+ * @return the number of bytes written.
+ */
+ public int write(@BytesLong long size) {
+ return nativeWrite(size);
+ }
+
+ /**
+ * Writes recording data to buffer.
+ *
+ * @param bytes the byte array stores the data to be written to DVR.
+ * @param offset the index of the first byte in {@code bytes} to be written to DVR.
+ * @param size the maximum number of bytes to write.
+ * @return the number of bytes written.
+ */
+ public int write(@NonNull byte[] bytes, @BytesLong long offset, @BytesLong long size) {
+ return nativeWrite(bytes, offset, size);
+ }
+}
diff --git a/media/java/android/media/RouteSessionInfo.aidl b/media/java/android/media/tv/tuner/dvr/DvrCallback.java
similarity index 62%
copy from media/java/android/media/RouteSessionInfo.aidl
copy to media/java/android/media/tv/tuner/dvr/DvrCallback.java
index fb5d836..3d140f0 100644
--- a/media/java/android/media/RouteSessionInfo.aidl
+++ b/media/java/android/media/tv/tuner/dvr/DvrCallback.java
@@ -14,6 +14,20 @@
* limitations under the License.
*/
-package android.media;
+package android.media.tv.tuner.dvr;
-parcelable RouteSessionInfo;
+/**
+ * Callback interface for receiving information from DVR interfaces.
+ *
+ * @hide
+ */
+public interface DvrCallback {
+ /**
+ * Invoked when record status changed.
+ */
+ void onRecordStatusChanged(int status);
+ /**
+ * Invoked when playback status changed.
+ */
+ void onPlaybackStatusChanged(int status);
+}
diff --git a/media/java/android/media/tv/tuner/dvr/DvrSettings.java b/media/java/android/media/tv/tuner/dvr/DvrSettings.java
new file mode 100644
index 0000000..46efd7a
--- /dev/null
+++ b/media/java/android/media/tv/tuner/dvr/DvrSettings.java
@@ -0,0 +1,179 @@
+/*
+ * 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.media.tv.tuner.dvr;
+
+import android.annotation.BytesLong;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerConstants.FilterStatus;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * DVR settings used to configure {@link Dvr}.
+ *
+ * @hide
+ */
+public class DvrSettings {
+
+ /** @hide */
+ @IntDef(prefix = "DATA_FORMAT_",
+ value = {DATA_FORMAT_TS, DATA_FORMAT_PES, DATA_FORMAT_ES, DATA_FORMAT_SHV_TLV})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DataFormat {}
+
+ /**
+ * Transport Stream.
+ */
+ public static final int DATA_FORMAT_TS = Constants.DataFormat.TS;
+ /**
+ * Packetized Elementary Stream.
+ */
+ public static final int DATA_FORMAT_PES = Constants.DataFormat.PES;
+ /**
+ * Elementary Stream.
+ */
+ public static final int DATA_FORMAT_ES = Constants.DataFormat.ES;
+ /**
+ * TLV (type-length-value) Stream for SHV
+ */
+ public static final int DATA_FORMAT_SHV_TLV = Constants.DataFormat.SHV_TLV;
+
+
+ /** @hide */
+ @IntDef(prefix = "TYPE_", value = {TYPE_RECORD, TYPE_PLAYBACK})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Type {}
+
+ /**
+ * DVR for recording.
+ */
+ public static final int TYPE_RECORD = Constants.DvrType.RECORD;
+ /**
+ * DVR for playback of recorded programs.
+ */
+ public static final int TYPE_PLAYBACK = Constants.DvrType.PLAYBACK;
+
+
+
+ private final int mStatusMask;
+ private final long mLowThreshold;
+ private final long mHighThreshold;
+ private final long mPacketSize;
+
+ @DataFormat
+ private final int mDataFormat;
+ @Type
+ private final int mType;
+
+ private DvrSettings(int statusMask, long lowThreshold, long highThreshold, long packetSize,
+ @DataFormat int dataFormat, @Type int type) {
+ mStatusMask = statusMask;
+ mLowThreshold = lowThreshold;
+ mHighThreshold = highThreshold;
+ mPacketSize = packetSize;
+ mDataFormat = dataFormat;
+ mType = type;
+ }
+
+ /**
+ * Creates a builder for {@link DvrSettings}.
+ */
+ @NonNull
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link DvrSettings}.
+ */
+ public static final class Builder {
+ private int mStatusMask;
+ private long mLowThreshold;
+ private long mHighThreshold;
+ private long mPacketSize;
+ @DataFormat
+ private int mDataFormat;
+ @Type
+ private int mType;
+
+ /**
+ * Sets status mask.
+ */
+ @NonNull
+ public Builder setStatusMask(@FilterStatus int statusMask) {
+ this.mStatusMask = statusMask;
+ return this;
+ }
+
+ /**
+ * Sets low threshold in bytes.
+ */
+ @NonNull
+ public Builder setLowThreshold(@BytesLong long lowThreshold) {
+ this.mLowThreshold = lowThreshold;
+ return this;
+ }
+
+ /**
+ * Sets high threshold in bytes.
+ */
+ @NonNull
+ public Builder setHighThreshold(@BytesLong long highThreshold) {
+ this.mHighThreshold = highThreshold;
+ return this;
+ }
+
+ /**
+ * Sets packet size in bytes.
+ */
+ @NonNull
+ public Builder setPacketSize(@BytesLong long packetSize) {
+ this.mPacketSize = packetSize;
+ return this;
+ }
+
+ /**
+ * Sets data format.
+ */
+ @NonNull
+ public Builder setDataFormat(@DataFormat int dataFormat) {
+ this.mDataFormat = dataFormat;
+ return this;
+ }
+
+ /**
+ * Sets settings type.
+ */
+ @NonNull
+ public Builder setType(@Type int type) {
+ this.mType = type;
+ return this;
+ }
+
+ /**
+ * Builds a {@link DvrSettings} object.
+ */
+ @NonNull
+ public DvrSettings build() {
+ return new DvrSettings(
+ mStatusMask, mLowThreshold, mHighThreshold, mPacketSize, mDataFormat, mType);
+ }
+ }
+}
diff --git a/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java
new file mode 100644
index 0000000..fcca6a1
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/AlpFilterConfiguration.java
@@ -0,0 +1,138 @@
+/*
+ * 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.media.tv.tuner.filter;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Filter configuration for a ALP filter.
+ * @hide
+ */
+public class AlpFilterConfiguration extends FilterConfiguration {
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "LENGTH_TYPE_", value =
+ {LENGTH_TYPE_UNDEFINED, LENGTH_TYPE_WITHOUT_ADDITIONAL_HEADER,
+ LENGTH_TYPE_WITH_ADDITIONAL_HEADER})
+ public @interface LengthType {}
+
+ /**
+ * Length type not defined.
+ */
+ public static final int LENGTH_TYPE_UNDEFINED = Constants.DemuxAlpLengthType.UNDEFINED;
+ /**
+ * Length does NOT include additional header.
+ */
+ public static final int LENGTH_TYPE_WITHOUT_ADDITIONAL_HEADER =
+ Constants.DemuxAlpLengthType.WITHOUT_ADDITIONAL_HEADER;
+ /**
+ * Length includes additional header.
+ */
+ public static final int LENGTH_TYPE_WITH_ADDITIONAL_HEADER =
+ Constants.DemuxAlpLengthType.WITH_ADDITIONAL_HEADER;
+
+
+ private final int mPacketType;
+ private final int mLengthType;
+
+ public AlpFilterConfiguration(Settings settings, int packetType, int lengthType) {
+ super(settings);
+ mPacketType = packetType;
+ mLengthType = lengthType;
+ }
+
+ @Override
+ public int getType() {
+ return FilterConfiguration.FILTER_TYPE_ALP;
+ }
+
+ /**
+ * Gets packet type.
+ */
+ @FilterConfiguration.PacketType
+ public int getPacketType() {
+ return mPacketType;
+ }
+ /**
+ * Gets length type.
+ */
+ @LengthType
+ public int getLengthType() {
+ return mLengthType;
+ }
+
+ /**
+ * Creates a builder for {@link AlpFilterConfiguration}.
+ *
+ * @param context the context of the caller.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link AlpFilterConfiguration}.
+ */
+ public static class Builder extends FilterConfiguration.Builder<Builder> {
+ private int mPacketType;
+ private int mLengthType;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets packet type.
+ */
+ @NonNull
+ public Builder setPacketType(@FilterConfiguration.PacketType int packetType) {
+ mPacketType = packetType;
+ return this;
+ }
+ /**
+ * Sets length type.
+ */
+ @NonNull
+ public Builder setLengthType(@LengthType int lengthType) {
+ mLengthType = lengthType;
+ return this;
+ }
+
+ /**
+ * Builds a {@link AlpFilterConfiguration} object.
+ */
+ @NonNull
+ public AlpFilterConfiguration build() {
+ return new AlpFilterConfiguration(mSettings, mPacketType, mLengthType);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
+ }
+}
diff --git a/media/java/android/media/tv/tuner/filter/AudioDescriptor.java b/media/java/android/media/tv/tuner/filter/AudioDescriptor.java
new file mode 100644
index 0000000..c88c07f
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/AudioDescriptor.java
@@ -0,0 +1,106 @@
+/*
+ * 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.media.tv.tuner.filter;
+
+/**
+ * Meta data from AD (Audio Descriptor) according to ETSI TS 101 154 V2.1.1.
+ *
+ * @hide
+ */
+public class AudioDescriptor {
+ private final byte mAdFade;
+ private final byte mAdPan;
+ private final char mVersionTextTag;
+ private final byte mAdGainCenter;
+ private final byte mAdGainFront;
+ private final byte mAdGainSurround;
+
+ // This constructor is used by JNI code only
+ private AudioDescriptor(byte adFade, byte adPan, char versionTextTag, byte adGainCenter,
+ byte adGainFront, byte adGainSurround) {
+ mAdFade = adFade;
+ mAdPan = adPan;
+ mVersionTextTag = versionTextTag;
+ mAdGainCenter = adGainCenter;
+ mAdGainFront = adGainFront;
+ mAdGainSurround = adGainSurround;
+ }
+
+ /**
+ * Gets AD fade byte.
+ *
+ * <p>Takes values between 0x00 (representing no fade of the main programme sound) and 0xFF
+ * (representing a full fade). Over the range 0x00 to 0xFE one lsb represents a step in
+ * attenuation of the programme sound of 0.3 dB giving a range of 76.2 dB. The fade value of
+ * 0xFF represents no programme sound at all (i.e. mute).
+ */
+ public byte getAdFade() {
+ return mAdFade;
+ }
+
+ /**
+ * Gets AD pan byte.
+ *
+ * <p>Takes values between 0x00 representing a central forward presentation of the audio
+ * description and 0xFF, each increment representing a 360/256 degree step clockwise looking
+ * down on the listener (i.e. just over 1.4 degrees).
+ */
+ public byte getAdPan() {
+ return mAdPan;
+ }
+
+ /**
+ * Gets AD version tag. A single ASCII character version indicates the version.
+ *
+ * <p>A single ASCII character version designator (here "1" indicates revision 1).
+ */
+ public char getVersionTextTag() {
+ return mVersionTextTag;
+ }
+
+ /**
+ * Gets AD gain byte center in dB.
+ *
+ * <p>Represents a signed value in dB. Takes values between 0x7F (representing +76.2 dB boost of
+ * the main programme center) and 0x80 (representing a full fade). Over the range 0x00 to 0x7F
+ * one lsb represents a step in boost of the programme center of 0.6 dB giving a maximum boost
+ * of +76.2 dB. Over the range 0x81 to 0x00 one lsb represents a step in attenuation of the
+ * programme center of 0.6 dB giving a maximum attenuation of -76.2 dB. The gain value of 0x80
+ * represents no main center level at all (i.e. mute).
+ */
+ public byte getAdGainCenter() {
+ return mAdGainCenter;
+ }
+
+ /**
+ * Gets AD gain byte front in dB.
+ *
+ * <p>Same as {@link #getAdGainCenter()}, but applied to left and right front channel.
+ */
+ public byte getAdGainFront() {
+ return mAdGainFront;
+ }
+
+ /**
+ * Gets AD gain byte surround in dB.
+ *
+ * <p>Same as {@link #getAdGainCenter()}, but applied to all surround channels
+ */
+ public byte getAdGainSurround() {
+ return mAdGainSurround;
+ }
+}
diff --git a/media/java/android/media/tv/tuner/filter/AvSettings.java b/media/java/android/media/tv/tuner/filter/AvSettings.java
new file mode 100644
index 0000000..940b5ae
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/AvSettings.java
@@ -0,0 +1,99 @@
+/*
+ * 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.media.tv.tuner.filter;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.media.tv.tuner.TunerConstants;
+import android.media.tv.tuner.TunerUtils;
+import android.media.tv.tuner.filter.FilterConfiguration.FilterType;
+
+/**
+ * Filter Settings for a Video and Audio.
+ *
+ * @hide
+ */
+public class AvSettings extends Settings {
+ private final boolean mIsPassthrough;
+
+ private AvSettings(int mainType, boolean isAudio, boolean isPassthrough) {
+ super(TunerUtils.getFilterSubtype(
+ mainType,
+ isAudio
+ ? TunerConstants.FILTER_SUBTYPE_AUDIO
+ : TunerConstants.FILTER_SUBTYPE_VIDEO));
+ mIsPassthrough = isPassthrough;
+ }
+
+ /**
+ * Checks whether it's passthrough.
+ */
+ public boolean isPassthrough() {
+ return mIsPassthrough;
+ }
+
+ /**
+ * Creates a builder for {@link AvSettings}.
+ *
+ * @param context the context of the caller.
+ * @param mainType the filter main type.
+ * @param isAudio {@code true} if it's audio settings; {@code false} if it's video settings.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(
+ @NonNull Context context, @FilterType int mainType, boolean isAudio) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder(mainType, isAudio);
+ }
+
+ /**
+ * Builder for {@link AvSettings}.
+ */
+ public static class Builder extends Settings.Builder<Builder> {
+ private final boolean mIsAudio;
+ private boolean mIsPassthrough;
+
+ private Builder(int mainType, boolean isAudio) {
+ super(mainType);
+ mIsAudio = isAudio;
+ }
+
+ /**
+ * Sets whether it's passthrough.
+ */
+ @NonNull
+ public Builder setPassthrough(boolean isPassthrough) {
+ mIsPassthrough = isPassthrough;
+ return this;
+ }
+
+ /**
+ * Builds a {@link AvSettings} object.
+ */
+ @NonNull
+ public AvSettings build() {
+ return new AvSettings(mMainType, mIsAudio, mIsPassthrough);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
+ }
+}
diff --git a/media/java/android/media/tv/tuner/filter/DownloadEvent.java b/media/java/android/media/tv/tuner/filter/DownloadEvent.java
new file mode 100644
index 0000000..591e4e5
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/DownloadEvent.java
@@ -0,0 +1,80 @@
+/*
+ * 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.media.tv.tuner.filter;
+
+import android.media.tv.tuner.Tuner.Filter;
+
+/**
+ * Filter event sent from {@link Filter} objects with download type.
+ *
+ * @hide
+ */
+public class DownloadEvent extends FilterEvent {
+ private final int mItemId;
+ private final int mMpuSequenceNumber;
+ private final int mItemFragmentIndex;
+ private final int mLastItemFragmentIndex;
+ private final int mDataLength;
+
+ // This constructor is used by JNI code only
+ private DownloadEvent(int itemId, int mpuSequenceNumber, int itemFragmentIndex,
+ int lastItemFragmentIndex, int dataLength) {
+ mItemId = itemId;
+ mMpuSequenceNumber = mpuSequenceNumber;
+ mItemFragmentIndex = itemFragmentIndex;
+ mLastItemFragmentIndex = lastItemFragmentIndex;
+ mDataLength = dataLength;
+ }
+
+ /**
+ * Gets item ID.
+ */
+ public int getItemId() {
+ return mItemId;
+ }
+
+ /**
+ * Gets MPU sequence number of filtered data.
+ */
+ public int getMpuSequenceNumber() {
+ return mMpuSequenceNumber;
+ }
+
+ /**
+ * Gets current index of the current item.
+ *
+ * An item can be stored in different fragments.
+ */
+ public int getItemFragmentIndex() {
+ return mItemFragmentIndex;
+ }
+
+ /**
+ * Gets last index of the current item.
+ */
+ public int getLastItemFragmentIndex() {
+ return mLastItemFragmentIndex;
+ }
+
+ /**
+ * Gets data size in bytes of filtered data.
+ */
+ public int getDataLength() {
+ return mDataLength;
+ }
+}
+
diff --git a/media/java/android/media/tv/tuner/filter/DownloadSettings.java b/media/java/android/media/tv/tuner/filter/DownloadSettings.java
new file mode 100644
index 0000000..e3e1df0
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/DownloadSettings.java
@@ -0,0 +1,90 @@
+/*
+ * 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.media.tv.tuner.filter;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.media.tv.tuner.TunerConstants;
+import android.media.tv.tuner.TunerUtils;
+import android.media.tv.tuner.filter.FilterConfiguration.FilterType;
+
+/**
+ * Filter Settings for a Download.
+ * @hide
+ */
+public class DownloadSettings extends Settings {
+ private final int mDownloadId;
+
+ private DownloadSettings(int mainType, int downloadId) {
+ super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_DOWNLOAD));
+ mDownloadId = downloadId;
+ }
+
+ /**
+ * Gets download ID.
+ */
+ public int getDownloadId() {
+ return mDownloadId;
+ }
+
+ /**
+ * Creates a builder for {@link DownloadSettings}.
+ *
+ * @param context the context of the caller.
+ * @param mainType the filter main type.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context, @FilterType int mainType) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder(mainType);
+ }
+
+ /**
+ * Builder for {@link DownloadSettings}.
+ */
+ public static class Builder extends Settings.Builder<Builder> {
+ private int mDownloadId;
+
+ private Builder(int mainType) {
+ super(mainType);
+ }
+
+ /**
+ * Sets download ID.
+ */
+ @NonNull
+ public Builder setDownloadId(int downloadId) {
+ mDownloadId = downloadId;
+ return this;
+ }
+
+ /**
+ * Builds a {@link DownloadSettings} object.
+ */
+ @NonNull
+ public DownloadSettings build() {
+ return new DownloadSettings(mMainType, mDownloadId);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
+ }
+}
diff --git a/media/java/android/media/tv/tuner/filter/Filter.java b/media/java/android/media/tv/tuner/filter/Filter.java
new file mode 100644
index 0000000..804c0c5
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/Filter.java
@@ -0,0 +1,139 @@
+/*
+ * 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.media.tv.tuner.filter;
+
+import android.annotation.BytesLong;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.media.tv.tuner.Tuner.FilterCallback;
+
+/**
+ * Tuner data filter.
+ *
+ * <p>This class is used to filter wanted data according to the filter's configuration.
+ *
+ * @hide
+ */
+public class Filter implements AutoCloseable {
+ private long mNativeContext;
+ private FilterCallback mCallback;
+ private final int mId;
+
+ private native int nativeConfigureFilter(
+ int type, int subType, FilterConfiguration settings);
+ private native int nativeGetId();
+ private native int nativeSetDataSource(Filter source);
+ private native int nativeStartFilter();
+ private native int nativeStopFilter();
+ private native int nativeFlushFilter();
+ private native int nativeRead(byte[] buffer, long offset, long size);
+ private native int nativeClose();
+
+ private Filter(int id) {
+ mId = id;
+ }
+
+ private void onFilterStatus(int status) {
+ }
+
+ /**
+ * Configures the filter.
+ *
+ * @param config the configuration of the filter.
+ * @return result status of the operation.
+ */
+ public int configure(@NonNull FilterConfiguration config) {
+ int subType = -1;
+ Settings s = config.getSettings();
+ if (s != null) {
+ subType = s.getType();
+ }
+ return nativeConfigureFilter(config.getType(), subType, config);
+ }
+
+ /**
+ * Gets the filter Id.
+ */
+ public int getId() {
+ return nativeGetId();
+ }
+
+ /**
+ * Sets the filter's data source.
+ *
+ * A filter uses demux as data source by default. If the data was packetized
+ * by multiple protocols, multiple filters may need to work together to
+ * extract all protocols' header. Then a filter's data source can be output
+ * from another filter.
+ *
+ * @param source the filter instance which provides data input. Switch to
+ * use demux as data source if the filter instance is NULL.
+ * @return result status of the operation.
+ */
+ public int setDataSource(@Nullable Filter source) {
+ return nativeSetDataSource(source);
+ }
+
+ /**
+ * Starts filtering data.
+ *
+ * @return result status of the operation.
+ */
+ public int start() {
+ return nativeStartFilter();
+ }
+
+
+ /**
+ * Stops filtering data.
+ *
+ * @return result status of the operation.
+ */
+ public int stop() {
+ return nativeStopFilter();
+ }
+
+ /**
+ * Flushes the filter. Data in filter buffer is cleared.
+ *
+ * @return result status of the operation.
+ */
+ public int flush() {
+ return nativeFlushFilter();
+ }
+
+ /**
+ * Copies filtered data from filter buffer to the given byte array.
+ *
+ * @param buffer the buffer to store the filtered data.
+ * @param offset the index of the first byte in {@code buffer} to write.
+ * @param size the maximum number of bytes to read.
+ * @return the number of bytes read.
+ */
+ public int read(@NonNull byte[] buffer, @BytesLong long offset, @BytesLong long size) {
+ size = Math.min(size, buffer.length - offset);
+ return nativeRead(buffer, offset, size);
+ }
+
+ /**
+ * Releases the Filter instance.
+ */
+ @Override
+ public void close() {
+ nativeClose();
+ }
+}
diff --git a/media/java/android/media/tv/tuner/filter/FilterCallback.java b/media/java/android/media/tv/tuner/filter/FilterCallback.java
new file mode 100644
index 0000000..888adc5
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/FilterCallback.java
@@ -0,0 +1,42 @@
+/*
+ * 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.media.tv.tuner.filter;
+
+import android.annotation.NonNull;
+import android.media.tv.tuner.TunerConstants.FilterStatus;
+
+/**
+ * Callback interface for receiving information from the corresponding filters.
+ *
+ * @hide
+ */
+public interface FilterCallback {
+ /**
+ * Invoked when there are filter events.
+ *
+ * @param filter the corresponding filter which sent the events.
+ * @param events the filter events sent from the filter.
+ */
+ void onFilterEvent(@NonNull Filter filter, @NonNull FilterEvent[] events);
+ /**
+ * Invoked when filter status changed.
+ *
+ * @param filter the corresponding filter whose status is changed.
+ * @param status the new status of the filter.
+ */
+ void onFilterStatusChanged(@NonNull Filter filter, @FilterStatus int status);
+}
diff --git a/media/java/android/media/tv/tuner/filter/FilterConfiguration.java b/media/java/android/media/tv/tuner/filter/FilterConfiguration.java
new file mode 100644
index 0000000..68c722f
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/FilterConfiguration.java
@@ -0,0 +1,128 @@
+/*
+ * 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.media.tv.tuner.filter;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.hardware.tv.tuner.V1_0.Constants;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Filter configuration used to configure filters.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class FilterConfiguration {
+
+ /** @hide */
+ @IntDef(prefix = "FILTER_TYPE_", value =
+ {FILTER_TYPE_TS, FILTER_TYPE_MMTP, FILTER_TYPE_IP, FILTER_TYPE_TLV, FILTER_TYPE_ALP})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FilterType {}
+
+ /**
+ * TS filter type.
+ */
+ public static final int FILTER_TYPE_TS = Constants.DemuxFilterMainType.TS;
+ /**
+ * MMTP filter type.
+ */
+ public static final int FILTER_TYPE_MMTP = Constants.DemuxFilterMainType.MMTP;
+ /**
+ * IP filter type.
+ */
+ public static final int FILTER_TYPE_IP = Constants.DemuxFilterMainType.IP;
+ /**
+ * TLV filter type.
+ */
+ public static final int FILTER_TYPE_TLV = Constants.DemuxFilterMainType.TLV;
+ /**
+ * ALP filter type.
+ */
+ public static final int FILTER_TYPE_ALP = Constants.DemuxFilterMainType.ALP;
+
+
+ /** @hide */
+ @IntDef(prefix = "PACKET_TYPE_", value =
+ {PACKET_TYPE_IPV4, PACKET_TYPE_COMPRESSED, PACKET_TYPE_SIGNALING})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PacketType {}
+
+ /**
+ * IP v4 packet type.
+ * @hide
+ */
+ public static final int PACKET_TYPE_IPV4 = 0;
+ /**
+ * Compressed packet type.
+ * @hide
+ */
+ public static final int PACKET_TYPE_COMPRESSED = 2;
+ /**
+ * Signaling packet type.
+ * @hide
+ */
+ public static final int PACKET_TYPE_SIGNALING = 4;
+
+
+ @Nullable
+ /* package */ final Settings mSettings;
+
+ /* package */ FilterConfiguration(Settings settings) {
+ mSettings = settings;
+ }
+
+ /**
+ * Gets filter configuration type.
+ * @hide
+ */
+ @FilterType
+ public abstract int getType();
+
+ /** @hide */
+ @Nullable
+ public Settings getSettings() {
+ return mSettings;
+ }
+
+ /**
+ * Builder for {@link FilterConfiguration}.
+ *
+ * @param <T> The subclass to be built.
+ * @hide
+ */
+ public abstract static class Builder<T extends Builder<T>> {
+ /* package */ Settings mSettings;
+
+ /* package */ Builder() {
+ }
+
+ /**
+ * Sets filter settings.
+ */
+ @Nullable
+ public T setFrequency(Settings settings) {
+ mSettings = settings;
+ return self();
+ }
+ /* package */ abstract T self();
+ }
+}
diff --git a/media/java/android/media/RouteSessionInfo.aidl b/media/java/android/media/tv/tuner/filter/FilterEvent.java
similarity index 74%
copy from media/java/android/media/RouteSessionInfo.aidl
copy to media/java/android/media/tv/tuner/filter/FilterEvent.java
index fb5d836..56a77d4 100644
--- a/media/java/android/media/RouteSessionInfo.aidl
+++ b/media/java/android/media/tv/tuner/filter/FilterEvent.java
@@ -14,6 +14,15 @@
* limitations under the License.
*/
-package android.media;
+package android.media.tv.tuner.filter;
-parcelable RouteSessionInfo;
+import android.annotation.SystemApi;
+
+/**
+ * An entity class that is passed to the filter callbacks.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class FilterEvent {
+}
diff --git a/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java
new file mode 100644
index 0000000..98edf10
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/IpFilterConfiguration.java
@@ -0,0 +1,167 @@
+/*
+ * 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.media.tv.tuner.filter;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.Size;
+import android.content.Context;
+import android.media.tv.tuner.TunerUtils;
+
+/**
+ * Filter configuration for a IP filter.
+ * @hide
+ */
+public class IpFilterConfiguration extends FilterConfiguration {
+ private final byte[] mSrcIpAddress;
+ private final byte[] mDstIpAddress;
+ private final int mSrcPort;
+ private final int mDstPort;
+ private final boolean mPassthrough;
+
+ public IpFilterConfiguration(Settings settings, byte[] srcAddr, byte[] dstAddr, int srcPort,
+ int dstPort, boolean passthrough) {
+ super(settings);
+ mSrcIpAddress = srcAddr;
+ mDstIpAddress = dstAddr;
+ mSrcPort = srcPort;
+ mDstPort = dstPort;
+ mPassthrough = passthrough;
+ }
+
+ @Override
+ public int getType() {
+ return FilterConfiguration.FILTER_TYPE_IP;
+ }
+
+ /**
+ * Gets source IP address.
+ */
+ @Size(min = 4, max = 16)
+ public byte[] getSrcIpAddress() {
+ return mSrcIpAddress;
+ }
+ /**
+ * Gets destination IP address.
+ */
+ @Size(min = 4, max = 16)
+ public byte[] getDstIpAddress() {
+ return mDstIpAddress;
+ }
+ /**
+ * Gets source port.
+ */
+ public int getSrcPort() {
+ return mSrcPort;
+ }
+ /**
+ * Gets destination port.
+ */
+ public int getDstPort() {
+ return mDstPort;
+ }
+ /**
+ * Checks whether the filter is passthrough.
+ *
+ * @return {@code true} if the data from IP subtype go to next filter directly;
+ * {@code false} otherwise.
+ */
+ public boolean isPassthrough() {
+ return mPassthrough;
+ }
+
+ /**
+ * Creates a builder for {@link IpFilterConfiguration}.
+ *
+ * @param context the context of the caller.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link IpFilterConfiguration}.
+ */
+ public static class Builder extends FilterConfiguration.Builder<Builder> {
+ private byte[] mSrcIpAddress;
+ private byte[] mDstIpAddress;
+ private int mSrcPort;
+ private int mDstPort;
+ private boolean mPassthrough;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets source IP address.
+ */
+ @NonNull
+ public Builder setSrcIpAddress(byte[] srcIpAddress) {
+ mSrcIpAddress = srcIpAddress;
+ return this;
+ }
+ /**
+ * Sets destination IP address.
+ */
+ @NonNull
+ public Builder setDstIpAddress(byte[] dstIpAddress) {
+ mDstIpAddress = dstIpAddress;
+ return this;
+ }
+ /**
+ * Sets source port.
+ */
+ @NonNull
+ public Builder setSrcPort(int srcPort) {
+ mSrcPort = srcPort;
+ return this;
+ }
+ /**
+ * Sets destination port.
+ */
+ @NonNull
+ public Builder setDstPort(int dstPort) {
+ mDstPort = dstPort;
+ return this;
+ }
+ /**
+ * Sets passthrough.
+ */
+ @NonNull
+ public Builder setPassthrough(boolean passthrough) {
+ mPassthrough = passthrough;
+ return this;
+ }
+
+ /**
+ * Builds a {@link IpFilterConfiguration} object.
+ */
+ @NonNull
+ public IpFilterConfiguration build() {
+ return new IpFilterConfiguration(
+ mSettings, mSrcIpAddress, mDstIpAddress, mSrcPort, mDstPort, mPassthrough);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
+ }
+}
diff --git a/media/java/android/media/tv/tuner/filter/IpPayloadEvent.java b/media/java/android/media/tv/tuner/filter/IpPayloadEvent.java
new file mode 100644
index 0000000..09489ed
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/IpPayloadEvent.java
@@ -0,0 +1,40 @@
+/*
+ * 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.media.tv.tuner.filter;
+
+import android.media.tv.tuner.Tuner.Filter;
+
+/**
+ * Filter event sent from {@link Filter} objects with IP payload type.
+ *
+ * @hide
+ */
+public class IpPayloadEvent extends FilterEvent {
+ private final int mDataLength;
+
+ // This constructor is used by JNI code only
+ private IpPayloadEvent(int dataLength) {
+ mDataLength = dataLength;
+ }
+
+ /**
+ * Gets data size in bytes of filtered data.
+ */
+ public int getDataLength() {
+ return mDataLength;
+ }
+}
diff --git a/media/java/android/media/tv/tuner/filter/MediaEvent.java b/media/java/android/media/tv/tuner/filter/MediaEvent.java
new file mode 100644
index 0000000..37f94ae
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/MediaEvent.java
@@ -0,0 +1,126 @@
+/*
+ * 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.media.tv.tuner.filter;
+
+import android.annotation.Nullable;
+import android.media.tv.tuner.Tuner.Filter;
+
+/**
+ * Filter event sent from {@link Filter} objects with media type.
+ *
+ * @hide
+ */
+public class MediaEvent extends FilterEvent{
+ private final int mStreamId;
+ private final boolean mIsPtsPresent;
+ private final long mPts;
+ private final int mDataLength;
+ private final Object mLinearBuffer;
+ private final boolean mIsSecureMemory;
+ private final int mMpuSequenceNumber;
+ private final boolean mIsPrivateData;
+ private final AudioDescriptor mExtraMetaData;
+
+ // This constructor is used by JNI code only
+ private MediaEvent(int streamId, boolean isPtsPresent, long pts, int dataLength, Object buffer,
+ boolean isSecureMemory, int mpuSequenceNumber, boolean isPrivateData,
+ AudioDescriptor extraMetaData) {
+ mStreamId = streamId;
+ mIsPtsPresent = isPtsPresent;
+ mPts = pts;
+ mDataLength = dataLength;
+ mLinearBuffer = buffer;
+ mIsSecureMemory = isSecureMemory;
+ mMpuSequenceNumber = mpuSequenceNumber;
+ mIsPrivateData = isPrivateData;
+ mExtraMetaData = extraMetaData;
+ }
+
+ /**
+ * Gets stream ID.
+ */
+ public int getStreamId() {
+ return mStreamId;
+ }
+
+ /**
+ * Returns whether PTS is present.
+ *
+ * @return {@code true} if PTS is present in PES header; {@code false} otherwise.
+ */
+ public boolean getIsPtsPresent() {
+ return mIsPtsPresent;
+ }
+
+ /**
+ * Gets PTS (Presentation Time Stamp) for audio or video frame.
+ */
+ public long getPts() {
+ return mPts;
+ }
+
+ /**
+ * Gets data size in bytes of audio or video frame.
+ */
+ public int getDataLength() {
+ return mDataLength;
+ }
+
+ /**
+ * Gets a linear buffer associated to the memory where audio or video data stays.
+ * TODO: use LinearBuffer when it's ready.
+ *
+ * @hide
+ */
+ public Object getLinearBuffer() {
+ return mLinearBuffer;
+ }
+
+ /**
+ * Returns whether the data is secure.
+ *
+ * @return {@code true} if the data is in secure area, and isn't mappable;
+ * {@code false} otherwise.
+ */
+ public boolean getIsSecureMemory() {
+ return mIsSecureMemory;
+ }
+
+ /**
+ * Gets MPU sequence number of filtered data.
+ */
+ public int getMpuSequenceNumber() {
+ return mMpuSequenceNumber;
+ }
+
+ /**
+ * Returns whether the data is private.
+ *
+ * @return {@code true} if the data is in private; {@code false} otherwise.
+ */
+ public boolean getIsPrivateData() {
+ return mIsPrivateData;
+ }
+
+ /**
+ * Gets audio extra metadata.
+ */
+ @Nullable
+ public AudioDescriptor getExtraMetaData() {
+ return mExtraMetaData;
+ }
+}
diff --git a/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java
new file mode 100644
index 0000000..83246e5
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/MmtpFilterConfiguration.java
@@ -0,0 +1,93 @@
+/*
+ * 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.media.tv.tuner.filter;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.media.tv.tuner.TunerUtils;
+
+/**
+ * Filter configuration for a MMTP filter.
+ * @hide
+ */
+public class MmtpFilterConfiguration extends FilterConfiguration {
+ private final int mMmtpPid;
+
+ public MmtpFilterConfiguration(Settings settings, int mmtpPid) {
+ super(settings);
+ mMmtpPid = mmtpPid;
+ }
+
+ @Override
+ public int getType() {
+ return FilterConfiguration.FILTER_TYPE_MMTP;
+ }
+
+ /**
+ * Gets MMPT PID.
+ *
+ * <p>Packet ID is used to specify packets in MMTP.
+ */
+ public int getMmtpPid() {
+ return mMmtpPid;
+ }
+
+ /**
+ * Creates a builder for {@link IpFilterConfiguration}.
+ *
+ * @param context the context of the caller.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link IpFilterConfiguration}.
+ */
+ public static class Builder extends FilterConfiguration.Builder<Builder> {
+ private int mMmtpPid;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets MMPT PID.
+ */
+ @NonNull
+ public Builder setMmtpPid(int mmtpPid) {
+ mMmtpPid = mmtpPid;
+ return this;
+ }
+
+ /**
+ * Builds a {@link IpFilterConfiguration} object.
+ */
+ @NonNull
+ public MmtpFilterConfiguration build() {
+ return new MmtpFilterConfiguration(mSettings, mMmtpPid);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
+ }
+}
diff --git a/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java b/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java
new file mode 100644
index 0000000..7f37994
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/MmtpRecordEvent.java
@@ -0,0 +1,49 @@
+/*
+ * 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.media.tv.tuner.filter;
+
+import android.media.tv.tuner.Tuner.Filter;
+
+/**
+ * Filter event sent from {@link Filter} objects with MMTP type.
+ *
+ * @hide
+ */
+public class MmtpRecordEvent extends FilterEvent {
+ private final int mScHevcIndexMask;
+ private final long mByteNumber;
+
+ // This constructor is used by JNI code only
+ private MmtpRecordEvent(int scHevcIndexMask, long byteNumber) {
+ mScHevcIndexMask = scHevcIndexMask;
+ mByteNumber = byteNumber;
+ }
+
+ /**
+ * Gets indexes which can be tagged by NAL unit group in HEVC according to ISO/IEC 23008-2.
+ */
+ public int getScHevcIndexMask() {
+ return mScHevcIndexMask;
+ }
+
+ /**
+ * Gets the byte number from beginning of the filter's output.
+ */
+ public long getByteNumber() {
+ return mByteNumber;
+ }
+}
diff --git a/media/java/android/media/tv/tuner/filter/PesEvent.java b/media/java/android/media/tv/tuner/filter/PesEvent.java
new file mode 100644
index 0000000..60251bf
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/PesEvent.java
@@ -0,0 +1,58 @@
+/*
+ * 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.media.tv.tuner.filter;
+
+import android.media.tv.tuner.Tuner.Filter;
+
+/**
+ * Filter event sent from {@link Filter} objects with PES type.
+ *
+ * @hide
+ */
+public class PesEvent extends FilterEvent {
+ private final int mStreamId;
+ private final int mDataLength;
+ private final int mMpuSequenceNumber;
+
+ // This constructor is used by JNI code only
+ private PesEvent(int streamId, int dataLength, int mpuSequenceNumber) {
+ mStreamId = streamId;
+ mDataLength = dataLength;
+ mMpuSequenceNumber = mpuSequenceNumber;
+ }
+
+ /**
+ * Gets stream ID.
+ */
+ public int getStreamId() {
+ return mStreamId;
+ }
+
+ /**
+ * Gets data size in bytes of filtered data.
+ */
+ public int getDataLength() {
+ return mDataLength;
+ }
+
+ /**
+ * Gets MPU sequence number of filtered data.
+ */
+ public int getMpuSequenceNumber() {
+ return mMpuSequenceNumber;
+ }
+}
diff --git a/media/java/android/media/tv/tuner/filter/PesSettings.java b/media/java/android/media/tv/tuner/filter/PesSettings.java
new file mode 100644
index 0000000..bfa1f8c
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/PesSettings.java
@@ -0,0 +1,116 @@
+/*
+ * 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.media.tv.tuner.filter;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.media.tv.tuner.TunerConstants;
+import android.media.tv.tuner.TunerUtils;
+import android.media.tv.tuner.filter.FilterConfiguration.FilterType;
+
+/**
+ * Filter Settings for a PES Data.
+ *
+ * @hide
+ */
+@SystemApi
+public class PesSettings extends Settings {
+ private final int mStreamId;
+ private final boolean mIsRaw;
+
+ private PesSettings(@FilterType int mainType, int streamId, boolean isRaw) {
+ super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_PES));
+ mStreamId = streamId;
+ mIsRaw = isRaw;
+ }
+
+ /**
+ * Gets stream ID.
+ */
+ public int getStreamId() {
+ return mStreamId;
+ }
+
+ /**
+ * Returns whether the data is raw.
+ *
+ * @return {@code true} if the data is raw. Filter sends onFilterStatus callback
+ * instead of onFilterEvent for raw data. {@code false} otherwise.
+ */
+ public boolean isRaw() {
+ return mIsRaw;
+ }
+
+ /**
+ * Creates a builder for {@link PesSettings}.
+ *
+ * @param mainType the filter main type of the settings.
+ * @param context the context of the caller.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context, @FilterType int mainType) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder(mainType);
+ }
+
+ /**
+ * Builder for {@link PesSettings}.
+ */
+ public static class Builder {
+ private final int mMainType;
+ private int mStreamId;
+ private boolean mIsRaw;
+
+ private Builder(int mainType) {
+ mMainType = mainType;
+ }
+
+ /**
+ * Sets stream ID.
+ *
+ * @param streamId the stream ID.
+ */
+ @NonNull
+ public Builder setStreamId(int streamId) {
+ mStreamId = streamId;
+ return this;
+ }
+
+ /**
+ * Sets whether the data is raw.
+ *
+ * @param isRaw {@code true} if the data is raw. Filter sends onFilterStatus callback
+ * instead of onFilterEvent for raw data. {@code false} otherwise.
+ */
+ @NonNull
+ public Builder setRaw(boolean isRaw) {
+ mIsRaw = isRaw;
+ return this;
+ }
+
+ /**
+ * Builds a {@link PesSettings} object.
+ */
+ @NonNull
+ public PesSettings build() {
+ return new PesSettings(mMainType, mStreamId, mIsRaw);
+ }
+ }
+}
diff --git a/media/java/android/media/tv/tuner/filter/RecordSettings.java b/media/java/android/media/tv/tuner/filter/RecordSettings.java
new file mode 100644
index 0000000..4833709
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/RecordSettings.java
@@ -0,0 +1,246 @@
+/*
+ * 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.media.tv.tuner.filter;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerConstants;
+import android.media.tv.tuner.TunerConstants.ScIndexType;
+import android.media.tv.tuner.TunerUtils;
+import android.media.tv.tuner.filter.FilterConfiguration.FilterType;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * The Settings for the record in DVR.
+ * @hide
+ */
+public class RecordSettings extends Settings {
+ /**
+ * Indexes can be tagged through TS (Transport Stream) header.
+ *
+ * @hide
+ */
+ @IntDef(flag = true,
+ prefix = "TS_INDEX_",
+ value = {TS_INDEX_FIRST_PACKET, TS_INDEX_PAYLOAD_UNIT_START_INDICATOR,
+ TS_INDEX_CHANGE_TO_NOT_SCRAMBLED, TS_INDEX_CHANGE_TO_EVEN_SCRAMBLED,
+ TS_INDEX_CHANGE_TO_ODD_SCRAMBLED, TS_INDEX_DISCONTINUITY_INDICATOR,
+ TS_INDEX_RANDOM_ACCESS_INDICATOR, TS_INDEX_PRIORITY_INDICATOR,
+ TS_INDEX_PCR_FLAG, TS_INDEX_OPCR_FLAG, TS_INDEX_SPLICING_POINT_FLAG,
+ TS_INDEX_PRIVATE_DATA, TS_INDEX_ADAPTATION_EXTENSION_FLAG})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TsIndexMask {}
+
+ /**
+ * TS index FIRST_PACKET.
+ * @hide
+ */
+ public static final int TS_INDEX_FIRST_PACKET = Constants.DemuxTsIndex.FIRST_PACKET;
+ /**
+ * TS index PAYLOAD_UNIT_START_INDICATOR.
+ * @hide
+ */
+ public static final int TS_INDEX_PAYLOAD_UNIT_START_INDICATOR =
+ Constants.DemuxTsIndex.PAYLOAD_UNIT_START_INDICATOR;
+ /**
+ * TS index CHANGE_TO_NOT_SCRAMBLED.
+ * @hide
+ */
+ public static final int TS_INDEX_CHANGE_TO_NOT_SCRAMBLED =
+ Constants.DemuxTsIndex.CHANGE_TO_NOT_SCRAMBLED;
+ /**
+ * TS index CHANGE_TO_EVEN_SCRAMBLED.
+ * @hide
+ */
+ public static final int TS_INDEX_CHANGE_TO_EVEN_SCRAMBLED =
+ Constants.DemuxTsIndex.CHANGE_TO_EVEN_SCRAMBLED;
+ /**
+ * TS index CHANGE_TO_ODD_SCRAMBLED.
+ * @hide
+ */
+ public static final int TS_INDEX_CHANGE_TO_ODD_SCRAMBLED =
+ Constants.DemuxTsIndex.CHANGE_TO_ODD_SCRAMBLED;
+ /**
+ * TS index DISCONTINUITY_INDICATOR.
+ * @hide
+ */
+ public static final int TS_INDEX_DISCONTINUITY_INDICATOR =
+ Constants.DemuxTsIndex.DISCONTINUITY_INDICATOR;
+ /**
+ * TS index RANDOM_ACCESS_INDICATOR.
+ * @hide
+ */
+ public static final int TS_INDEX_RANDOM_ACCESS_INDICATOR =
+ Constants.DemuxTsIndex.RANDOM_ACCESS_INDICATOR;
+ /**
+ * TS index PRIORITY_INDICATOR.
+ * @hide
+ */
+ public static final int TS_INDEX_PRIORITY_INDICATOR = Constants.DemuxTsIndex.PRIORITY_INDICATOR;
+ /**
+ * TS index PCR_FLAG.
+ * @hide
+ */
+ public static final int TS_INDEX_PCR_FLAG = Constants.DemuxTsIndex.PCR_FLAG;
+ /**
+ * TS index OPCR_FLAG.
+ * @hide
+ */
+ public static final int TS_INDEX_OPCR_FLAG = Constants.DemuxTsIndex.OPCR_FLAG;
+ /**
+ * TS index SPLICING_POINT_FLAG.
+ * @hide
+ */
+ public static final int TS_INDEX_SPLICING_POINT_FLAG =
+ Constants.DemuxTsIndex.SPLICING_POINT_FLAG;
+ /**
+ * TS index PRIVATE_DATA.
+ * @hide
+ */
+ public static final int TS_INDEX_PRIVATE_DATA = Constants.DemuxTsIndex.PRIVATE_DATA;
+ /**
+ * TS index ADAPTATION_EXTENSION_FLAG.
+ * @hide
+ */
+ public static final int TS_INDEX_ADAPTATION_EXTENSION_FLAG =
+ Constants.DemuxTsIndex.ADAPTATION_EXTENSION_FLAG;
+ /**
+ * @hide
+ */
+ @IntDef(flag = true,
+ prefix = "SC_",
+ value = {
+ TunerConstants.SC_INDEX_I_FRAME,
+ TunerConstants.SC_INDEX_P_FRAME,
+ TunerConstants.SC_INDEX_B_FRAME,
+ TunerConstants.SC_INDEX_SEQUENCE,
+ TunerConstants.SC_HEVC_INDEX_SPS,
+ TunerConstants.SC_HEVC_INDEX_AUD,
+ TunerConstants.SC_HEVC_INDEX_SLICE_CE_BLA_W_LP,
+ TunerConstants.SC_HEVC_INDEX_SLICE_BLA_W_RADL,
+ TunerConstants.SC_HEVC_INDEX_SLICE_BLA_N_LP,
+ TunerConstants.SC_HEVC_INDEX_SLICE_IDR_W_RADL,
+ TunerConstants.SC_HEVC_INDEX_SLICE_IDR_N_LP,
+ TunerConstants.SC_HEVC_INDEX_SLICE_TRAIL_CRA,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface ScIndexMask {}
+
+
+ private final int mTsIndexMask;
+ private final int mScIndexType;
+ private final int mScIndexMask;
+
+ private RecordSettings(int mainType, int tsIndexType, int scIndexType, int scIndexMask) {
+ super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_RECORD));
+ mTsIndexMask = tsIndexType;
+ mScIndexType = scIndexType;
+ mScIndexMask = scIndexMask;
+ }
+
+ /**
+ * Gets TS index mask.
+ */
+ @TsIndexMask
+ public int getTsIndexMask() {
+ return mTsIndexMask;
+ }
+ /**
+ * Gets Start Code index type.
+ */
+ @ScIndexType
+ public int getScIndexType() {
+ return mScIndexType;
+ }
+ /**
+ * Gets Start Code index mask.
+ */
+ @ScIndexMask
+ public int getScIndexMask() {
+ return mScIndexMask;
+ }
+
+ /**
+ * Creates a builder for {@link RecordSettings}.
+ *
+ * @param context the context of the caller.
+ * @param mainType the filter main type.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context, @FilterType int mainType) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder(mainType);
+ }
+
+ /**
+ * Builder for {@link RecordSettings}.
+ */
+ public static class Builder extends Settings.Builder<Builder> {
+ private int mTsIndexMask;
+ private int mScIndexType;
+ private int mScIndexMask;
+
+ private Builder(int mainType) {
+ super(mainType);
+ }
+
+ /**
+ * Sets TS index mask.
+ */
+ @NonNull
+ public Builder setTsIndexMask(@TsIndexMask int indexMask) {
+ mTsIndexMask = indexMask;
+ return this;
+ }
+ /**
+ * Sets index type.
+ */
+ @NonNull
+ public Builder setScIndexType(@ScIndexType int indexType) {
+ mScIndexType = indexType;
+ return this;
+ }
+ /**
+ * Sets Start Code index mask.
+ */
+ @NonNull
+ public Builder setScIndexMask(@ScIndexMask int indexMask) {
+ mScIndexMask = indexMask;
+ return this;
+ }
+
+ /**
+ * Builds a {@link RecordSettings} object.
+ */
+ @NonNull
+ public RecordSettings build() {
+ return new RecordSettings(mMainType, mTsIndexMask, mScIndexType, mScIndexMask);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
+ }
+
+}
diff --git a/media/java/android/media/tv/tuner/filter/SectionEvent.java b/media/java/android/media/tv/tuner/filter/SectionEvent.java
new file mode 100644
index 0000000..e211dda
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/SectionEvent.java
@@ -0,0 +1,69 @@
+/*
+ * 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.media.tv.tuner.filter;
+
+import android.annotation.SystemApi;
+import android.media.tv.tuner.Tuner.Filter;
+
+/**
+ * Filter event sent from {@link Filter} objects with section type.
+ *
+ * @hide
+ */
+@SystemApi
+public class SectionEvent extends FilterEvent {
+ private final int mTableId;
+ private final int mVersion;
+ private final int mSectionNum;
+ private final int mDataLength;
+
+ // This constructor is used by JNI code only
+ private SectionEvent(int tableId, int version, int sectionNum, int dataLength) {
+ mTableId = tableId;
+ mVersion = version;
+ mSectionNum = sectionNum;
+ mDataLength = dataLength;
+ }
+
+ /**
+ * Gets table ID of filtered data.
+ */
+ public int getTableId() {
+ return mTableId;
+ }
+
+ /**
+ * Gets version number of filtered data.
+ */
+ public int getVersion() {
+ return mVersion;
+ }
+
+ /**
+ * Gets section number of filtered data.
+ */
+ public int getSectionNumber() {
+ return mSectionNum;
+ }
+
+ /**
+ * Gets data size in bytes of filtered data.
+ */
+ public int getDataLength() {
+ return mDataLength;
+ }
+}
diff --git a/media/java/android/media/RouteSessionInfo.aidl b/media/java/android/media/tv/tuner/filter/SectionSettings.java
similarity index 60%
copy from media/java/android/media/RouteSessionInfo.aidl
copy to media/java/android/media/tv/tuner/filter/SectionSettings.java
index fb5d836..36e3d7c 100644
--- a/media/java/android/media/RouteSessionInfo.aidl
+++ b/media/java/android/media/tv/tuner/filter/SectionSettings.java
@@ -14,6 +14,18 @@
* limitations under the License.
*/
-package android.media;
+package android.media.tv.tuner.filter;
-parcelable RouteSessionInfo;
+import android.media.tv.tuner.TunerConstants;
+import android.media.tv.tuner.TunerUtils;
+
+/**
+ * Filter Settings for Section data according to ISO/IEC 13818-1.
+ * @hide
+ */
+public class SectionSettings extends Settings {
+
+ SectionSettings(int mainType) {
+ super(TunerUtils.getFilterSubtype(mainType, TunerConstants.FILTER_SUBTYPE_SECTION));
+ }
+}
diff --git a/media/java/android/media/tv/tuner/filter/SectionSettingsWithSectionBits.java b/media/java/android/media/tv/tuner/filter/SectionSettingsWithSectionBits.java
new file mode 100644
index 0000000..0fa982e
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/SectionSettingsWithSectionBits.java
@@ -0,0 +1,131 @@
+/*
+ * 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.media.tv.tuner.filter;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.media.tv.tuner.TunerUtils;
+import android.media.tv.tuner.filter.FilterConfiguration.FilterType;
+
+/**
+ * Bits Settings for Section Filters.
+ * @hide
+ */
+public class SectionSettingsWithSectionBits extends SectionSettings {
+ private final byte[] mFilter;
+ private final byte[] mMask;
+ private final byte[] mMode;
+
+
+ private SectionSettingsWithSectionBits(int mainType, byte[] filter, byte[] mask, byte[] mode) {
+ super(mainType);
+ mFilter = filter;
+ mMask = mask;
+ mMode = mode;
+ }
+
+ /**
+ * Gets the bytes configured for Section Filter
+ */
+ public byte[] getFilterBytes() {
+ return mFilter;
+ }
+ /**
+ * Gets bit mask.
+ *
+ * <p>The bits in the bytes are used for filtering.
+ */
+ public byte[] getMask() {
+ return mMask;
+ }
+ /**
+ * Gets mode.
+ *
+ * <p>Do positive match at the bit position of the configured bytes when the bit at same
+ * position of the mode is 0.
+ * <p>Do negative match at the bit position of the configured bytes when the bit at same
+ * position of the mode is 1.
+ */
+ public byte[] getMode() {
+ return mMode;
+ }
+
+ /**
+ * Creates a builder for {@link SectionSettingsWithSectionBits}.
+ *
+ * @param context the context of the caller.
+ * @param mainType the filter main type.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context, @FilterType int mainType) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder(mainType);
+ }
+
+ /**
+ * Builder for {@link SectionSettingsWithSectionBits}.
+ */
+ public static class Builder extends Settings.Builder<Builder> {
+ private byte[] mFilter;
+ private byte[] mMask;
+ private byte[] mMode;
+
+ private Builder(int mainType) {
+ super(mainType);
+ }
+
+ /**
+ * Sets filter bytes.
+ */
+ @NonNull
+ public Builder setFilter(byte[] filter) {
+ mFilter = filter;
+ return this;
+ }
+ /**
+ * Sets bit mask.
+ */
+ @NonNull
+ public Builder setMask(byte[] mask) {
+ mMask = mask;
+ return this;
+ }
+ /**
+ * Sets mode.
+ */
+ @NonNull
+ public Builder setMode(byte[] mode) {
+ mMode = mode;
+ return this;
+ }
+
+ /**
+ * Builds a {@link SectionSettingsWithSectionBits} object.
+ */
+ @NonNull
+ public SectionSettingsWithSectionBits build() {
+ return new SectionSettingsWithSectionBits(mMainType, mFilter, mMask, mMode);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
+ }
+}
diff --git a/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java b/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java
new file mode 100644
index 0000000..6542b89
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/SectionSettingsWithTableInfo.java
@@ -0,0 +1,107 @@
+/*
+ * 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.media.tv.tuner.filter;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.media.tv.tuner.TunerUtils;
+import android.media.tv.tuner.filter.FilterConfiguration.FilterType;
+
+/**
+ * Table information for Section Filter.
+ * @hide
+ */
+public class SectionSettingsWithTableInfo extends SectionSettings {
+ private final int mTableId;
+ private final int mVersion;
+
+ private SectionSettingsWithTableInfo(int mainType, int tableId, int version) {
+ super(mainType);
+ mTableId = tableId;
+ mVersion = version;
+ }
+
+ /**
+ * Gets table ID.
+ */
+ public int getTableId() {
+ return mTableId;
+ }
+ /**
+ * Gets version.
+ */
+ public int getVersion() {
+ return mVersion;
+ }
+
+ /**
+ * Creates a builder for {@link SectionSettingsWithTableInfo}.
+ *
+ * @param context the context of the caller.
+ * @param mainType the filter main type.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context, @FilterType int mainType) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder(mainType);
+ }
+
+ /**
+ * Builder for {@link SectionSettingsWithTableInfo}.
+ */
+ public static class Builder extends Settings.Builder<Builder> {
+ private int mTableId;
+ private int mVersion;
+
+ private Builder(int mainType) {
+ super(mainType);
+ }
+
+ /**
+ * Sets table ID.
+ */
+ @NonNull
+ public Builder setTableId(int tableId) {
+ mTableId = tableId;
+ return this;
+ }
+ /**
+ * Sets version.
+ */
+ @NonNull
+ public Builder setVersion(int version) {
+ mVersion = version;
+ return this;
+ }
+
+ /**
+ * Builds a {@link SectionSettingsWithTableInfo} object.
+ */
+ @NonNull
+ public SectionSettingsWithTableInfo build() {
+ return new SectionSettingsWithTableInfo(mMainType, mTableId, mVersion);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
+ }
+
+}
diff --git a/media/java/android/media/tv/tuner/filter/Settings.java b/media/java/android/media/tv/tuner/filter/Settings.java
new file mode 100644
index 0000000..d697280
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/Settings.java
@@ -0,0 +1,58 @@
+/*
+ * 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.media.tv.tuner.filter;
+
+import android.annotation.SystemApi;
+
+/**
+ * Settings for filters of different subtypes.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class Settings {
+ private final int mType;
+
+ /* package */ Settings(int type) {
+ mType = type;
+ }
+
+ /**
+ * Gets filter settings type.
+ *
+ * @hide
+ */
+ public int getType() {
+ return mType;
+ }
+
+
+ /**
+ * Builder for {@link Settings}.
+ *
+ * @param <T> The subclass to be built.
+ * @hide
+ */
+ public abstract static class Builder<T extends Builder<T>> {
+ /* package */ final int mMainType;
+
+ /* package */ Builder(int mainType) {
+ mMainType = mainType;
+ }
+ /* package */ abstract T self();
+ }
+}
diff --git a/media/java/android/media/tv/tuner/filter/TemiEvent.java b/media/java/android/media/tv/tuner/filter/TemiEvent.java
new file mode 100644
index 0000000..031fa5c
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/TemiEvent.java
@@ -0,0 +1,61 @@
+/*
+ * 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.media.tv.tuner.filter;
+
+import android.annotation.NonNull;
+import android.media.tv.tuner.Tuner.Filter;
+
+/**
+ * Filter event sent from {@link Filter} objects for Timed External Media Information (TEMI) data.
+ *
+ * @hide
+ */
+public class TemiEvent extends FilterEvent {
+ private final long mPts;
+ private final byte mDescrTag;
+ private final byte[] mDescrData;
+
+ // This constructor is used by JNI code only
+ private TemiEvent(long pts, byte descrTag, byte[] descrData) {
+ mPts = pts;
+ mDescrTag = descrTag;
+ mDescrData = descrData;
+ }
+
+
+ /**
+ * Gets PTS (Presentation Time Stamp) for audio or video frame.
+ */
+ public long getPts() {
+ return mPts;
+ }
+
+ /**
+ * Gets TEMI descriptor tag.
+ */
+ public byte getDescriptorTag() {
+ return mDescrTag;
+ }
+
+ /**
+ * Gets TEMI descriptor.
+ */
+ @NonNull
+ public byte[] getDescriptorData() {
+ return mDescrData;
+ }
+}
diff --git a/media/java/android/media/tv/tuner/filter/TimeFilter.java b/media/java/android/media/tv/tuner/filter/TimeFilter.java
new file mode 100644
index 0000000..c975004
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/TimeFilter.java
@@ -0,0 +1,127 @@
+/*
+ * 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.media.tv.tuner.filter;
+
+import android.annotation.Nullable;
+import android.media.tv.tuner.TunerConstants.Result;
+
+/**
+ * A timer filter is used to filter data based on timestamps.
+ *
+ * <p> If the timestamp is set, data is discarded if its timestamp is smaller than the
+ * timestamp in this time filter.
+ *
+ * <p> The format of the timestamps is the same as PTS defined in ISO/IEC 13818-1:2019. The
+ * timestamps may or may not be related to PTS or DTS.
+ *
+ * @hide
+ */
+public class TimeFilter implements AutoCloseable {
+ private native int nativeSetTimestamp(long timestamp);
+ private native int nativeClearTimestamp();
+ private native Long nativeGetTimestamp();
+ private native Long nativeGetSourceTime();
+ private native int nativeClose();
+
+ private boolean mEnable = false;
+
+ /**
+ * Set timestamp for time based filter.
+ *
+ * It is used to set initial timestamp and enable time filtering. Once set, the time will be
+ * increased automatically like a clock. Contents are discarded if their timestamps are
+ * older than the time in the time filter.
+ *
+ * This method can be called more than once to reset the initial timestamp.
+ *
+ * @param timestamp initial timestamp for the time filter before it's increased. It's
+ * based on the 90KHz counter, and it's the same format as PTS (Presentation Time Stamp)
+ * defined in ISO/IEC 13818-1:2019. The timestamps may or may not be related to PTS or DTS.
+ * @return result status of the operation.
+ */
+ @Result
+ public int setCurrentTimestamp(long timestamp) {
+ int res = nativeSetTimestamp(timestamp);
+ // TODO: use a constant for SUCCESS
+ if (res == 0) {
+ mEnable = true;
+ }
+ return res;
+ }
+
+ /**
+ * Clear the timestamp in the time filter.
+ *
+ * It is used to clear the time value of the time filter. Time filtering is disabled then.
+ *
+ * @return result status of the operation.
+ */
+ @Result
+ public int clearTimestamp() {
+ int res = nativeClearTimestamp();
+ if (res == 0) {
+ mEnable = false;
+ }
+ return res;
+ }
+
+ /**
+ * Get the current time in the time filter.
+ *
+ * It is used to inquiry current time in the time filter.
+ *
+ * @return current timestamp in the time filter. It's based on the 90KHz counter, and it's
+ * the same format as PTS (Presentation Time Stamp) defined in ISO/IEC 13818-1:2019. The
+ * timestamps may or may not be related to PTS or DTS. {@code null} if the timestamp is
+ * never set.
+ */
+ @Nullable
+ public Long getTimeStamp() {
+ if (!mEnable) {
+ return null;
+ }
+ return nativeGetTimestamp();
+ }
+
+ /**
+ * Get the timestamp from the beginning of incoming data stream.
+ *
+ * It is used to inquiry the timestamp from the beginning of incoming data stream.
+ *
+ * @return first timestamp of incoming data stream. It's based on the 90KHz counter, and
+ * it's the same format as PTS (Presentation Time Stamp) defined in ISO/IEC 13818-1:2019.
+ * The timestamps may or may not be related to PTS or DTS.
+ */
+ @Nullable
+ public Long getSourceTime() {
+ if (!mEnable) {
+ return null;
+ }
+ return nativeGetSourceTime();
+ }
+
+ /**
+ * Close the Time Filter instance
+ *
+ * It is to release the TimeFilter instance. Resources are reclaimed so the instance must
+ * not be accessed after this method is called.
+ */
+ @Override
+ public void close() {
+ nativeClose();
+ }
+}
diff --git a/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java
new file mode 100644
index 0000000..eb97fc0
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/TlvFilterConfiguration.java
@@ -0,0 +1,133 @@
+/*
+ * 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.media.tv.tuner.filter;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.media.tv.tuner.TunerUtils;
+
+/**
+ * Filter configuration for a TLV filter.
+ * @hide
+ */
+public class TlvFilterConfiguration extends FilterConfiguration {
+ private final int mPacketType;
+ private final boolean mIsCompressedIpPacket;
+ private final boolean mPassthrough;
+
+ public TlvFilterConfiguration(Settings settings, int packetType, boolean isCompressed,
+ boolean passthrough) {
+ super(settings);
+ mPacketType = packetType;
+ mIsCompressedIpPacket = isCompressed;
+ mPassthrough = passthrough;
+ }
+
+ @Override
+ public int getType() {
+ return FilterConfiguration.FILTER_TYPE_TLV;
+ }
+
+ /**
+ * Gets packet type.
+ */
+ @FilterConfiguration.PacketType
+ public int getPacketType() {
+ return mPacketType;
+ }
+ /**
+ * Checks whether the data is compressed IP packet.
+ *
+ * @return {@code true} if the filtered data is compressed IP packet; {@code false} otherwise.
+ */
+ public boolean isCompressedIpPacket() {
+ return mIsCompressedIpPacket;
+ }
+ /**
+ * Checks whether it's passthrough.
+ *
+ * @return {@code true} if the data from TLV subtype go to next filter directly;
+ * {@code false} otherwise.
+ */
+ public boolean isPassthrough() {
+ return mPassthrough;
+ }
+
+ /**
+ * Creates a builder for {@link TlvFilterConfiguration}.
+ *
+ * @param context the context of the caller.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link TlvFilterConfiguration}.
+ */
+ public static class Builder extends FilterConfiguration.Builder<Builder> {
+ private int mPacketType;
+ private boolean mIsCompressedIpPacket;
+ private boolean mPassthrough;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets packet type.
+ */
+ @NonNull
+ public Builder setPacketType(@FilterConfiguration.PacketType int packetType) {
+ mPacketType = packetType;
+ return this;
+ }
+ /**
+ * Sets whether the data is compressed IP packet.
+ */
+ @NonNull
+ public Builder setIsCompressedIpPacket(boolean isCompressedIpPacket) {
+ mIsCompressedIpPacket = isCompressedIpPacket;
+ return this;
+ }
+ /**
+ * Sets whether it's passthrough.
+ */
+ @NonNull
+ public Builder setPassthrough(boolean passthrough) {
+ mPassthrough = passthrough;
+ return this;
+ }
+
+ /**
+ * Builds a {@link TlvFilterConfiguration} object.
+ */
+ @NonNull
+ public TlvFilterConfiguration build() {
+ return new TlvFilterConfiguration(
+ mSettings, mPacketType, mIsCompressedIpPacket, mPassthrough);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
+ }
+}
diff --git a/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java b/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java
new file mode 100644
index 0000000..5c38cfa
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/TsFilterConfiguration.java
@@ -0,0 +1,111 @@
+/*
+ * 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.media.tv.tuner.filter;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.media.tv.tuner.TunerUtils;
+
+/**
+ * Filter configuration for a TS filter.
+ *
+ * @hide
+ */
+@SystemApi
+public class TsFilterConfiguration extends FilterConfiguration {
+ private final int mTpid;
+
+ private TsFilterConfiguration(Settings settings, int tpid) {
+ super(settings);
+ mTpid = tpid;
+ }
+
+ @Override
+ public int getType() {
+ return FilterConfiguration.FILTER_TYPE_TS;
+ }
+
+ /**
+ * Gets the {@link Settings} object of this filter configuration.
+ */
+ @Nullable
+ public Settings getSettings() {
+ return mSettings;
+ }
+ /**
+ * Gets Tag Protocol ID.
+ */
+ public int getTpid() {
+ return mTpid;
+ }
+
+ /**
+ * Creates a builder for {@link TsFilterConfiguration}.
+ *
+ * @param context the context of the caller.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link TsFilterConfiguration}.
+ */
+ public static class Builder {
+ private Settings mSettings;
+ private int mTpid;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets filter settings.
+ *
+ * @param settings the filter settings.
+ */
+ @NonNull
+ public Builder setSettings(@NonNull Settings settings) {
+ mSettings = settings;
+ return this;
+ }
+
+ /**
+ * Sets Tag Protocol ID.
+ *
+ * @param tpid the Tag Protocol ID.
+ */
+ @NonNull
+ public Builder setTpid(int tpid) {
+ mTpid = tpid;
+ return this;
+ }
+
+ /**
+ * Builds a {@link TsFilterConfiguration} object.
+ */
+ @NonNull
+ public TsFilterConfiguration build() {
+ return new TsFilterConfiguration(mSettings, mTpid);
+ }
+ }
+}
diff --git a/media/java/android/media/tv/tuner/filter/TsRecordEvent.java b/media/java/android/media/tv/tuner/filter/TsRecordEvent.java
new file mode 100644
index 0000000..1b8485e
--- /dev/null
+++ b/media/java/android/media/tv/tuner/filter/TsRecordEvent.java
@@ -0,0 +1,72 @@
+/*
+ * 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.media.tv.tuner.filter;
+
+import android.media.tv.tuner.Tuner.Filter;
+
+
+/**
+ * Filter event sent from {@link Filter} objects for TS record data.
+ *
+ * @hide
+ */
+public class TsRecordEvent extends FilterEvent {
+
+ private final int mPid;
+ private final int mTsIndexMask;
+ private final int mScIndexMask;
+ private final long mByteNumber;
+
+ // This constructor is used by JNI code only
+ private TsRecordEvent(int pid, int tsIndexMask, int scIndexMask, long byteNumber) {
+ mPid = pid;
+ mTsIndexMask = tsIndexMask;
+ mScIndexMask = scIndexMask;
+ mByteNumber = byteNumber;
+ }
+
+ /**
+ * Gets packet ID.
+ */
+ public int getTpid() {
+ return mPid;
+ }
+
+ /**
+ * Gets TS index mask.
+ */
+ @RecordSettings.TsIndexMask
+ public int getTsIndexMask() {
+ return mTsIndexMask;
+ }
+ /**
+ * Gets SC index mask.
+ *
+ * <p>The index type is SC or SC-HEVC, and is set when configuring the filter.
+ */
+ @RecordSettings.ScIndexMask
+ public int getScIndexMask() {
+ return mScIndexMask;
+ }
+
+ /**
+ * Gets the byte number from beginning of the filter's output.
+ */
+ public long getByteNumber() {
+ return mByteNumber;
+ }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/AnalogFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/AnalogFrontendCapabilities.java
new file mode 100644
index 0000000..aa64df5
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/AnalogFrontendCapabilities.java
@@ -0,0 +1,50 @@
+/*
+ * 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.media.tv.tuner.frontend;
+
+/**
+ * Capabilities for analog tuners.
+ *
+ * @hide
+ */
+public class AnalogFrontendCapabilities extends FrontendCapabilities {
+ @AnalogFrontendSettings.SignalType
+ private final int mTypeCap;
+ @AnalogFrontendSettings.SifStandard
+ private final int mSifStandardCap;
+
+ // Called by JNI code.
+ private AnalogFrontendCapabilities(int typeCap, int sifStandardCap) {
+ mTypeCap = typeCap;
+ mSifStandardCap = sifStandardCap;
+ }
+
+ /**
+ * Gets analog signal type capability.
+ */
+ @AnalogFrontendSettings.SignalType
+ public int getSignalTypeCapability() {
+ return mTypeCap;
+ }
+ /**
+ * Gets Standard Interchange Format (SIF) capability.
+ */
+ @AnalogFrontendSettings.SifStandard
+ public int getSifStandardCapability() {
+ return mSifStandardCap;
+ }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java
new file mode 100644
index 0000000..a30ddc7
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/AnalogFrontendSettings.java
@@ -0,0 +1,224 @@
+/*
+ * 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.media.tv.tuner.frontend;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.hardware.tv.tuner.V1_0.Constants;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Frontend settings for analog tuner.
+ *
+ * @hide
+ */
+public class AnalogFrontendSettings extends FrontendSettings {
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "SIGNAL_TYPE_",
+ value = {SIGNAL_TYPE_UNDEFINED, SIGNAL_TYPE_PAL, SIGNAL_TYPE_SECAM, SIGNAL_TYPE_NTSC})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SignalType {}
+
+ /**
+ * Undefined analog signal type.
+ */
+ public static final int SIGNAL_TYPE_UNDEFINED = Constants.FrontendAnalogType.UNDEFINED;
+ /**
+ * PAL analog signal type.
+ */
+ public static final int SIGNAL_TYPE_PAL = Constants.FrontendAnalogType.PAL;
+ /**
+ * SECM analog signal type.
+ */
+ public static final int SIGNAL_TYPE_SECAM = Constants.FrontendAnalogType.SECAM;
+ /**
+ * NTSC analog signal type.
+ */
+ public static final int SIGNAL_TYPE_NTSC = Constants.FrontendAnalogType.NTSC;
+
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "SIF_",
+ value = {SIF_UNDEFINED, SIF_BG, SIF_BG_A2, SIF_BG_NICAM, SIF_I, SIF_DK,
+ SIF_DK1, SIF_DK2, SIF_DK3, SIF_DK_NICAM, SIF_L, SIF_M, SIF_M_BTSC, SIF_M_A2,
+ SIF_M_EIA_J, SIF_I_NICAM, SIF_L_NICAM, SIF_L_PRIME})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SifStandard {}
+
+ /**
+ * Undefined Analog Standard Interchange Format (SIF).
+ */
+ public static final int SIF_UNDEFINED = Constants.FrontendAnalogSifStandard.UNDEFINED;
+ /**
+ * BG Analog Standard Interchange Format (SIF).
+ */
+ public static final int SIF_BG = Constants.FrontendAnalogSifStandard.BG;
+ /**
+ * BG-A2 Analog Standard Interchange Format (SIF).
+ */
+ public static final int SIF_BG_A2 = Constants.FrontendAnalogSifStandard.BG_A2;
+ /**
+ * BG-NICAM Analog Standard Interchange Format (SIF).
+ */
+ public static final int SIF_BG_NICAM = Constants.FrontendAnalogSifStandard.BG_NICAM;
+ /**
+ * I Analog Standard Interchange Format (SIF).
+ */
+ public static final int SIF_I = Constants.FrontendAnalogSifStandard.I;
+ /**
+ * DK Analog Standard Interchange Format (SIF).
+ */
+ public static final int SIF_DK = Constants.FrontendAnalogSifStandard.DK;
+ /**
+ * DK1 Analog Standard Interchange Format (SIF).
+ */
+ public static final int SIF_DK1 = Constants.FrontendAnalogSifStandard.DK1;
+ /**
+ * DK2 Analog Standard Interchange Format (SIF).
+ */
+ public static final int SIF_DK2 = Constants.FrontendAnalogSifStandard.DK2;
+ /**
+ * DK3 Analog Standard Interchange Format (SIF).
+ */
+ public static final int SIF_DK3 = Constants.FrontendAnalogSifStandard.DK3;
+ /**
+ * DK-NICAM Analog Standard Interchange Format (SIF).
+ */
+ public static final int SIF_DK_NICAM = Constants.FrontendAnalogSifStandard.DK_NICAM;
+ /**
+ * L Analog Standard Interchange Format (SIF).
+ */
+ public static final int SIF_L = Constants.FrontendAnalogSifStandard.L;
+ /**
+ * M Analog Standard Interchange Format (SIF).
+ */
+ public static final int SIF_M = Constants.FrontendAnalogSifStandard.M;
+ /**
+ * M-BTSC Analog Standard Interchange Format (SIF).
+ */
+ public static final int SIF_M_BTSC = Constants.FrontendAnalogSifStandard.M_BTSC;
+ /**
+ * M-A2 Analog Standard Interchange Format (SIF).
+ */
+ public static final int SIF_M_A2 = Constants.FrontendAnalogSifStandard.M_A2;
+ /**
+ * M-EIA-J Analog Standard Interchange Format (SIF).
+ */
+ public static final int SIF_M_EIA_J = Constants.FrontendAnalogSifStandard.M_EIA_J;
+ /**
+ * I-NICAM Analog Standard Interchange Format (SIF).
+ */
+ public static final int SIF_I_NICAM = Constants.FrontendAnalogSifStandard.I_NICAM;
+ /**
+ * L-NICAM Analog Standard Interchange Format (SIF).
+ */
+ public static final int SIF_L_NICAM = Constants.FrontendAnalogSifStandard.L_NICAM;
+ /**
+ * L-PRIME Analog Standard Interchange Format (SIF).
+ */
+ public static final int SIF_L_PRIME = Constants.FrontendAnalogSifStandard.L_PRIME;
+
+
+ private final int mAnalogType;
+ private final int mSifStandard;
+
+ @Override
+ public int getType() {
+ return FrontendSettings.TYPE_ANALOG;
+ }
+
+
+ /**
+ * Gets analog signal type.
+ */
+ @SignalType
+ public int getAnalogType() {
+ return mAnalogType;
+ }
+
+ /**
+ * Gets Standard Interchange Format (SIF).
+ */
+ @SifStandard
+ public int getSifStandard() {
+ return mSifStandard;
+ }
+
+ /**
+ * Creates a builder for {@link AnalogFrontendSettings}.
+ */
+ @NonNull
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ private AnalogFrontendSettings(int frequency, int analogType, int sifStandard) {
+ super(frequency);
+ mAnalogType = analogType;
+ mSifStandard = sifStandard;
+ }
+
+ /**
+ * Builder for {@link AnalogFrontendSettings}.
+ */
+ public static class Builder {
+ private int mFrequency;
+ private int mAnalogType;
+ private int mSifStandard;
+
+ private Builder() {}
+
+ /**
+ * Sets frequency in Hz.
+ */
+ @NonNull
+ public Builder setFrequency(int frequency) {
+ mFrequency = frequency;
+ return this;
+ }
+
+ /**
+ * Sets analog type.
+ */
+ @NonNull
+ public Builder setAnalogType(@SignalType int analogType) {
+ mAnalogType = analogType;
+ return this;
+ }
+
+ /**
+ * Sets Standard Interchange Format (SIF).
+ */
+ @NonNull
+ public Builder setSifStandard(@SifStandard int sifStandard) {
+ mSifStandard = sifStandard;
+ return this;
+ }
+
+ /**
+ * Builds a {@link AnalogFrontendSettings} object.
+ */
+ @NonNull
+ public AnalogFrontendSettings build() {
+ return new AnalogFrontendSettings(mFrequency, mAnalogType, mSifStandard);
+ }
+ }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/Atsc3FrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendCapabilities.java
new file mode 100644
index 0000000..1fd1f63
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendCapabilities.java
@@ -0,0 +1,83 @@
+/*
+ * 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.media.tv.tuner.frontend;
+
+/**
+ * ATSC-3 Capabilities.
+ * @hide
+ */
+public class Atsc3FrontendCapabilities extends FrontendCapabilities {
+ private final int mBandwidthCap;
+ private final int mModulationCap;
+ private final int mTimeInterleaveModeCap;
+ private final int mCodeRateCap;
+ private final int mFecCap;
+ private final int mDemodOutputFormatCap;
+
+ private Atsc3FrontendCapabilities(int bandwidthCap, int modulationCap,
+ int timeInterleaveModeCap, int codeRateCap, int fecCap, int demodOutputFormatCap) {
+ mBandwidthCap = bandwidthCap;
+ mModulationCap = modulationCap;
+ mTimeInterleaveModeCap = timeInterleaveModeCap;
+ mCodeRateCap = codeRateCap;
+ mFecCap = fecCap;
+ mDemodOutputFormatCap = demodOutputFormatCap;
+ }
+
+ /**
+ * Gets bandwidth capability.
+ */
+ @Atsc3FrontendSettings.Bandwidth
+ public int getBandwidthCapability() {
+ return mBandwidthCap;
+ }
+ /**
+ * Gets modulation capability.
+ */
+ @Atsc3FrontendSettings.Modulation
+ public int getModulationCapability() {
+ return mModulationCap;
+ }
+ /**
+ * Gets time interleave mod capability.
+ */
+ @Atsc3FrontendSettings.TimeInterleaveMode
+ public int getTimeInterleaveModeCapability() {
+ return mTimeInterleaveModeCap;
+ }
+ /**
+ * Gets code rate capability.
+ */
+ @Atsc3FrontendSettings.CodeRate
+ public int getCodeRateCapability() {
+ return mCodeRateCap;
+ }
+ /**
+ * Gets FEC capability.
+ */
+ @Atsc3FrontendSettings.Fec
+ public int getFecCapability() {
+ return mFecCap;
+ }
+ /**
+ * Gets demodulator output format capability.
+ */
+ @Atsc3FrontendSettings.DemodOutputFormat
+ public int getDemodOutputFormatCapability() {
+ return mDemodOutputFormatCap;
+ }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java
new file mode 100644
index 0000000..5e1ba72
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/Atsc3FrontendSettings.java
@@ -0,0 +1,376 @@
+/*
+ * 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.media.tv.tuner.frontend;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Frontend settings for ATSC-3.
+ * @hide
+ */
+public class Atsc3FrontendSettings extends FrontendSettings {
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "BANDWIDTH_",
+ value = {BANDWIDTH_UNDEFINED, BANDWIDTH_AUTO, BANDWIDTH_BANDWIDTH_6MHZ,
+ BANDWIDTH_BANDWIDTH_7MHZ, BANDWIDTH_BANDWIDTH_8MHZ})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Bandwidth {}
+
+ /**
+ * Bandwidth not defined.
+ */
+ public static final int BANDWIDTH_UNDEFINED =
+ Constants.FrontendAtsc3Bandwidth.UNDEFINED;
+ /**
+ * Hardware is able to detect and set bandwidth automatically
+ */
+ public static final int BANDWIDTH_AUTO = Constants.FrontendAtsc3Bandwidth.AUTO;
+ /**
+ * 6 MHz bandwidth.
+ */
+ public static final int BANDWIDTH_BANDWIDTH_6MHZ =
+ Constants.FrontendAtsc3Bandwidth.BANDWIDTH_6MHZ;
+ /**
+ * 7 MHz bandwidth.
+ */
+ public static final int BANDWIDTH_BANDWIDTH_7MHZ =
+ Constants.FrontendAtsc3Bandwidth.BANDWIDTH_7MHZ;
+ /**
+ * 8 MHz bandwidth.
+ */
+ public static final int BANDWIDTH_BANDWIDTH_8MHZ =
+ Constants.FrontendAtsc3Bandwidth.BANDWIDTH_8MHZ;
+
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "MODULATION_",
+ value = {MODULATION_UNDEFINED, MODULATION_AUTO,
+ MODULATION_MOD_QPSK, MODULATION_MOD_16QAM,
+ MODULATION_MOD_64QAM, MODULATION_MOD_256QAM,
+ MODULATION_MOD_1024QAM, MODULATION_MOD_4096QAM})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Modulation {}
+
+ /**
+ * Modulation undefined.
+ */
+ public static final int MODULATION_UNDEFINED = Constants.FrontendAtsc3Modulation.UNDEFINED;
+ /**
+ * Hardware is able to detect and set modulation automatically.
+ */
+ public static final int MODULATION_AUTO = Constants.FrontendAtsc3Modulation.AUTO;
+ /**
+ * QPSK modulation.
+ */
+ public static final int MODULATION_MOD_QPSK = Constants.FrontendAtsc3Modulation.MOD_QPSK;
+ /**
+ * 16QAM modulation.
+ */
+ public static final int MODULATION_MOD_16QAM = Constants.FrontendAtsc3Modulation.MOD_16QAM;
+ /**
+ * 64QAM modulation.
+ */
+ public static final int MODULATION_MOD_64QAM = Constants.FrontendAtsc3Modulation.MOD_64QAM;
+ /**
+ * 256QAM modulation.
+ */
+ public static final int MODULATION_MOD_256QAM = Constants.FrontendAtsc3Modulation.MOD_256QAM;
+ /**
+ * 1024QAM modulation.
+ */
+ public static final int MODULATION_MOD_1024QAM = Constants.FrontendAtsc3Modulation.MOD_1024QAM;
+ /**
+ * 4096QAM modulation.
+ */
+ public static final int MODULATION_MOD_4096QAM = Constants.FrontendAtsc3Modulation.MOD_4096QAM;
+
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "TIME_INTERLEAVE_MODE_",
+ value = {TIME_INTERLEAVE_MODE_UNDEFINED, TIME_INTERLEAVE_MODE_AUTO,
+ TIME_INTERLEAVE_MODE_CTI, TIME_INTERLEAVE_MODE_HTI})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TimeInterleaveMode {}
+
+ /**
+ * Time interleave mode undefined.
+ */
+ public static final int TIME_INTERLEAVE_MODE_UNDEFINED =
+ Constants.FrontendAtsc3TimeInterleaveMode.UNDEFINED;
+ /**
+ * Hardware is able to detect and set Time Interleave Mode automatically.
+ */
+ public static final int TIME_INTERLEAVE_MODE_AUTO =
+ Constants.FrontendAtsc3TimeInterleaveMode.AUTO;
+ /**
+ * CTI Time Interleave Mode.
+ */
+ public static final int TIME_INTERLEAVE_MODE_CTI =
+ Constants.FrontendAtsc3TimeInterleaveMode.CTI;
+ /**
+ * HTI Time Interleave Mode.
+ */
+ public static final int TIME_INTERLEAVE_MODE_HTI =
+ Constants.FrontendAtsc3TimeInterleaveMode.HTI;
+
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "CODERATE_",
+ value = {CODERATE_UNDEFINED, CODERATE_AUTO, CODERATE_2_15, CODERATE_3_15, CODERATE_4_15,
+ CODERATE_5_15, CODERATE_6_15, CODERATE_7_15, CODERATE_8_15, CODERATE_9_15,
+ CODERATE_10_15, CODERATE_11_15, CODERATE_12_15, CODERATE_13_15})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CodeRate {}
+
+ /**
+ * Code rate undefined.
+ */
+ public static final int CODERATE_UNDEFINED = Constants.FrontendAtsc3CodeRate.UNDEFINED;
+ /**
+ * Hardware is able to detect and set code rate automatically
+ */
+ public static final int CODERATE_AUTO = Constants.FrontendAtsc3CodeRate.AUTO;
+ /**
+ * 2/15 code rate.
+ */
+ public static final int CODERATE_2_15 = Constants.FrontendAtsc3CodeRate.CODERATE_2_15;
+ /**
+ * 3/15 code rate.
+ */
+ public static final int CODERATE_3_15 = Constants.FrontendAtsc3CodeRate.CODERATE_3_15;
+ /**
+ * 4/15 code rate.
+ */
+ public static final int CODERATE_4_15 = Constants.FrontendAtsc3CodeRate.CODERATE_4_15;
+ /**
+ * 5/15 code rate.
+ */
+ public static final int CODERATE_5_15 = Constants.FrontendAtsc3CodeRate.CODERATE_5_15;
+ /**
+ * 6/15 code rate.
+ */
+ public static final int CODERATE_6_15 = Constants.FrontendAtsc3CodeRate.CODERATE_6_15;
+ /**
+ * 7/15 code rate.
+ */
+ public static final int CODERATE_7_15 = Constants.FrontendAtsc3CodeRate.CODERATE_7_15;
+ /**
+ * 8/15 code rate.
+ */
+ public static final int CODERATE_8_15 = Constants.FrontendAtsc3CodeRate.CODERATE_8_15;
+ /**
+ * 9/15 code rate.
+ */
+ public static final int CODERATE_9_15 = Constants.FrontendAtsc3CodeRate.CODERATE_9_15;
+ /**
+ * 10/15 code rate.
+ */
+ public static final int CODERATE_10_15 = Constants.FrontendAtsc3CodeRate.CODERATE_10_15;
+ /**
+ * 11/15 code rate.
+ */
+ public static final int CODERATE_11_15 = Constants.FrontendAtsc3CodeRate.CODERATE_11_15;
+ /**
+ * 12/15 code rate.
+ */
+ public static final int CODERATE_12_15 = Constants.FrontendAtsc3CodeRate.CODERATE_12_15;
+ /**
+ * 13/15 code rate.
+ */
+ public static final int CODERATE_13_15 = Constants.FrontendAtsc3CodeRate.CODERATE_13_15;
+
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "FEC_",
+ value = {FEC_UNDEFINED, FEC_AUTO, FEC_BCH_LDPC_16K, FEC_BCH_LDPC_64K, FEC_CRC_LDPC_16K,
+ FEC_CRC_LDPC_64K, FEC_LDPC_16K, FEC_LDPC_64K})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Fec {}
+
+ /**
+ * Forward Error Correction undefined.
+ */
+ public static final int FEC_UNDEFINED = Constants.FrontendAtsc3Fec.UNDEFINED;
+ /**
+ * Hardware is able to detect and set FEC automatically
+ */
+ public static final int FEC_AUTO = Constants.FrontendAtsc3Fec.AUTO;
+ /**
+ * BCH LDPC 16K Forward Error Correction
+ */
+ public static final int FEC_BCH_LDPC_16K = Constants.FrontendAtsc3Fec.BCH_LDPC_16K;
+ /**
+ * BCH LDPC 64K Forward Error Correction
+ */
+ public static final int FEC_BCH_LDPC_64K = Constants.FrontendAtsc3Fec.BCH_LDPC_64K;
+ /**
+ * CRC LDPC 16K Forward Error Correction
+ */
+ public static final int FEC_CRC_LDPC_16K = Constants.FrontendAtsc3Fec.CRC_LDPC_16K;
+ /**
+ * CRC LDPC 64K Forward Error Correction
+ */
+ public static final int FEC_CRC_LDPC_64K = Constants.FrontendAtsc3Fec.CRC_LDPC_64K;
+ /**
+ * LDPC 16K Forward Error Correction
+ */
+ public static final int FEC_LDPC_16K = Constants.FrontendAtsc3Fec.LDPC_16K;
+ /**
+ * LDPC 64K Forward Error Correction
+ */
+ public static final int FEC_LDPC_64K = Constants.FrontendAtsc3Fec.LDPC_64K;
+
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "DEMOD_OUTPUT_FORMAT_",
+ value = {DEMOD_OUTPUT_FORMAT_UNDEFINED, DEMOD_OUTPUT_FORMAT_ATSC3_LINKLAYER_PACKET,
+ DEMOD_OUTPUT_FORMAT_BASEBAND_PACKET})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface DemodOutputFormat {}
+
+ /**
+ * Demod output format undefined.
+ */
+ public static final int DEMOD_OUTPUT_FORMAT_UNDEFINED =
+ Constants.FrontendAtsc3DemodOutputFormat.UNDEFINED;
+ /**
+ * ALP format. Typically used in US region.
+ */
+ public static final int DEMOD_OUTPUT_FORMAT_ATSC3_LINKLAYER_PACKET =
+ Constants.FrontendAtsc3DemodOutputFormat.ATSC3_LINKLAYER_PACKET;
+ /**
+ * BaseBand packet format. Typically used in Korea region.
+ */
+ public static final int DEMOD_OUTPUT_FORMAT_BASEBAND_PACKET =
+ Constants.FrontendAtsc3DemodOutputFormat.BASEBAND_PACKET;
+
+ public final int mBandwidth;
+ public final int mDemodOutputFormat;
+ public final Atsc3PlpSettings[] mPlpSettings;
+
+ private Atsc3FrontendSettings(int frequency, int bandwidth, int demodOutputFormat,
+ Atsc3PlpSettings[] plpSettings) {
+ super(frequency);
+ mBandwidth = bandwidth;
+ mDemodOutputFormat = demodOutputFormat;
+ mPlpSettings = plpSettings;
+ }
+
+ /**
+ * Gets bandwidth.
+ */
+ @Bandwidth
+ public int getBandwidth() {
+ return mBandwidth;
+ }
+ /**
+ * Gets Demod Output Format.
+ */
+ @DemodOutputFormat
+ public int getDemodOutputFormat() {
+ return mDemodOutputFormat;
+ }
+ /**
+ * Gets PLP Settings.
+ */
+ public Atsc3PlpSettings[] getPlpSettings() {
+ return mPlpSettings;
+ }
+
+ /**
+ * Creates a builder for {@link Atsc3FrontendSettings}.
+ *
+ * @param context the context of the caller.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link Atsc3FrontendSettings}.
+ */
+ public static class Builder extends FrontendSettings.Builder<Builder> {
+ private int mBandwidth;
+ private byte mDemodOutputFormat;
+ private Atsc3PlpSettings[] mPlpSettings;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets bandwidth.
+ */
+ @NonNull
+ public Builder setBandwidth(int bandwidth) {
+ mBandwidth = bandwidth;
+ return this;
+ }
+ /**
+ * Sets Demod Output Format.
+ */
+ @NonNull
+ public Builder setDemodOutputFormat(byte demodOutputFormat) {
+ mDemodOutputFormat = demodOutputFormat;
+ return this;
+ }
+ /**
+ * Sets PLP Settings.
+ */
+ @NonNull
+ public Builder setPlpSettings(Atsc3PlpSettings[] plpSettings) {
+ mPlpSettings = plpSettings;
+ return this;
+ }
+
+ /**
+ * Builds a {@link Atsc3FrontendSettings} object.
+ */
+ @NonNull
+ public Atsc3FrontendSettings build() {
+ return new Atsc3FrontendSettings(
+ mFrequency, mBandwidth, mDemodOutputFormat, mPlpSettings);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
+ }
+
+ @Override
+ public int getType() {
+ return FrontendSettings.TYPE_ATSC3;
+ }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/Atsc3PlpSettings.java b/media/java/android/media/tv/tuner/frontend/Atsc3PlpSettings.java
new file mode 100644
index 0000000..43a68a0
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/Atsc3PlpSettings.java
@@ -0,0 +1,153 @@
+/*
+ * 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.media.tv.tuner.frontend;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.media.tv.tuner.TunerUtils;
+
+/**
+ * PLP settings for ATSC-3.
+ * @hide
+ */
+public class Atsc3PlpSettings {
+ private final int mPlpId;
+ private final int mModulation;
+ private final int mInterleaveMode;
+ private final int mCodeRate;
+ private final int mFec;
+
+ private Atsc3PlpSettings(int plpId, int modulation, int interleaveMode, int codeRate, int fec) {
+ mPlpId = plpId;
+ mModulation = modulation;
+ mInterleaveMode = interleaveMode;
+ mCodeRate = codeRate;
+ mFec = fec;
+ }
+
+ /**
+ * Gets Physical Layer Pipe (PLP) ID.
+ */
+ public int getPlpId() {
+ return mPlpId;
+ }
+ /**
+ * Gets Modulation.
+ */
+ @Atsc3FrontendSettings.Modulation
+ public int getModulation() {
+ return mModulation;
+ }
+ /**
+ * Gets Interleave Mode.
+ */
+ @Atsc3FrontendSettings.TimeInterleaveMode
+ public int getInterleaveMode() {
+ return mInterleaveMode;
+ }
+ /**
+ * Gets Code Rate.
+ */
+ @Atsc3FrontendSettings.CodeRate
+ public int getCodeRate() {
+ return mCodeRate;
+ }
+ /**
+ * Gets Forward Error Correction.
+ */
+ @Atsc3FrontendSettings.Fec
+ public int getFec() {
+ return mFec;
+ }
+
+ /**
+ * Creates a builder for {@link Atsc3PlpSettings}.
+ *
+ * @param context the context of the caller.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link Atsc3PlpSettings}.
+ */
+ public static class Builder {
+ private int mPlpId;
+ private int mModulation;
+ private int mInterleaveMode;
+ private int mCodeRate;
+ private int mFec;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets Physical Layer Pipe (PLP) ID.
+ */
+ @NonNull
+ public Builder setPlpId(int plpId) {
+ mPlpId = plpId;
+ return this;
+ }
+ /**
+ * Sets Modulation.
+ */
+ @NonNull
+ public Builder setModulation(@Atsc3FrontendSettings.Modulation int modulation) {
+ mModulation = modulation;
+ return this;
+ }
+ /**
+ * Sets Interleave Mode.
+ */
+ @NonNull
+ public Builder setInterleaveMode(
+ @Atsc3FrontendSettings.TimeInterleaveMode int interleaveMode) {
+ mInterleaveMode = interleaveMode;
+ return this;
+ }
+ /**
+ * Sets Code Rate.
+ */
+ @NonNull
+ public Builder setCodeRate(@Atsc3FrontendSettings.CodeRate int codeRate) {
+ mCodeRate = codeRate;
+ return this;
+ }
+ /**
+ * Sets Forward Error Correction.
+ */
+ @NonNull
+ public Builder setFec(@Atsc3FrontendSettings.Fec int fec) {
+ mFec = fec;
+ return this;
+ }
+
+ /**
+ * Builds a {@link Atsc3PlpSettings} object.
+ */
+ @NonNull
+ public Atsc3PlpSettings build() {
+ return new Atsc3PlpSettings(mPlpId, mModulation, mInterleaveMode, mCodeRate, mFec);
+ }
+ }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/AtscFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/AtscFrontendCapabilities.java
new file mode 100644
index 0000000..0ff516d
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/AtscFrontendCapabilities.java
@@ -0,0 +1,37 @@
+/*
+ * 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.media.tv.tuner.frontend;
+
+/**
+ * ATSC Capabilities.
+ * @hide
+ */
+public class AtscFrontendCapabilities extends FrontendCapabilities {
+ private final int mModulationCap;
+
+ private AtscFrontendCapabilities(int modulationCap) {
+ mModulationCap = modulationCap;
+ }
+
+ /**
+ * Gets modulation capability.
+ */
+ @AtscFrontendSettings.Modulation
+ public int getModulationCapability() {
+ return mModulationCap;
+ }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java
new file mode 100644
index 0000000..32901d8
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/AtscFrontendSettings.java
@@ -0,0 +1,124 @@
+/*
+ * 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.media.tv.tuner.frontend;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Frontend settings for ATSC.
+ * @hide
+ */
+public class AtscFrontendSettings extends FrontendSettings {
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "MODULATION_",
+ value = {MODULATION_UNDEFINED, MODULATION_AUTO, MODULATION_MOD_8VSB,
+ MODULATION_MOD_16VSB})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Modulation {}
+
+ /**
+ * Modulation undefined.
+ */
+ public static final int MODULATION_UNDEFINED = Constants.FrontendAtscModulation.UNDEFINED;
+ /**
+ * Hardware is able to detect and set modulation automatically
+ */
+ public static final int MODULATION_AUTO = Constants.FrontendAtscModulation.AUTO;
+ /**
+ * 8VSB Modulation.
+ */
+ public static final int MODULATION_MOD_8VSB = Constants.FrontendAtscModulation.MOD_8VSB;
+ /**
+ * 16VSB Modulation.
+ */
+ public static final int MODULATION_MOD_16VSB = Constants.FrontendAtscModulation.MOD_16VSB;
+
+
+ private final int mModulation;
+
+ private AtscFrontendSettings(int frequency, int modulation) {
+ super(frequency);
+ mModulation = modulation;
+ }
+
+ /**
+ * Gets Modulation.
+ */
+ @Modulation
+ public int getModulation() {
+ return mModulation;
+ }
+
+ /**
+ * Creates a builder for {@link AtscFrontendSettings}.
+ *
+ * @param context the context of the caller.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link AtscFrontendSettings}.
+ */
+ public static class Builder extends FrontendSettings.Builder<Builder> {
+ private int mModulation;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets Modulation.
+ */
+ @NonNull
+ public Builder setModulation(@Modulation int modulation) {
+ mModulation = modulation;
+ return this;
+ }
+
+ /**
+ * Builds a {@link AtscFrontendSettings} object.
+ */
+ @NonNull
+ public AtscFrontendSettings build() {
+ return new AtscFrontendSettings(mFrequency, mModulation);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
+ }
+
+ @Override
+ public int getType() {
+ return FrontendSettings.TYPE_ATSC;
+ }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/DvbcFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/DvbcFrontendCapabilities.java
new file mode 100644
index 0000000..f3fbdb5
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/DvbcFrontendCapabilities.java
@@ -0,0 +1,57 @@
+/*
+ * 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.media.tv.tuner.frontend;
+
+import android.media.tv.tuner.TunerConstants.FrontendInnerFec;
+
+/**
+ * DVBC Capabilities.
+ * @hide
+ */
+public class DvbcFrontendCapabilities extends FrontendCapabilities {
+ private final int mModulationCap;
+ private final int mFecCap;
+ private final int mAnnexCap;
+
+ private DvbcFrontendCapabilities(int modulationCap, int fecCap, int annexCap) {
+ mModulationCap = modulationCap;
+ mFecCap = fecCap;
+ mAnnexCap = annexCap;
+ }
+
+ /**
+ * Gets modulation capability.
+ */
+ @DvbcFrontendSettings.Modulation
+ public int getModulationCapability() {
+ return mModulationCap;
+ }
+ /**
+ * Gets inner FEC capability.
+ */
+ @FrontendInnerFec
+ public int getFecCapability() {
+ return mFecCap;
+ }
+ /**
+ * Gets annex capability.
+ */
+ @DvbcFrontendSettings.Annex
+ public int getAnnexCapability() {
+ return mAnnexCap;
+ }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java
new file mode 100644
index 0000000..3d212d3
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/DvbcFrontendSettings.java
@@ -0,0 +1,298 @@
+/*
+ * 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.media.tv.tuner.frontend;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerConstants.FrontendInnerFec;
+import android.media.tv.tuner.TunerUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Frontend settings for DVBC.
+ * @hide
+ */
+public class DvbcFrontendSettings extends FrontendSettings {
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "MODULATION_",
+ value = {MODULATION_UNDEFINED, MODULATION_AUTO, MODULATION_MOD_16QAM,
+ MODULATION_MOD_32QAM, MODULATION_MOD_64QAM, MODULATION_MOD_128QAM,
+ MODULATION_MOD_256QAM})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Modulation {}
+
+ /**
+ * Modulation undefined.
+ */
+ public static final int MODULATION_UNDEFINED = Constants.FrontendDvbcModulation.UNDEFINED;
+ /**
+ * Hardware is able to detect and set modulation automatically
+ */
+ public static final int MODULATION_AUTO = Constants.FrontendDvbcModulation.AUTO;
+ /**
+ * 16QAM Modulation.
+ */
+ public static final int MODULATION_MOD_16QAM = Constants.FrontendDvbcModulation.MOD_16QAM;
+ /**
+ * 32QAM Modulation.
+ */
+ public static final int MODULATION_MOD_32QAM = Constants.FrontendDvbcModulation.MOD_32QAM;
+ /**
+ * 64QAM Modulation.
+ */
+ public static final int MODULATION_MOD_64QAM = Constants.FrontendDvbcModulation.MOD_64QAM;
+ /**
+ * 128QAM Modulation.
+ */
+ public static final int MODULATION_MOD_128QAM = Constants.FrontendDvbcModulation.MOD_128QAM;
+ /**
+ * 256QAM Modulation.
+ */
+ public static final int MODULATION_MOD_256QAM = Constants.FrontendDvbcModulation.MOD_256QAM;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "OUTER_FEC_",
+ value = {OUTER_FEC_UNDEFINED, OUTER_FEC_OUTER_FEC_NONE, OUTER_FEC_OUTER_FEC_RS})
+ public @interface OuterFec {}
+
+ /**
+ * Outer Forward Error Correction (FEC) Type undefined.
+ */
+ public static final int OUTER_FEC_UNDEFINED = Constants.FrontendDvbcOuterFec.UNDEFINED;
+ /**
+ * None Outer Forward Error Correction (FEC) Type.
+ */
+ public static final int OUTER_FEC_OUTER_FEC_NONE =
+ Constants.FrontendDvbcOuterFec.OUTER_FEC_NONE;
+ /**
+ * RS Outer Forward Error Correction (FEC) Type.
+ */
+ public static final int OUTER_FEC_OUTER_FEC_RS = Constants.FrontendDvbcOuterFec.OUTER_FEC_RS;
+
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "ANNEX_",
+ value = {ANNEX_UNDEFINED, ANNEX_A, ANNEX_B, ANNEX_C})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Annex {}
+
+ /**
+ * Annex Type undefined.
+ */
+ public static final int ANNEX_UNDEFINED = Constants.FrontendDvbcAnnex.UNDEFINED;
+ /**
+ * Annex Type A.
+ */
+ public static final int ANNEX_A = Constants.FrontendDvbcAnnex.A;
+ /**
+ * Annex Type B.
+ */
+ public static final int ANNEX_B = Constants.FrontendDvbcAnnex.B;
+ /**
+ * Annex Type C.
+ */
+ public static final int ANNEX_C = Constants.FrontendDvbcAnnex.C;
+
+
+ /** @hide */
+ @IntDef(prefix = "SPECTRAL_INVERSION_",
+ value = {SPECTRAL_INVERSION_UNDEFINED, SPECTRAL_INVERSION_NORMAL,
+ SPECTRAL_INVERSION_INVERTED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SpectralInversion {}
+
+ /**
+ * Spectral Inversion Type undefined.
+ */
+ public static final int SPECTRAL_INVERSION_UNDEFINED =
+ Constants.FrontendDvbcSpectralInversion.UNDEFINED;
+ /**
+ * Normal Spectral Inversion.
+ */
+ public static final int SPECTRAL_INVERSION_NORMAL =
+ Constants.FrontendDvbcSpectralInversion.NORMAL;
+ /**
+ * Inverted Spectral Inversion.
+ */
+ public static final int SPECTRAL_INVERSION_INVERTED =
+ Constants.FrontendDvbcSpectralInversion.INVERTED;
+
+
+ private final int mModulation;
+ private final long mFec;
+ private final int mSymbolRate;
+ private final int mOuterFec;
+ private final byte mAnnex;
+ private final int mSpectralInversion;
+
+ private DvbcFrontendSettings(int frequency, int modulation, long fec, int symbolRate,
+ int outerFec, byte annex, int spectralInversion) {
+ super(frequency);
+ mModulation = modulation;
+ mFec = fec;
+ mSymbolRate = symbolRate;
+ mOuterFec = outerFec;
+ mAnnex = annex;
+ mSpectralInversion = spectralInversion;
+ }
+
+ /**
+ * Gets Modulation.
+ */
+ @Modulation
+ public int getModulation() {
+ return mModulation;
+ }
+ /**
+ * Gets Inner Forward Error Correction.
+ */
+ @FrontendInnerFec
+ public long getFec() {
+ return mFec;
+ }
+ /**
+ * Gets Symbol Rate in symbols per second.
+ */
+ public int getSymbolRate() {
+ return mSymbolRate;
+ }
+ /**
+ * Gets Outer Forward Error Correction.
+ */
+ @OuterFec
+ public int getOuterFec() {
+ return mOuterFec;
+ }
+ /**
+ * Gets Annex.
+ */
+ @Annex
+ public byte getAnnex() {
+ return mAnnex;
+ }
+ /**
+ * Gets Spectral Inversion.
+ */
+ @SpectralInversion
+ public int getSpectralInversion() {
+ return mSpectralInversion;
+ }
+
+ /**
+ * Creates a builder for {@link DvbcFrontendSettings}.
+ *
+ * @param context the context of the caller.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link DvbcFrontendSettings}.
+ */
+ public static class Builder extends FrontendSettings.Builder<Builder> {
+ private int mModulation;
+ private long mFec;
+ private int mSymbolRate;
+ private int mOuterFec;
+ private byte mAnnex;
+ private int mSpectralInversion;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets Modulation.
+ */
+ @NonNull
+ public Builder setModulation(@Modulation int modulation) {
+ mModulation = modulation;
+ return this;
+ }
+ /**
+ * Sets Inner Forward Error Correction.
+ */
+ @NonNull
+ public Builder setFec(@FrontendInnerFec long fec) {
+ mFec = fec;
+ return this;
+ }
+ /**
+ * Sets Symbol Rate in symbols per second.
+ */
+ @NonNull
+ public Builder setSymbolRate(int symbolRate) {
+ mSymbolRate = symbolRate;
+ return this;
+ }
+ /**
+ * Sets Outer Forward Error Correction.
+ */
+ @NonNull
+ public Builder setOuterFec(@OuterFec int outerFec) {
+ mOuterFec = outerFec;
+ return this;
+ }
+ /**
+ * Sets Annex.
+ */
+ @NonNull
+ public Builder setAnnex(@Annex byte annex) {
+ mAnnex = annex;
+ return this;
+ }
+ /**
+ * Sets Spectral Inversion.
+ */
+ @NonNull
+ public Builder setSpectralInversion(@SpectralInversion int spectralInversion) {
+ mSpectralInversion = spectralInversion;
+ return this;
+ }
+
+ /**
+ * Builds a {@link DvbcFrontendSettings} object.
+ */
+ @NonNull
+ public DvbcFrontendSettings build() {
+ return new DvbcFrontendSettings(mFrequency, mModulation, mFec, mSymbolRate, mOuterFec,
+ mAnnex, mSpectralInversion);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
+ }
+
+ @Override
+ public int getType() {
+ return FrontendSettings.TYPE_DVBC;
+ }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/DvbsCodeRate.java b/media/java/android/media/tv/tuner/frontend/DvbsCodeRate.java
new file mode 100644
index 0000000..04d3375
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/DvbsCodeRate.java
@@ -0,0 +1,133 @@
+/*
+ * 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.media.tv.tuner.frontend;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.media.tv.tuner.TunerConstants.FrontendInnerFec;
+import android.media.tv.tuner.TunerUtils;
+
+/**
+ * Code rate for DVBS.
+ * @hide
+ */
+public class DvbsCodeRate {
+ private final long mFec;
+ private final boolean mIsLinear;
+ private final boolean mIsShortFrames;
+ private final int mBitsPer1000Symbol;
+
+ private DvbsCodeRate(long fec, boolean isLinear, boolean isShortFrames, int bitsPer1000Symbol) {
+ mFec = fec;
+ mIsLinear = isLinear;
+ mIsShortFrames = isShortFrames;
+ mBitsPer1000Symbol = bitsPer1000Symbol;
+ }
+
+ /**
+ * Gets inner FEC.
+ */
+ @FrontendInnerFec
+ public long getFec() {
+ return mFec;
+ }
+ /**
+ * Checks whether it's linear.
+ */
+ public boolean isLinear() {
+ return mIsLinear;
+ }
+ /**
+ * Checks whether short frame enabled.
+ */
+ public boolean isShortFrameEnabled() {
+ return mIsShortFrames;
+ }
+ /**
+ * Gets bits number in 1000 symbols. 0 by default.
+ */
+ public int getBitsPer1000Symbol() {
+ return mBitsPer1000Symbol;
+ }
+
+ /**
+ * Creates a builder for {@link DvbsCodeRate}.
+ *
+ * @param context the context of the caller.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link DvbsCodeRate}.
+ */
+ public static class Builder {
+ private long mFec;
+ private boolean mIsLinear;
+ private boolean mIsShortFrames;
+ private int mBitsPer1000Symbol;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets inner FEC.
+ */
+ @NonNull
+ public Builder setFec(@FrontendInnerFec long fec) {
+ mFec = fec;
+ return this;
+ }
+ /**
+ * Sets whether it's linear.
+ */
+ @NonNull
+ public Builder setLinear(boolean isLinear) {
+ mIsLinear = isLinear;
+ return this;
+ }
+ /**
+ * Sets whether short frame enabled.
+ */
+ @NonNull
+ public Builder setShortFrameEnabled(boolean isShortFrames) {
+ mIsShortFrames = isShortFrames;
+ return this;
+ }
+ /**
+ * Sets bits number in 1000 symbols.
+ */
+ @NonNull
+ public Builder setBitsPer1000Symbol(int bitsPer1000Symbol) {
+ mBitsPer1000Symbol = bitsPer1000Symbol;
+ return this;
+ }
+
+ /**
+ * Builds a {@link DvbsCodeRate} object.
+ */
+ @NonNull
+ public DvbsCodeRate build() {
+ return new DvbsCodeRate(mFec, mIsLinear, mIsShortFrames, mBitsPer1000Symbol);
+ }
+ }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/DvbsFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/DvbsFrontendCapabilities.java
new file mode 100644
index 0000000..bd615d0
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/DvbsFrontendCapabilities.java
@@ -0,0 +1,57 @@
+/*
+ * 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.media.tv.tuner.frontend;
+
+import android.media.tv.tuner.TunerConstants.FrontendInnerFec;
+
+/**
+ * DVBS Capabilities.
+ * @hide
+ */
+public class DvbsFrontendCapabilities extends FrontendCapabilities {
+ private final int mModulationCap;
+ private final long mInnerFecCap;
+ private final int mStandard;
+
+ private DvbsFrontendCapabilities(int modulationCap, long innerFecCap, int standard) {
+ mModulationCap = modulationCap;
+ mInnerFecCap = innerFecCap;
+ mStandard = standard;
+ }
+
+ /**
+ * Gets modulation capability.
+ */
+ @DvbsFrontendSettings.Modulation
+ public int getModulationCapability() {
+ return mModulationCap;
+ }
+ /**
+ * Gets inner FEC capability.
+ */
+ @FrontendInnerFec
+ public long getInnerFecCapability() {
+ return mInnerFecCap;
+ }
+ /**
+ * Gets DVBS standard capability.
+ */
+ @DvbsFrontendSettings.Standard
+ public int getStandardCapability() {
+ return mStandard;
+ }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java
new file mode 100644
index 0000000..5b3bffc4
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/DvbsFrontendSettings.java
@@ -0,0 +1,363 @@
+/*
+ * 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.media.tv.tuner.frontend;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Frontend settings for DVBS.
+ * @hide
+ */
+public class DvbsFrontendSettings extends FrontendSettings {
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "MODULATION_",
+ value = {MODULATION_UNDEFINED, MODULATION_AUTO, MODULATION_MOD_QPSK,
+ MODULATION_MOD_8PSK, MODULATION_MOD_16QAM, MODULATION_MOD_16PSK,
+ MODULATION_MOD_32PSK, MODULATION_MOD_ACM, MODULATION_MOD_8APSK,
+ MODULATION_MOD_16APSK, MODULATION_MOD_32APSK, MODULATION_MOD_64APSK,
+ MODULATION_MOD_128APSK, MODULATION_MOD_256APSK, MODULATION_MOD_RESERVED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Modulation {}
+
+ /**
+ * Modulation undefined.
+ */
+ public static final int MODULATION_UNDEFINED = Constants.FrontendDvbsModulation.UNDEFINED;
+ /**
+ * Hardware is able to detect and set modulation automatically
+ */
+ public static final int MODULATION_AUTO = Constants.FrontendDvbsModulation.AUTO;
+ /**
+ * QPSK Modulation.
+ */
+ public static final int MODULATION_MOD_QPSK = Constants.FrontendDvbsModulation.MOD_QPSK;
+ /**
+ * 8PSK Modulation.
+ */
+ public static final int MODULATION_MOD_8PSK = Constants.FrontendDvbsModulation.MOD_8PSK;
+ /**
+ * 16QAM Modulation.
+ */
+ public static final int MODULATION_MOD_16QAM = Constants.FrontendDvbsModulation.MOD_16QAM;
+ /**
+ * 16PSK Modulation.
+ */
+ public static final int MODULATION_MOD_16PSK = Constants.FrontendDvbsModulation.MOD_16PSK;
+ /**
+ * 32PSK Modulation.
+ */
+ public static final int MODULATION_MOD_32PSK = Constants.FrontendDvbsModulation.MOD_32PSK;
+ /**
+ * ACM Modulation.
+ */
+ public static final int MODULATION_MOD_ACM = Constants.FrontendDvbsModulation.MOD_ACM;
+ /**
+ * 8APSK Modulation.
+ */
+ public static final int MODULATION_MOD_8APSK = Constants.FrontendDvbsModulation.MOD_8APSK;
+ /**
+ * 16APSK Modulation.
+ */
+ public static final int MODULATION_MOD_16APSK = Constants.FrontendDvbsModulation.MOD_16APSK;
+ /**
+ * 32APSK Modulation.
+ */
+ public static final int MODULATION_MOD_32APSK = Constants.FrontendDvbsModulation.MOD_32APSK;
+ /**
+ * 64APSK Modulation.
+ */
+ public static final int MODULATION_MOD_64APSK = Constants.FrontendDvbsModulation.MOD_64APSK;
+ /**
+ * 128APSK Modulation.
+ */
+ public static final int MODULATION_MOD_128APSK = Constants.FrontendDvbsModulation.MOD_128APSK;
+ /**
+ * 256APSK Modulation.
+ */
+ public static final int MODULATION_MOD_256APSK = Constants.FrontendDvbsModulation.MOD_256APSK;
+ /**
+ * Reversed Modulation.
+ */
+ public static final int MODULATION_MOD_RESERVED = Constants.FrontendDvbsModulation.MOD_RESERVED;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "ROLLOFF_",
+ value = {ROLLOFF_UNDEFINED, ROLLOFF_0_35, ROLLOFF_0_25, ROLLOFF_0_20, ROLLOFF_0_15,
+ ROLLOFF_0_10, ROLLOFF_0_5})
+ public @interface Rolloff {}
+
+ /**
+ * Roll Off undefined.
+ */
+ public static final int ROLLOFF_UNDEFINED = Constants.FrontendDvbsRolloff.UNDEFINED;
+ /**
+ * Roll Off 0_35.
+ */
+ public static final int ROLLOFF_0_35 = Constants.FrontendDvbsRolloff.ROLLOFF_0_35;
+ /**
+ * Roll Off 0_25.
+ */
+ public static final int ROLLOFF_0_25 = Constants.FrontendDvbsRolloff.ROLLOFF_0_25;
+ /**
+ * Roll Off 0_2.
+ */
+ public static final int ROLLOFF_0_20 = Constants.FrontendDvbsRolloff.ROLLOFF_0_20;
+ /**
+ * Roll Off 0_15.
+ */
+ public static final int ROLLOFF_0_15 = Constants.FrontendDvbsRolloff.ROLLOFF_0_15;
+ /**
+ * Roll Off 0_1.
+ */
+ public static final int ROLLOFF_0_10 = Constants.FrontendDvbsRolloff.ROLLOFF_0_10;
+ /**
+ * Roll Off 0_5.
+ */
+ public static final int ROLLOFF_0_5 = Constants.FrontendDvbsRolloff.ROLLOFF_0_5;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "PILOT_",
+ value = {PILOT_UNDEFINED, PILOT_ON, PILOT_OFF, PILOT_AUTO})
+ public @interface Pilot {}
+
+ /**
+ * Pilot mode undefined.
+ */
+ public static final int PILOT_UNDEFINED = Constants.FrontendDvbsPilot.UNDEFINED;
+ /**
+ * Pilot mode on.
+ */
+ public static final int PILOT_ON = Constants.FrontendDvbsPilot.ON;
+ /**
+ * Pilot mode off.
+ */
+ public static final int PILOT_OFF = Constants.FrontendDvbsPilot.OFF;
+ /**
+ * Pilot mode auto.
+ */
+ public static final int PILOT_AUTO = Constants.FrontendDvbsPilot.AUTO;
+
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "STANDARD_",
+ value = {STANDARD_AUTO, STANDARD_S, STANDARD_S2, STANDARD_S2X})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Standard {}
+
+ /**
+ * Standard undefined.
+ */
+ public static final int STANDARD_AUTO = Constants.FrontendDvbsStandard.AUTO;
+ /**
+ * Standard S.
+ */
+ public static final int STANDARD_S = Constants.FrontendDvbsStandard.S;
+ /**
+ * Standard S2.
+ */
+ public static final int STANDARD_S2 = Constants.FrontendDvbsStandard.S2;
+ /**
+ * Standard S2X.
+ */
+ public static final int STANDARD_S2X = Constants.FrontendDvbsStandard.S2X;
+
+
+ private final int mModulation;
+ private final DvbsCodeRate mCoderate;
+ private final int mSymbolRate;
+ private final int mRolloff;
+ private final int mPilot;
+ private final int mInputStreamId;
+ private final int mStandard;
+
+ private DvbsFrontendSettings(int frequency, int modulation, DvbsCodeRate coderate,
+ int symbolRate, int rolloff, int pilot, int inputStreamId, int standard) {
+ super(frequency);
+ mModulation = modulation;
+ mCoderate = coderate;
+ mSymbolRate = symbolRate;
+ mRolloff = rolloff;
+ mPilot = pilot;
+ mInputStreamId = inputStreamId;
+ mStandard = standard;
+ }
+
+ /**
+ * Gets Modulation.
+ */
+ @Modulation
+ public int getModulation() {
+ return mModulation;
+ }
+ /**
+ * Gets Code rate.
+ */
+ @Nullable
+ public DvbsCodeRate getCoderate() {
+ return mCoderate;
+ }
+ /**
+ * Gets Symbol Rate in symbols per second.
+ */
+ public int getSymbolRate() {
+ return mSymbolRate;
+ }
+ /**
+ * Gets Rolloff.
+ */
+ @Rolloff
+ public int getRolloff() {
+ return mRolloff;
+ }
+ /**
+ * Gets Pilot mode.
+ */
+ @Pilot
+ public int getPilot() {
+ return mPilot;
+ }
+ /**
+ * Gets Input Stream ID.
+ */
+ public int getInputStreamId() {
+ return mInputStreamId;
+ }
+ /**
+ * Gets DVBS sub-standard.
+ */
+ @Standard
+ public int getStandard() {
+ return mStandard;
+ }
+
+ /**
+ * Creates a builder for {@link DvbsFrontendSettings}.
+ *
+ * @param context the context of the caller.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link DvbsFrontendSettings}.
+ */
+ public static class Builder extends FrontendSettings.Builder<Builder> {
+ private int mModulation;
+ private DvbsCodeRate mCoderate;
+ private int mSymbolRate;
+ private int mRolloff;
+ private int mPilot;
+ private int mInputStreamId;
+ private int mStandard;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets Modulation.
+ */
+ @NonNull
+ public Builder setModulation(@Modulation int modulation) {
+ mModulation = modulation;
+ return this;
+ }
+ /**
+ * Sets Code rate.
+ */
+ @NonNull
+ public Builder setCoderate(@Nullable DvbsCodeRate coderate) {
+ mCoderate = coderate;
+ return this;
+ }
+ /**
+ * Sets Symbol Rate.
+ */
+ @NonNull
+ public Builder setSymbolRate(int symbolRate) {
+ mSymbolRate = symbolRate;
+ return this;
+ }
+ /**
+ * Sets Rolloff.
+ */
+ @NonNull
+ public Builder setRolloff(@Rolloff int rolloff) {
+ mRolloff = rolloff;
+ return this;
+ }
+ /**
+ * Sets Pilot mode.
+ */
+ @NonNull
+ public Builder setPilot(@Pilot int pilot) {
+ mPilot = pilot;
+ return this;
+ }
+ /**
+ * Sets Input Stream ID.
+ */
+ @NonNull
+ public Builder setInputStreamId(int inputStreamId) {
+ mInputStreamId = inputStreamId;
+ return this;
+ }
+ /**
+ * Sets Standard.
+ */
+ @NonNull
+ public Builder setStandard(@Standard int standard) {
+ mStandard = standard;
+ return this;
+ }
+
+ /**
+ * Builds a {@link DvbsFrontendSettings} object.
+ */
+ @NonNull
+ public DvbsFrontendSettings build() {
+ return new DvbsFrontendSettings(mFrequency, mModulation, mCoderate, mSymbolRate,
+ mRolloff, mPilot, mInputStreamId, mStandard);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
+ }
+
+ @Override
+ public int getType() {
+ return FrontendSettings.TYPE_DVBS;
+ }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/DvbtFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/DvbtFrontendCapabilities.java
new file mode 100644
index 0000000..0d47179
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/DvbtFrontendCapabilities.java
@@ -0,0 +1,100 @@
+/*
+ * 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.media.tv.tuner.frontend;
+
+/**
+ * DVBT Capabilities.
+ * @hide
+ */
+public class DvbtFrontendCapabilities extends FrontendCapabilities {
+ private final int mTransmissionModeCap;
+ private final int mBandwidthCap;
+ private final int mConstellationCap;
+ private final int mCoderateCap;
+ private final int mHierarchyCap;
+ private final int mGuardIntervalCap;
+ private final boolean mIsT2Supported;
+ private final boolean mIsMisoSupported;
+
+ private DvbtFrontendCapabilities(int transmissionModeCap, int bandwidthCap,
+ int constellationCap, int coderateCap, int hierarchyCap, int guardIntervalCap,
+ boolean isT2Supported, boolean isMisoSupported) {
+ mTransmissionModeCap = transmissionModeCap;
+ mBandwidthCap = bandwidthCap;
+ mConstellationCap = constellationCap;
+ mCoderateCap = coderateCap;
+ mHierarchyCap = hierarchyCap;
+ mGuardIntervalCap = guardIntervalCap;
+ mIsT2Supported = isT2Supported;
+ mIsMisoSupported = isMisoSupported;
+ }
+
+ /**
+ * Gets transmission mode capability.
+ */
+ @DvbtFrontendSettings.TransmissionMode
+ public int getTransmissionModeCapability() {
+ return mTransmissionModeCap;
+ }
+ /**
+ * Gets bandwidth capability.
+ */
+ @DvbtFrontendSettings.Bandwidth
+ public int getBandwidthCapability() {
+ return mBandwidthCap;
+ }
+ /**
+ * Gets constellation capability.
+ */
+ @DvbtFrontendSettings.Constellation
+ public int getConstellationCapability() {
+ return mConstellationCap;
+ }
+ /**
+ * Gets code rate capability.
+ */
+ @DvbtFrontendSettings.Coderate
+ public int getCodeRateCapability() {
+ return mCoderateCap;
+ }
+ /**
+ * Gets hierarchy capability.
+ */
+ @DvbtFrontendSettings.Hierarchy
+ public int getHierarchyCapability() {
+ return mHierarchyCap;
+ }
+ /**
+ * Gets guard interval capability.
+ */
+ @DvbtFrontendSettings.GuardInterval
+ public int getGuardIntervalCapability() {
+ return mGuardIntervalCap;
+ }
+ /**
+ * Returns whether T2 is supported.
+ */
+ public boolean isT2Supported() {
+ return mIsT2Supported;
+ }
+ /**
+ * Returns whether MISO is supported.
+ */
+ public boolean isMisoSupported() {
+ return mIsMisoSupported;
+ }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java
new file mode 100644
index 0000000..f0469b7
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/DvbtFrontendSettings.java
@@ -0,0 +1,650 @@
+/*
+ * 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.media.tv.tuner.frontend;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Frontend settings for DVBT.
+ * @hide
+ */
+public class DvbtFrontendSettings extends FrontendSettings {
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "TRANSMISSION_MODE_",
+ value = {TRANSMISSION_MODE_UNDEFINED, TRANSMISSION_MODE_AUTO,
+ TRANSMISSION_MODE_2K, TRANSMISSION_MODE_8K, TRANSMISSION_MODE_4K,
+ TRANSMISSION_MODE_1K, TRANSMISSION_MODE_16K, TRANSMISSION_MODE_32K})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface TransmissionMode {}
+
+ /**
+ * Transmission Mode undefined.
+ */
+ public static final int TRANSMISSION_MODE_UNDEFINED =
+ Constants.FrontendDvbtTransmissionMode.UNDEFINED;
+ /**
+ * Hardware is able to detect and set Transmission Mode automatically
+ */
+ public static final int TRANSMISSION_MODE_AUTO = Constants.FrontendDvbtTransmissionMode.AUTO;
+ /**
+ * 2K Transmission Mode.
+ */
+ public static final int TRANSMISSION_MODE_2K = Constants.FrontendDvbtTransmissionMode.MODE_2K;
+ /**
+ * 8K Transmission Mode.
+ */
+ public static final int TRANSMISSION_MODE_8K = Constants.FrontendDvbtTransmissionMode.MODE_8K;
+ /**
+ * 4K Transmission Mode.
+ */
+ public static final int TRANSMISSION_MODE_4K = Constants.FrontendDvbtTransmissionMode.MODE_4K;
+ /**
+ * 1K Transmission Mode.
+ */
+ public static final int TRANSMISSION_MODE_1K = Constants.FrontendDvbtTransmissionMode.MODE_1K;
+ /**
+ * 16K Transmission Mode.
+ */
+ public static final int TRANSMISSION_MODE_16K = Constants.FrontendDvbtTransmissionMode.MODE_16K;
+ /**
+ * 32K Transmission Mode.
+ */
+ public static final int TRANSMISSION_MODE_32K = Constants.FrontendDvbtTransmissionMode.MODE_32K;
+
+
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "BANDWIDTH_",
+ value = {BANDWIDTH_UNDEFINED, BANDWIDTH_AUTO, BANDWIDTH_8MHZ, BANDWIDTH_7MHZ,
+ BANDWIDTH_6MHZ, BANDWIDTH_5MHZ, BANDWIDTH_1_7MHZ, BANDWIDTH_10MHZ})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Bandwidth {}
+
+ /**
+ * Bandwidth undefined.
+ */
+ public static final int BANDWIDTH_UNDEFINED = Constants.FrontendDvbtBandwidth.UNDEFINED;
+ /**
+ * Hardware is able to detect and set Bandwidth automatically.
+ */
+ public static final int BANDWIDTH_AUTO = Constants.FrontendDvbtBandwidth.AUTO;
+ /**
+ * 8 MHz bandwidth.
+ */
+ public static final int BANDWIDTH_8MHZ = Constants.FrontendDvbtBandwidth.BANDWIDTH_8MHZ;
+ /**
+ * 7 MHz bandwidth.
+ */
+ public static final int BANDWIDTH_7MHZ = Constants.FrontendDvbtBandwidth.BANDWIDTH_7MHZ;
+ /**
+ * 6 MHz bandwidth.
+ */
+ public static final int BANDWIDTH_6MHZ = Constants.FrontendDvbtBandwidth.BANDWIDTH_6MHZ;
+ /**
+ * 5 MHz bandwidth.
+ */
+ public static final int BANDWIDTH_5MHZ = Constants.FrontendDvbtBandwidth.BANDWIDTH_5MHZ;
+ /**
+ * 1.7 MHz bandwidth.
+ */
+ public static final int BANDWIDTH_1_7MHZ = Constants.FrontendDvbtBandwidth.BANDWIDTH_1_7MHZ;
+ /**
+ * 10 MHz bandwidth.
+ */
+ public static final int BANDWIDTH_10MHZ = Constants.FrontendDvbtBandwidth.BANDWIDTH_10MHZ;
+
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "CONSTELLATION_",
+ value = {CONSTELLATION_UNDEFINED, CONSTELLATION_AUTO, CONSTELLATION_CONSTELLATION_QPSK,
+ CONSTELLATION_CONSTELLATION_16QAM, CONSTELLATION_CONSTELLATION_64QAM,
+ CONSTELLATION_CONSTELLATION_256QAM})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Constellation {}
+
+ /**
+ * Constellation not defined.
+ */
+ public static final int CONSTELLATION_UNDEFINED = Constants.FrontendDvbtConstellation.UNDEFINED;
+ /**
+ * Hardware is able to detect and set Constellation automatically.
+ */
+ public static final int CONSTELLATION_AUTO = Constants.FrontendDvbtConstellation.AUTO;
+ /**
+ * QPSK Constellation.
+ */
+ public static final int CONSTELLATION_CONSTELLATION_QPSK =
+ Constants.FrontendDvbtConstellation.CONSTELLATION_QPSK;
+ /**
+ * 16QAM Constellation.
+ */
+ public static final int CONSTELLATION_CONSTELLATION_16QAM =
+ Constants.FrontendDvbtConstellation.CONSTELLATION_16QAM;
+ /**
+ * 64QAM Constellation.
+ */
+ public static final int CONSTELLATION_CONSTELLATION_64QAM =
+ Constants.FrontendDvbtConstellation.CONSTELLATION_64QAM;
+ /**
+ * 256QAM Constellation.
+ */
+ public static final int CONSTELLATION_CONSTELLATION_256QAM =
+ Constants.FrontendDvbtConstellation.CONSTELLATION_256QAM;
+
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "HIERARCHY_",
+ value = {HIERARCHY_UNDEFINED, HIERARCHY_AUTO, HIERARCHY_NON_NATIVE, HIERARCHY_1_NATIVE,
+ HIERARCHY_2_NATIVE, HIERARCHY_4_NATIVE, HIERARCHY_NON_INDEPTH, HIERARCHY_1_INDEPTH,
+ HIERARCHY_2_INDEPTH, HIERARCHY_4_INDEPTH})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Hierarchy {}
+
+ /**
+ * Hierarchy undefined.
+ */
+ public static final int HIERARCHY_UNDEFINED = Constants.FrontendDvbtHierarchy.UNDEFINED;
+ /**
+ * Hardware is able to detect and set Hierarchy automatically.
+ */
+ public static final int HIERARCHY_AUTO = Constants.FrontendDvbtHierarchy.AUTO;
+ /**
+ * Non-native Hierarchy
+ */
+ public static final int HIERARCHY_NON_NATIVE =
+ Constants.FrontendDvbtHierarchy.HIERARCHY_NON_NATIVE;
+ /**
+ * 1-native Hierarchy
+ */
+ public static final int HIERARCHY_1_NATIVE = Constants.FrontendDvbtHierarchy.HIERARCHY_1_NATIVE;
+ /**
+ * 2-native Hierarchy
+ */
+ public static final int HIERARCHY_2_NATIVE = Constants.FrontendDvbtHierarchy.HIERARCHY_2_NATIVE;
+ /**
+ * 4-native Hierarchy
+ */
+ public static final int HIERARCHY_4_NATIVE = Constants.FrontendDvbtHierarchy.HIERARCHY_4_NATIVE;
+ /**
+ * Non-indepth Hierarchy
+ */
+ public static final int HIERARCHY_NON_INDEPTH =
+ Constants.FrontendDvbtHierarchy.HIERARCHY_NON_INDEPTH;
+ /**
+ * 1-indepth Hierarchy
+ */
+ public static final int HIERARCHY_1_INDEPTH =
+ Constants.FrontendDvbtHierarchy.HIERARCHY_1_INDEPTH;
+ /**
+ * 2-indepth Hierarchy
+ */
+ public static final int HIERARCHY_2_INDEPTH =
+ Constants.FrontendDvbtHierarchy.HIERARCHY_2_INDEPTH;
+ /**
+ * 4-indepth Hierarchy
+ */
+ public static final int HIERARCHY_4_INDEPTH =
+ Constants.FrontendDvbtHierarchy.HIERARCHY_4_INDEPTH;
+
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "CODERATE_",
+ value = {CODERATE_UNDEFINED, CODERATE_AUTO, CODERATE_1_2, CODERATE_2_3, CODERATE_3_4,
+ CODERATE_5_6, CODERATE_7_8, CODERATE_3_5, CODERATE_4_5, CODERATE_6_7, CODERATE_8_9})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Coderate {}
+
+ /**
+ * Code rate undefined.
+ */
+ public static final int CODERATE_UNDEFINED =
+ Constants.FrontendDvbtCoderate.UNDEFINED;
+ /**
+ * Hardware is able to detect and set code rate automatically.
+ */
+ public static final int CODERATE_AUTO = Constants.FrontendDvbtCoderate.AUTO;
+ /**
+ * 1_2 code rate.
+ */
+ public static final int CODERATE_1_2 = Constants.FrontendDvbtCoderate.CODERATE_1_2;
+ /**
+ * 2_3 code rate.
+ */
+ public static final int CODERATE_2_3 = Constants.FrontendDvbtCoderate.CODERATE_2_3;
+ /**
+ * 3_4 code rate.
+ */
+ public static final int CODERATE_3_4 = Constants.FrontendDvbtCoderate.CODERATE_3_4;
+ /**
+ * 5_6 code rate.
+ */
+ public static final int CODERATE_5_6 = Constants.FrontendDvbtCoderate.CODERATE_5_6;
+ /**
+ * 7_8 code rate.
+ */
+ public static final int CODERATE_7_8 = Constants.FrontendDvbtCoderate.CODERATE_7_8;
+ /**
+ * 4_5 code rate.
+ */
+ public static final int CODERATE_3_5 = Constants.FrontendDvbtCoderate.CODERATE_3_5;
+ /**
+ * 4_5 code rate.
+ */
+ public static final int CODERATE_4_5 = Constants.FrontendDvbtCoderate.CODERATE_4_5;
+ /**
+ * 6_7 code rate.
+ */
+ public static final int CODERATE_6_7 = Constants.FrontendDvbtCoderate.CODERATE_6_7;
+ /**
+ * 8_9 code rate.
+ */
+ public static final int CODERATE_8_9 = Constants.FrontendDvbtCoderate.CODERATE_8_9;
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "GUARD_INTERVAL_",
+ value = {GUARD_INTERVAL_UNDEFINED, GUARD_INTERVAL_AUTO,
+ GUARD_INTERVAL_INTERVAL_1_32, GUARD_INTERVAL_INTERVAL_1_16,
+ GUARD_INTERVAL_INTERVAL_1_8, GUARD_INTERVAL_INTERVAL_1_4,
+ GUARD_INTERVAL_INTERVAL_1_128,
+ GUARD_INTERVAL_INTERVAL_19_128,
+ GUARD_INTERVAL_INTERVAL_19_256})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface GuardInterval {}
+
+ /**
+ * Guard Interval undefined.
+ */
+ public static final int GUARD_INTERVAL_UNDEFINED =
+ Constants.FrontendDvbtGuardInterval.UNDEFINED;
+ /**
+ * Hardware is able to detect and set Guard Interval automatically.
+ */
+ public static final int GUARD_INTERVAL_AUTO = Constants.FrontendDvbtGuardInterval.AUTO;
+ /**
+ * 1/32 Guard Interval.
+ */
+ public static final int GUARD_INTERVAL_INTERVAL_1_32 =
+ Constants.FrontendDvbtGuardInterval.INTERVAL_1_32;
+ /**
+ * 1/16 Guard Interval.
+ */
+ public static final int GUARD_INTERVAL_INTERVAL_1_16 =
+ Constants.FrontendDvbtGuardInterval.INTERVAL_1_16;
+ /**
+ * 1/8 Guard Interval.
+ */
+ public static final int GUARD_INTERVAL_INTERVAL_1_8 =
+ Constants.FrontendDvbtGuardInterval.INTERVAL_1_8;
+ /**
+ * 1/4 Guard Interval.
+ */
+ public static final int GUARD_INTERVAL_INTERVAL_1_4 =
+ Constants.FrontendDvbtGuardInterval.INTERVAL_1_4;
+ /**
+ * 1/128 Guard Interval.
+ */
+ public static final int GUARD_INTERVAL_INTERVAL_1_128 =
+ Constants.FrontendDvbtGuardInterval.INTERVAL_1_128;
+ /**
+ * 19/128 Guard Interval.
+ */
+ public static final int GUARD_INTERVAL_INTERVAL_19_128 =
+ Constants.FrontendDvbtGuardInterval.INTERVAL_19_128;
+ /**
+ * 19/256 Guard Interval.
+ */
+ public static final int GUARD_INTERVAL_INTERVAL_19_256 =
+ Constants.FrontendDvbtGuardInterval.INTERVAL_19_256;
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "STANDARD",
+ value = {STANDARD_AUTO, STANDARD_T, STANDARD_T2}
+ )
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Standard {}
+
+ /**
+ * Hardware is able to detect and set Standard automatically.
+ */
+ public static final int STANDARD_AUTO = Constants.FrontendDvbtStandard.AUTO;
+ /**
+ * T standard.
+ */
+ public static final int STANDARD_T = Constants.FrontendDvbtStandard.T;
+ /**
+ * T2 standard.
+ */
+ public static final int STANDARD_T2 = Constants.FrontendDvbtStandard.T2;
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "PLP_MODE_",
+ value = {PLP_MODE_UNDEFINED, PLP_MODE_AUTO, PLP_MODE_MANUAL})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PlpMode {}
+
+ /**
+ * Physical Layer Pipe (PLP) Mode undefined.
+ */
+ public static final int PLP_MODE_UNDEFINED = Constants.FrontendDvbtPlpMode.UNDEFINED;
+ /**
+ * Hardware is able to detect and set Physical Layer Pipe (PLP) Mode automatically.
+ */
+ public static final int PLP_MODE_AUTO = Constants.FrontendDvbtPlpMode.AUTO;
+ /**
+ * Physical Layer Pipe (PLP) manual Mode.
+ */
+ public static final int PLP_MODE_MANUAL = Constants.FrontendDvbtPlpMode.MANUAL;
+
+
+ private final int mTransmissionMode;
+ private final int mBandwidth;
+ private final int mConstellation;
+ private final int mHierarchy;
+ private final int mHpCoderate;
+ private final int mLpCoderate;
+ private final int mGuardInterval;
+ private final boolean mIsHighPriority;
+ private final int mStandard;
+ private final boolean mIsMiso;
+ private final int mPlpMode;
+ private final int mPlpId;
+ private final int mPlpGroupId;
+
+ private DvbtFrontendSettings(int frequency, int transmissionMode, int bandwidth,
+ int constellation, int hierarchy, int hpCoderate, int lpCoderate, int guardInterval,
+ boolean isHighPriority, int standard, boolean isMiso, int plpMode, int plpId,
+ int plpGroupId) {
+ super(frequency);
+ mTransmissionMode = transmissionMode;
+ mBandwidth = bandwidth;
+ mConstellation = constellation;
+ mHierarchy = hierarchy;
+ mHpCoderate = hpCoderate;
+ mLpCoderate = lpCoderate;
+ mGuardInterval = guardInterval;
+ mIsHighPriority = isHighPriority;
+ mStandard = standard;
+ mIsMiso = isMiso;
+ mPlpMode = plpMode;
+ mPlpId = plpId;
+ mPlpGroupId = plpGroupId;
+ }
+
+ /**
+ * Gets Transmission Mode.
+ */
+ @TransmissionMode
+ public int getTransmissionMode() {
+ return mTransmissionMode;
+ }
+ /**
+ * Gets Bandwidth.
+ */
+ @Bandwidth
+ public int getBandwidth() {
+ return mBandwidth;
+ }
+ /**
+ * Gets Constellation.
+ */
+ @Constellation
+ public int getConstellation() {
+ return mConstellation;
+ }
+ /**
+ * Gets Hierarchy.
+ */
+ @Hierarchy
+ public int getHierarchy() {
+ return mHierarchy;
+ }
+ /**
+ * Gets Code Rate for High Priority level.
+ */
+ @Coderate
+ public int getHpCoderate() {
+ return mHpCoderate;
+ }
+ /**
+ * Gets Code Rate for Low Priority level.
+ */
+ @Coderate
+ public int getLpCoderate() {
+ return mLpCoderate;
+ }
+ /**
+ * Gets Guard Interval.
+ */
+ @GuardInterval
+ public int getGuardInterval() {
+ return mGuardInterval;
+ }
+ /**
+ * Checks whether it's high priority.
+ */
+ public boolean isHighPriority() {
+ return mIsHighPriority;
+ }
+ /**
+ * Gets Standard.
+ */
+ @Standard
+ public int getStandard() {
+ return mStandard;
+ }
+ /**
+ * Gets whether it's MISO.
+ */
+ public boolean isMiso() {
+ return mIsMiso;
+ }
+ /**
+ * Gets Physical Layer Pipe (PLP) Mode.
+ */
+ @PlpMode
+ public int getPlpMode() {
+ return mPlpMode;
+ }
+ /**
+ * Gets Physical Layer Pipe (PLP) ID.
+ */
+ public int getPlpId() {
+ return mPlpId;
+ }
+ /**
+ * Gets Physical Layer Pipe (PLP) group ID.
+ */
+ public int getPlpGroupId() {
+ return mPlpGroupId;
+ }
+
+ /**
+ * Creates a builder for {@link DvbtFrontendSettings}.
+ *
+ * @param context the context of the caller.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link DvbtFrontendSettings}.
+ */
+ public static class Builder extends FrontendSettings.Builder<Builder> {
+ private int mTransmissionMode;
+ private int mBandwidth;
+ private int mConstellation;
+ private int mHierarchy;
+ private int mHpCoderate;
+ private int mLpCoderate;
+ private int mGuardInterval;
+ private boolean mIsHighPriority;
+ private int mStandard;
+ private boolean mIsMiso;
+ private int mPlpMode;
+ private int mPlpId;
+ private int mPlpGroupId;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets Transmission Mode.
+ */
+ @NonNull
+ public Builder setTransmissionMode(@TransmissionMode int transmissionMode) {
+ mTransmissionMode = transmissionMode;
+ return this;
+ }
+ /**
+ * Sets Bandwidth.
+ */
+ @NonNull
+ public Builder setBandwidth(@Bandwidth int bandwidth) {
+ mBandwidth = bandwidth;
+ return this;
+ }
+ /**
+ * Sets Constellation.
+ */
+ @NonNull
+ public Builder setConstellation(@Constellation int constellation) {
+ mConstellation = constellation;
+ return this;
+ }
+ /**
+ * Sets Hierarchy.
+ */
+ @NonNull
+ public Builder setHierarchy(@Hierarchy int hierarchy) {
+ mHierarchy = hierarchy;
+ return this;
+ }
+ /**
+ * Sets Code Rate for High Priority level.
+ */
+ @NonNull
+ public Builder setHpCoderate(@Coderate int hpCoderate) {
+ mHpCoderate = hpCoderate;
+ return this;
+ }
+ /**
+ * Sets Code Rate for Low Priority level.
+ */
+ @NonNull
+ public Builder setLpCoderate(@Coderate int lpCoderate) {
+ mLpCoderate = lpCoderate;
+ return this;
+ }
+ /**
+ * Sets Guard Interval.
+ */
+ @NonNull
+ public Builder setGuardInterval(@GuardInterval int guardInterval) {
+ mGuardInterval = guardInterval;
+ return this;
+ }
+ /**
+ * Sets whether it's high priority.
+ */
+ @NonNull
+ public Builder setHighPriority(boolean isHighPriority) {
+ mIsHighPriority = isHighPriority;
+ return this;
+ }
+ /**
+ * Sets Standard.
+ */
+ @NonNull
+ public Builder setStandard(@Standard int standard) {
+ mStandard = standard;
+ return this;
+ }
+ /**
+ * Sets whether it's MISO.
+ */
+ @NonNull
+ public Builder setMiso(boolean isMiso) {
+ mIsMiso = isMiso;
+ return this;
+ }
+ /**
+ * Sets Physical Layer Pipe (PLP) Mode.
+ */
+ @NonNull
+ public Builder setPlpMode(@PlpMode int plpMode) {
+ mPlpMode = plpMode;
+ return this;
+ }
+ /**
+ * Sets Physical Layer Pipe (PLP) ID.
+ */
+ @NonNull
+ public Builder setPlpId(int plpId) {
+ mPlpId = plpId;
+ return this;
+ }
+ /**
+ * Sets Physical Layer Pipe (PLP) group ID.
+ */
+ @NonNull
+ public Builder setPlpGroupId(int plpGroupId) {
+ mPlpGroupId = plpGroupId;
+ return this;
+ }
+
+ /**
+ * Builds a {@link DvbtFrontendSettings} object.
+ */
+ @NonNull
+ public DvbtFrontendSettings build() {
+ return new DvbtFrontendSettings(mFrequency, mTransmissionMode, mBandwidth,
+ mConstellation, mHierarchy, mHpCoderate, mLpCoderate, mGuardInterval,
+ mIsHighPriority, mStandard, mIsMiso, mPlpMode, mPlpId, mPlpGroupId);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
+ }
+
+ @Override
+ public int getType() {
+ return FrontendSettings.TYPE_DVBT;
+ }
+}
diff --git a/media/java/android/media/RouteSessionInfo.aidl b/media/java/android/media/tv/tuner/frontend/FrontendCallback.java
similarity index 66%
copy from media/java/android/media/RouteSessionInfo.aidl
copy to media/java/android/media/tv/tuner/frontend/FrontendCallback.java
index fb5d836..9c4f460 100644
--- a/media/java/android/media/RouteSessionInfo.aidl
+++ b/media/java/android/media/tv/tuner/frontend/FrontendCallback.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 The Android Open Source Project
+ * 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.
@@ -14,6 +14,17 @@
* limitations under the License.
*/
-package android.media;
+package android.media.tv.tuner.frontend;
-parcelable RouteSessionInfo;
+/**
+ * Frontend Callback.
+ *
+ * @hide
+ */
+public interface FrontendCallback {
+
+ /**
+ * Invoked when there is a frontend event.
+ */
+ void onEvent(int frontendEventType);
+}
diff --git a/media/java/android/media/RouteSessionInfo.aidl b/media/java/android/media/tv/tuner/frontend/FrontendCapabilities.java
similarity index 81%
copy from media/java/android/media/RouteSessionInfo.aidl
copy to media/java/android/media/tv/tuner/frontend/FrontendCapabilities.java
index fb5d836..e4f66b8 100644
--- a/media/java/android/media/RouteSessionInfo.aidl
+++ b/media/java/android/media/tv/tuner/frontend/FrontendCapabilities.java
@@ -14,6 +14,12 @@
* limitations under the License.
*/
-package android.media;
+package android.media.tv.tuner.frontend;
-parcelable RouteSessionInfo;
+/**
+ * Frontend capabilities.
+ *
+ * @hide
+ */
+public abstract class FrontendCapabilities {
+}
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendInfo.java b/media/java/android/media/tv/tuner/frontend/FrontendInfo.java
new file mode 100644
index 0000000..360c84a
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/FrontendInfo.java
@@ -0,0 +1,114 @@
+/*
+ * 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.media.tv.tuner.frontend;
+
+import android.annotation.NonNull;
+import android.media.tv.tuner.frontend.FrontendSettings.Type;
+import android.media.tv.tuner.frontend.FrontendStatus.FrontendStatusType;
+import android.util.Range;
+
+/**
+ * This class is used to specify meta information of a frontend.
+ *
+ * @hide
+ */
+public class FrontendInfo {
+ private final int mId;
+ private final int mType;
+ private final Range<Integer> mFrequencyRange;
+ private final Range<Integer> mSymbolRateRange;
+ private final int mAcquireRange;
+ private final int mExclusiveGroupId;
+ private final int[] mStatusCaps;
+ private final FrontendCapabilities mFrontendCap;
+
+ private FrontendInfo(int id, int type, int minFrequency, int maxFrequency, int minSymbolRate,
+ int maxSymbolRate, int acquireRange, int exclusiveGroupId, int[] statusCaps,
+ FrontendCapabilities frontendCap) {
+ mId = id;
+ mType = type;
+ mFrequencyRange = new Range<>(minFrequency, maxFrequency);
+ mSymbolRateRange = new Range<>(minSymbolRate, maxSymbolRate);
+ mAcquireRange = acquireRange;
+ mExclusiveGroupId = exclusiveGroupId;
+ mStatusCaps = statusCaps;
+ mFrontendCap = frontendCap;
+ }
+
+ /**
+ * Gets frontend ID.
+ */
+ public int getId() {
+ return mId;
+ }
+ /**
+ * Gets frontend type.
+ */
+ @Type
+ public int getType() {
+ return mType;
+ }
+
+ /**
+ * Gets supported frequency range in Hz.
+ */
+ @NonNull
+ public Range<Integer> getFrequencyRange() {
+ return mFrequencyRange;
+ }
+
+ /**
+ * Gets symbol rate range in symbols per second.
+ */
+ @NonNull
+ public Range<Integer> getSymbolRateRange() {
+ return mSymbolRateRange;
+ }
+
+ /**
+ * Gets acquire range in Hz.
+ *
+ * <p>The maximum frequency difference the frontend can detect.
+ */
+ public int getAcquireRange() {
+ return mAcquireRange;
+ }
+ /**
+ * Gets exclusive group ID.
+ *
+ * <p>Frontends with the same exclusive group ID indicates they can't function at same time. For
+ * instance, they share some hardware modules.
+ */
+ public int getExclusiveGroupId() {
+ return mExclusiveGroupId;
+ }
+ /**
+ * Gets status capabilities.
+ *
+ * @return An array of supported status types.
+ */
+ @FrontendStatusType
+ public int[] getStatusCapabilities() {
+ return mStatusCaps;
+ }
+ /**
+ * Gets frontend capabilities.
+ */
+ public FrontendCapabilities getFrontendCapability() {
+ return mFrontendCap;
+ }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/FrontendSettings.java
new file mode 100644
index 0000000..617d608
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/FrontendSettings.java
@@ -0,0 +1,124 @@
+/*
+ * 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.media.tv.tuner.frontend;
+
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.hardware.tv.tuner.V1_0.Constants;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Frontend settings for tune and scan operations.
+ *
+ * @hide
+ */
+public abstract class FrontendSettings {
+ /** @hide */
+ @IntDef({TYPE_UNDEFINED, TYPE_ANALOG, TYPE_ATSC, TYPE_ATSC3, TYPE_DVBC, TYPE_DVBS, TYPE_DVBT,
+ TYPE_ISDBS, TYPE_ISDBS3, TYPE_ISDBT})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Type {}
+
+ /**
+ * Undefined frontend type.
+ */
+ public static final int TYPE_UNDEFINED = Constants.FrontendType.UNDEFINED;
+ /**
+ * Analog frontend type.
+ */
+ public static final int TYPE_ANALOG = Constants.FrontendType.ANALOG;
+ /**
+ * Advanced Television Systems Committee (ATSC) frontend type.
+ */
+ public static final int TYPE_ATSC = Constants.FrontendType.ATSC;
+ /**
+ * Advanced Television Systems Committee 3.0 (ATSC-3) frontend type.
+ */
+ public static final int TYPE_ATSC3 = Constants.FrontendType.ATSC3;
+ /**
+ * Digital Video Broadcasting-Cable (DVB-C) frontend type.
+ */
+ public static final int TYPE_DVBC = Constants.FrontendType.DVBC;
+ /**
+ * Digital Video Broadcasting-Satellite (DVB-S) frontend type.
+ */
+ public static final int TYPE_DVBS = Constants.FrontendType.DVBS;
+ /**
+ * Digital Video Broadcasting-Terrestrial (DVB-T) frontend type.
+ */
+ public static final int TYPE_DVBT = Constants.FrontendType.DVBT;
+ /**
+ * Integrated Services Digital Broadcasting-Satellite (ISDB-S) frontend type.
+ */
+ public static final int TYPE_ISDBS = Constants.FrontendType.ISDBS;
+ /**
+ * Integrated Services Digital Broadcasting-Satellite 3 (ISDB-S3) frontend type.
+ */
+ public static final int TYPE_ISDBS3 = Constants.FrontendType.ISDBS3;
+ /**
+ * Integrated Services Digital Broadcasting-Terrestrial (ISDB-T) frontend type.
+ */
+ public static final int TYPE_ISDBT = Constants.FrontendType.ISDBT;
+
+ private final int mFrequency;
+
+ /** @hide */
+ public FrontendSettings(int frequency) {
+ mFrequency = frequency;
+ }
+
+ /**
+ * Returns the frontend type.
+ */
+ @Type
+ public abstract int getType();
+
+ /**
+ * Gets the frequency.
+ *
+ * @return the frequency in Hz.
+ */
+ public int getFrequency() {
+ return mFrequency;
+ }
+
+ /**
+ * Builder for {@link FrontendSettings}.
+ *
+ * @param <T> The subclass to be built.
+ */
+ public abstract static class Builder<T extends Builder<T>> {
+ /* package */ int mFrequency;
+
+ /* package */ Builder() {}
+
+ /**
+ * Sets frequency in Hz.
+ */
+ @NonNull
+ @IntRange(from = 1)
+ public T setFrequency(int frequency) {
+ mFrequency = frequency;
+ return self();
+ }
+
+ /* package */ abstract T self();
+ }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/FrontendStatus.java b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
new file mode 100644
index 0000000..088adff
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/FrontendStatus.java
@@ -0,0 +1,469 @@
+/*
+ * 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.media.tv.tuner.frontend;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.Lnb;
+import android.media.tv.tuner.TunerConstants.FrontendInnerFec;
+import android.media.tv.tuner.TunerConstants.FrontendModulation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Frontend status
+ *
+ * @hide
+ */
+public class FrontendStatus {
+
+ /** @hide */
+ @IntDef({FRONTEND_STATUS_TYPE_DEMOD_LOCK, FRONTEND_STATUS_TYPE_SNR, FRONTEND_STATUS_TYPE_BER,
+ FRONTEND_STATUS_TYPE_PER, FRONTEND_STATUS_TYPE_PRE_BER,
+ FRONTEND_STATUS_TYPE_SIGNAL_QUALITY, FRONTEND_STATUS_TYPE_SIGNAL_STRENGTH,
+ FRONTEND_STATUS_TYPE_SYMBOL_RATE, FRONTEND_STATUS_TYPE_FEC,
+ FRONTEND_STATUS_TYPE_MODULATION, FRONTEND_STATUS_TYPE_SPECTRAL,
+ FRONTEND_STATUS_TYPE_LNB_VOLTAGE, FRONTEND_STATUS_TYPE_PLP_ID,
+ FRONTEND_STATUS_TYPE_EWBS, FRONTEND_STATUS_TYPE_AGC, FRONTEND_STATUS_TYPE_LNA,
+ FRONTEND_STATUS_TYPE_LAYER_ERROR, FRONTEND_STATUS_TYPE_VBER_CN,
+ FRONTEND_STATUS_TYPE_LBER_CN, FRONTEND_STATUS_TYPE_XER_CN, FRONTEND_STATUS_TYPE_MER,
+ FRONTEND_STATUS_TYPE_FREQ_OFFSET, FRONTEND_STATUS_TYPE_HIERARCHY,
+ FRONTEND_STATUS_TYPE_RF_LOCK, FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FrontendStatusType {}
+
+ /**
+ * Lock status for Demod.
+ */
+ public static final int FRONTEND_STATUS_TYPE_DEMOD_LOCK =
+ Constants.FrontendStatusType.DEMOD_LOCK;
+ /**
+ * Signal to Noise Ratio.
+ */
+ public static final int FRONTEND_STATUS_TYPE_SNR = Constants.FrontendStatusType.SNR;
+ /**
+ * Bit Error Ratio.
+ */
+ public static final int FRONTEND_STATUS_TYPE_BER = Constants.FrontendStatusType.BER;
+ /**
+ * Packages Error Ratio.
+ */
+ public static final int FRONTEND_STATUS_TYPE_PER = Constants.FrontendStatusType.PER;
+ /**
+ * Bit Error Ratio before FEC.
+ */
+ public static final int FRONTEND_STATUS_TYPE_PRE_BER = Constants.FrontendStatusType.PRE_BER;
+ /**
+ * Signal Quality (0..100). Good data over total data in percent can be
+ * used as a way to present Signal Quality.
+ */
+ public static final int FRONTEND_STATUS_TYPE_SIGNAL_QUALITY =
+ Constants.FrontendStatusType.SIGNAL_QUALITY;
+ /**
+ * Signal Strength.
+ */
+ public static final int FRONTEND_STATUS_TYPE_SIGNAL_STRENGTH =
+ Constants.FrontendStatusType.SIGNAL_STRENGTH;
+ /**
+ * Symbol Rate in symbols per second.
+ */
+ public static final int FRONTEND_STATUS_TYPE_SYMBOL_RATE =
+ Constants.FrontendStatusType.SYMBOL_RATE;
+ /**
+ * Forward Error Correction Type.
+ */
+ public static final int FRONTEND_STATUS_TYPE_FEC = Constants.FrontendStatusType.FEC;
+ /**
+ * Modulation Type.
+ */
+ public static final int FRONTEND_STATUS_TYPE_MODULATION =
+ Constants.FrontendStatusType.MODULATION;
+ /**
+ * Spectral Inversion Type.
+ */
+ public static final int FRONTEND_STATUS_TYPE_SPECTRAL = Constants.FrontendStatusType.SPECTRAL;
+ /**
+ * LNB Voltage.
+ */
+ public static final int FRONTEND_STATUS_TYPE_LNB_VOLTAGE =
+ Constants.FrontendStatusType.LNB_VOLTAGE;
+ /**
+ * Physical Layer Pipe ID.
+ */
+ public static final int FRONTEND_STATUS_TYPE_PLP_ID = Constants.FrontendStatusType.PLP_ID;
+ /**
+ * Status for Emergency Warning Broadcasting System.
+ */
+ public static final int FRONTEND_STATUS_TYPE_EWBS = Constants.FrontendStatusType.EWBS;
+ /**
+ * Automatic Gain Control.
+ */
+ public static final int FRONTEND_STATUS_TYPE_AGC = Constants.FrontendStatusType.AGC;
+ /**
+ * Low Noise Amplifier.
+ */
+ public static final int FRONTEND_STATUS_TYPE_LNA = Constants.FrontendStatusType.LNA;
+ /**
+ * Error status by layer.
+ */
+ public static final int FRONTEND_STATUS_TYPE_LAYER_ERROR =
+ Constants.FrontendStatusType.LAYER_ERROR;
+ /**
+ * CN value by VBER.
+ */
+ public static final int FRONTEND_STATUS_TYPE_VBER_CN = Constants.FrontendStatusType.VBER_CN;
+ /**
+ * CN value by LBER.
+ */
+ public static final int FRONTEND_STATUS_TYPE_LBER_CN = Constants.FrontendStatusType.LBER_CN;
+ /**
+ * CN value by XER.
+ */
+ public static final int FRONTEND_STATUS_TYPE_XER_CN = Constants.FrontendStatusType.XER_CN;
+ /**
+ * Modulation Error Ratio.
+ */
+ public static final int FRONTEND_STATUS_TYPE_MER = Constants.FrontendStatusType.MER;
+ /**
+ * Difference between tuning frequency and actual locked frequency.
+ */
+ public static final int FRONTEND_STATUS_TYPE_FREQ_OFFSET =
+ Constants.FrontendStatusType.FREQ_OFFSET;
+ /**
+ * Hierarchy for DVBT.
+ */
+ public static final int FRONTEND_STATUS_TYPE_HIERARCHY = Constants.FrontendStatusType.HIERARCHY;
+ /**
+ * Lock status for RF.
+ */
+ public static final int FRONTEND_STATUS_TYPE_RF_LOCK = Constants.FrontendStatusType.RF_LOCK;
+ /**
+ * PLP information in a frequency band for ATSC-3.0 frontend.
+ */
+ public static final int FRONTEND_STATUS_TYPE_ATSC3_PLP_INFO =
+ Constants.FrontendStatusType.ATSC3_PLP_INFO;
+
+
+ private Boolean mIsDemodLocked;
+ private Integer mSnr;
+ private Integer mBer;
+ private Integer mPer;
+ private Integer mPerBer;
+ private Integer mSignalQuality;
+ private Integer mSignalStrength;
+ private Integer mSymbolRate;
+ private Long mInnerFec;
+ private Integer mModulation;
+ private Integer mInversion;
+ private Integer mLnbVoltage;
+ private Integer mPlpId;
+ private Boolean mIsEwbs;
+ private Integer mAgc;
+ private Boolean mIsLnaOn;
+ private boolean[] mIsLayerErrors;
+ private Integer mVberCn;
+ private Integer mLberCn;
+ private Integer mXerCn;
+ private Integer mMer;
+ private Integer mFreqOffset;
+ private Integer mHierarchy;
+ private Boolean mIsRfLocked;
+ private Atsc3PlpInfo[] mPlpInfo;
+
+ // Constructed and fields set by JNI code.
+ private FrontendStatus() {
+ }
+
+ /**
+ * Lock status for Demod.
+ */
+ public boolean isDemodLocked() {
+ if (mIsDemodLocked == null) {
+ throw new IllegalStateException();
+ }
+ return mIsDemodLocked;
+ }
+ /**
+ * Gets Signal to Noise Ratio in thousandths of a deciBel (0.001dB).
+ */
+ public int getSnr() {
+ if (mSnr == null) {
+ throw new IllegalStateException();
+ }
+ return mSnr;
+ }
+ /**
+ * Gets Bit Error Ratio.
+ *
+ * <p>The number of error bit per 1 billion bits.
+ */
+ public int getBer() {
+ if (mBer == null) {
+ throw new IllegalStateException();
+ }
+ return mBer;
+ }
+
+ /**
+ * Gets Packages Error Ratio.
+ *
+ * <p>The number of error package per 1 billion packages.
+ */
+ public int getPer() {
+ if (mPer == null) {
+ throw new IllegalStateException();
+ }
+ return mPer;
+ }
+ /**
+ * Gets Bit Error Ratio before Forward Error Correction (FEC).
+ *
+ * <p>The number of error bit per 1 billion bits before FEC.
+ */
+ public int getPerBer() {
+ if (mPerBer == null) {
+ throw new IllegalStateException();
+ }
+ return mPerBer;
+ }
+ /**
+ * Gets Signal Quality in percent.
+ */
+ public int getSignalQuality() {
+ if (mSignalQuality == null) {
+ throw new IllegalStateException();
+ }
+ return mSignalQuality;
+ }
+ /**
+ * Gets Signal Strength in thousandths of a dBm (0.001dBm).
+ */
+ public int getSignalStrength() {
+ if (mSignalStrength == null) {
+ throw new IllegalStateException();
+ }
+ return mSignalStrength;
+ }
+ /**
+ * Gets symbol rate in symbols per second.
+ */
+ public int getSymbolRate() {
+ if (mSymbolRate == null) {
+ throw new IllegalStateException();
+ }
+ return mSymbolRate;
+ }
+ /**
+ * Gets Inner Forward Error Correction type as specified in ETSI EN 300 468 V1.15.1
+ * and ETSI EN 302 307-2 V1.1.1.
+ */
+ @FrontendInnerFec
+ public long getFec() {
+ if (mInnerFec == null) {
+ throw new IllegalStateException();
+ }
+ return mInnerFec;
+ }
+ /**
+ * Gets modulation.
+ */
+ @FrontendModulation
+ public int getModulation() {
+ if (mModulation == null) {
+ throw new IllegalStateException();
+ }
+ return mModulation;
+ }
+ /**
+ * Gets Spectral Inversion for DVBC.
+ */
+ @DvbcFrontendSettings.SpectralInversion
+ public int getSpectralInversion() {
+ if (mInversion == null) {
+ throw new IllegalStateException();
+ }
+ return mInversion;
+ }
+ /**
+ * Gets Power Voltage Type for LNB.
+ */
+ @Lnb.Voltage
+ public int getLnbVoltage() {
+ if (mLnbVoltage == null) {
+ throw new IllegalStateException();
+ }
+ return mLnbVoltage;
+ }
+ /**
+ * Gets Physical Layer Pipe ID.
+ */
+ public int getPlpId() {
+ if (mPlpId == null) {
+ throw new IllegalStateException();
+ }
+ return mPlpId;
+ }
+ /**
+ * Checks whether it's Emergency Warning Broadcasting System
+ */
+ public boolean isEwbs() {
+ if (mIsEwbs == null) {
+ throw new IllegalStateException();
+ }
+ return mIsEwbs;
+ }
+ /**
+ * Gets Automatic Gain Control value which is normalized from 0 to 255.
+ */
+ public int getAgc() {
+ if (mAgc == null) {
+ throw new IllegalStateException();
+ }
+ return mAgc;
+ }
+ /**
+ * Checks LNA (Low Noise Amplifier) is on or not.
+ */
+ public boolean isLnaOn() {
+ if (mIsLnaOn == null) {
+ throw new IllegalStateException();
+ }
+ return mIsLnaOn;
+ }
+ /**
+ * Gets Error status by layer.
+ */
+ @NonNull
+ public boolean[] getLayerErrors() {
+ if (mIsLayerErrors == null) {
+ throw new IllegalStateException();
+ }
+ return mIsLayerErrors;
+ }
+ /**
+ * Gets CN value by VBER in thousandths of a deciBel (0.001dB).
+ */
+ public int getVberCn() {
+ if (mVberCn == null) {
+ throw new IllegalStateException();
+ }
+ return mVberCn;
+ }
+ /**
+ * Gets CN value by LBER in thousandths of a deciBel (0.001dB).
+ */
+ public int getLberCn() {
+ if (mLberCn == null) {
+ throw new IllegalStateException();
+ }
+ return mLberCn;
+ }
+ /**
+ * Gets CN value by XER in thousandths of a deciBel (0.001dB).
+ */
+ public int getXerCn() {
+ if (mXerCn == null) {
+ throw new IllegalStateException();
+ }
+ return mXerCn;
+ }
+ /**
+ * Gets Modulation Error Ratio in thousandths of a deciBel (0.001dB).
+ */
+ public int getMer() {
+ if (mMer == null) {
+ throw new IllegalStateException();
+ }
+ return mMer;
+ }
+ /**
+ * Gets frequency difference in Hz.
+ *
+ * <p>Difference between tuning frequency and actual locked frequency.
+ */
+ public int getFreqOffset() {
+ if (mFreqOffset == null) {
+ throw new IllegalStateException();
+ }
+ return mFreqOffset;
+ }
+ /**
+ * Gets hierarchy Type for DVBT.
+ */
+ @DvbtFrontendSettings.Hierarchy
+ public int getHierarchy() {
+ if (mHierarchy == null) {
+ throw new IllegalStateException();
+ }
+ return mHierarchy;
+ }
+ /**
+ * Gets lock status for RF.
+ */
+ public boolean isRfLock() {
+ if (mIsRfLocked == null) {
+ throw new IllegalStateException();
+ }
+ return mIsRfLocked;
+ }
+ /**
+ * Gets an array of PLP status for tuned PLPs for ATSC3 frontend.
+ */
+ @NonNull
+ public Atsc3PlpInfo[] getAtsc3PlpInfo() {
+ if (mPlpInfo == null) {
+ throw new IllegalStateException();
+ }
+ return mPlpInfo;
+ }
+
+ /**
+ * Status for each tuning Physical Layer Pipes.
+ */
+ public static class Atsc3PlpInfo {
+ private final int mPlpId;
+ private final boolean mIsLock;
+ private final int mUec;
+
+ private Atsc3PlpInfo(int plpId, boolean isLock, int uec) {
+ mPlpId = plpId;
+ mIsLock = isLock;
+ mUec = uec;
+ }
+
+ /**
+ * Gets Physical Layer Pipe ID.
+ */
+ public int getPlpId() {
+ return mPlpId;
+ }
+ /**
+ * Gets Demod Lock/Unlock status of this particular PLP.
+ */
+ public boolean isLock() {
+ return mIsLock;
+ }
+ /**
+ * Gets Uncorrectable Error Counts (UEC) of this particular PLP since last tune operation.
+ */
+ public int getUec() {
+ return mUec;
+ }
+ }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendCapabilities.java
new file mode 100644
index 0000000..61cba1c
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendCapabilities.java
@@ -0,0 +1,46 @@
+/*
+ * 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.media.tv.tuner.frontend;
+
+/**
+ * ISDBS-3 Capabilities.
+ * @hide
+ */
+public class Isdbs3FrontendCapabilities extends FrontendCapabilities {
+ private final int mModulationCap;
+ private final int mCoderateCap;
+
+ private Isdbs3FrontendCapabilities(int modulationCap, int coderateCap) {
+ mModulationCap = modulationCap;
+ mCoderateCap = coderateCap;
+ }
+
+ /**
+ * Gets modulation capability.
+ */
+ @Isdbs3FrontendSettings.Modulation
+ public int getModulationCapability() {
+ return mModulationCap;
+ }
+ /**
+ * Gets code rate capability.
+ */
+ @Isdbs3FrontendSettings.Coderate
+ public int getCodeRateCapability() {
+ return mCoderateCap;
+ }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java
new file mode 100644
index 0000000..7e6f188
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/Isdbs3FrontendSettings.java
@@ -0,0 +1,303 @@
+/*
+ * 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.media.tv.tuner.frontend;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Frontend settings for ISDBS-3.
+ * @hide
+ */
+public class Isdbs3FrontendSettings extends FrontendSettings {
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "MODULATION_",
+ value = {MODULATION_UNDEFINED, MODULATION_AUTO, MODULATION_MOD_BPSK,
+ MODULATION_MOD_QPSK, MODULATION_MOD_8PSK, MODULATION_MOD_16APSK,
+ MODULATION_MOD_32APSK})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Modulation {}
+
+ /**
+ * Modulation undefined.
+ */
+ public static final int MODULATION_UNDEFINED = Constants.FrontendIsdbs3Modulation.UNDEFINED;
+ /**
+ * Hardware is able to detect and set modulation automatically.
+ */
+ public static final int MODULATION_AUTO = Constants.FrontendIsdbs3Modulation.AUTO;
+ /**
+ * BPSK Modulation.
+ */
+ public static final int MODULATION_MOD_BPSK = Constants.FrontendIsdbs3Modulation.MOD_BPSK;
+ /**
+ * QPSK Modulation.
+ */
+ public static final int MODULATION_MOD_QPSK = Constants.FrontendIsdbs3Modulation.MOD_QPSK;
+ /**
+ * 8PSK Modulation.
+ */
+ public static final int MODULATION_MOD_8PSK = Constants.FrontendIsdbs3Modulation.MOD_8PSK;
+ /**
+ * 16APSK Modulation.
+ */
+ public static final int MODULATION_MOD_16APSK = Constants.FrontendIsdbs3Modulation.MOD_16APSK;
+ /**
+ * 32APSK Modulation.
+ */
+ public static final int MODULATION_MOD_32APSK = Constants.FrontendIsdbs3Modulation.MOD_32APSK;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag = true,
+ prefix = "CODERATE_",
+ value = {CODERATE_UNDEFINED, CODERATE_AUTO, CODERATE_1_3, CODERATE_2_5, CODERATE_1_2,
+ CODERATE_3_5, CODERATE_2_3, CODERATE_3_4, CODERATE_7_9, CODERATE_4_5,
+ CODERATE_5_6, CODERATE_7_8, CODERATE_9_10})
+ public @interface Coderate {}
+
+ /**
+ * Code rate undefined.
+ */
+ public static final int CODERATE_UNDEFINED = Constants.FrontendIsdbs3Coderate.UNDEFINED;
+ /**
+ * Hardware is able to detect and set code rate automatically.
+ */
+ public static final int CODERATE_AUTO = Constants.FrontendIsdbs3Coderate.AUTO;
+ /**
+ * 1_3 code rate.
+ */
+ public static final int CODERATE_1_3 = Constants.FrontendIsdbs3Coderate.CODERATE_1_3;
+ /**
+ * 2_5 code rate.
+ */
+ public static final int CODERATE_2_5 = Constants.FrontendIsdbs3Coderate.CODERATE_2_5;
+ /**
+ * 1_2 code rate.
+ */
+ public static final int CODERATE_1_2 = Constants.FrontendIsdbs3Coderate.CODERATE_1_2;
+ /**
+ * 3_5 code rate.
+ */
+ public static final int CODERATE_3_5 = Constants.FrontendIsdbs3Coderate.CODERATE_3_5;
+ /**
+ * 2_3 code rate.
+ */
+ public static final int CODERATE_2_3 = Constants.FrontendIsdbs3Coderate.CODERATE_2_3;
+ /**
+ * 3_4 code rate.
+ */
+ public static final int CODERATE_3_4 = Constants.FrontendIsdbs3Coderate.CODERATE_3_4;
+ /**
+ * 7_9 code rate.
+ */
+ public static final int CODERATE_7_9 = Constants.FrontendIsdbs3Coderate.CODERATE_7_9;
+ /**
+ * 4_5 code rate.
+ */
+ public static final int CODERATE_4_5 = Constants.FrontendIsdbs3Coderate.CODERATE_4_5;
+ /**
+ * 5_6 code rate.
+ */
+ public static final int CODERATE_5_6 = Constants.FrontendIsdbs3Coderate.CODERATE_5_6;
+ /**
+ * 7_8 code rate.
+ */
+ public static final int CODERATE_7_8 = Constants.FrontendIsdbs3Coderate.CODERATE_7_8;
+ /**
+ * 9_10 code rate.
+ */
+ public static final int CODERATE_9_10 = Constants.FrontendIsdbs3Coderate.CODERATE_9_10;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "ROLLOFF_",
+ value = {ROLLOFF_UNDEFINED, ROLLOFF_0_03})
+ public @interface Rolloff {}
+
+ /**
+ * Roll off type undefined.
+ */
+ public static final int ROLLOFF_UNDEFINED = Constants.FrontendIsdbs3Rolloff.UNDEFINED;
+ /**
+ * 0.03 roll off type.
+ */
+ public static final int ROLLOFF_0_03 = Constants.FrontendIsdbs3Rolloff.ROLLOFF_0_03;
+
+
+ private final int mStreamId;
+ private final int mStreamIdType;
+ private final int mModulation;
+ private final int mCoderate;
+ private final int mSymbolRate;
+ private final int mRolloff;
+
+ private Isdbs3FrontendSettings(int frequency, int streamId, int streamIdType, int modulation,
+ int coderate, int symbolRate, int rolloff) {
+ super(frequency);
+ mStreamId = streamId;
+ mStreamIdType = streamIdType;
+ mModulation = modulation;
+ mCoderate = coderate;
+ mSymbolRate = symbolRate;
+ mRolloff = rolloff;
+ }
+
+ /**
+ * Gets Stream ID.
+ */
+ public int getStreamId() {
+ return mStreamId;
+ }
+ /**
+ * Gets Stream ID Type.
+ */
+ @IsdbsFrontendSettings.StreamIdType
+ public int getStreamIdType() {
+ return mStreamIdType;
+ }
+ /**
+ * Gets Modulation.
+ */
+ @Modulation
+ public int getModulation() {
+ return mModulation;
+ }
+ /**
+ * Gets Code rate.
+ */
+ @Coderate
+ public int getCoderate() {
+ return mCoderate;
+ }
+ /**
+ * Gets Symbol Rate in symbols per second.
+ */
+ public int getSymbolRate() {
+ return mSymbolRate;
+ }
+ /**
+ * Gets Roll off type.
+ */
+ @Rolloff
+ public int getRolloff() {
+ return mRolloff;
+ }
+
+ /**
+ * Creates a builder for {@link Isdbs3FrontendSettings}.
+ *
+ * @param context the context of the caller.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link Isdbs3FrontendSettings}.
+ */
+ public static class Builder extends FrontendSettings.Builder<Builder> {
+ private int mStreamId;
+ private int mStreamIdType;
+ private int mModulation;
+ private int mCoderate;
+ private int mSymbolRate;
+ private int mRolloff;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets Stream ID.
+ */
+ @NonNull
+ public Builder setStreamId(int streamId) {
+ mStreamId = streamId;
+ return this;
+ }
+ /**
+ * Sets StreamIdType.
+ */
+ @NonNull
+ public Builder setStreamIdType(@IsdbsFrontendSettings.StreamIdType int streamIdType) {
+ mStreamIdType = streamIdType;
+ return this;
+ }
+ /**
+ * Sets Modulation.
+ */
+ @NonNull
+ public Builder setModulation(@Modulation int modulation) {
+ mModulation = modulation;
+ return this;
+ }
+ /**
+ * Sets Code rate.
+ */
+ @NonNull
+ public Builder setCoderate(@Coderate int coderate) {
+ mCoderate = coderate;
+ return this;
+ }
+ /**
+ * Sets Symbol Rate in symbols per second.
+ */
+ @NonNull
+ public Builder setSymbolRate(int symbolRate) {
+ mSymbolRate = symbolRate;
+ return this;
+ }
+ /**
+ * Sets Roll off type.
+ */
+ @NonNull
+ public Builder setRolloff(@Rolloff int rolloff) {
+ mRolloff = rolloff;
+ return this;
+ }
+
+ /**
+ * Builds a {@link Isdbs3FrontendSettings} object.
+ */
+ @NonNull
+ public Isdbs3FrontendSettings build() {
+ return new Isdbs3FrontendSettings(mFrequency, mStreamId, mStreamIdType, mModulation,
+ mCoderate, mSymbolRate, mRolloff);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
+ }
+
+ @Override
+ public int getType() {
+ return FrontendSettings.TYPE_ISDBS3;
+ }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/IsdbsFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendCapabilities.java
new file mode 100644
index 0000000..8e5ecc4
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendCapabilities.java
@@ -0,0 +1,46 @@
+/*
+ * 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.media.tv.tuner.frontend;
+
+/**
+ * ISDBS Capabilities.
+ * @hide
+ */
+public class IsdbsFrontendCapabilities extends FrontendCapabilities {
+ private final int mModulationCap;
+ private final int mCoderateCap;
+
+ private IsdbsFrontendCapabilities(int modulationCap, int coderateCap) {
+ mModulationCap = modulationCap;
+ mCoderateCap = coderateCap;
+ }
+
+ /**
+ * Gets modulation capability.
+ */
+ @IsdbsFrontendSettings.Modulation
+ public int getModulationCapability() {
+ return mModulationCap;
+ }
+ /**
+ * Gets code rate capability.
+ */
+ @IsdbsFrontendSettings.Coderate
+ public int getCodeRateCapability() {
+ return mCoderateCap;
+ }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java
new file mode 100644
index 0000000..fe100f8
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/IsdbsFrontendSettings.java
@@ -0,0 +1,288 @@
+/*
+ * 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.media.tv.tuner.frontend;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Frontend settings for ISDBS.
+ * @hide
+ */
+public class IsdbsFrontendSettings extends FrontendSettings {
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "STREAM_ID_TYPE_",
+ value = {STREAM_ID_TYPE_ID, STREAM_ID_TYPE_RELATIVE_NUMBER})
+ public @interface StreamIdType {}
+
+ /**
+ * Uses stream ID.
+ */
+ public static final int STREAM_ID_TYPE_ID = Constants.FrontendIsdbsStreamIdType.STREAM_ID;
+ /**
+ * Uses relative number.
+ */
+ public static final int STREAM_ID_TYPE_RELATIVE_NUMBER =
+ Constants.FrontendIsdbsStreamIdType.RELATIVE_STREAM_NUMBER;
+
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "MODULATION_",
+ value = {MODULATION_UNDEFINED, MODULATION_AUTO, MODULATION_MOD_BPSK,
+ MODULATION_MOD_QPSK, MODULATION_MOD_TC8PSK})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Modulation {}
+
+ /**
+ * Modulation undefined.
+ */
+ public static final int MODULATION_UNDEFINED = Constants.FrontendIsdbsModulation.UNDEFINED;
+ /**
+ * Hardware is able to detect and set modulation automatically
+ */
+ public static final int MODULATION_AUTO = Constants.FrontendIsdbsModulation.AUTO;
+ /**
+ * BPSK Modulation.
+ */
+ public static final int MODULATION_MOD_BPSK = Constants.FrontendIsdbsModulation.MOD_BPSK;
+ /**
+ * QPSK Modulation.
+ */
+ public static final int MODULATION_MOD_QPSK = Constants.FrontendIsdbsModulation.MOD_QPSK;
+ /**
+ * TC8PSK Modulation.
+ */
+ public static final int MODULATION_MOD_TC8PSK = Constants.FrontendIsdbsModulation.MOD_TC8PSK;
+
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "CODERATE_",
+ value = {CODERATE_UNDEFINED, CODERATE_AUTO,
+ CODERATE_1_2, CODERATE_2_3, CODERATE_3_4,
+ CODERATE_5_6, CODERATE_7_8})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Coderate {}
+
+ /**
+ * Code rate undefined.
+ */
+ public static final int CODERATE_UNDEFINED = Constants.FrontendIsdbsCoderate.UNDEFINED;
+ /**
+ * Hardware is able to detect and set code rate automatically.
+ */
+ public static final int CODERATE_AUTO = Constants.FrontendIsdbsCoderate.AUTO;
+ /**
+ * 1_2 code rate.
+ */
+ public static final int CODERATE_1_2 = Constants.FrontendIsdbsCoderate.CODERATE_1_2;
+ /**
+ * 2_3 code rate.
+ */
+ public static final int CODERATE_2_3 = Constants.FrontendIsdbsCoderate.CODERATE_2_3;
+ /**
+ * 3_4 code rate.
+ */
+ public static final int CODERATE_3_4 = Constants.FrontendIsdbsCoderate.CODERATE_3_4;
+ /**
+ * 5_6 code rate.
+ */
+ public static final int CODERATE_5_6 = Constants.FrontendIsdbsCoderate.CODERATE_5_6;
+ /**
+ * 7_8 code rate.
+ */
+ public static final int CODERATE_7_8 = Constants.FrontendIsdbsCoderate.CODERATE_7_8;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "ROLLOFF_",
+ value = {ROLLOFF_UNDEFINED, ROLLOFF_0_35})
+ public @interface Rolloff {}
+
+ /**
+ * Roll off type undefined.
+ */
+ public static final int ROLLOFF_UNDEFINED = Constants.FrontendIsdbs3Rolloff.UNDEFINED;
+ /**
+ * 0.35 roll off type.
+ */
+ public static final int ROLLOFF_0_35 = Constants.FrontendIsdbsRolloff.ROLLOFF_0_35;
+
+
+ private final int mStreamId;
+ private final int mStreamIdType;
+ private final int mModulation;
+ private final int mCoderate;
+ private final int mSymbolRate;
+ private final int mRolloff;
+
+ private IsdbsFrontendSettings(int frequency, int streamId, int streamIdType, int modulation,
+ int coderate, int symbolRate, int rolloff) {
+ super(frequency);
+ mStreamId = streamId;
+ mStreamIdType = streamIdType;
+ mModulation = modulation;
+ mCoderate = coderate;
+ mSymbolRate = symbolRate;
+ mRolloff = rolloff;
+ }
+
+ /**
+ * Gets Stream ID.
+ */
+ public int getStreamId() {
+ return mStreamId;
+ }
+ /**
+ * Gets Stream ID Type.
+ */
+ @StreamIdType
+ public int getStreamIdType() {
+ return mStreamIdType;
+ }
+ /**
+ * Gets Modulation.
+ */
+ @Modulation
+ public int getModulation() {
+ return mModulation;
+ }
+ /**
+ * Gets Code rate.
+ */
+ @Coderate
+ public int getCoderate() {
+ return mCoderate;
+ }
+ /**
+ * Gets Symbol Rate in symbols per second.
+ */
+ public int getSymbolRate() {
+ return mSymbolRate;
+ }
+ /**
+ * Gets Roll off type.
+ */
+ @Rolloff
+ public int getRolloff() {
+ return mRolloff;
+ }
+
+ /**
+ * Creates a builder for {@link IsdbsFrontendSettings}.
+ *
+ * @param context the context of the caller.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link IsdbsFrontendSettings}.
+ */
+ public static class Builder extends FrontendSettings.Builder<Builder> {
+ private int mStreamId;
+ private int mStreamIdType;
+ private int mModulation;
+ private int mCoderate;
+ private int mSymbolRate;
+ private int mRolloff;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets Stream ID.
+ */
+ @NonNull
+ public Builder setStreamId(int streamId) {
+ mStreamId = streamId;
+ return this;
+ }
+ /**
+ * Sets StreamIdType.
+ */
+ @NonNull
+ public Builder setStreamIdType(@StreamIdType int streamIdType) {
+ mStreamIdType = streamIdType;
+ return this;
+ }
+ /**
+ * Sets Modulation.
+ */
+ @NonNull
+ public Builder setModulation(@Modulation int modulation) {
+ mModulation = modulation;
+ return this;
+ }
+ /**
+ * Sets Code rate.
+ */
+ @NonNull
+ public Builder setCoderate(@Coderate int coderate) {
+ mCoderate = coderate;
+ return this;
+ }
+ /**
+ * Sets Symbol Rate in symbols per second.
+ */
+ @NonNull
+ public Builder setSymbolRate(int symbolRate) {
+ mSymbolRate = symbolRate;
+ return this;
+ }
+ /**
+ * Sets Roll off type.
+ */
+ @NonNull
+ public Builder setRolloff(@Rolloff int rolloff) {
+ mRolloff = rolloff;
+ return this;
+ }
+
+ /**
+ * Builds a {@link IsdbsFrontendSettings} object.
+ */
+ @NonNull
+ public IsdbsFrontendSettings build() {
+ return new IsdbsFrontendSettings(mFrequency, mStreamId, mStreamIdType, mModulation,
+ mCoderate, mSymbolRate, mRolloff);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
+ }
+
+ @Override
+ public int getType() {
+ return FrontendSettings.TYPE_ISDBS;
+ }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/IsdbtFrontendCapabilities.java b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendCapabilities.java
new file mode 100644
index 0000000..19f04de
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendCapabilities.java
@@ -0,0 +1,74 @@
+/*
+ * 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.media.tv.tuner.frontend;
+
+/**
+ * ISDBT Capabilities.
+ * @hide
+ */
+public class IsdbtFrontendCapabilities extends FrontendCapabilities {
+ private final int mModeCap;
+ private final int mBandwidthCap;
+ private final int mModulationCap;
+ private final int mCoderateCap;
+ private final int mGuardIntervalCap;
+
+ private IsdbtFrontendCapabilities(int modeCap, int bandwidthCap, int modulationCap,
+ int coderateCap, int guardIntervalCap) {
+ mModeCap = modeCap;
+ mBandwidthCap = bandwidthCap;
+ mModulationCap = modulationCap;
+ mCoderateCap = coderateCap;
+ mGuardIntervalCap = guardIntervalCap;
+ }
+
+ /**
+ * Gets mode capability.
+ */
+ @IsdbtFrontendSettings.Mode
+ public int getModeCapability() {
+ return mModeCap;
+ }
+ /**
+ * Gets bandwidth capability.
+ */
+ @IsdbtFrontendSettings.Bandwidth
+ public int getBandwidthCapability() {
+ return mBandwidthCap;
+ }
+ /**
+ * Gets modulation capability.
+ */
+ @IsdbtFrontendSettings.Modulation
+ public int getModulationCapability() {
+ return mModulationCap;
+ }
+ /**
+ * Gets code rate capability.
+ */
+ @DvbtFrontendSettings.Coderate
+ public int getCodeRateCapability() {
+ return mCoderateCap;
+ }
+ /**
+ * Gets guard interval capability.
+ */
+ @DvbtFrontendSettings.GuardInterval
+ public int getGuardIntervalCapability() {
+ return mGuardIntervalCap;
+ }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java
new file mode 100644
index 0000000..1510193
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/IsdbtFrontendSettings.java
@@ -0,0 +1,262 @@
+/*
+ * 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.media.tv.tuner.frontend;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.content.Context;
+import android.hardware.tv.tuner.V1_0.Constants;
+import android.media.tv.tuner.TunerUtils;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Frontend settings for ISDBT.
+ * @hide
+ */
+public class IsdbtFrontendSettings extends FrontendSettings {
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "MODULATION_",
+ value = {MODULATION_UNDEFINED, MODULATION_AUTO, MODULATION_MOD_DQPSK,
+ MODULATION_MOD_QPSK, MODULATION_MOD_16QAM, MODULATION_MOD_64QAM})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Modulation {}
+
+ /**
+ * Modulation undefined.
+ */
+ public static final int MODULATION_UNDEFINED = Constants.FrontendIsdbtModulation.UNDEFINED;
+ /**
+ * Hardware is able to detect and set modulation automatically
+ */
+ public static final int MODULATION_AUTO = Constants.FrontendIsdbtModulation.AUTO;
+ /**
+ * DQPSK Modulation.
+ */
+ public static final int MODULATION_MOD_DQPSK = Constants.FrontendIsdbtModulation.MOD_DQPSK;
+ /**
+ * QPSK Modulation.
+ */
+ public static final int MODULATION_MOD_QPSK = Constants.FrontendIsdbtModulation.MOD_QPSK;
+ /**
+ * 16QAM Modulation.
+ */
+ public static final int MODULATION_MOD_16QAM = Constants.FrontendIsdbtModulation.MOD_16QAM;
+ /**
+ * 64QAM Modulation.
+ */
+ public static final int MODULATION_MOD_64QAM = Constants.FrontendIsdbtModulation.MOD_64QAM;
+
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "MODE_",
+ value = {MODE_UNDEFINED, MODE_AUTO, MODE_1, MODE_2, MODE_3})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Mode {}
+
+ /**
+ * Mode undefined.
+ */
+ public static final int MODE_UNDEFINED = Constants.FrontendIsdbtMode.UNDEFINED;
+ /**
+ * Hardware is able to detect and set Mode automatically.
+ */
+ public static final int MODE_AUTO = Constants.FrontendIsdbtMode.AUTO;
+ /**
+ * Mode 1
+ */
+ public static final int MODE_1 = Constants.FrontendIsdbtMode.MODE_1;
+ /**
+ * Mode 2
+ */
+ public static final int MODE_2 = Constants.FrontendIsdbtMode.MODE_2;
+ /**
+ * Mode 3
+ */
+ public static final int MODE_3 = Constants.FrontendIsdbtMode.MODE_3;
+
+
+ /** @hide */
+ @IntDef(flag = true,
+ prefix = "BANDWIDTH_",
+ value = {BANDWIDTH_UNDEFINED, BANDWIDTH_AUTO, BANDWIDTH_8MHZ, BANDWIDTH_7MHZ,
+ BANDWIDTH_6MHZ})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Bandwidth {}
+
+ /**
+ * Bandwidth undefined.
+ */
+ public static final int BANDWIDTH_UNDEFINED = Constants.FrontendIsdbtBandwidth.UNDEFINED;
+ /**
+ * Hardware is able to detect and set Bandwidth automatically.
+ */
+ public static final int BANDWIDTH_AUTO = Constants.FrontendIsdbtBandwidth.AUTO;
+ /**
+ * 8 MHz bandwidth.
+ */
+ public static final int BANDWIDTH_8MHZ = Constants.FrontendIsdbtBandwidth.BANDWIDTH_8MHZ;
+ /**
+ * 7 MHz bandwidth.
+ */
+ public static final int BANDWIDTH_7MHZ = Constants.FrontendIsdbtBandwidth.BANDWIDTH_7MHZ;
+ /**
+ * 6 MHz bandwidth.
+ */
+ public static final int BANDWIDTH_6MHZ = Constants.FrontendIsdbtBandwidth.BANDWIDTH_6MHZ;
+
+ private final int mModulation;
+ private final int mBandwidth;
+ private final int mCoderate;
+ private final int mGuardInterval;
+ private final int mServiceAreaId;
+
+ private IsdbtFrontendSettings(int frequency, int modulation, int bandwidth, int coderate,
+ int guardInterval, int serviceAreaId) {
+ super(frequency);
+ mModulation = modulation;
+ mBandwidth = bandwidth;
+ mCoderate = coderate;
+ mGuardInterval = guardInterval;
+ mServiceAreaId = serviceAreaId;
+ }
+
+ /**
+ * Gets Modulation.
+ */
+ @Modulation
+ public int getModulation() {
+ return mModulation;
+ }
+ /**
+ * Gets Bandwidth.
+ */
+ @Bandwidth
+ public int getBandwidth() {
+ return mBandwidth;
+ }
+ /**
+ * Gets Code rate.
+ */
+ @DvbtFrontendSettings.Coderate
+ public int getCoderate() {
+ return mCoderate;
+ }
+ /**
+ * Gets Guard Interval.
+ */
+ @DvbtFrontendSettings.GuardInterval
+ public int getGuardInterval() {
+ return mGuardInterval;
+ }
+ /**
+ * Gets Service Area ID.
+ */
+ public int getServiceAreaId() {
+ return mServiceAreaId;
+ }
+
+ /**
+ * Creates a builder for {@link IsdbtFrontendSettings}.
+ *
+ * @param context the context of the caller.
+ */
+ @RequiresPermission(android.Manifest.permission.ACCESS_TV_TUNER)
+ @NonNull
+ public static Builder builder(@NonNull Context context) {
+ TunerUtils.checkTunerPermission(context);
+ return new Builder();
+ }
+
+ /**
+ * Builder for {@link IsdbtFrontendSettings}.
+ */
+ public static class Builder extends FrontendSettings.Builder<Builder> {
+ private int mModulation;
+ private int mBandwidth;
+ private int mCoderate;
+ private int mGuardInterval;
+ private int mServiceAreaId;
+
+ private Builder() {
+ }
+
+ /**
+ * Sets Modulation.
+ */
+ @NonNull
+ public Builder setModulation(@Modulation int modulation) {
+ mModulation = modulation;
+ return this;
+ }
+ /**
+ * Sets Bandwidth.
+ */
+ @NonNull
+ public Builder setBandwidth(@Bandwidth int bandwidth) {
+ mBandwidth = bandwidth;
+ return this;
+ }
+ /**
+ * Sets Code rate.
+ */
+ @NonNull
+ public Builder setCoderate(@DvbtFrontendSettings.Coderate int coderate) {
+ mCoderate = coderate;
+ return this;
+ }
+ /**
+ * Sets Guard Interval.
+ */
+ @NonNull
+ public Builder setGuardInterval(@DvbtFrontendSettings.GuardInterval int guardInterval) {
+ mGuardInterval = guardInterval;
+ return this;
+ }
+ /**
+ * Sets Service Area ID.
+ */
+ @NonNull
+ public Builder setServiceAreaId(int serviceAreaId) {
+ mServiceAreaId = serviceAreaId;
+ return this;
+ }
+
+ /**
+ * Builds a {@link IsdbtFrontendSettings} object.
+ */
+ @NonNull
+ public IsdbtFrontendSettings build() {
+ return new IsdbtFrontendSettings(
+ mFrequency, mModulation, mBandwidth, mCoderate, mGuardInterval, mServiceAreaId);
+ }
+
+ @Override
+ Builder self() {
+ return this;
+ }
+ }
+
+ @Override
+ public int getType() {
+ return FrontendSettings.TYPE_ISDBT;
+ }
+}
diff --git a/media/java/android/media/tv/tuner/frontend/ScanCallback.java b/media/java/android/media/tv/tuner/frontend/ScanCallback.java
new file mode 100644
index 0000000..5e7d218
--- /dev/null
+++ b/media/java/android/media/tv/tuner/frontend/ScanCallback.java
@@ -0,0 +1,78 @@
+/*
+ * 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.media.tv.tuner.frontend;
+
+/**
+ * Scan callback.
+ *
+ * @hide
+ */
+public interface ScanCallback {
+ /** Scan locked the signal. */
+ void onLocked(boolean isLocked);
+
+ /** Scan stopped. */
+ void onEnd(boolean isEnd);
+
+ /** scan progress percent (0..100) */
+ void onProgress(int percent);
+
+ /** Signal frequency in Hertz */
+ void onFrequencyReport(int frequency);
+
+ /** Symbols per second */
+ void onSymbolRate(int rate);
+
+ /** Locked Plp Ids for DVBT2 frontend. */
+ void onPlpIds(int[] plpIds);
+
+ /** Locked group Ids for DVBT2 frontend. */
+ void onGroupIds(int[] groupIds);
+
+ /** Stream Ids. */
+ void onInputStreamIds(int[] inputStreamIds);
+
+ /** Locked signal standard. */
+ void onDvbsStandard(@DvbsFrontendSettings.Standard int dvbsStandandard);
+
+ /** Locked signal standard. */
+ void onDvbtStandard(@DvbtFrontendSettings.Standard int dvbtStandard);
+
+ /** PLP status in a tuned frequency band for ATSC3 frontend. */
+ void onAtsc3PlpInfos(Atsc3PlpInfo[] atsc3PlpInfos);
+
+ /** PLP information for ATSC3. */
+ class Atsc3PlpInfo {
+ private final int mPlpId;
+ private final boolean mLlsFlag;
+
+ private Atsc3PlpInfo(int plpId, boolean llsFlag) {
+ mPlpId = plpId;
+ mLlsFlag = llsFlag;
+ }
+
+ /** Gets PLP IDs. */
+ public int getPlpId() {
+ return mPlpId;
+ }
+
+ /** Gets LLS flag. */
+ public boolean getLlsFlag() {
+ return mLlsFlag;
+ }
+ }
+}
diff --git a/media/java/android/mtp/MtpPropertyList.java b/media/java/android/mtp/MtpPropertyList.java
index 557f099..53d838d 100644
--- a/media/java/android/mtp/MtpPropertyList.java
+++ b/media/java/android/mtp/MtpPropertyList.java
@@ -16,7 +16,8 @@
package android.mtp;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
import java.util.ArrayList;
import java.util.List;
diff --git a/media/java/android/mtp/MtpStorage.java b/media/java/android/mtp/MtpStorage.java
index c7dbca6..ba75263 100644
--- a/media/java/android/mtp/MtpStorage.java
+++ b/media/java/android/mtp/MtpStorage.java
@@ -16,9 +16,8 @@
package android.mtp;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.storage.StorageVolume;
-import android.provider.MediaStore;
/**
* This class represents a storage unit on an MTP device.
diff --git a/media/java/android/service/media/MediaBrowserService.java b/media/java/android/service/media/MediaBrowserService.java
index 86a1076..06adf30 100644
--- a/media/java/android/service/media/MediaBrowserService.java
+++ b/media/java/android/service/media/MediaBrowserService.java
@@ -21,8 +21,8 @@
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
-import android.annotation.UnsupportedAppUsage;
import android.app.Service;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
diff --git a/media/jni/Android.bp b/media/jni/Android.bp
index 4ca23a1..aeacd8f 100644
--- a/media/jni/Android.bp
+++ b/media/jni/Android.bp
@@ -19,6 +19,7 @@
"android_media_MediaProfiles.cpp",
"android_media_MediaRecorder.cpp",
"android_media_MediaSync.cpp",
+ "android_media_MediaTranscodeManager.cpp",
"android_media_ResampleInputStream.cpp",
"android_media_Streams.cpp",
"android_media_SyncParams.cpp",
@@ -139,9 +140,14 @@
"libfmq",
"libhidlbase",
"liblog",
+ "libnativehelper",
"libutils",
],
+ header_libs: [
+ "libstagefright_foundation_headers",
+ ],
+
export_include_dirs: ["."],
cflags: [
diff --git a/media/jni/android_media_MediaPlayer.cpp b/media/jni/android_media_MediaPlayer.cpp
index 963b650..5cb42a9a 100644
--- a/media/jni/android_media_MediaPlayer.cpp
+++ b/media/jni/android_media_MediaPlayer.cpp
@@ -1453,6 +1453,7 @@
extern int register_android_mtp_MtpDatabase(JNIEnv *env);
extern int register_android_mtp_MtpDevice(JNIEnv *env);
extern int register_android_mtp_MtpServer(JNIEnv *env);
+extern int register_android_media_MediaTranscodeManager(JNIEnv *env);
jint JNI_OnLoad(JavaVM* vm, void* /* reserved */)
{
@@ -1565,6 +1566,11 @@
goto bail;
}
+ if (register_android_media_MediaTranscodeManager(env) < 0) {
+ ALOGE("ERROR: MediaTranscodeManager native registration failed");
+ goto bail;
+ }
+
/* success -- return valid version number */
result = JNI_VERSION_1_4;
diff --git a/media/jni/android_media_MediaTranscodeManager.cpp b/media/jni/android_media_MediaTranscodeManager.cpp
new file mode 100644
index 0000000..0b4048c
--- /dev/null
+++ b/media/jni/android_media_MediaTranscodeManager.cpp
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ */
+//#define LOG_NDEBUG 0
+#define LOG_TAG "MediaTranscodeManager_JNI"
+
+#include "android_runtime/AndroidRuntime.h"
+#include "jni.h"
+
+#include <nativehelper/JNIHelp.h>
+#include <utils/Log.h>
+
+namespace {
+
+// NOTE: Keep these enums in sync with their equivalents in MediaTranscodeManager.java.
+enum {
+ ID_INVALID = -1
+};
+
+enum {
+ EVENT_JOB_STARTED = 1,
+ EVENT_JOB_PROGRESSED = 2,
+ EVENT_JOB_FINISHED = 3,
+};
+
+enum {
+ RESULT_NONE = 1,
+ RESULT_SUCCESS = 2,
+ RESULT_ERROR = 3,
+ RESULT_CANCELED = 4,
+};
+
+struct {
+ jmethodID postEventFromNative;
+} gMediaTranscodeManagerClassInfo;
+
+using namespace android;
+
+void android_media_MediaTranscodeManager_native_init(JNIEnv *env, jclass clazz) {
+ ALOGV("android_media_MediaTranscodeManager_native_init");
+
+ gMediaTranscodeManagerClassInfo.postEventFromNative = env->GetMethodID(
+ clazz, "postEventFromNative", "(IJI)V");
+ LOG_ALWAYS_FATAL_IF(gMediaTranscodeManagerClassInfo.postEventFromNative == NULL,
+ "can't find android/media/MediaTranscodeManager.postEventFromNative");
+}
+
+jlong android_media_MediaTranscodeManager_requestUniqueJobID(
+ JNIEnv *env __unused, jobject thiz __unused) {
+ ALOGV("android_media_MediaTranscodeManager_reserveUniqueJobID");
+ static std::atomic_int32_t sJobIDCounter{0};
+ jlong id = (jlong)++sJobIDCounter;
+ return id;
+}
+
+jboolean android_media_MediaTranscodeManager_enqueueTranscodingRequest(
+ JNIEnv *env, jobject thiz, jlong id, jobject request, jobject context __unused) {
+ ALOGV("android_media_MediaTranscodeManager_enqueueTranscodingRequest");
+ if (!request) {
+ return ID_INVALID;
+ }
+
+ env->CallVoidMethod(thiz, gMediaTranscodeManagerClassInfo.postEventFromNative,
+ EVENT_JOB_FINISHED, id, RESULT_ERROR);
+ return true;
+}
+
+void android_media_MediaTranscodeManager_cancelTranscodingRequest(
+ JNIEnv *env __unused, jobject thiz __unused, jlong jobID __unused) {
+ ALOGV("android_media_MediaTranscodeManager_cancelTranscodingRequest");
+}
+
+const JNINativeMethod gMethods[] = {
+ { "native_init", "()V",
+ (void *)android_media_MediaTranscodeManager_native_init },
+ { "native_requestUniqueJobID", "()J",
+ (void *)android_media_MediaTranscodeManager_requestUniqueJobID },
+ { "native_enqueueTranscodingRequest",
+ "(JLandroid/media/MediaTranscodeManager$TranscodingRequest;Landroid/content/Context;)Z",
+ (void *)android_media_MediaTranscodeManager_enqueueTranscodingRequest },
+ { "native_cancelTranscodingRequest", "(J)V",
+ (void *)android_media_MediaTranscodeManager_cancelTranscodingRequest },
+};
+
+} // namespace anonymous
+
+int register_android_media_MediaTranscodeManager(JNIEnv *env) {
+ return AndroidRuntime::registerNativeMethods(env,
+ "android/media/MediaTranscodeManager", gMethods, NELEM(gMethods));
+}
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index da52696..4f1125f 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -22,6 +22,7 @@
#include <android/hardware/tv/tuner/1.0/ITuner.h>
#include <media/stagefright/foundation/ADebug.h>
+#include <nativehelper/JNIHelp.h>
#pragma GCC diagnostic ignored "-Wunused-function"
@@ -92,19 +93,35 @@
}
void DvrCallback::setDvr(const jobject dvr) {
- ALOGD("FilterCallback::setDvr");
+ ALOGD("DvrCallback::setDvr");
JNIEnv *env = AndroidRuntime::getJNIEnv();
mDvr = env->NewWeakGlobalRef(dvr);
}
/////////////// Dvr ///////////////////////
-Dvr::Dvr(sp<IDvr> sp, jweak obj) : mDvrSp(sp), mDvrObj(obj) {}
+Dvr::Dvr(sp<IDvr> sp, jweak obj) : mDvrSp(sp), mDvrObj(obj), mDvrMQEventFlag(nullptr) {}
+
+Dvr::~Dvr() {
+ EventFlag::deleteEventFlag(&mDvrMQEventFlag);
+}
+
+int Dvr::close() {
+ Result r = mDvrSp->close();
+ if (r == Result::SUCCESS) {
+ EventFlag::deleteEventFlag(&mDvrMQEventFlag);
+ }
+ return (int)r;
+}
sp<IDvr> Dvr::getIDvr() {
return mDvrSp;
}
+DvrMQ& Dvr::getDvrMQ() {
+ return *mDvrMQ;
+}
+
/////////////// FilterCallback ///////////////////////
//TODO: implement filter callback
Return<void> FilterCallback::onFilterEvent(const DemuxFilterEvent& /*filterEvent*/) {
@@ -636,6 +653,26 @@
return NULL;
}
+static int android_media_tv_Tuner_gat_av_sync_hw_id(JNIEnv*, jobject, jobject) {
+ return 0;
+}
+
+static jlong android_media_tv_Tuner_gat_av_sync_time(JNIEnv*, jobject, jint) {
+ return 0;
+}
+
+static int android_media_tv_Tuner_connect_cicam(JNIEnv*, jobject, jint) {
+ return 0;
+}
+
+static int android_media_tv_Tuner_disconnect_cicam(JNIEnv*, jobject) {
+ return 0;
+}
+
+static jobject android_media_tv_Tuner_get_frontend_info(JNIEnv*, jobject, jint) {
+ return NULL;
+}
+
static jobject android_media_tv_Tuner_get_lnb_ids(JNIEnv *env, jobject thiz) {
sp<JTuner> tuner = getTuner(env, thiz);
return tuner->getLnbIds();
@@ -659,6 +696,10 @@
return tuner->openFilter(filterType, bufferSize);
}
+static jobject android_media_tv_Tuner_open_time_filter(JNIEnv, jobject) {
+ return NULL;
+}
+
static DemuxFilterSettings getFilterSettings(
JNIEnv *env, int type, int subtype, jobject filterSettingsObj) {
DemuxFilterSettings filterSettings;
@@ -803,6 +844,28 @@
return 0;
}
+// TODO: implement TimeFilter functions
+static int android_media_tv_Tuner_time_filter_set_timestamp(
+ JNIEnv, jobject, jlong) {
+ return 0;
+}
+
+static int android_media_tv_Tuner_time_filter_clear_timestamp(JNIEnv, jobject) {
+ return 0;
+}
+
+static jobject android_media_tv_Tuner_time_filter_get_timestamp(JNIEnv, jobject) {
+ return NULL;
+}
+
+static jobject android_media_tv_Tuner_time_filter_get_source_time(JNIEnv, jobject) {
+ return NULL;
+}
+
+static int android_media_tv_Tuner_time_filter_close(JNIEnv, jobject) {
+ return 0;
+}
+
static jobject android_media_tv_Tuner_open_descrambler(JNIEnv *env, jobject thiz) {
sp<JTuner> tuner = getTuner(env, thiz);
return tuner->openDescrambler();
@@ -843,6 +906,10 @@
return tuner->openDvr(static_cast<DvrType>(type), bufferSize);
}
+static jobject android_media_tv_Tuner_get_demux_caps(JNIEnv*, jobject) {
+ return NULL;
+}
+
static int android_media_tv_Tuner_attach_filter(JNIEnv *env, jobject dvr, jobject filter) {
sp<IDvr> dvrSp = getDvr(env, dvr)->getIDvr();
sp<IFilter> filterSp = getFilter(env, filter)->getIFilter();
@@ -864,12 +931,28 @@
}
static int android_media_tv_Tuner_configure_dvr(JNIEnv *env, jobject dvr, jobject settings) {
- sp<IDvr> dvrSp = getDvr(env, dvr)->getIDvr();
+ sp<Dvr> dvrSp = getDvr(env, dvr);
+ sp<IDvr> iDvrSp = dvrSp->getIDvr();
if (dvrSp == NULL) {
ALOGD("Failed to configure dvr: dvr not found");
return (int)Result::INVALID_STATE;
}
- Result result = dvrSp->configure(getDvrSettings(env, settings));
+ Result result = iDvrSp->configure(getDvrSettings(env, settings));
+ MQDescriptorSync<uint8_t> dvrMQDesc;
+ if (result == Result::SUCCESS) {
+ Result getQueueDescResult = Result::UNKNOWN_ERROR;
+ iDvrSp->getQueueDesc(
+ [&](Result r, const MQDescriptorSync<uint8_t>& desc) {
+ dvrMQDesc = desc;
+ getQueueDescResult = r;
+ ALOGD("getDvrQueueDesc");
+ });
+ if (getQueueDescResult == Result::SUCCESS) {
+ dvrSp->mDvrMQ = std::make_unique<DvrMQ>(dvrMQDesc, true);
+ EventFlag::createEventFlag(
+ dvrSp->mDvrMQ->getEventFlagWord(), &(dvrSp->mDvrMQEventFlag));
+ }
+ }
return (int)result;
}
@@ -927,6 +1010,115 @@
return 0;
}
+static void android_media_tv_Tuner_dvr_set_fd(JNIEnv *env, jobject dvr, jobject jfd) {
+ sp<Dvr> dvrSp = getDvr(env, dvr);
+ if (dvrSp == NULL) {
+ ALOGD("Failed to set FD for dvr: dvr not found");
+ }
+ dvrSp->mFd = jniGetFDFromFileDescriptor(env, jfd);
+ ALOGD("set fd = %d", dvrSp->mFd);
+}
+
+static int android_media_tv_Tuner_read_dvr(JNIEnv *env, jobject dvr, jint size) {
+ sp<Dvr> dvrSp = getDvr(env, dvr);
+ if (dvrSp == NULL) {
+ ALOGD("Failed to read dvr: dvr not found");
+ }
+
+ int available = dvrSp->mDvrMQ->availableToWrite();
+ int write = std::min(size, available);
+
+ DvrMQ::MemTransaction tx;
+ int ret = 0;
+ if (dvrSp->mDvrMQ->beginWrite(write, &tx)) {
+ auto first = tx.getFirstRegion();
+ auto data = first.getAddress();
+ int length = first.getLength();
+ int firstToWrite = std::min(length, write);
+ ret = read(dvrSp->mFd, data, firstToWrite);
+ if (ret < firstToWrite) {
+ ALOGW("[DVR] file to MQ, first region: %d bytes to write, but %d bytes written",
+ firstToWrite, ret);
+ } else if (firstToWrite < write) {
+ ALOGD("[DVR] write second region: %d bytes written, %d bytes in total", ret, write);
+ auto second = tx.getSecondRegion();
+ data = second.getAddress();
+ length = second.getLength();
+ int secondToWrite = std::min(length, write - firstToWrite);
+ ret += read(dvrSp->mFd, data, secondToWrite);
+ }
+ ALOGD("[DVR] file to MQ: %d bytes need to be written, %d bytes written", write, ret);
+ if (!dvrSp->mDvrMQ->commitWrite(ret)) {
+ ALOGE("[DVR] Error: failed to commit write!");
+ }
+
+ } else {
+ ALOGE("dvrMq.beginWrite failed");
+ }
+ return ret;
+}
+
+static int android_media_tv_Tuner_read_dvr_from_array(
+ JNIEnv /* *env */, jobject /* dvr */, jbyteArray /* bytes */, jint /* offset */,
+ jint /* size */) {
+ //TODO: impl
+ return 0;
+}
+
+static int android_media_tv_Tuner_write_dvr(JNIEnv *env, jobject dvr, jint size) {
+ sp<Dvr> dvrSp = getDvr(env, dvr);
+ if (dvrSp == NULL) {
+ ALOGW("Failed to write dvr: dvr not found");
+ return 0;
+ }
+
+ if (dvrSp->mDvrMQ == NULL) {
+ ALOGW("Failed to write dvr: dvr not configured");
+ return 0;
+ }
+
+ DvrMQ& dvrMq = dvrSp->getDvrMQ();
+
+ int available = dvrMq.availableToRead();
+ int toRead = std::min(size, available);
+
+ int ret = 0;
+ DvrMQ::MemTransaction tx;
+ if (dvrMq.beginRead(toRead, &tx)) {
+ auto first = tx.getFirstRegion();
+ auto data = first.getAddress();
+ int length = first.getLength();
+ int firstToRead = std::min(length, toRead);
+ ret = write(dvrSp->mFd, data, firstToRead);
+ if (ret < firstToRead) {
+ ALOGW("[DVR] MQ to file: %d bytes read, but %d bytes written", firstToRead, ret);
+ } else if (firstToRead < toRead) {
+ ALOGD("[DVR] read second region: %d bytes read, %d bytes in total", ret, toRead);
+ auto second = tx.getSecondRegion();
+ data = second.getAddress();
+ length = second.getLength();
+ int secondToRead = toRead - firstToRead;
+ ret += write(dvrSp->mFd, data, secondToRead);
+ }
+ ALOGD("[DVR] MQ to file: %d bytes to be read, %d bytes written", toRead, ret);
+ if (!dvrMq.commitRead(ret)) {
+ ALOGE("[DVR] Error: failed to commit read!");
+ }
+
+ } else {
+ ALOGE("dvrMq.beginRead failed");
+ }
+
+ return ret;
+}
+
+static int android_media_tv_Tuner_write_dvr_to_array(
+ JNIEnv /* *env */, jobject /* dvr */, jbyteArray /* bytes */, jint /* offset */,
+ jint /* size */) {
+ //TODO: impl
+ return 0;
+}
+
static const JNINativeMethod gTunerMethods[] = {
{ "nativeInit", "()V", (void *)android_media_tv_Tuner_native_init },
{ "nativeSetup", "()V", (void *)android_media_tv_Tuner_native_setup },
@@ -944,8 +1136,17 @@
{ "nativeSetLna", "(Z)I", (void *)android_media_tv_Tuner_set_lna },
{ "nativeGetFrontendStatus", "([I)[Landroid/media/tv/tuner/FrontendStatus;",
(void *)android_media_tv_Tuner_get_frontend_status },
+ { "nativeGetAvSyncHwId", "(Landroid/media/tv/tuner/Tuner$Filter;)I",
+ (void *)android_media_tv_Tuner_gat_av_sync_hw_id },
+ { "nativeGetAvSyncTime", "(I)J", (void *)android_media_tv_Tuner_gat_av_sync_time },
+ { "nativeConnectCiCam", "(I)I", (void *)android_media_tv_Tuner_connect_cicam },
+ { "nativeDisconnectCiCam", "()I", (void *)android_media_tv_Tuner_disconnect_cicam },
+ { "nativeGetFrontendInfo", "(I)[Landroid/media/tv/tuner/FrontendInfo;",
+ (void *)android_media_tv_Tuner_get_frontend_info },
{ "nativeOpenFilter", "(III)Landroid/media/tv/tuner/Tuner$Filter;",
(void *)android_media_tv_Tuner_open_filter },
+ { "nativeOpenTimeFilter", "()Landroid/media/tv/tuner/Tuner$TimeFilter;",
+ (void *)android_media_tv_Tuner_open_time_filter },
{ "nativeGetLnbIds", "()Ljava/util/List;",
(void *)android_media_tv_Tuner_get_lnb_ids },
{ "nativeOpenLnbById", "(I)Landroid/media/tv/tuner/Tuner$Lnb;",
@@ -954,6 +1155,8 @@
(void *)android_media_tv_Tuner_open_descrambler },
{ "nativeOpenDvr", "(II)Landroid/media/tv/tuner/Tuner$Dvr;",
(void *)android_media_tv_Tuner_open_dvr },
+ { "nativeGetDemuxCapabilities", "()Landroid/media/tv/tuner/DemuxCapabilities;",
+ (void *)android_media_tv_Tuner_get_demux_caps },
};
static const JNINativeMethod gFilterMethods[] = {
@@ -969,6 +1172,16 @@
{ "nativeClose", "()I", (void *)android_media_tv_Tuner_close_filter },
};
+static const JNINativeMethod gTimeFilterMethods[] = {
+ { "nativeSetTimeStamp", "(J)I", (void *)android_media_tv_Tuner_time_filter_set_timestamp },
+ { "nativeClearTimeStamp", "()I", (void *)android_media_tv_Tuner_time_filter_clear_timestamp },
+ { "nativeGetTimeStamp", "()Ljava/lang/Long;",
+ (void *)android_media_tv_Tuner_time_filter_get_timestamp },
+ { "nativeGetSourceTime", "()Ljava/lang/Long;",
+ (void *)android_media_tv_Tuner_time_filter_get_source_time },
+ { "nativeClose", "()I", (void *)android_media_tv_Tuner_time_filter_close },
+};
+
static const JNINativeMethod gDescramblerMethods[] = {
{ "nativeAddPid", "(IILandroid/media/tv/tuner/Tuner$Filter;)I",
(void *)android_media_tv_Tuner_add_pid },
@@ -989,6 +1202,12 @@
{ "nativeStopDvr", "()I", (void *)android_media_tv_Tuner_stop_dvr },
{ "nativeFlushDvr", "()I", (void *)android_media_tv_Tuner_flush_dvr },
{ "nativeClose", "()I", (void *)android_media_tv_Tuner_close_dvr },
+ { "nativeSetFileDescriptor", "(Ljava/io/FileDescriptor;)V",
+ (void *)android_media_tv_Tuner_dvr_set_fd },
+ { "nativeRead", "(I)I", (void *)android_media_tv_Tuner_read_dvr },
+ { "nativeRead", "([BII)I", (void *)android_media_tv_Tuner_read_dvr_from_array },
+ { "nativeWrite", "(I)I", (void *)android_media_tv_Tuner_write_dvr },
+ { "nativeWrite", "([BII)I", (void *)android_media_tv_Tuner_write_dvr_to_array },
};
static const JNINativeMethod gLnbMethods[] = {
@@ -1013,6 +1232,13 @@
return false;
}
if (AndroidRuntime::registerNativeMethods(
+ env, "android/media/tv/tuner/Tuner$TimeFilter",
+ gTimeFilterMethods,
+ NELEM(gTimeFilterMethods)) != JNI_OK) {
+ ALOGE("Failed to register time filter native methods");
+ return false;
+ }
+ if (AndroidRuntime::registerNativeMethods(
env, "android/media/tv/tuner/Tuner$Descrambler",
gDescramblerMethods,
NELEM(gDescramblerMethods)) != JNI_OK) {
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index d37a2d9..5c012bb 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -19,6 +19,8 @@
#include <android/hardware/tv/tuner/1.0/ITuner.h>
#include <fmq/MessageQueue.h>
+#include <fstream>
+#include <string>
#include <unordered_map>
#include <utils/RefBase.h>
@@ -58,6 +60,7 @@
using ::android::hardware::tv::tuner::V1_0::RecordStatus;
using FilterMQ = MessageQueue<uint8_t, kSynchronizedReadWrite>;
+using DvrMQ = MessageQueue<uint8_t, kSynchronizedReadWrite>;
namespace android {
@@ -80,9 +83,16 @@
struct Dvr : public RefBase {
Dvr(sp<IDvr> sp, jweak obj);
+ ~Dvr();
+ int close();
+ DvrMQ& getDvrMQ();
sp<IDvr> getIDvr();
sp<IDvr> mDvrSp;
jweak mDvrObj;
+ std::unique_ptr<DvrMQ> mDvrMQ;
+ EventFlag* mDvrMQEventFlag;
+ std::string mFilePath;
+ int mFd;
};
struct FilterCallback : public IFilterCallback {
diff --git a/media/jni/audioeffect/android_media_AudioEffect.cpp b/media/jni/audioeffect/android_media_AudioEffect.cpp
index 747d4c01..007dd10 100644
--- a/media/jni/audioeffect/android_media_AudioEffect.cpp
+++ b/media/jni/audioeffect/android_media_AudioEffect.cpp
@@ -268,8 +268,9 @@
static jint
android_media_AudioEffect_native_setup(JNIEnv *env, jobject thiz, jobject weak_this,
- jstring type, jstring uuid, jint priority, jint sessionId, jintArray jId,
- jobjectArray javadesc, jstring opPackageName)
+ jstring type, jstring uuid, jint priority, jint sessionId,
+ jint deviceType, jstring deviceAddress,
+ jintArray jId, jobjectArray javadesc, jstring opPackageName)
{
ALOGV("android_media_AudioEffect_native_setup");
AudioEffectJniStorage* lpJniStorage = NULL;
@@ -280,6 +281,7 @@
const char *uuidStr = NULL;
effect_descriptor_t desc;
jobject jdesc;
+ AudioDeviceTypeAddr device;
ScopedUtfChars opPackageNameStr(env, opPackageName);
@@ -328,6 +330,12 @@
goto setup_failure;
}
+ if (deviceType != AUDIO_DEVICE_NONE) {
+ device.mType = deviceType;
+ ScopedUtfChars address(env, deviceAddress);
+ device.mAddress = address.c_str();
+ }
+
// create the native AudioEffect object
lpAudioEffect = new AudioEffect(typeStr,
String16(opPackageNameStr.c_str()),
@@ -336,7 +344,8 @@
effectCallback,
&lpJniStorage->mCallbackData,
(audio_session_t) sessionId,
- AUDIO_IO_HANDLE_NONE);
+ AUDIO_IO_HANDLE_NONE,
+ device);
if (lpAudioEffect == 0) {
ALOGE("Error creating AudioEffect");
goto setup_failure;
@@ -757,7 +766,7 @@
// Dalvik VM type signatures
static const JNINativeMethod gMethods[] = {
{"native_init", "()V", (void *)android_media_AudioEffect_native_init},
- {"native_setup", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;II[I[Ljava/lang/Object;Ljava/lang/String;)I",
+ {"native_setup", "(Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;IIILjava/lang/String;[I[Ljava/lang/Object;Ljava/lang/String;)I",
(void *)android_media_AudioEffect_native_setup},
{"native_finalize", "()V", (void *)android_media_AudioEffect_native_finalize},
{"native_release", "()V", (void *)android_media_AudioEffect_native_release},
diff --git a/media/mca/effect/java/android/media/effect/SingleFilterEffect.java b/media/mca/effect/java/android/media/effect/SingleFilterEffect.java
index dfbf5d2..121443f 100644
--- a/media/mca/effect/java/android/media/effect/SingleFilterEffect.java
+++ b/media/mca/effect/java/android/media/effect/SingleFilterEffect.java
@@ -17,12 +17,11 @@
package android.media.effect;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.filterfw.core.Filter;
import android.filterfw.core.FilterFactory;
import android.filterfw.core.FilterFunction;
import android.filterfw.core.Frame;
-import android.media.effect.EffectContext;
/**
* Effect subclass for effects based on a single Filter. Subclasses need only invoke the
diff --git a/media/mca/filterfw/java/android/filterfw/GraphEnvironment.java b/media/mca/filterfw/java/android/filterfw/GraphEnvironment.java
index 52615bf..3a7f1ed 100644
--- a/media/mca/filterfw/java/android/filterfw/GraphEnvironment.java
+++ b/media/mca/filterfw/java/android/filterfw/GraphEnvironment.java
@@ -17,11 +17,11 @@
package android.filterfw;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.filterfw.core.AsyncRunner;
-import android.filterfw.core.FilterGraph;
import android.filterfw.core.FilterContext;
+import android.filterfw.core.FilterGraph;
import android.filterfw.core.FrameManager;
import android.filterfw.core.GraphRunner;
import android.filterfw.core.RoundRobinScheduler;
diff --git a/media/mca/filterfw/java/android/filterfw/core/Filter.java b/media/mca/filterfw/java/android/filterfw/core/Filter.java
index 4f56b92..a608ef5 100644
--- a/media/mca/filterfw/java/android/filterfw/core/Filter.java
+++ b/media/mca/filterfw/java/android/filterfw/core/Filter.java
@@ -17,19 +17,15 @@
package android.filterfw.core;
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.FilterContext;
-import android.filterfw.core.FilterPort;
-import android.filterfw.core.KeyValueMap;
-import android.filterfw.io.TextGraphReader;
-import android.filterfw.io.GraphIOException;
+import android.compat.annotation.UnsupportedAppUsage;
import android.filterfw.format.ObjectFormat;
+import android.filterfw.io.GraphIOException;
+import android.filterfw.io.TextGraphReader;
import android.util.Log;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
-import java.lang.Thread;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
diff --git a/media/mca/filterfw/java/android/filterfw/core/FilterContext.java b/media/mca/filterfw/java/android/filterfw/core/FilterContext.java
index a19220e..6b0a219 100644
--- a/media/mca/filterfw/java/android/filterfw/core/FilterContext.java
+++ b/media/mca/filterfw/java/android/filterfw/core/FilterContext.java
@@ -17,11 +17,7 @@
package android.filterfw.core;
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.Filter;
-import android.filterfw.core.Frame;
-import android.filterfw.core.FrameManager;
-import android.filterfw.core.GLEnvironment;
+import android.compat.annotation.UnsupportedAppUsage;
import java.util.HashMap;
import java.util.HashSet;
diff --git a/media/mca/filterfw/java/android/filterfw/core/FilterGraph.java b/media/mca/filterfw/java/android/filterfw/core/FilterGraph.java
index e6ca11f..35a298f 100644
--- a/media/mca/filterfw/java/android/filterfw/core/FilterGraph.java
+++ b/media/mca/filterfw/java/android/filterfw/core/FilterGraph.java
@@ -17,6 +17,11 @@
package android.filterfw.core;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.filterpacks.base.FrameBranch;
+import android.filterpacks.base.NullFilter;
+import android.util.Log;
+
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -25,14 +30,6 @@
import java.util.Set;
import java.util.Stack;
-import android.filterfw.core.FilterContext;
-import android.filterfw.core.KeyValueMap;
-import android.filterpacks.base.FrameBranch;
-import android.filterpacks.base.NullFilter;
-
-import android.annotation.UnsupportedAppUsage;
-import android.util.Log;
-
/**
* @hide
*/
diff --git a/media/mca/filterfw/java/android/filterfw/core/Frame.java b/media/mca/filterfw/java/android/filterfw/core/Frame.java
index e880783..c4d935a 100644
--- a/media/mca/filterfw/java/android/filterfw/core/Frame.java
+++ b/media/mca/filterfw/java/android/filterfw/core/Frame.java
@@ -17,9 +17,7 @@
package android.filterfw.core;
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.FrameFormat;
-import android.filterfw.core.FrameManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Bitmap;
import java.nio.ByteBuffer;
diff --git a/media/mca/filterfw/java/android/filterfw/core/FrameFormat.java b/media/mca/filterfw/java/android/filterfw/core/FrameFormat.java
index eb0ff0a..a87e9b9 100644
--- a/media/mca/filterfw/java/android/filterfw/core/FrameFormat.java
+++ b/media/mca/filterfw/java/android/filterfw/core/FrameFormat.java
@@ -17,9 +17,7 @@
package android.filterfw.core;
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.KeyValueMap;
-import android.filterfw.core.MutableFrameFormat;
+import android.compat.annotation.UnsupportedAppUsage;
import java.util.Arrays;
import java.util.Map.Entry;
diff --git a/media/mca/filterfw/java/android/filterfw/core/FrameManager.java b/media/mca/filterfw/java/android/filterfw/core/FrameManager.java
index 85c8fcd..e49aaf1 100644
--- a/media/mca/filterfw/java/android/filterfw/core/FrameManager.java
+++ b/media/mca/filterfw/java/android/filterfw/core/FrameManager.java
@@ -17,10 +17,7 @@
package android.filterfw.core;
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.Frame;
-import android.filterfw.core.FrameFormat;
-import android.filterfw.core.MutableFrameFormat;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* @hide
diff --git a/media/mca/filterfw/java/android/filterfw/core/GLEnvironment.java b/media/mca/filterfw/java/android/filterfw/core/GLEnvironment.java
index e25d6a7..7e4e8a6 100644
--- a/media/mca/filterfw/java/android/filterfw/core/GLEnvironment.java
+++ b/media/mca/filterfw/java/android/filterfw/core/GLEnvironment.java
@@ -17,13 +17,12 @@
package android.filterfw.core;
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.NativeAllocatorTag;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.SurfaceTexture;
+import android.media.MediaRecorder;
import android.os.Looper;
import android.util.Log;
import android.view.Surface;
-import android.media.MediaRecorder;
/**
* @hide
diff --git a/media/mca/filterfw/java/android/filterfw/core/GLFrame.java b/media/mca/filterfw/java/android/filterfw/core/GLFrame.java
index 9e3025f..1ccd7fe 100644
--- a/media/mca/filterfw/java/android/filterfw/core/GLFrame.java
+++ b/media/mca/filterfw/java/android/filterfw/core/GLFrame.java
@@ -17,15 +17,10 @@
package android.filterfw.core;
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.Frame;
-import android.filterfw.core.FrameFormat;
-import android.filterfw.core.FrameManager;
-import android.filterfw.core.NativeFrame;
-import android.filterfw.core.StopWatchMap;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.Bitmap;
-import android.opengl.GLES20;
import android.graphics.Rect;
+import android.opengl.GLES20;
import java.nio.ByteBuffer;
diff --git a/media/mca/filterfw/java/android/filterfw/core/GraphRunner.java b/media/mca/filterfw/java/android/filterfw/core/GraphRunner.java
index 250cfaa..b57e8bb 100644
--- a/media/mca/filterfw/java/android/filterfw/core/GraphRunner.java
+++ b/media/mca/filterfw/java/android/filterfw/core/GraphRunner.java
@@ -17,7 +17,7 @@
package android.filterfw.core;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* @hide
diff --git a/media/mca/filterfw/java/android/filterfw/core/MutableFrameFormat.java b/media/mca/filterfw/java/android/filterfw/core/MutableFrameFormat.java
index ae2ad99..da00b1f 100644
--- a/media/mca/filterfw/java/android/filterfw/core/MutableFrameFormat.java
+++ b/media/mca/filterfw/java/android/filterfw/core/MutableFrameFormat.java
@@ -17,9 +17,7 @@
package android.filterfw.core;
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.FrameFormat;
-import android.filterfw.core.KeyValueMap;
+import android.compat.annotation.UnsupportedAppUsage;
import java.util.Arrays;
diff --git a/media/mca/filterfw/java/android/filterfw/core/Program.java b/media/mca/filterfw/java/android/filterfw/core/Program.java
index 376c085..145388e 100644
--- a/media/mca/filterfw/java/android/filterfw/core/Program.java
+++ b/media/mca/filterfw/java/android/filterfw/core/Program.java
@@ -17,8 +17,7 @@
package android.filterfw.core;
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.Frame;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* @hide
diff --git a/media/mca/filterfw/java/android/filterfw/core/ShaderProgram.java b/media/mca/filterfw/java/android/filterfw/core/ShaderProgram.java
index f41636e..e043be0 100644
--- a/media/mca/filterfw/java/android/filterfw/core/ShaderProgram.java
+++ b/media/mca/filterfw/java/android/filterfw/core/ShaderProgram.java
@@ -17,12 +17,7 @@
package android.filterfw.core;
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.core.Frame;
-import android.filterfw.core.NativeAllocatorTag;
-import android.filterfw.core.Program;
-import android.filterfw.core.StopWatchMap;
-import android.filterfw.core.VertexFrame;
+import android.compat.annotation.UnsupportedAppUsage;
import android.filterfw.geometry.Quad;
import android.opengl.GLES20;
diff --git a/media/mca/filterfw/java/android/filterfw/format/ImageFormat.java b/media/mca/filterfw/java/android/filterfw/format/ImageFormat.java
index ac08730..0e05092 100644
--- a/media/mca/filterfw/java/android/filterfw/format/ImageFormat.java
+++ b/media/mca/filterfw/java/android/filterfw/format/ImageFormat.java
@@ -17,7 +17,7 @@
package android.filterfw.format;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.filterfw.core.FrameFormat;
import android.filterfw.core.MutableFrameFormat;
import android.graphics.Bitmap;
diff --git a/media/mca/filterfw/java/android/filterfw/geometry/Point.java b/media/mca/filterfw/java/android/filterfw/geometry/Point.java
index d7acf12..96d2d7b 100644
--- a/media/mca/filterfw/java/android/filterfw/geometry/Point.java
+++ b/media/mca/filterfw/java/android/filterfw/geometry/Point.java
@@ -17,8 +17,7 @@
package android.filterfw.geometry;
-import android.annotation.UnsupportedAppUsage;
-import java.lang.Math;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* @hide
diff --git a/media/mca/filterfw/java/android/filterfw/geometry/Quad.java b/media/mca/filterfw/java/android/filterfw/geometry/Quad.java
index 610e5b8..2b308a9 100644
--- a/media/mca/filterfw/java/android/filterfw/geometry/Quad.java
+++ b/media/mca/filterfw/java/android/filterfw/geometry/Quad.java
@@ -17,10 +17,8 @@
package android.filterfw.geometry;
-import android.annotation.UnsupportedAppUsage;
-import android.filterfw.geometry.Point;
+import android.compat.annotation.UnsupportedAppUsage;
-import java.lang.Float;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
diff --git a/media/tests/MediaFrameworkTest/Android.bp b/media/tests/MediaFrameworkTest/Android.bp
index f0fbc50..ecbe2b3 100644
--- a/media/tests/MediaFrameworkTest/Android.bp
+++ b/media/tests/MediaFrameworkTest/Android.bp
@@ -7,6 +7,7 @@
],
static_libs: [
"mockito-target-minus-junit4",
+ "androidx.test.ext.junit",
"androidx.test.rules",
"android-ex-camera2",
],
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediatranscodemanager/MediaTranscodeManagerTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediatranscodemanager/MediaTranscodeManagerTest.java
new file mode 100644
index 0000000..eeda50e
--- /dev/null
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/functional/mediatranscodemanager/MediaTranscodeManagerTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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 com.android.mediaframeworktest.functional.mediatranscodemanager;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.media.MediaTranscodeManager;
+import android.util.Log;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class MediaTranscodeManagerTest {
+ private static final String TAG = "MediaTranscodeManagerTest";
+
+ /** The time to wait for the transcode operation to complete before failing the test. */
+ private static final int TRANSCODE_TIMEOUT_SECONDS = 2;
+
+ @Test
+ public void testMediaTranscodeManager() throws InterruptedException {
+ Log.d(TAG, "Starting: testMediaTranscodeManager");
+
+ Semaphore transcodeCompleteSemaphore = new Semaphore(0);
+ MediaTranscodeManager.TranscodingRequest request =
+ new MediaTranscodeManager.TranscodingRequest.Builder().build();
+ Executor listenerExecutor = Executors.newSingleThreadExecutor();
+
+ MediaTranscodeManager mediaTranscodeManager =
+ MediaTranscodeManager.getInstance(ApplicationProvider.getApplicationContext());
+ assertNotNull(mediaTranscodeManager);
+
+ MediaTranscodeManager.TranscodingJob job;
+ job = mediaTranscodeManager.enqueueTranscodingRequest(request, listenerExecutor,
+ transcodingJob -> {
+ Log.d(TAG, "Transcoding completed with result: " + transcodingJob.getResult());
+ transcodeCompleteSemaphore.release();
+ });
+ assertNotNull(job);
+
+ job.setOnProgressChangedListener(
+ listenerExecutor, progress -> Log.d(TAG, "Progress: " + progress));
+
+ if (job != null) {
+ Log.d(TAG, "testMediaTranscodeManager - Waiting for transcode to complete.");
+ boolean finishedOnTime = transcodeCompleteSemaphore.tryAcquire(
+ TRANSCODE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ assertTrue("Transcode failed to complete in time.", finishedOnTime);
+ }
+ }
+}
diff --git a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
index 04fccc7..ed93112 100644
--- a/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
+++ b/media/tests/MediaRouteProvider/src/com/android/mediarouteprovider/example/SampleMediaRoute2ProviderService.java
@@ -16,14 +16,13 @@
package com.android.mediarouteprovider.example;
-import static android.media.MediaRoute2Info.DEVICE_TYPE_SPEAKER;
-import static android.media.MediaRoute2Info.DEVICE_TYPE_TV;
+import static android.media.MediaRoute2Info.DEVICE_TYPE_REMOTE_SPEAKER;
+import static android.media.MediaRoute2Info.DEVICE_TYPE_REMOTE_TV;
import android.content.Intent;
import android.media.MediaRoute2Info;
-import android.media.MediaRoute2ProviderInfo;
import android.media.MediaRoute2ProviderService;
-import android.media.RouteSessionInfo;
+import android.media.RoutingSessionInfo;
import android.os.IBinder;
import android.text.TextUtils;
@@ -46,8 +45,8 @@
public static final String ROUTE_ID5_TO_TRANSFER_TO = "route_id5_to_transfer_to";
public static final String ROUTE_NAME5 = "Sample Route 5 - Route to transfer to";
- public static final String ROUTE_ID_SPECIAL_CATEGORY = "route_special_category";
- public static final String ROUTE_NAME_SPECIAL_CATEGORY = "Special Category Route";
+ public static final String ROUTE_ID_SPECIAL_FEATURE = "route_special_feature";
+ public static final String ROUTE_NAME_SPECIAL_FEATURE = "Special Feature Route";
public static final int VOLUME_MAX = 100;
public static final String ROUTE_ID_FIXED_VOLUME = "route_fixed_volume";
@@ -58,49 +57,49 @@
public static final String ACTION_REMOVE_ROUTE =
"com.android.mediarouteprovider.action_remove_route";
- public static final String CATEGORY_SAMPLE =
- "com.android.mediarouteprovider.CATEGORY_SAMPLE";
- public static final String CATEGORY_SPECIAL =
- "com.android.mediarouteprovider.CATEGORY_SPECIAL";
+ public static final String FEATURE_SAMPLE =
+ "com.android.mediarouteprovider.FEATURE_SAMPLE";
+ public static final String FEATURE_SPECIAL =
+ "com.android.mediarouteprovider.FEATURE_SPECIAL";
Map<String, MediaRoute2Info> mRoutes = new HashMap<>();
- Map<String, Integer> mRouteSessionMap = new HashMap<>();
+ Map<String, String> mRouteIdToSessionId = new HashMap<>();
private int mNextSessionId = 1000;
private void initializeRoutes() {
MediaRoute2Info route1 = new MediaRoute2Info.Builder(ROUTE_ID1, ROUTE_NAME1)
- .addSupportedCategory(CATEGORY_SAMPLE)
- .setDeviceType(DEVICE_TYPE_TV)
+ .addFeature(FEATURE_SAMPLE)
+ .setDeviceType(DEVICE_TYPE_REMOTE_TV)
.build();
MediaRoute2Info route2 = new MediaRoute2Info.Builder(ROUTE_ID2, ROUTE_NAME2)
- .addSupportedCategory(CATEGORY_SAMPLE)
- .setDeviceType(DEVICE_TYPE_SPEAKER)
+ .addFeature(FEATURE_SAMPLE)
+ .setDeviceType(DEVICE_TYPE_REMOTE_SPEAKER)
.build();
MediaRoute2Info route3 = new MediaRoute2Info.Builder(
ROUTE_ID3_SESSION_CREATION_FAILED, ROUTE_NAME3)
- .addSupportedCategory(CATEGORY_SAMPLE)
+ .addFeature(FEATURE_SAMPLE)
.build();
MediaRoute2Info route4 = new MediaRoute2Info.Builder(
ROUTE_ID4_TO_SELECT_AND_DESELECT, ROUTE_NAME4)
- .addSupportedCategory(CATEGORY_SAMPLE)
+ .addFeature(FEATURE_SAMPLE)
.build();
MediaRoute2Info route5 = new MediaRoute2Info.Builder(
ROUTE_ID5_TO_TRANSFER_TO, ROUTE_NAME5)
- .addSupportedCategory(CATEGORY_SAMPLE)
+ .addFeature(FEATURE_SAMPLE)
.build();
MediaRoute2Info routeSpecial =
- new MediaRoute2Info.Builder(ROUTE_ID_SPECIAL_CATEGORY, ROUTE_NAME_SPECIAL_CATEGORY)
- .addSupportedCategory(CATEGORY_SAMPLE)
- .addSupportedCategory(CATEGORY_SPECIAL)
+ new MediaRoute2Info.Builder(ROUTE_ID_SPECIAL_FEATURE, ROUTE_NAME_SPECIAL_FEATURE)
+ .addFeature(FEATURE_SAMPLE)
+ .addFeature(FEATURE_SPECIAL)
.build();
MediaRoute2Info fixedVolumeRoute =
new MediaRoute2Info.Builder(ROUTE_ID_FIXED_VOLUME, ROUTE_NAME_FIXED_VOLUME)
- .addSupportedCategory(CATEGORY_SAMPLE)
+ .addFeature(FEATURE_SAMPLE)
.setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_FIXED)
.build();
MediaRoute2Info variableVolumeRoute =
new MediaRoute2Info.Builder(ROUTE_ID_VARIABLE_VOLUME, ROUTE_NAME_VARIABLE_VOLUME)
- .addSupportedCategory(CATEGORY_SAMPLE)
+ .addFeature(FEATURE_SAMPLE)
.setVolumeHandling(MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
.setVolumeMax(VOLUME_MAX)
.build();
@@ -154,6 +153,7 @@
@Override
public void onUpdateVolume(String routeId, int delta) {
+ android.util.Log.d(TAG, "onUpdateVolume routeId= " + routeId + "delta=" + delta);
MediaRoute2Info route = mRoutes.get(routeId);
if (route == null) {
return;
@@ -167,26 +167,26 @@
}
@Override
- public void onCreateSession(String packageName, String routeId, String controlCategory,
+ public void onCreateSession(String packageName, String routeId, String routeFeature,
long requestId) {
MediaRoute2Info route = mRoutes.get(routeId);
if (route == null || TextUtils.equals(ROUTE_ID3_SESSION_CREATION_FAILED, routeId)) {
// Tell the router that session cannot be created by passing null as sessionInfo.
- notifySessionCreated(/* sessionInfo= */ null, requestId);
+ notifySessionCreationFailed(requestId);
return;
}
maybeDeselectRoute(routeId);
- final int sessionId = mNextSessionId;
+ final String sessionId = String.valueOf(mNextSessionId);
mNextSessionId++;
mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
.setClientPackageName(packageName)
.build());
- mRouteSessionMap.put(routeId, sessionId);
+ mRouteIdToSessionId.put(routeId, sessionId);
- RouteSessionInfo sessionInfo = new RouteSessionInfo.Builder(
- sessionId, packageName, controlCategory)
+ RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+ sessionId, packageName, routeFeature)
.addSelectedRoute(routeId)
.addSelectableRoute(ROUTE_ID4_TO_SELECT_AND_DESELECT)
.addTransferrableRoute(ROUTE_ID5_TO_TRANSFER_TO)
@@ -196,9 +196,14 @@
}
@Override
- public void onDestroySession(int sessionId, RouteSessionInfo lastSessionInfo) {
- for (String routeId : lastSessionInfo.getSelectedRoutes()) {
- mRouteSessionMap.remove(routeId);
+ public void onReleaseSession(String sessionId) {
+ RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
+ if (sessionInfo == null) {
+ return;
+ }
+
+ for (String routeId : sessionInfo.getSelectedRoutes()) {
+ mRouteIdToSessionId.remove(routeId);
MediaRoute2Info route = mRoutes.get(routeId);
if (route != null) {
mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
@@ -206,11 +211,12 @@
.build());
}
}
+ notifySessionReleased(sessionId);
}
@Override
- public void onSelectRoute(int sessionId, String routeId) {
- RouteSessionInfo sessionInfo = getSessionInfo(sessionId);
+ public void onSelectRoute(String sessionId, String routeId) {
+ RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
MediaRoute2Info route = mRoutes.get(routeId);
if (route == null || sessionInfo == null) {
return;
@@ -218,67 +224,68 @@
maybeDeselectRoute(routeId);
mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
- .setClientPackageName(sessionInfo.getPackageName())
+ .setClientPackageName(sessionInfo.getClientPackageName())
.build());
- mRouteSessionMap.put(routeId, sessionId);
+ mRouteIdToSessionId.put(routeId, sessionId);
- RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo)
+ RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
.addSelectedRoute(routeId)
.removeSelectableRoute(routeId)
.addDeselectableRoute(routeId)
.build();
- updateSessionInfo(newSessionInfo);
- notifySessionInfoChanged(newSessionInfo);
+ notifySessionUpdated(newSessionInfo);
}
@Override
- public void onDeselectRoute(int sessionId, String routeId) {
- RouteSessionInfo sessionInfo = getSessionInfo(sessionId);
+ public void onDeselectRoute(String sessionId, String routeId) {
+ RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
MediaRoute2Info route = mRoutes.get(routeId);
- mRouteSessionMap.remove(routeId);
- if (sessionInfo == null || route == null) {
+ if (sessionInfo == null || route == null
+ || !sessionInfo.getSelectedRoutes().contains(routeId)) {
return;
}
+
+ mRouteIdToSessionId.remove(routeId);
mRoutes.put(routeId, new MediaRoute2Info.Builder(route)
.setClientPackageName(null)
.build());
- RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo)
+ if (sessionInfo.getSelectedRoutes().size() == 1) {
+ notifySessionReleased(sessionId);
+ return;
+ }
+
+ RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
.removeSelectedRoute(routeId)
.addSelectableRoute(routeId)
.removeDeselectableRoute(routeId)
.build();
- updateSessionInfo(newSessionInfo);
- notifySessionInfoChanged(newSessionInfo);
+ notifySessionUpdated(newSessionInfo);
}
@Override
- public void onTransferToRoute(int sessionId, String routeId) {
- RouteSessionInfo sessionInfo = getSessionInfo(sessionId);
- RouteSessionInfo newSessionInfo = new RouteSessionInfo.Builder(sessionInfo)
+ public void onTransferToRoute(String sessionId, String routeId) {
+ RoutingSessionInfo sessionInfo = getSessionInfo(sessionId);
+ RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
.clearSelectedRoutes()
.addSelectedRoute(routeId)
.removeDeselectableRoute(routeId)
.removeTransferrableRoute(routeId)
.build();
- updateSessionInfo(newSessionInfo);
- notifySessionInfoChanged(newSessionInfo);
+ notifySessionUpdated(newSessionInfo);
}
void maybeDeselectRoute(String routeId) {
- if (!mRouteSessionMap.containsKey(routeId)) {
+ if (!mRouteIdToSessionId.containsKey(routeId)) {
return;
}
- int sessionId = mRouteSessionMap.get(routeId);
+ String sessionId = mRouteIdToSessionId.get(routeId);
onDeselectRoute(sessionId, routeId);
}
void publishRoutes() {
- MediaRoute2ProviderInfo info = new MediaRoute2ProviderInfo.Builder()
- .addRoutes(mRoutes.values())
- .build();
- updateProviderInfo(info);
+ notifyRoutes(mRoutes.values());
}
}
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRoute2InfoTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRoute2InfoTest.java
new file mode 100644
index 0000000..c46966f
--- /dev/null
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRoute2InfoTest.java
@@ -0,0 +1,363 @@
+/*
+ * 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.mediaroutertest;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.testng.Assert.assertThrows;
+
+import android.media.MediaRoute2Info;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests {@link MediaRoute2Info} and its {@link MediaRoute2Info.Builder builder}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MediaRoute2InfoTest {
+
+ public static final String TEST_ID = "test_id";
+ public static final String TEST_NAME = "test_name";
+ public static final String TEST_ROUTE_TYPE_0 = "test_route_type_0";
+ public static final String TEST_ROUTE_TYPE_1 = "test_route_type_1";
+ public static final int TEST_DEVICE_TYPE = MediaRoute2Info.DEVICE_TYPE_REMOTE_SPEAKER;
+ public static final Uri TEST_ICON_URI = Uri.parse("https://developer.android.com");
+ public static final String TEST_DESCRIPTION = "test_description";
+ public static final int TEST_CONNECTION_STATE = MediaRoute2Info.CONNECTION_STATE_CONNECTING;
+ public static final String TEST_CLIENT_PACKAGE_NAME = "com.test.client.package.name";
+ public static final int TEST_VOLUME_HANDLING = MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE;
+ public static final int TEST_VOLUME_MAX = 100;
+ public static final int TEST_VOLUME = 65;
+
+ public static final String TEST_KEY = "test_key";
+ public static final String TEST_VALUE = "test_value";
+
+ @Test
+ public void testBuilderConstructorWithInvalidValues() {
+ final String nullId = null;
+ final String nullName = null;
+ final String emptyId = "";
+ final String emptyName = "";
+ final String validId = "valid_id";
+ final String validName = "valid_name";
+
+ // ID is invalid
+ assertThrows(IllegalArgumentException.class,
+ () -> new MediaRoute2Info.Builder(nullId, validName));
+ assertThrows(IllegalArgumentException.class,
+ () -> new MediaRoute2Info.Builder(emptyId, validName));
+
+ // name is invalid
+ assertThrows(IllegalArgumentException.class,
+ () -> new MediaRoute2Info.Builder(validId, nullName));
+ assertThrows(IllegalArgumentException.class,
+ () -> new MediaRoute2Info.Builder(validId, emptyName));
+
+ // Both are invalid
+ assertThrows(IllegalArgumentException.class,
+ () -> new MediaRoute2Info.Builder(nullId, nullName));
+ assertThrows(IllegalArgumentException.class,
+ () -> new MediaRoute2Info.Builder(nullId, emptyName));
+ assertThrows(IllegalArgumentException.class,
+ () -> new MediaRoute2Info.Builder(emptyId, nullName));
+ assertThrows(IllegalArgumentException.class,
+ () -> new MediaRoute2Info.Builder(emptyId, emptyName));
+
+
+ // Null RouteInfo (1-argument constructor)
+ final MediaRoute2Info nullRouteInfo = null;
+ assertThrows(NullPointerException.class,
+ () -> new MediaRoute2Info.Builder(nullRouteInfo));
+ }
+
+ @Test
+ public void testBuilderBuildWithEmptyRouteTypesShouldThrowIAE() {
+ MediaRoute2Info.Builder builder = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME);
+ assertThrows(IllegalArgumentException.class, () -> builder.build());
+ }
+
+ @Test
+ public void testBuilderAndGettersOfMediaRoute2Info() {
+ Bundle extras = new Bundle();
+ extras.putString(TEST_KEY, TEST_VALUE);
+
+ MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+ .addFeature(TEST_ROUTE_TYPE_0)
+ .addFeature(TEST_ROUTE_TYPE_1)
+ .setDeviceType(TEST_DEVICE_TYPE)
+ .setIconUri(TEST_ICON_URI)
+ .setDescription(TEST_DESCRIPTION)
+ .setConnectionState(TEST_CONNECTION_STATE)
+ .setClientPackageName(TEST_CLIENT_PACKAGE_NAME)
+ .setVolumeHandling(TEST_VOLUME_HANDLING)
+ .setVolumeMax(TEST_VOLUME_MAX)
+ .setVolume(TEST_VOLUME)
+ .setExtras(extras)
+ .build();
+
+ assertEquals(TEST_ID, routeInfo.getId());
+ assertEquals(TEST_NAME, routeInfo.getName());
+
+ assertEquals(2, routeInfo.getFeatures().size());
+ assertEquals(TEST_ROUTE_TYPE_0, routeInfo.getFeatures().get(0));
+ assertEquals(TEST_ROUTE_TYPE_1, routeInfo.getFeatures().get(1));
+
+ assertEquals(TEST_DEVICE_TYPE, routeInfo.getDeviceType());
+ assertEquals(TEST_ICON_URI, routeInfo.getIconUri());
+ assertEquals(TEST_DESCRIPTION, routeInfo.getDescription());
+ assertEquals(TEST_CONNECTION_STATE, routeInfo.getConnectionState());
+ assertEquals(TEST_CLIENT_PACKAGE_NAME, routeInfo.getClientPackageName());
+ assertEquals(TEST_VOLUME_HANDLING, routeInfo.getVolumeHandling());
+ assertEquals(TEST_VOLUME_MAX, routeInfo.getVolumeMax());
+ assertEquals(TEST_VOLUME, routeInfo.getVolume());
+
+ Bundle extrasOut = routeInfo.getExtras();
+ assertNotNull(extrasOut);
+ assertTrue(extrasOut.containsKey(TEST_KEY));
+ assertEquals(TEST_VALUE, extrasOut.getString(TEST_KEY));
+ }
+
+ @Test
+ public void testBuilderSetExtrasWithNull() {
+ MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+ .addFeature(TEST_ROUTE_TYPE_0)
+ .setExtras(null)
+ .build();
+
+ assertNull(routeInfo.getExtras());
+ }
+
+ @Test
+ public void testBuilderaddFeatures() {
+ List<String> routeTypes = new ArrayList<>();
+ routeTypes.add(TEST_ROUTE_TYPE_0);
+ routeTypes.add(TEST_ROUTE_TYPE_1);
+
+ MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+ .addFeatures(routeTypes)
+ .build();
+
+ assertEquals(routeTypes, routeInfo.getFeatures());
+ }
+
+ @Test
+ public void testBuilderclearFeatures() {
+ MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+ .addFeature(TEST_ROUTE_TYPE_0)
+ .addFeature(TEST_ROUTE_TYPE_1)
+ // clearFeatures should clear the route types.
+ .clearFeatures()
+ .addFeature(TEST_ROUTE_TYPE_1)
+ .build();
+
+ assertEquals(1, routeInfo.getFeatures().size());
+ assertEquals(TEST_ROUTE_TYPE_1, routeInfo.getFeatures().get(0));
+ }
+
+ @Test
+ public void testhasAnyFeaturesReturnsFalse() {
+ MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+ .addFeature(TEST_ROUTE_TYPE_0)
+ .addFeature(TEST_ROUTE_TYPE_1)
+ .build();
+
+ List<String> testRouteTypes = new ArrayList<>();
+ testRouteTypes.add("non_matching_route_type_1");
+ testRouteTypes.add("non_matching_route_type_2");
+ testRouteTypes.add("non_matching_route_type_3");
+ testRouteTypes.add("non_matching_route_type_4");
+
+ assertFalse(routeInfo.hasAnyFeatures(testRouteTypes));
+ }
+
+ @Test
+ public void testhasAnyFeaturesReturnsTrue() {
+ MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+ .addFeature(TEST_ROUTE_TYPE_0)
+ .addFeature(TEST_ROUTE_TYPE_1)
+ .build();
+
+ List<String> testRouteTypes = new ArrayList<>();
+ testRouteTypes.add("non_matching_route_type_1");
+ testRouteTypes.add("non_matching_route_type_2");
+ testRouteTypes.add("non_matching_route_type_3");
+ testRouteTypes.add(TEST_ROUTE_TYPE_1);
+
+ assertTrue(routeInfo.hasAnyFeatures(testRouteTypes));
+ }
+
+ @Test
+ public void testEqualsCreatedWithSameArguments() {
+ Bundle extras = new Bundle();
+ extras.putString(TEST_KEY, TEST_VALUE);
+
+ MediaRoute2Info routeInfo1 = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+ .addFeature(TEST_ROUTE_TYPE_0)
+ .addFeature(TEST_ROUTE_TYPE_1)
+ .setDeviceType(TEST_DEVICE_TYPE)
+ .setIconUri(TEST_ICON_URI)
+ .setDescription(TEST_DESCRIPTION)
+ .setConnectionState(TEST_CONNECTION_STATE)
+ .setClientPackageName(TEST_CLIENT_PACKAGE_NAME)
+ .setVolumeHandling(TEST_VOLUME_HANDLING)
+ .setVolumeMax(TEST_VOLUME_MAX)
+ .setVolume(TEST_VOLUME)
+ .setExtras(extras)
+ .build();
+
+ MediaRoute2Info routeInfo2 = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+ .addFeature(TEST_ROUTE_TYPE_0)
+ .addFeature(TEST_ROUTE_TYPE_1)
+ .setDeviceType(TEST_DEVICE_TYPE)
+ .setIconUri(TEST_ICON_URI)
+ .setDescription(TEST_DESCRIPTION)
+ .setConnectionState(TEST_CONNECTION_STATE)
+ .setClientPackageName(TEST_CLIENT_PACKAGE_NAME)
+ .setVolumeHandling(TEST_VOLUME_HANDLING)
+ .setVolumeMax(TEST_VOLUME_MAX)
+ .setVolume(TEST_VOLUME)
+ .setExtras(extras)
+ .build();
+
+ assertEquals(routeInfo1, routeInfo2);
+ assertEquals(routeInfo1.hashCode(), routeInfo2.hashCode());
+ }
+
+ @Test
+ public void testEqualsCreatedWithBuilderCopyConstructor() {
+ Bundle extras = new Bundle();
+ extras.putString(TEST_KEY, TEST_VALUE);
+
+ MediaRoute2Info routeInfo1 = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+ .addFeature(TEST_ROUTE_TYPE_0)
+ .addFeature(TEST_ROUTE_TYPE_1)
+ .setDeviceType(TEST_DEVICE_TYPE)
+ .setIconUri(TEST_ICON_URI)
+ .setDescription(TEST_DESCRIPTION)
+ .setConnectionState(TEST_CONNECTION_STATE)
+ .setClientPackageName(TEST_CLIENT_PACKAGE_NAME)
+ .setVolumeHandling(TEST_VOLUME_HANDLING)
+ .setVolumeMax(TEST_VOLUME_MAX)
+ .setVolume(TEST_VOLUME)
+ .setExtras(extras)
+ .build();
+
+ MediaRoute2Info routeInfo2 = new MediaRoute2Info.Builder(routeInfo1).build();
+
+ assertEquals(routeInfo1, routeInfo2);
+ assertEquals(routeInfo1.hashCode(), routeInfo2.hashCode());
+ }
+
+ @Test
+ public void testEqualsReturnFalse() {
+ Bundle extras = new Bundle();
+ extras.putString(TEST_KEY, TEST_VALUE);
+
+ MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+ .addFeature(TEST_ROUTE_TYPE_0)
+ .addFeature(TEST_ROUTE_TYPE_1)
+ .setDeviceType(TEST_DEVICE_TYPE)
+ .setIconUri(TEST_ICON_URI)
+ .setDescription(TEST_DESCRIPTION)
+ .setConnectionState(TEST_CONNECTION_STATE)
+ .setClientPackageName(TEST_CLIENT_PACKAGE_NAME)
+ .setVolumeHandling(TEST_VOLUME_HANDLING)
+ .setVolumeMax(TEST_VOLUME_MAX)
+ .setVolume(TEST_VOLUME)
+ .setExtras(extras)
+ .build();
+
+ // Now, we will use copy constructor
+ assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
+ .addFeature("randomRouteType")
+ .build());
+ assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
+ .setDeviceType(TEST_DEVICE_TYPE + 1)
+ .build());
+ assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
+ .setIconUri(Uri.parse("randomUri"))
+ .build());
+ assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
+ .setDescription("randomDescription")
+ .build());
+ assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
+ .setConnectionState(TEST_CONNECTION_STATE + 1)
+ .build());
+ assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
+ .setClientPackageName("randomPackageName")
+ .build());
+ assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
+ .setVolumeHandling(TEST_VOLUME_HANDLING + 1)
+ .build());
+ assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
+ .setVolumeMax(TEST_VOLUME_MAX + 100)
+ .build());
+ assertNotEquals(routeInfo, new MediaRoute2Info.Builder(routeInfo)
+ .setVolume(TEST_VOLUME + 10)
+ .build());
+ // Note: Extras will not affect the equals.
+ }
+
+ @Test
+ public void testParcelingAndUnParceling() {
+ Bundle extras = new Bundle();
+ extras.putString(TEST_KEY, TEST_VALUE);
+
+ MediaRoute2Info routeInfo = new MediaRoute2Info.Builder(TEST_ID, TEST_NAME)
+ .addFeature(TEST_ROUTE_TYPE_0)
+ .addFeature(TEST_ROUTE_TYPE_1)
+ .setDeviceType(TEST_DEVICE_TYPE)
+ .setIconUri(TEST_ICON_URI)
+ .setDescription(TEST_DESCRIPTION)
+ .setConnectionState(TEST_CONNECTION_STATE)
+ .setClientPackageName(TEST_CLIENT_PACKAGE_NAME)
+ .setVolumeHandling(TEST_VOLUME_HANDLING)
+ .setVolumeMax(TEST_VOLUME_MAX)
+ .setVolume(TEST_VOLUME)
+ .setExtras(extras)
+ .build();
+
+ Parcel parcel = Parcel.obtain();
+ routeInfo.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+
+ MediaRoute2Info routeInfoFromParcel = MediaRoute2Info.CREATOR.createFromParcel(parcel);
+ assertEquals(routeInfo, routeInfoFromParcel);
+ assertEquals(routeInfo.hashCode(), routeInfoFromParcel.hashCode());
+
+ // Check extras
+ Bundle extrasOut = routeInfoFromParcel.getExtras();
+ assertNotNull(extrasOut);
+ assertTrue(extrasOut.containsKey(TEST_KEY));
+ assertEquals(TEST_VALUE, extrasOut.getString(TEST_KEY));
+ }
+}
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
index 10c17dc..59e1122 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2Test.java
@@ -16,23 +16,19 @@
package com.android.mediaroutertest;
-import static android.media.MediaRoute2Info.CONNECTION_STATE_CONNECTED;
import static android.media.MediaRoute2Info.CONNECTION_STATE_CONNECTING;
-import static android.media.MediaRoute2Info.DEVICE_TYPE_SPEAKER;
-import static android.media.MediaRoute2Info.DEVICE_TYPE_TV;
-import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_FIXED;
+import static android.media.MediaRoute2Info.DEVICE_TYPE_REMOTE_SPEAKER;
import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE;
-import static com.android.mediaroutertest.MediaRouterManagerTest.CATEGORIES_ALL;
-import static com.android.mediaroutertest.MediaRouterManagerTest.CATEGORIES_SPECIAL;
-import static com.android.mediaroutertest.MediaRouterManagerTest.CATEGORY_SAMPLE;
-import static com.android.mediaroutertest.MediaRouterManagerTest.CATEGORY_SPECIAL;
+import static com.android.mediaroutertest.MediaRouterManagerTest.FEATURES_ALL;
+import static com.android.mediaroutertest.MediaRouterManagerTest.FEATURES_SPECIAL;
+import static com.android.mediaroutertest.MediaRouterManagerTest.FEATURE_SAMPLE;
import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID1;
import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID2;
import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID3_SESSION_CREATION_FAILED;
import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID4_TO_SELECT_AND_DESELECT;
import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID5_TO_TRANSFER_TO;
-import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID_SPECIAL_CATEGORY;
+import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID_SPECIAL_FEATURE;
import static com.android.mediaroutertest.MediaRouterManagerTest.ROUTE_ID_VARIABLE_VOLUME;
import static com.android.mediaroutertest.MediaRouterManagerTest.SYSTEM_PROVIDER_ID;
@@ -48,9 +44,10 @@
import android.media.MediaRoute2Info;
import android.media.MediaRouter2;
import android.media.MediaRouter2.RouteCallback;
-import android.media.MediaRouter2.RouteSessionController;
+import android.media.MediaRouter2.RoutingController;
import android.media.MediaRouter2.SessionCallback;
-import android.media.RouteSessionInfo;
+import android.media.RouteDiscoveryPreference;
+import android.media.RoutingSessionInfo;
import android.net.Uri;
import android.os.Parcel;
import android.support.test.InstrumentationRegistry;
@@ -95,14 +92,14 @@
}
/**
- * Tests if we get proper routes for application that has special control category.
+ * Tests if we get proper routes for application that has special route type.
*/
@Test
public void testGetRoutes() throws Exception {
- Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CATEGORIES_SPECIAL);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURES_SPECIAL);
assertEquals(1, routes.size());
- assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY));
+ assertNotNull(routes.get(ROUTE_ID_SPECIAL_FEATURE));
}
@Test
@@ -114,9 +111,9 @@
.setIconUri(new Uri.Builder().path("icon").build())
.setVolume(5)
.setVolumeMax(20)
- .addSupportedCategory(CATEGORY_SAMPLE)
+ .addFeature(FEATURE_SAMPLE)
.setVolumeHandling(PLAYBACK_VOLUME_VARIABLE)
- .setDeviceType(DEVICE_TYPE_SPEAKER)
+ .setDeviceType(DEVICE_TYPE_REMOTE_SPEAKER)
.build();
MediaRoute2Info routeInfoRebuilt = new MediaRoute2Info.Builder(routeInfo).build();
@@ -131,67 +128,8 @@
}
@Test
- public void testRouteInfoInequality() {
- MediaRoute2Info route = new MediaRoute2Info.Builder("id", "name")
- .setDescription("description")
- .setClientPackageName("com.android.mediaroutertest")
- .setConnectionState(CONNECTION_STATE_CONNECTING)
- .setIconUri(new Uri.Builder().path("icon").build())
- .addSupportedCategory(CATEGORY_SAMPLE)
- .setVolume(5)
- .setVolumeMax(20)
- .setVolumeHandling(PLAYBACK_VOLUME_VARIABLE)
- .setDeviceType(DEVICE_TYPE_SPEAKER)
- .build();
-
- MediaRoute2Info routeId = new MediaRoute2Info.Builder(route)
- .setId("another id").build();
- assertNotEquals(route, routeId);
-
- MediaRoute2Info routeName = new MediaRoute2Info.Builder(route)
- .setName("another name").build();
- assertNotEquals(route, routeName);
-
- MediaRoute2Info routeDescription = new MediaRoute2Info.Builder(route)
- .setDescription("another description").build();
- assertNotEquals(route, routeDescription);
-
- MediaRoute2Info routeConnectionState = new MediaRoute2Info.Builder(route)
- .setConnectionState(CONNECTION_STATE_CONNECTED).build();
- assertNotEquals(route, routeConnectionState);
-
- MediaRoute2Info routeIcon = new MediaRoute2Info.Builder(route)
- .setIconUri(new Uri.Builder().path("new icon").build()).build();
- assertNotEquals(route, routeIcon);
-
- MediaRoute2Info routeClient = new MediaRoute2Info.Builder(route)
- .setClientPackageName("another.client.package").build();
- assertNotEquals(route, routeClient);
-
- MediaRoute2Info routeCategory = new MediaRoute2Info.Builder(route)
- .addSupportedCategory(CATEGORY_SPECIAL).build();
- assertNotEquals(route, routeCategory);
-
- MediaRoute2Info routeVolume = new MediaRoute2Info.Builder(route)
- .setVolume(10).build();
- assertNotEquals(route, routeVolume);
-
- MediaRoute2Info routeVolumeMax = new MediaRoute2Info.Builder(route)
- .setVolumeMax(30).build();
- assertNotEquals(route, routeVolumeMax);
-
- MediaRoute2Info routeVolumeHandling = new MediaRoute2Info.Builder(route)
- .setVolumeHandling(PLAYBACK_VOLUME_FIXED).build();
- assertNotEquals(route, routeVolumeHandling);
-
- MediaRoute2Info routeDeviceType = new MediaRoute2Info.Builder(route)
- .setVolume(DEVICE_TYPE_TV).build();
- assertNotEquals(route, routeDeviceType);
- }
-
- @Test
public void testControlVolumeWithRouter() throws Exception {
- Map<String, MediaRoute2Info> routes = waitAndGetRoutes(CATEGORIES_ALL);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURES_ALL);
MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
assertNotNull(volRoute);
@@ -202,12 +140,14 @@
awaitOnRouteChanged(
() -> mRouter2.requestUpdateVolume(volRoute, deltaVolume),
ROUTE_ID_VARIABLE_VOLUME,
- (route -> route.getVolume() == originalVolume + deltaVolume));
+ (route -> route.getVolume() == originalVolume + deltaVolume),
+ FEATURES_ALL);
awaitOnRouteChanged(
() -> mRouter2.requestSetVolume(volRoute, originalVolume),
ROUTE_ID_VARIABLE_VOLUME,
- (route -> route.getVolume() == originalVolume));
+ (route -> route.getVolume() == originalVolume),
+ FEATURES_ALL);
}
@Test
@@ -233,14 +173,16 @@
@Test
public void testRequestCreateSessionWithInvalidArguments() {
- MediaRoute2Info route = new MediaRoute2Info.Builder("id", "name").build();
- String controlCategory = "controlCategory";
+ String routeFeature = "routeFeature";
+ MediaRoute2Info route = new MediaRoute2Info.Builder("id", "name")
+ .addFeature(routeFeature)
+ .build();
// Tests null route
assertThrows(NullPointerException.class,
- () -> mRouter2.requestCreateSession(null, controlCategory));
+ () -> mRouter2.requestCreateSession(null, routeFeature));
- // Tests null or empty control category
+ // Tests null or empty route feature
assertThrows(IllegalArgumentException.class,
() -> mRouter2.requestCreateSession(route, null));
assertThrows(IllegalArgumentException.class,
@@ -249,46 +191,48 @@
@Test
public void testRequestCreateSessionSuccess() throws Exception {
- final List<String> sampleControlCategory = new ArrayList<>();
- sampleControlCategory.add(CATEGORY_SAMPLE);
+ final List<String> sampleRouteFeature = new ArrayList<>();
+ sampleRouteFeature.add(FEATURE_SAMPLE);
- Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleControlCategory);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteFeature);
MediaRoute2Info route = routes.get(ROUTE_ID1);
assertNotNull(route);
final CountDownLatch successLatch = new CountDownLatch(1);
final CountDownLatch failureLatch = new CountDownLatch(1);
+ final List<RoutingController> controllers = new ArrayList<>();
// Create session with this route
SessionCallback sessionCallback = new SessionCallback() {
@Override
- public void onSessionCreated(RouteSessionController controller) {
+ public void onSessionCreated(RoutingController controller) {
assertNotNull(controller);
assertTrue(createRouteMap(controller.getSelectedRoutes()).containsKey(ROUTE_ID1));
- assertTrue(TextUtils.equals(CATEGORY_SAMPLE, controller.getControlCategory()));
+ assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller.getRouteFeature()));
+ controllers.add(controller);
successLatch.countDown();
}
@Override
public void onSessionCreationFailed(MediaRoute2Info requestedRoute,
- String requestedControlCategory) {
+ String requestedRouteFeature) {
failureLatch.countDown();
}
};
// TODO: Remove this once the MediaRouter2 becomes always connected to the service.
RouteCallback routeCallback = new RouteCallback();
- mRouter2.registerRouteCallback(mExecutor, routeCallback);
+ mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
try {
mRouter2.registerSessionCallback(mExecutor, sessionCallback);
- mRouter2.requestCreateSession(route, CATEGORY_SAMPLE);
+ mRouter2.requestCreateSession(route, FEATURE_SAMPLE);
assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
// onSessionCreationFailed should not be called.
assertFalse(failureLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
} finally {
- // TODO: Release controllers
+ releaseControllers(controllers);
mRouter2.unregisterRouteCallback(routeCallback);
mRouter2.unregisterSessionCallback(sessionCallback);
}
@@ -296,45 +240,47 @@
@Test
public void testRequestCreateSessionFailure() throws Exception {
- final List<String> sampleControlCategory = new ArrayList<>();
- sampleControlCategory.add(CATEGORY_SAMPLE);
+ final List<String> sampleRouteType = new ArrayList<>();
+ sampleRouteType.add(FEATURE_SAMPLE);
- Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleControlCategory);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
MediaRoute2Info route = routes.get(ROUTE_ID3_SESSION_CREATION_FAILED);
assertNotNull(route);
final CountDownLatch successLatch = new CountDownLatch(1);
final CountDownLatch failureLatch = new CountDownLatch(1);
+ final List<RoutingController> controllers = new ArrayList<>();
// Create session with this route
SessionCallback sessionCallback = new SessionCallback() {
@Override
- public void onSessionCreated(RouteSessionController controller) {
+ public void onSessionCreated(RoutingController controller) {
+ controllers.add(controller);
successLatch.countDown();
}
@Override
public void onSessionCreationFailed(MediaRoute2Info requestedRoute,
- String requestedControlCategory) {
+ String requestedRouteFeature) {
assertEquals(route, requestedRoute);
- assertTrue(TextUtils.equals(CATEGORY_SAMPLE, requestedControlCategory));
+ assertTrue(TextUtils.equals(FEATURE_SAMPLE, requestedRouteFeature));
failureLatch.countDown();
}
};
// TODO: Remove this once the MediaRouter2 becomes always connected to the service.
RouteCallback routeCallback = new RouteCallback();
- mRouter2.registerRouteCallback(mExecutor, routeCallback);
+ mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
try {
mRouter2.registerSessionCallback(mExecutor, sessionCallback);
- mRouter2.requestCreateSession(route, CATEGORY_SAMPLE);
+ mRouter2.requestCreateSession(route, FEATURE_SAMPLE);
assertTrue(failureLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
// onSessionCreated should not be called.
assertFalse(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
} finally {
- // TODO: Release controllers
+ releaseControllers(controllers);
mRouter2.unregisterRouteCallback(routeCallback);
mRouter2.unregisterSessionCallback(sessionCallback);
}
@@ -342,30 +288,29 @@
@Test
public void testRequestCreateSessionMultipleSessions() throws Exception {
- final List<String> sampleControlCategory = new ArrayList<>();
- sampleControlCategory.add(CATEGORY_SAMPLE);
+ final List<String> sampleRouteType = new ArrayList<>();
+ sampleRouteType.add(FEATURE_SAMPLE);
final CountDownLatch successLatch = new CountDownLatch(2);
final CountDownLatch failureLatch = new CountDownLatch(1);
-
- final List<RouteSessionController> createdControllers = new ArrayList<>();
+ final List<RoutingController> createdControllers = new ArrayList<>();
// Create session with this route
SessionCallback sessionCallback = new SessionCallback() {
@Override
- public void onSessionCreated(RouteSessionController controller) {
+ public void onSessionCreated(RoutingController controller) {
createdControllers.add(controller);
successLatch.countDown();
}
@Override
public void onSessionCreationFailed(MediaRoute2Info requestedRoute,
- String requestedControlCategory) {
+ String requestedRouteFeature) {
failureLatch.countDown();
}
};
- Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleControlCategory);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
MediaRoute2Info route1 = routes.get(ROUTE_ID1);
MediaRoute2Info route2 = routes.get(ROUTE_ID2);
assertNotNull(route1);
@@ -373,12 +318,12 @@
// TODO: Remove this once the MediaRouter2 becomes always connected to the service.
RouteCallback routeCallback = new RouteCallback();
- mRouter2.registerRouteCallback(mExecutor, routeCallback);
+ mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
try {
mRouter2.registerSessionCallback(mExecutor, sessionCallback);
- mRouter2.requestCreateSession(route1, CATEGORY_SAMPLE);
- mRouter2.requestCreateSession(route2, CATEGORY_SAMPLE);
+ mRouter2.requestCreateSession(route1, FEATURE_SAMPLE);
+ mRouter2.requestCreateSession(route2, FEATURE_SAMPLE);
assertTrue(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
// onSessionCreationFailed should not be called.
@@ -386,16 +331,17 @@
// Created controllers should have proper info
assertEquals(2, createdControllers.size());
- RouteSessionController controller1 = createdControllers.get(0);
- RouteSessionController controller2 = createdControllers.get(1);
+ RoutingController controller1 = createdControllers.get(0);
+ RoutingController controller2 = createdControllers.get(1);
assertNotEquals(controller1.getSessionId(), controller2.getSessionId());
assertTrue(createRouteMap(controller1.getSelectedRoutes()).containsKey(ROUTE_ID1));
assertTrue(createRouteMap(controller2.getSelectedRoutes()).containsKey(ROUTE_ID2));
- assertTrue(TextUtils.equals(CATEGORY_SAMPLE, controller1.getControlCategory()));
- assertTrue(TextUtils.equals(CATEGORY_SAMPLE, controller2.getControlCategory()));
+ assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller1.getRouteFeature()));
+ assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller2.getRouteFeature()));
+
} finally {
- // TODO: Release controllers
+ releaseControllers(createdControllers);
mRouter2.unregisterRouteCallback(routeCallback);
mRouter2.unregisterSessionCallback(sessionCallback);
}
@@ -403,37 +349,39 @@
@Test
public void testSessionCallbackIsNotCalledAfterUnregistered() throws Exception {
- final List<String> sampleControlCategory = new ArrayList<>();
- sampleControlCategory.add(CATEGORY_SAMPLE);
+ final List<String> sampleRouteType = new ArrayList<>();
+ sampleRouteType.add(FEATURE_SAMPLE);
- Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleControlCategory);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
MediaRoute2Info route = routes.get(ROUTE_ID1);
assertNotNull(route);
final CountDownLatch successLatch = new CountDownLatch(1);
final CountDownLatch failureLatch = new CountDownLatch(1);
+ final List<RoutingController> controllers = new ArrayList<>();
// Create session with this route
SessionCallback sessionCallback = new SessionCallback() {
@Override
- public void onSessionCreated(RouteSessionController controller) {
+ public void onSessionCreated(RoutingController controller) {
+ controllers.add(controller);
successLatch.countDown();
}
@Override
public void onSessionCreationFailed(MediaRoute2Info requestedRoute,
- String requestedControlCategory) {
+ String requestedRouteFeature) {
failureLatch.countDown();
}
};
// TODO: Remove this once the MediaRouter2 becomes always connected to the service.
RouteCallback routeCallback = new RouteCallback();
- mRouter2.registerRouteCallback(mExecutor, routeCallback);
+ mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
try {
mRouter2.registerSessionCallback(mExecutor, sessionCallback);
- mRouter2.requestCreateSession(route, CATEGORY_SAMPLE);
+ mRouter2.requestCreateSession(route, FEATURE_SAMPLE);
// Unregisters session callback
mRouter2.unregisterSessionCallback(sessionCallback);
@@ -442,7 +390,7 @@
assertFalse(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertFalse(failureLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
} finally {
- // TODO: Release controllers
+ releaseControllers(controllers);
mRouter2.unregisterRouteCallback(routeCallback);
mRouter2.unregisterSessionCallback(sessionCallback);
}
@@ -450,48 +398,49 @@
// TODO: Add tests for illegal inputs if needed (e.g. selecting already selected route)
@Test
- public void testRouteSessionControllerSelectAndDeselectRoute() throws Exception {
- final List<String> sampleControlCategory = new ArrayList<>();
- sampleControlCategory.add(CATEGORY_SAMPLE);
+ public void testRoutingControllerSelectAndDeselectRoute() throws Exception {
+ final List<String> sampleRouteType = new ArrayList<>();
+ sampleRouteType.add(FEATURE_SAMPLE);
- Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleControlCategory);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
MediaRoute2Info routeToCreateSessionWith = routes.get(ROUTE_ID1);
assertNotNull(routeToCreateSessionWith);
final CountDownLatch onSessionCreatedLatch = new CountDownLatch(1);
final CountDownLatch onSessionInfoChangedLatchForSelect = new CountDownLatch(1);
final CountDownLatch onSessionInfoChangedLatchForDeselect = new CountDownLatch(1);
- final List<RouteSessionController> controllers = new ArrayList<>();
+ final List<RoutingController> controllers = new ArrayList<>();
// Create session with ROUTE_ID1
SessionCallback sessionCallback = new SessionCallback() {
@Override
- public void onSessionCreated(RouteSessionController controller) {
+ public void onSessionCreated(RoutingController controller) {
assertNotNull(controller);
assertTrue(getRouteIds(controller.getSelectedRoutes()).contains(ROUTE_ID1));
- assertTrue(TextUtils.equals(CATEGORY_SAMPLE, controller.getControlCategory()));
+ assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller.getRouteFeature()));
controllers.add(controller);
onSessionCreatedLatch.countDown();
}
@Override
- public void onSessionInfoChanged(RouteSessionController controller,
- RouteSessionInfo oldInfo, RouteSessionInfo newInfo) {
+ public void onSessionInfoChanged(RoutingController controller,
+ RoutingSessionInfo oldInfo, RoutingSessionInfo newInfo) {
if (onSessionCreatedLatch.getCount() != 0
- || controllers.get(0).getSessionId() != controller.getSessionId()) {
+ || !TextUtils.equals(
+ controllers.get(0).getSessionId(), controller.getSessionId())) {
return;
}
if (onSessionInfoChangedLatchForSelect.getCount() != 0) {
// Check oldInfo
- assertEquals(controller.getSessionId(), oldInfo.getSessionId());
+ assertEquals(controller.getSessionId(), oldInfo.getId());
assertEquals(1, oldInfo.getSelectedRoutes().size());
assertTrue(oldInfo.getSelectedRoutes().contains(ROUTE_ID1));
assertTrue(oldInfo.getSelectableRoutes().contains(
ROUTE_ID4_TO_SELECT_AND_DESELECT));
// Check newInfo
- assertEquals(controller.getSessionId(), newInfo.getSessionId());
+ assertEquals(controller.getSessionId(), newInfo.getId());
assertEquals(2, newInfo.getSelectedRoutes().size());
assertTrue(newInfo.getSelectedRoutes().contains(ROUTE_ID1));
assertTrue(newInfo.getSelectedRoutes().contains(
@@ -502,7 +451,7 @@
onSessionInfoChangedLatchForSelect.countDown();
} else {
// Check newInfo
- assertEquals(controller.getSessionId(), newInfo.getSessionId());
+ assertEquals(controller.getSessionId(), newInfo.getId());
assertEquals(1, newInfo.getSelectedRoutes().size());
assertTrue(newInfo.getSelectedRoutes().contains(ROUTE_ID1));
assertFalse(newInfo.getSelectedRoutes().contains(
@@ -517,15 +466,15 @@
// TODO: Remove this once the MediaRouter2 becomes always connected to the service.
RouteCallback routeCallback = new RouteCallback();
- mRouter2.registerRouteCallback(mExecutor, routeCallback);
+ mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
try {
mRouter2.registerSessionCallback(mExecutor, sessionCallback);
- mRouter2.requestCreateSession(routeToCreateSessionWith, CATEGORY_SAMPLE);
+ mRouter2.requestCreateSession(routeToCreateSessionWith, FEATURE_SAMPLE);
assertTrue(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertEquals(1, controllers.size());
- RouteSessionController controller = controllers.get(0);
+ RoutingController controller = controllers.get(0);
assertTrue(getRouteIds(controller.getSelectableRoutes())
.contains(ROUTE_ID4_TO_SELECT_AND_DESELECT));
@@ -541,53 +490,53 @@
assertTrue(onSessionInfoChangedLatchForDeselect.await(
TIMEOUT_MS, TimeUnit.MILLISECONDS));
} finally {
- // TODO: Release controllers
- controllers.clear();
+ releaseControllers(controllers);
mRouter2.unregisterRouteCallback(routeCallback);
mRouter2.unregisterSessionCallback(sessionCallback);
}
}
@Test
- public void testRouteSessionControllerTransferToRoute() throws Exception {
- final List<String> sampleControlCategory = new ArrayList<>();
- sampleControlCategory.add(CATEGORY_SAMPLE);
+ public void testRoutingControllerTransferToRoute() throws Exception {
+ final List<String> sampleRouteType = new ArrayList<>();
+ sampleRouteType.add(FEATURE_SAMPLE);
- Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleControlCategory);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
MediaRoute2Info routeToCreateSessionWith = routes.get(ROUTE_ID1);
assertNotNull(routeToCreateSessionWith);
final CountDownLatch onSessionCreatedLatch = new CountDownLatch(1);
final CountDownLatch onSessionInfoChangedLatch = new CountDownLatch(1);
- final List<RouteSessionController> controllers = new ArrayList<>();
+ final List<RoutingController> controllers = new ArrayList<>();
// Create session with ROUTE_ID1
SessionCallback sessionCallback = new SessionCallback() {
@Override
- public void onSessionCreated(RouteSessionController controller) {
+ public void onSessionCreated(RoutingController controller) {
assertNotNull(controller);
assertTrue(getRouteIds(controller.getSelectedRoutes()).contains(ROUTE_ID1));
- assertTrue(TextUtils.equals(CATEGORY_SAMPLE, controller.getControlCategory()));
+ assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller.getRouteFeature()));
controllers.add(controller);
onSessionCreatedLatch.countDown();
}
@Override
- public void onSessionInfoChanged(RouteSessionController controller,
- RouteSessionInfo oldInfo, RouteSessionInfo newInfo) {
+ public void onSessionInfoChanged(RoutingController controller,
+ RoutingSessionInfo oldInfo, RoutingSessionInfo newInfo) {
if (onSessionCreatedLatch.getCount() != 0
- || controllers.get(0).getSessionId() != controller.getSessionId()) {
+ || !TextUtils.equals(
+ controllers.get(0).getSessionId(), controller.getSessionId())) {
return;
}
// Check oldInfo
- assertEquals(controller.getSessionId(), oldInfo.getSessionId());
+ assertEquals(controller.getSessionId(), oldInfo.getId());
assertEquals(1, oldInfo.getSelectedRoutes().size());
assertTrue(oldInfo.getSelectedRoutes().contains(ROUTE_ID1));
assertTrue(oldInfo.getTransferrableRoutes().contains(ROUTE_ID5_TO_TRANSFER_TO));
// Check newInfo
- assertEquals(controller.getSessionId(), newInfo.getSessionId());
+ assertEquals(controller.getSessionId(), newInfo.getId());
assertEquals(1, newInfo.getSelectedRoutes().size());
assertFalse(newInfo.getSelectedRoutes().contains(ROUTE_ID1));
assertTrue(newInfo.getSelectedRoutes().contains(ROUTE_ID5_TO_TRANSFER_TO));
@@ -599,15 +548,15 @@
// TODO: Remove this once the MediaRouter2 becomes always connected to the service.
RouteCallback routeCallback = new RouteCallback();
- mRouter2.registerRouteCallback(mExecutor, routeCallback);
+ mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
try {
mRouter2.registerSessionCallback(mExecutor, sessionCallback);
- mRouter2.requestCreateSession(routeToCreateSessionWith, CATEGORY_SAMPLE);
+ mRouter2.requestCreateSession(routeToCreateSessionWith, FEATURE_SAMPLE);
assertTrue(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertEquals(1, controllers.size());
- RouteSessionController controller = controllers.get(0);
+ RoutingController controller = controllers.get(0);
assertTrue(getRouteIds(controller.getTransferrableRoutes())
.contains(ROUTE_ID5_TO_TRANSFER_TO));
@@ -618,29 +567,96 @@
controller.transferToRoute(routeToTransferTo);
assertTrue(onSessionInfoChangedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
} finally {
- // TODO: Release controllers
- controllers.clear();
+ releaseControllers(controllers);
mRouter2.unregisterRouteCallback(routeCallback);
mRouter2.unregisterSessionCallback(sessionCallback);
}
+ }
+ // TODO: Add tests for onSessionReleased() call.
+
+ @Test
+ public void testRoutingControllerReleaseShouldIgnoreTransferTo() throws Exception {
+ final List<String> sampleRouteType = new ArrayList<>();
+ sampleRouteType.add(FEATURE_SAMPLE);
+
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutes(sampleRouteType);
+ MediaRoute2Info routeToCreateSessionWith = routes.get(ROUTE_ID1);
+ assertNotNull(routeToCreateSessionWith);
+
+ final CountDownLatch onSessionCreatedLatch = new CountDownLatch(1);
+ final CountDownLatch onSessionInfoChangedLatch = new CountDownLatch(1);
+ final List<RoutingController> controllers = new ArrayList<>();
+
+ // Create session with ROUTE_ID1
+ SessionCallback sessionCallback = new SessionCallback() {
+ @Override
+ public void onSessionCreated(RoutingController controller) {
+ assertNotNull(controller);
+ assertTrue(getRouteIds(controller.getSelectedRoutes()).contains(ROUTE_ID1));
+ assertTrue(TextUtils.equals(FEATURE_SAMPLE, controller.getRouteFeature()));
+ controllers.add(controller);
+ onSessionCreatedLatch.countDown();
+ }
+
+ @Override
+ public void onSessionInfoChanged(RoutingController controller,
+ RoutingSessionInfo oldInfo, RoutingSessionInfo newInfo) {
+ if (onSessionCreatedLatch.getCount() != 0
+ || !TextUtils.equals(
+ controllers.get(0).getSessionId(), controller.getSessionId())) {
+ return;
+ }
+ onSessionInfoChangedLatch.countDown();
+ }
+ };
+
+ // TODO: Remove this once the MediaRouter2 becomes always connected to the service.
+ RouteCallback routeCallback = new RouteCallback();
+ mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
+
+ try {
+ mRouter2.registerSessionCallback(mExecutor, sessionCallback);
+ mRouter2.requestCreateSession(routeToCreateSessionWith, FEATURE_SAMPLE);
+ assertTrue(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+
+ assertEquals(1, controllers.size());
+ RoutingController controller = controllers.get(0);
+ assertTrue(getRouteIds(controller.getTransferrableRoutes())
+ .contains(ROUTE_ID5_TO_TRANSFER_TO));
+
+ // Release controller. Future calls should be ignored.
+ controller.release();
+
+ // Transfer to ROUTE_ID5_TO_TRANSFER_TO
+ MediaRoute2Info routeToTransferTo = routes.get(ROUTE_ID5_TO_TRANSFER_TO);
+ assertNotNull(routeToTransferTo);
+
+ // This call should be ignored.
+ // The onSessionInfoChanged() shouldn't be called.
+ controller.transferToRoute(routeToTransferTo);
+ assertFalse(onSessionInfoChangedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ } finally {
+ releaseControllers(controllers);
+ mRouter2.unregisterRouteCallback(routeCallback);
+ mRouter2.unregisterSessionCallback(sessionCallback);
+ }
}
// Helper for getting routes easily
static Map<String, MediaRoute2Info> createRouteMap(List<MediaRoute2Info> routes) {
Map<String, MediaRoute2Info> routeMap = new HashMap<>();
for (MediaRoute2Info route : routes) {
- // intentionally not using route.getUniqueId() for convenience.
routeMap.put(route.getId(), route);
}
return routeMap;
}
- Map<String, MediaRoute2Info> waitAndGetRoutes(List<String> controlCategories)
+ Map<String, MediaRoute2Info> waitAndGetRoutes(List<String> routeTypes)
throws Exception {
CountDownLatch latch = new CountDownLatch(1);
- // A dummy callback is required to send control category info.
+ // A dummy callback is required to send route type info.
RouteCallback routeCallback = new RouteCallback() {
@Override
public void onRoutesAdded(List<MediaRoute2Info> routes) {
@@ -653,8 +669,8 @@
}
};
- mRouter2.setControlCategories(controlCategories);
- mRouter2.registerRouteCallback(mExecutor, routeCallback);
+ mRouter2.registerRouteCallback(mExecutor, routeCallback,
+ new RouteDiscoveryPreference.Builder(routeTypes, true).build());
try {
latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
return createRouteMap(mRouter2.getRoutes());
@@ -663,8 +679,14 @@
}
}
+ static void releaseControllers(@NonNull List<RoutingController> controllers) {
+ for (RoutingController controller : controllers) {
+ controller.release();
+ }
+ }
+
/**
- * Returns a list of IDs (not uniqueId) of the given route list.
+ * Returns a list of IDs of the given route list.
*/
List<String> getRouteIds(@NonNull List<MediaRoute2Info> routes) {
List<String> result = new ArrayList<>();
@@ -675,7 +697,8 @@
}
void awaitOnRouteChanged(Runnable task, String routeId,
- Predicate<MediaRoute2Info> predicate) throws Exception {
+ Predicate<MediaRoute2Info> predicate,
+ List<String> routeTypes) throws Exception {
CountDownLatch latch = new CountDownLatch(1);
RouteCallback routeCallback = new RouteCallback() {
@Override
@@ -686,7 +709,8 @@
}
}
};
- mRouter2.registerRouteCallback(mExecutor, routeCallback);
+ mRouter2.registerRouteCallback(mExecutor, routeCallback,
+ new RouteDiscoveryPreference.Builder(routeTypes, true).build());
try {
task.run();
assertTrue(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
index 83c7c17..83dd0c0 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouterManagerTest.java
@@ -20,7 +20,6 @@
import static android.media.MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
@@ -31,6 +30,7 @@
import android.media.MediaRouter2.RouteCallback;
import android.media.MediaRouter2.SessionCallback;
import android.media.MediaRouter2Manager;
+import android.media.RouteDiscoveryPreference;
import android.support.test.InstrumentationRegistry;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
@@ -57,41 +57,48 @@
public class MediaRouterManagerTest {
private static final String TAG = "MediaRouterManagerTest";
- // Must be the same as SampleMediaRoute2ProviderService
- public static final String ROUTE_ID1 = "route_id1";
+ public static final String SAMPLE_PROVIDER_ROUTES_ID_PREFIX =
+ "com.android.mediarouteprovider.example/.SampleMediaRoute2ProviderService:";
+
+ // Must be the same as SampleMediaRoute2ProviderService except the prefix of IDs.
+ public static final String ROUTE_ID1 = SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_id1";
public static final String ROUTE_NAME1 = "Sample Route 1";
- public static final String ROUTE_ID2 = "route_id2";
+ public static final String ROUTE_ID2 = SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_id2";
public static final String ROUTE_NAME2 = "Sample Route 2";
public static final String ROUTE_ID3_SESSION_CREATION_FAILED =
- "route_id3_session_creation_failed";
+ SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_id3_session_creation_failed";
public static final String ROUTE_NAME3 = "Sample Route 3 - Session creation failed";
public static final String ROUTE_ID4_TO_SELECT_AND_DESELECT =
- "route_id4_to_select_and_deselect";
+ SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_id4_to_select_and_deselect";
public static final String ROUTE_NAME4 = "Sample Route 4 - Route to select and deselect";
- public static final String ROUTE_ID5_TO_TRANSFER_TO = "route_id5_to_transfer_to";
+ public static final String ROUTE_ID5_TO_TRANSFER_TO =
+ SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_id5_to_transfer_to";
public static final String ROUTE_NAME5 = "Sample Route 5 - Route to transfer to";
- public static final String ROUTE_ID_SPECIAL_CATEGORY = "route_special_category";
- public static final String ROUTE_NAME_SPECIAL_CATEGORY = "Special Category Route";
+ public static final String ROUTE_ID_SPECIAL_FEATURE =
+ SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_special_feature";
+ public static final String ROUTE_NAME_SPECIAL_FEATURE = "Special Feature Route";
public static final String SYSTEM_PROVIDER_ID =
"com.android.server.media/.SystemMediaRoute2Provider";
public static final int VOLUME_MAX = 100;
- public static final String ROUTE_ID_FIXED_VOLUME = "route_fixed_volume";
+ public static final String ROUTE_ID_FIXED_VOLUME =
+ SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_fixed_volume";
public static final String ROUTE_NAME_FIXED_VOLUME = "Fixed Volume Route";
- public static final String ROUTE_ID_VARIABLE_VOLUME = "route_variable_volume";
+ public static final String ROUTE_ID_VARIABLE_VOLUME =
+ SAMPLE_PROVIDER_ROUTES_ID_PREFIX + "route_variable_volume";
public static final String ROUTE_NAME_VARIABLE_VOLUME = "Variable Volume Route";
public static final String ACTION_REMOVE_ROUTE =
"com.android.mediarouteprovider.action_remove_route";
- public static final String CATEGORY_SAMPLE =
- "com.android.mediarouteprovider.CATEGORY_SAMPLE";
- public static final String CATEGORY_SPECIAL =
- "com.android.mediarouteprovider.CATEGORY_SPECIAL";
+ public static final String FEATURE_SAMPLE =
+ "com.android.mediarouteprovider.FEATURE_SAMPLE";
+ public static final String FEATURE_SPECIAL =
+ "com.android.mediarouteprovider.FEATURE_SPECIAL";
- private static final String CATEGORY_LIVE_AUDIO = "android.media.intent.category.LIVE_AUDIO";
+ private static final String FEATURE_LIVE_AUDIO = "android.media.intent.route.LIVE_AUDIO";
private static final int TIMEOUT_MS = 5000;
@@ -105,18 +112,18 @@
private final List<RouteCallback> mRouteCallbacks = new ArrayList<>();
private final List<SessionCallback> mSessionCallbacks = new ArrayList<>();
- public static final List<String> CATEGORIES_ALL = new ArrayList();
- public static final List<String> CATEGORIES_SPECIAL = new ArrayList();
- private static final List<String> CATEGORIES_LIVE_AUDIO = new ArrayList<>();
+ public static final List<String> FEATURES_ALL = new ArrayList();
+ public static final List<String> FEATURES_SPECIAL = new ArrayList();
+ private static final List<String> FEATURES_LIVE_AUDIO = new ArrayList<>();
static {
- CATEGORIES_ALL.add(CATEGORY_SAMPLE);
- CATEGORIES_ALL.add(CATEGORY_SPECIAL);
- CATEGORIES_ALL.add(CATEGORY_LIVE_AUDIO);
+ FEATURES_ALL.add(FEATURE_SAMPLE);
+ FEATURES_ALL.add(FEATURE_SPECIAL);
+ FEATURES_ALL.add(FEATURE_LIVE_AUDIO);
- CATEGORIES_SPECIAL.add(CATEGORY_SPECIAL);
+ FEATURES_SPECIAL.add(FEATURE_SPECIAL);
- CATEGORIES_LIVE_AUDIO.add(CATEGORY_LIVE_AUDIO);
+ FEATURES_LIVE_AUDIO.add(FEATURE_LIVE_AUDIO);
}
@Before
@@ -135,20 +142,6 @@
clearCallbacks();
}
- //TODO: Move to a separate file
- @Test
- public void testMediaRoute2Info() {
- MediaRoute2Info routeInfo1 = new MediaRoute2Info.Builder("id", "name")
- .build();
- MediaRoute2Info routeInfo2 = new MediaRoute2Info.Builder(routeInfo1).build();
-
- MediaRoute2Info routeInfo3 = new MediaRoute2Info.Builder(routeInfo1)
- .setClientPackageName(mPackageName).build();
-
- assertEquals(routeInfo1, routeInfo2);
- assertNotEquals(routeInfo1, routeInfo3);
- }
-
/**
* Tests if routes are added correctly when a new callback is registered.
*/
@@ -173,7 +166,7 @@
@Test
public void testOnRoutesRemoved() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
- Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
addRouterCallback(new RouteCallback());
addManagerCallback(new MediaRouter2Manager.Callback() {
@@ -195,14 +188,14 @@
}
/**
- * Tests if we get proper routes for application that has special control category.
+ * Tests if we get proper routes for application that has special route feature.
*/
@Test
- public void testControlCategory() throws Exception {
- Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_SPECIAL);
+ public void testRouteFeatures() throws Exception {
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_SPECIAL);
assertEquals(1, routes.size());
- assertNotNull(routes.get(ROUTE_ID_SPECIAL_CATEGORY));
+ assertNotNull(routes.get(ROUTE_ID_SPECIAL_FEATURE));
}
/**
@@ -211,7 +204,7 @@
*/
@Test
public void testRouterOnSessionCreated() throws Exception {
- Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
CountDownLatch latch = new CountDownLatch(1);
@@ -220,7 +213,7 @@
addRouterCallback(new MediaRouter2.RouteCallback());
addSessionCallback(new SessionCallback() {
@Override
- public void onSessionCreated(MediaRouter2.RouteSessionController controller) {
+ public void onSessionCreated(MediaRouter2.RoutingController controller) {
if (createRouteMap(controller.getSelectedRoutes()).containsKey(ROUTE_ID1)) {
latch.countDown();
}
@@ -247,7 +240,7 @@
@Ignore("TODO: test session created callback instead of onRouteSelected")
public void testManagerOnRouteSelected() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
- Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
addRouterCallback(new RouteCallback());
addManagerCallback(new MediaRouter2Manager.Callback() {
@@ -277,7 +270,7 @@
public void testGetActiveRoutes() throws Exception {
CountDownLatch latch = new CountDownLatch(1);
- Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
addRouterCallback(new RouteCallback());
addManagerCallback(new MediaRouter2Manager.Callback() {
@Override
@@ -313,7 +306,7 @@
@Test
@Ignore("TODO: enable when session is released")
public void testSingleProviderSelect() throws Exception {
- Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
addRouterCallback(new RouteCallback());
awaitOnRouteChangedManager(
@@ -338,7 +331,7 @@
@Test
public void testControlVolumeWithManager() throws Exception {
- Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
MediaRoute2Info volRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
int originalVolume = volRoute.getVolume();
@@ -357,7 +350,7 @@
@Test
public void testVolumeHandling() throws Exception {
- Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(CATEGORIES_ALL);
+ Map<String, MediaRoute2Info> routes = waitAndGetRoutesWithManager(FEATURES_ALL);
MediaRoute2Info fixedVolumeRoute = routes.get(ROUTE_ID_FIXED_VOLUME);
MediaRoute2Info variableVolumeRoute = routes.get(ROUTE_ID_VARIABLE_VOLUME);
@@ -367,11 +360,11 @@
assertEquals(VOLUME_MAX, variableVolumeRoute.getVolumeMax());
}
- Map<String, MediaRoute2Info> waitAndGetRoutesWithManager(List<String> controlCategories)
+ Map<String, MediaRoute2Info> waitAndGetRoutesWithManager(List<String> routeFeatures)
throws Exception {
CountDownLatch latch = new CountDownLatch(2);
- // A dummy callback is required to send control category info.
+ // A dummy callback is required to send route feature info.
RouteCallback routeCallback = new RouteCallback();
MediaRouter2Manager.Callback managerCallback = new MediaRouter2Manager.Callback() {
@Override
@@ -386,16 +379,17 @@
}
@Override
- public void onControlCategoriesChanged(String packageName, List<String> categories) {
+ public void onControlCategoriesChanged(String packageName,
+ List<String> preferredFeatures) {
if (TextUtils.equals(mPackageName, packageName)
- && controlCategories.equals(categories)) {
+ && preferredFeatures.equals(preferredFeatures)) {
latch.countDown();
}
}
};
mManager.registerCallback(mExecutor, managerCallback);
- mRouter2.setControlCategories(controlCategories);
- mRouter2.registerRouteCallback(mExecutor, routeCallback);
+ mRouter2.registerRouteCallback(mExecutor, routeCallback,
+ new RouteDiscoveryPreference.Builder(routeFeatures, true).build());
try {
latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
return createRouteMap(mManager.getAvailableRoutes(mPackageName));
@@ -430,7 +424,6 @@
static Map<String, MediaRoute2Info> createRouteMap(List<MediaRoute2Info> routes) {
Map<String, MediaRoute2Info> routeMap = new HashMap<>();
for (MediaRoute2Info route : routes) {
- // intentionally not using route.getUniqueId() for convenience.
routeMap.put(route.getId(), route);
}
return routeMap;
@@ -443,7 +436,7 @@
private void addRouterCallback(RouteCallback routeCallback) {
mRouteCallbacks.add(routeCallback);
- mRouter2.registerRouteCallback(mExecutor, routeCallback);
+ mRouter2.registerRouteCallback(mExecutor, routeCallback, RouteDiscoveryPreference.EMPTY);
}
private void addSessionCallback(SessionCallback sessionCallback) {
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteDiscoveryPreferenceTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteDiscoveryPreferenceTest.java
new file mode 100644
index 0000000..fa12935
--- /dev/null
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteDiscoveryPreferenceTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.mediaroutertest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import android.media.RouteDiscoveryPreference;
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RouteDiscoveryPreferenceTest {
+ @Before
+ public void setUp() throws Exception { }
+
+ @After
+ public void tearDown() throws Exception { }
+
+ @Test
+ public void testEquality() {
+ List<String> testTypes = new ArrayList<>();
+ testTypes.add("TEST_TYPE_1");
+ testTypes.add("TEST_TYPE_2");
+ RouteDiscoveryPreference request = new RouteDiscoveryPreference.Builder(testTypes, true)
+ .build();
+
+ RouteDiscoveryPreference requestRebuilt = new RouteDiscoveryPreference.Builder(request)
+ .build();
+
+ assertEquals(request, requestRebuilt);
+
+ Parcel parcel = Parcel.obtain();
+ parcel.writeParcelable(request, 0);
+ parcel.setDataPosition(0);
+ RouteDiscoveryPreference requestFromParcel = parcel.readParcelable(null);
+
+ assertEquals(request, requestFromParcel);
+ }
+
+ @Test
+ public void testInequality() {
+ List<String> testTypes = new ArrayList<>();
+ testTypes.add("TEST_TYPE_1");
+ testTypes.add("TEST_TYPE_2");
+
+ List<String> testTypes2 = new ArrayList<>();
+ testTypes.add("TEST_TYPE_3");
+
+ RouteDiscoveryPreference request = new RouteDiscoveryPreference.Builder(testTypes, true)
+ .build();
+
+ RouteDiscoveryPreference requestTypes = new RouteDiscoveryPreference.Builder(request)
+ .setPreferredFeatures(testTypes2)
+ .build();
+ assertNotEquals(request, requestTypes);
+
+ RouteDiscoveryPreference requestActiveScan = new RouteDiscoveryPreference.Builder(request)
+ .setActiveScan(false)
+ .build();
+ assertNotEquals(request, requestActiveScan);
+ }
+}
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java
deleted file mode 100644
index 2e81a64..0000000
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/RouteSessionTest.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * 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 com.android.mediaroutertest;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.media.RouteSessionInfo;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class RouteSessionTest {
- private static final String TEST_PACKAGE_NAME = "com.android.mediaroutertest";
- private static final String TEST_CONTROL_CATEGORY = "com.android.mediaroutertest.category";
-
- private static final String TEST_ROUTE_ID1 = "route_id1";
-
- @Test
- public void testValidity() {
- RouteSessionInfo emptyPackageSession = new RouteSessionInfo.Builder(1,
- "",
- TEST_CONTROL_CATEGORY)
- .addSelectedRoute(TEST_ROUTE_ID1)
- .build();
- RouteSessionInfo emptyCategorySession = new RouteSessionInfo.Builder(1,
- TEST_PACKAGE_NAME, "")
- .addSelectedRoute(TEST_ROUTE_ID1)
- .build();
-
- RouteSessionInfo emptySelectedRouteSession = new RouteSessionInfo.Builder(1,
- TEST_PACKAGE_NAME, TEST_CONTROL_CATEGORY)
- .build();
-
- RouteSessionInfo validSession = new RouteSessionInfo.Builder(emptySelectedRouteSession)
- .addSelectedRoute(TEST_ROUTE_ID1)
- .build();
-
- assertFalse(emptySelectedRouteSession.isValid());
- assertFalse(emptyPackageSession.isValid());
- assertFalse(emptyCategorySession.isValid());
- assertTrue(validSession.isValid());
- }
-}
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/RoutingSessionInfoTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/RoutingSessionInfoTest.java
new file mode 100644
index 0000000..3f59736
--- /dev/null
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/RoutingSessionInfoTest.java
@@ -0,0 +1,560 @@
+/*
+ * 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.mediaroutertest;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.testng.Assert.assertThrows;
+
+import android.media.RoutingSessionInfo;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests {@link RoutingSessionInfo} and its {@link RoutingSessionInfo.Builder builder}.
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class RoutingSessionInfoTest {
+ public static final String TEST_ID = "test_id";
+ public static final String TEST_CLIENT_PACKAGE_NAME = "com.test.client.package.name";
+ public static final String TEST_ROUTE_FEATURE = "test_route_feature";
+
+ public static final String TEST_ROUTE_ID_0 = "test_route_type_0";
+ public static final String TEST_ROUTE_ID_1 = "test_route_type_1";
+ public static final String TEST_ROUTE_ID_2 = "test_route_type_2";
+ public static final String TEST_ROUTE_ID_3 = "test_route_type_3";
+ public static final String TEST_ROUTE_ID_4 = "test_route_type_4";
+ public static final String TEST_ROUTE_ID_5 = "test_route_type_5";
+ public static final String TEST_ROUTE_ID_6 = "test_route_type_6";
+ public static final String TEST_ROUTE_ID_7 = "test_route_type_7";
+
+ public static final String TEST_KEY = "test_key";
+ public static final String TEST_VALUE = "test_value";
+
+ @Test
+ public void testBuilderConstructorWithInvalidValues() {
+ final String nullId = null;
+ final String nullClientPackageName = null;
+ final String nullRouteFeature = null;
+
+ final String emptyId = "";
+ // Note: An empty string as client package name is valid.
+ final String emptyRouteFeature = "";
+
+ final String validId = TEST_ID;
+ final String validClientPackageName = TEST_CLIENT_PACKAGE_NAME;
+ final String validRouteFeature = TEST_ROUTE_FEATURE;
+
+ // ID is invalid
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ nullId, validClientPackageName, validRouteFeature));
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ emptyId, validClientPackageName, validRouteFeature));
+
+ // client package name is invalid (null)
+ assertThrows(NullPointerException.class, () -> new RoutingSessionInfo.Builder(
+ validId, nullClientPackageName, validRouteFeature));
+
+ // route feature is invalid
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ validId, validClientPackageName, nullRouteFeature));
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ validId, validClientPackageName, emptyRouteFeature));
+
+ // Two arguments are invalid - (1) ID and clientPackageName
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ nullId, nullClientPackageName, validRouteFeature));
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ emptyId, nullClientPackageName, validRouteFeature));
+
+ // Two arguments are invalid - (2) ID and routeFeature
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ nullId, validClientPackageName, nullRouteFeature));
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ nullId, validClientPackageName, emptyRouteFeature));
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ emptyId, validClientPackageName, nullRouteFeature));
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ emptyId, validClientPackageName, emptyRouteFeature));
+
+ // Two arguments are invalid - (3) clientPackageName and routeFeature
+ // Note that this throws NullPointerException.
+ assertThrows(NullPointerException.class, () -> new RoutingSessionInfo.Builder(
+ validId, nullClientPackageName, nullRouteFeature));
+ assertThrows(NullPointerException.class, () -> new RoutingSessionInfo.Builder(
+ validId, nullClientPackageName, emptyRouteFeature));
+
+ // All arguments are invalid
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ nullId, nullClientPackageName, nullRouteFeature));
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ nullId, nullClientPackageName, emptyRouteFeature));
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ emptyId, nullClientPackageName, nullRouteFeature));
+ assertThrows(IllegalArgumentException.class, () -> new RoutingSessionInfo.Builder(
+ emptyId, nullClientPackageName, emptyRouteFeature));
+
+ // Null RouteInfo (1-argument constructor)
+ final RoutingSessionInfo nullRoutingSessionInfo = null;
+ assertThrows(NullPointerException.class,
+ () -> new RoutingSessionInfo.Builder(nullRoutingSessionInfo));
+ }
+
+ @Test
+ public void testBuilderConstructorWithEmptyClientPackageName() {
+ // An empty string for client package name is valid. (for unknown cases)
+ // Creating builder with it should not throw any exception.
+ RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
+ TEST_ID, "" /* clientPackageName*/, TEST_ROUTE_FEATURE);
+ }
+
+ @Test
+ public void testBuilderBuildWithEmptySelectedRoutesThrowsIAE() {
+ RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE);
+ // Note: Calling build() without adding any selected routes.
+ assertThrows(IllegalArgumentException.class, () -> builder.build());
+ }
+
+ @Test
+ public void testBuilderAddRouteMethodsWithIllegalArgumentsThrowsIAE() {
+ RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE);
+
+ final String nullRouteId = null;
+ final String emptyRouteId = "";
+
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.addSelectedRoute(nullRouteId));
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.addSelectableRoute(nullRouteId));
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.addDeselectableRoute(nullRouteId));
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.addTransferrableRoute(nullRouteId));
+
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.addSelectedRoute(emptyRouteId));
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.addSelectableRoute(emptyRouteId));
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.addDeselectableRoute(emptyRouteId));
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.addTransferrableRoute(emptyRouteId));
+ }
+
+ @Test
+ public void testBuilderRemoveRouteMethodsWithIllegalArgumentsThrowsIAE() {
+ RoutingSessionInfo.Builder builder = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE);
+
+ final String nullRouteId = null;
+ final String emptyRouteId = "";
+
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.removeSelectedRoute(nullRouteId));
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.removeSelectableRoute(nullRouteId));
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.removeDeselectableRoute(nullRouteId));
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.removeTransferrableRoute(nullRouteId));
+
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.removeSelectedRoute(emptyRouteId));
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.removeSelectableRoute(emptyRouteId));
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.removeDeselectableRoute(emptyRouteId));
+ assertThrows(IllegalArgumentException.class,
+ () -> builder.removeTransferrableRoute(emptyRouteId));
+ }
+
+ @Test
+ public void testBuilderAndGettersOfRoutingSessionInfo() {
+ Bundle controlHints = new Bundle();
+ controlHints.putString(TEST_KEY, TEST_VALUE);
+
+ RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .addSelectedRoute(TEST_ROUTE_ID_1)
+ .addSelectableRoute(TEST_ROUTE_ID_2)
+ .addSelectableRoute(TEST_ROUTE_ID_3)
+ .addDeselectableRoute(TEST_ROUTE_ID_4)
+ .addDeselectableRoute(TEST_ROUTE_ID_5)
+ .addTransferrableRoute(TEST_ROUTE_ID_6)
+ .addTransferrableRoute(TEST_ROUTE_ID_7)
+ .setControlHints(controlHints)
+ .build();
+
+ assertEquals(TEST_ID, sessionInfo.getId());
+ assertEquals(TEST_CLIENT_PACKAGE_NAME, sessionInfo.getClientPackageName());
+ assertEquals(TEST_ROUTE_FEATURE, sessionInfo.getRouteFeature());
+
+ assertEquals(2, sessionInfo.getSelectedRoutes().size());
+ assertEquals(TEST_ROUTE_ID_0, sessionInfo.getSelectedRoutes().get(0));
+ assertEquals(TEST_ROUTE_ID_1, sessionInfo.getSelectedRoutes().get(1));
+
+ assertEquals(2, sessionInfo.getSelectableRoutes().size());
+ assertEquals(TEST_ROUTE_ID_2, sessionInfo.getSelectableRoutes().get(0));
+ assertEquals(TEST_ROUTE_ID_3, sessionInfo.getSelectableRoutes().get(1));
+
+ assertEquals(2, sessionInfo.getDeselectableRoutes().size());
+ assertEquals(TEST_ROUTE_ID_4, sessionInfo.getDeselectableRoutes().get(0));
+ assertEquals(TEST_ROUTE_ID_5, sessionInfo.getDeselectableRoutes().get(1));
+
+ assertEquals(2, sessionInfo.getTransferrableRoutes().size());
+ assertEquals(TEST_ROUTE_ID_6, sessionInfo.getTransferrableRoutes().get(0));
+ assertEquals(TEST_ROUTE_ID_7, sessionInfo.getTransferrableRoutes().get(1));
+
+ Bundle controlHintsOut = sessionInfo.getControlHints();
+ assertNotNull(controlHintsOut);
+ assertTrue(controlHintsOut.containsKey(TEST_KEY));
+ assertEquals(TEST_VALUE, controlHintsOut.getString(TEST_KEY));
+ }
+
+ @Test
+ public void testBuilderAddRouteMethodsWithBuilderCopyConstructor() {
+ RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .addSelectableRoute(TEST_ROUTE_ID_2)
+ .addDeselectableRoute(TEST_ROUTE_ID_4)
+ .addTransferrableRoute(TEST_ROUTE_ID_6)
+ .build();
+
+ RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
+ .addSelectedRoute(TEST_ROUTE_ID_1)
+ .addSelectableRoute(TEST_ROUTE_ID_3)
+ .addDeselectableRoute(TEST_ROUTE_ID_5)
+ .addTransferrableRoute(TEST_ROUTE_ID_7)
+ .build();
+
+ assertEquals(2, newSessionInfo.getSelectedRoutes().size());
+ assertEquals(TEST_ROUTE_ID_0, newSessionInfo.getSelectedRoutes().get(0));
+ assertEquals(TEST_ROUTE_ID_1, newSessionInfo.getSelectedRoutes().get(1));
+
+ assertEquals(2, newSessionInfo.getSelectableRoutes().size());
+ assertEquals(TEST_ROUTE_ID_2, newSessionInfo.getSelectableRoutes().get(0));
+ assertEquals(TEST_ROUTE_ID_3, newSessionInfo.getSelectableRoutes().get(1));
+
+ assertEquals(2, newSessionInfo.getDeselectableRoutes().size());
+ assertEquals(TEST_ROUTE_ID_4, newSessionInfo.getDeselectableRoutes().get(0));
+ assertEquals(TEST_ROUTE_ID_5, newSessionInfo.getDeselectableRoutes().get(1));
+
+ assertEquals(2, newSessionInfo.getTransferrableRoutes().size());
+ assertEquals(TEST_ROUTE_ID_6, newSessionInfo.getTransferrableRoutes().get(0));
+ assertEquals(TEST_ROUTE_ID_7, newSessionInfo.getTransferrableRoutes().get(1));
+ }
+
+ @Test
+ public void testBuilderRemoveRouteMethods() {
+ RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .addSelectedRoute(TEST_ROUTE_ID_1)
+ .removeSelectedRoute(TEST_ROUTE_ID_1)
+
+ .addSelectableRoute(TEST_ROUTE_ID_2)
+ .addSelectableRoute(TEST_ROUTE_ID_3)
+ .removeSelectableRoute(TEST_ROUTE_ID_3)
+
+ .addDeselectableRoute(TEST_ROUTE_ID_4)
+ .addDeselectableRoute(TEST_ROUTE_ID_5)
+ .removeDeselectableRoute(TEST_ROUTE_ID_5)
+
+ .addTransferrableRoute(TEST_ROUTE_ID_6)
+ .addTransferrableRoute(TEST_ROUTE_ID_7)
+ .removeTransferrableRoute(TEST_ROUTE_ID_7)
+
+ .build();
+
+ assertEquals(1, sessionInfo.getSelectedRoutes().size());
+ assertEquals(TEST_ROUTE_ID_0, sessionInfo.getSelectedRoutes().get(0));
+
+ assertEquals(1, sessionInfo.getSelectableRoutes().size());
+ assertEquals(TEST_ROUTE_ID_2, sessionInfo.getSelectableRoutes().get(0));
+
+ assertEquals(1, sessionInfo.getDeselectableRoutes().size());
+ assertEquals(TEST_ROUTE_ID_4, sessionInfo.getDeselectableRoutes().get(0));
+
+ assertEquals(1, sessionInfo.getTransferrableRoutes().size());
+ assertEquals(TEST_ROUTE_ID_6, sessionInfo.getTransferrableRoutes().get(0));
+ }
+
+ @Test
+ public void testBuilderRemoveRouteMethodsWithBuilderCopyConstructor() {
+ RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .addSelectedRoute(TEST_ROUTE_ID_1)
+ .addSelectableRoute(TEST_ROUTE_ID_2)
+ .addSelectableRoute(TEST_ROUTE_ID_3)
+ .addDeselectableRoute(TEST_ROUTE_ID_4)
+ .addDeselectableRoute(TEST_ROUTE_ID_5)
+ .addTransferrableRoute(TEST_ROUTE_ID_6)
+ .addTransferrableRoute(TEST_ROUTE_ID_7)
+ .build();
+
+ RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
+ .removeSelectedRoute(TEST_ROUTE_ID_1)
+ .removeSelectableRoute(TEST_ROUTE_ID_3)
+ .removeDeselectableRoute(TEST_ROUTE_ID_5)
+ .removeTransferrableRoute(TEST_ROUTE_ID_7)
+ .build();
+
+ assertEquals(1, newSessionInfo.getSelectedRoutes().size());
+ assertEquals(TEST_ROUTE_ID_0, newSessionInfo.getSelectedRoutes().get(0));
+
+ assertEquals(1, newSessionInfo.getSelectableRoutes().size());
+ assertEquals(TEST_ROUTE_ID_2, newSessionInfo.getSelectableRoutes().get(0));
+
+ assertEquals(1, newSessionInfo.getDeselectableRoutes().size());
+ assertEquals(TEST_ROUTE_ID_4, newSessionInfo.getDeselectableRoutes().get(0));
+
+ assertEquals(1, newSessionInfo.getTransferrableRoutes().size());
+ assertEquals(TEST_ROUTE_ID_6, newSessionInfo.getTransferrableRoutes().get(0));
+ }
+
+ @Test
+ public void testBuilderClearRouteMethods() {
+ RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .addSelectedRoute(TEST_ROUTE_ID_1)
+ .clearSelectedRoutes()
+
+ .addSelectableRoute(TEST_ROUTE_ID_2)
+ .addSelectableRoute(TEST_ROUTE_ID_3)
+ .clearSelectableRoutes()
+
+ .addDeselectableRoute(TEST_ROUTE_ID_4)
+ .addDeselectableRoute(TEST_ROUTE_ID_5)
+ .clearDeselectableRoutes()
+
+ .addTransferrableRoute(TEST_ROUTE_ID_6)
+ .addTransferrableRoute(TEST_ROUTE_ID_7)
+ .clearTransferrableRoutes()
+
+ // SelectedRoutes must not be empty
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .build();
+
+ assertEquals(1, sessionInfo.getSelectedRoutes().size());
+ assertEquals(TEST_ROUTE_ID_0, sessionInfo.getSelectedRoutes().get(0));
+
+ assertTrue(sessionInfo.getSelectableRoutes().isEmpty());
+ assertTrue(sessionInfo.getDeselectableRoutes().isEmpty());
+ assertTrue(sessionInfo.getTransferrableRoutes().isEmpty());
+ }
+
+ @Test
+ public void testBuilderClearRouteMethodsWithBuilderCopyConstructor() {
+ RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .addSelectedRoute(TEST_ROUTE_ID_1)
+ .addSelectableRoute(TEST_ROUTE_ID_2)
+ .addSelectableRoute(TEST_ROUTE_ID_3)
+ .addDeselectableRoute(TEST_ROUTE_ID_4)
+ .addDeselectableRoute(TEST_ROUTE_ID_5)
+ .addTransferrableRoute(TEST_ROUTE_ID_6)
+ .addTransferrableRoute(TEST_ROUTE_ID_7)
+ .build();
+
+ RoutingSessionInfo newSessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
+ .clearSelectedRoutes()
+ .clearSelectableRoutes()
+ .clearDeselectableRoutes()
+ .clearTransferrableRoutes()
+ // SelectedRoutes must not be empty
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .build();
+
+ assertEquals(1, newSessionInfo.getSelectedRoutes().size());
+ assertEquals(TEST_ROUTE_ID_0, newSessionInfo.getSelectedRoutes().get(0));
+
+ assertTrue(newSessionInfo.getSelectableRoutes().isEmpty());
+ assertTrue(newSessionInfo.getDeselectableRoutes().isEmpty());
+ assertTrue(newSessionInfo.getTransferrableRoutes().isEmpty());
+ }
+
+ @Test
+ public void testEqualsCreatedWithSameArguments() {
+ Bundle controlHints = new Bundle();
+ controlHints.putString(TEST_KEY, TEST_VALUE);
+
+ RoutingSessionInfo sessionInfo1 = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .addSelectedRoute(TEST_ROUTE_ID_1)
+ .addSelectableRoute(TEST_ROUTE_ID_2)
+ .addSelectableRoute(TEST_ROUTE_ID_3)
+ .addDeselectableRoute(TEST_ROUTE_ID_4)
+ .addDeselectableRoute(TEST_ROUTE_ID_5)
+ .addTransferrableRoute(TEST_ROUTE_ID_6)
+ .addTransferrableRoute(TEST_ROUTE_ID_7)
+ .setControlHints(controlHints)
+ .build();
+
+ RoutingSessionInfo sessionInfo2 = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .addSelectedRoute(TEST_ROUTE_ID_1)
+ .addSelectableRoute(TEST_ROUTE_ID_2)
+ .addSelectableRoute(TEST_ROUTE_ID_3)
+ .addDeselectableRoute(TEST_ROUTE_ID_4)
+ .addDeselectableRoute(TEST_ROUTE_ID_5)
+ .addTransferrableRoute(TEST_ROUTE_ID_6)
+ .addTransferrableRoute(TEST_ROUTE_ID_7)
+ .setControlHints(controlHints)
+ .build();
+
+ assertEquals(sessionInfo1, sessionInfo2);
+ assertEquals(sessionInfo1.hashCode(), sessionInfo2.hashCode());
+ }
+
+ @Test
+ public void testEqualsCreatedWithBuilderCopyConstructor() {
+ Bundle controlHints = new Bundle();
+ controlHints.putString(TEST_KEY, TEST_VALUE);
+
+ RoutingSessionInfo sessionInfo1 = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .addSelectedRoute(TEST_ROUTE_ID_1)
+ .addSelectableRoute(TEST_ROUTE_ID_2)
+ .addSelectableRoute(TEST_ROUTE_ID_3)
+ .addDeselectableRoute(TEST_ROUTE_ID_4)
+ .addDeselectableRoute(TEST_ROUTE_ID_5)
+ .addTransferrableRoute(TEST_ROUTE_ID_6)
+ .addTransferrableRoute(TEST_ROUTE_ID_7)
+ .setControlHints(controlHints)
+ .build();
+
+ RoutingSessionInfo sessionInfo2 = new RoutingSessionInfo.Builder(sessionInfo1).build();
+
+ assertEquals(sessionInfo1, sessionInfo2);
+ assertEquals(sessionInfo1.hashCode(), sessionInfo2.hashCode());
+ }
+
+ @Test
+ public void testEqualsReturnFalse() {
+ Bundle controlHints = new Bundle();
+ controlHints.putString(TEST_KEY, TEST_VALUE);
+
+ RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .addSelectedRoute(TEST_ROUTE_ID_1)
+ .addSelectableRoute(TEST_ROUTE_ID_2)
+ .addSelectableRoute(TEST_ROUTE_ID_3)
+ .addDeselectableRoute(TEST_ROUTE_ID_4)
+ .addDeselectableRoute(TEST_ROUTE_ID_5)
+ .addTransferrableRoute(TEST_ROUTE_ID_6)
+ .addTransferrableRoute(TEST_ROUTE_ID_7)
+ .setControlHints(controlHints)
+ .build();
+
+ // Now, we will use copy constructor
+ assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+ .addSelectedRoute("randomRoute")
+ .build());
+ assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+ .addSelectableRoute("randomRoute")
+ .build());
+ assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+ .addDeselectableRoute("randomRoute")
+ .build());
+ assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+ .addTransferrableRoute("randomRoute")
+ .build());
+
+ assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+ .removeSelectedRoute(TEST_ROUTE_ID_1)
+ .build());
+ assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+ .removeSelectableRoute(TEST_ROUTE_ID_3)
+ .build());
+ assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+ .removeDeselectableRoute(TEST_ROUTE_ID_5)
+ .build());
+ assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+ .removeTransferrableRoute(TEST_ROUTE_ID_7)
+ .build());
+
+ assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+ .clearSelectedRoutes()
+ // Note: Calling build() with empty selected routes will throw IAE.
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .build());
+ assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+ .clearSelectableRoutes()
+ .build());
+ assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+ .clearDeselectableRoutes()
+ .build());
+ assertNotEquals(sessionInfo, new RoutingSessionInfo.Builder(sessionInfo)
+ .clearTransferrableRoutes()
+ .build());
+
+ // Note: ControlHints will not affect the equals.
+ }
+
+ @Test
+ public void testParcelingAndUnParceling() {
+ Bundle controlHints = new Bundle();
+ controlHints.putString(TEST_KEY, TEST_VALUE);
+
+ RoutingSessionInfo sessionInfo = new RoutingSessionInfo.Builder(
+ TEST_ID, TEST_CLIENT_PACKAGE_NAME, TEST_ROUTE_FEATURE)
+ .addSelectedRoute(TEST_ROUTE_ID_0)
+ .addSelectedRoute(TEST_ROUTE_ID_1)
+ .addSelectableRoute(TEST_ROUTE_ID_2)
+ .addSelectableRoute(TEST_ROUTE_ID_3)
+ .addDeselectableRoute(TEST_ROUTE_ID_4)
+ .addDeselectableRoute(TEST_ROUTE_ID_5)
+ .addTransferrableRoute(TEST_ROUTE_ID_6)
+ .addTransferrableRoute(TEST_ROUTE_ID_7)
+ .setControlHints(controlHints)
+ .build();
+
+ Parcel parcel = Parcel.obtain();
+ sessionInfo.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+
+ RoutingSessionInfo sessionInfoFromParcel =
+ RoutingSessionInfo.CREATOR.createFromParcel(parcel);
+ assertEquals(sessionInfo, sessionInfoFromParcel);
+ assertEquals(sessionInfo.hashCode(), sessionInfoFromParcel.hashCode());
+
+ // Check controlHints
+ Bundle controlHintsOut = sessionInfoFromParcel.getControlHints();
+ assertNotNull(controlHintsOut);
+ assertTrue(controlHintsOut.containsKey(TEST_KEY));
+ assertEquals(TEST_VALUE, controlHintsOut.getString(TEST_KEY));
+ }
+}
diff --git a/mime/java-res/android.mime.types b/mime/java-res/android.mime.types
index cb04d92..c1f8b3f 100644
--- a/mime/java-res/android.mime.types
+++ b/mime/java-res/android.mime.types
@@ -46,6 +46,7 @@
# <extension1> and <extension2> are mapped (or remapped) to <mimeType>.
?application/epub+zip epub
+?application/lrc lrc
?application/pkix-cert cer
?application/rss+xml rss
?application/sdp sdp
@@ -65,6 +66,7 @@
?application/x-mpegurl m3u m3u8
?application/x-pem-file pem
?application/x-pkcs12 p12 pfx
+?application/x-subrip srt
?application/x-webarchive webarchive
?application/x-webarchive-xml webarchivexml
?application/x-x509-server-cert crt
diff --git a/mms/OWNERS b/mms/OWNERS
index ba00d5d..befc320 100644
--- a/mms/OWNERS
+++ b/mms/OWNERS
@@ -12,3 +12,5 @@
shuoq@google.com
refuhoo@google.com
nazaninb@google.com
+sarahchin@google.com
+dbright@google.com
\ No newline at end of file
diff --git a/mms/java/android/telephony/MmsManager.java b/mms/java/android/telephony/MmsManager.java
index 4bcf046..cf55eba 100644
--- a/mms/java/android/telephony/MmsManager.java
+++ b/mms/java/android/telephony/MmsManager.java
@@ -16,8 +16,12 @@
package android.telephony;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemService;
import android.app.ActivityThread;
import android.app.PendingIntent;
+import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.os.RemoteException;
@@ -27,22 +31,18 @@
/**
* Manages MMS operations such as sending multimedia messages.
- * Get this object by calling the static method {@link #getInstance()}.
- * @hide
+ * Get this object by calling Context#getSystemService(Context#MMS_SERVICE).
*/
+@SystemService(Context.MMS_SERVICE)
public class MmsManager {
private static final String TAG = "MmsManager";
-
- /** Singleton object constructed during class initialization. */
- private static final MmsManager sInstance = new MmsManager();
+ private final Context mContext;
/**
- * Get the MmsManager singleton instance.
- *
- * @return the {@link MmsManager} singleton instance.
+ * @hide
*/
- public static MmsManager getInstance() {
- return sInstance;
+ public MmsManager(@NonNull Context context) {
+ mContext = context;
}
/**
@@ -56,8 +56,9 @@
* @param sentIntent if not NULL this <code>PendingIntent</code> is broadcast when the message
* is successfully sent, or failed
*/
- public void sendMultimediaMessage(int subId, Uri contentUri, String locationUrl,
- Bundle configOverrides, PendingIntent sentIntent) {
+ public void sendMultimediaMessage(int subId, @NonNull Uri contentUri,
+ @Nullable String locationUrl, @Nullable Bundle configOverrides,
+ @Nullable PendingIntent sentIntent) {
try {
final IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
if (iMms == null) {
@@ -84,8 +85,9 @@
* broadcast when the message is downloaded, or the download is failed
* @throws IllegalArgumentException if locationUrl or contentUri is empty
*/
- public void downloadMultimediaMessage(int subId, String locationUrl, Uri contentUri,
- Bundle configOverrides, PendingIntent downloadedIntent) {
+ public void downloadMultimediaMessage(int subId, @NonNull String locationUrl,
+ @NonNull Uri contentUri, @Nullable Bundle configOverrides,
+ @Nullable PendingIntent downloadedIntent) {
try {
final IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
if (iMms == null) {
@@ -97,22 +99,4 @@
// Ignore it
}
}
-
- /**
- * Get carrier-dependent configuration values.
- *
- * @param subId the subscription id
- * @return bundle key/values pairs of configuration values
- */
- public Bundle getCarrierConfigValues(int subId) {
- try {
- IMms iMms = IMms.Stub.asInterface(ServiceManager.getService("imms"));
- if (iMms != null) {
- return iMms.getCarrierConfigValues(subId);
- }
- } catch (RemoteException ex) {
- // ignore it
- }
- return null;
- }
}
diff --git a/mms/java/com/android/internal/telephony/IMms.aidl b/mms/java/com/android/internal/telephony/IMms.aidl
index fa5073e..8be5111 100644
--- a/mms/java/com/android/internal/telephony/IMms.aidl
+++ b/mms/java/com/android/internal/telephony/IMms.aidl
@@ -60,13 +60,6 @@
in PendingIntent downloadedIntent);
/**
- * Get carrier-dependent configuration values.
- *
- * @param subId the SIM id
- */
- Bundle getCarrierConfigValues(int subId);
-
- /**
* Import a text message into system's SMS store
*
* @param callingPkg the calling app's package name
diff --git a/native/graphics/jni/Android.bp b/native/graphics/jni/Android.bp
index 942eafd..376ea77 100644
--- a/native/graphics/jni/Android.bp
+++ b/native/graphics/jni/Android.bp
@@ -24,12 +24,21 @@
// our source files
//
- srcs: ["bitmap.cpp"],
+ srcs: [
+ "aassetstreamadaptor.cpp",
+ "bitmap.cpp",
+ "imagedecoder.cpp",
+ ],
shared_libs: [
+ "libandroid",
"libandroid_runtime",
+ "libhwui",
+ "liblog",
],
+ static_libs: ["libarect"],
+
arch: {
arm: {
// TODO: This is to work around b/24465209. Remove after root cause is fixed
diff --git a/native/graphics/jni/aassetstreamadaptor.cpp b/native/graphics/jni/aassetstreamadaptor.cpp
new file mode 100644
index 0000000..016008b
--- /dev/null
+++ b/native/graphics/jni/aassetstreamadaptor.cpp
@@ -0,0 +1,117 @@
+/*
+ * 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.
+ */
+
+#include "aassetstreamadaptor.h"
+
+#include <log/log.h>
+
+AAssetStreamAdaptor::AAssetStreamAdaptor(AAsset* asset)
+ : mAAsset(asset)
+{
+}
+
+bool AAssetStreamAdaptor::rewind() {
+ off64_t pos = AAsset_seek64(mAAsset, 0, SEEK_SET);
+ if (pos == (off64_t)-1) {
+ ALOGE("rewind failed!");
+ return false;
+ }
+ return true;
+}
+
+size_t AAssetStreamAdaptor::getLength() const {
+ return AAsset_getLength64(mAAsset);
+}
+
+bool AAssetStreamAdaptor::isAtEnd() const {
+ return AAsset_getRemainingLength64(mAAsset) == 0;
+}
+
+SkStreamRewindable* AAssetStreamAdaptor::onDuplicate() const {
+ // Cannot sensibly create a duplicate, since each AAssetStreamAdaptor
+ // would be modifying the same AAsset.
+ //return new AAssetStreamAdaptor(mAAsset);
+ return nullptr;
+}
+
+bool AAssetStreamAdaptor::hasPosition() const {
+ return AAsset_seek64(mAAsset, 0, SEEK_CUR) != -1;
+}
+
+size_t AAssetStreamAdaptor::getPosition() const {
+ const off64_t offset = AAsset_seek64(mAAsset, 0, SEEK_CUR);
+ if (offset == -1) {
+ ALOGE("getPosition failed!");
+ return 0;
+ }
+
+ return offset;
+}
+
+bool AAssetStreamAdaptor::seek(size_t position) {
+ if (AAsset_seek64(mAAsset, position, SEEK_SET) == -1) {
+ ALOGE("seek failed!");
+ return false;
+ }
+
+ return true;
+}
+
+bool AAssetStreamAdaptor::move(long offset) {
+ if (AAsset_seek64(mAAsset, offset, SEEK_CUR) == -1) {
+ ALOGE("move failed!");
+ return false;
+ }
+
+ return true;
+}
+
+size_t AAssetStreamAdaptor::read(void* buffer, size_t size) {
+ ssize_t amount;
+
+ if (!buffer) {
+ if (!size) {
+ return 0;
+ }
+
+ // asset->seek returns new total offset
+ // we want to return amount that was skipped
+ const off64_t oldOffset = AAsset_seek64(mAAsset, 0, SEEK_CUR);
+ if (oldOffset == -1) {
+ ALOGE("seek(oldOffset) failed!");
+ return 0;
+ }
+
+ const off64_t newOffset = AAsset_seek64(mAAsset, size, SEEK_CUR);
+ if (-1 == newOffset) {
+ ALOGE("seek(%zu) failed!", size);
+ return 0;
+ }
+ amount = newOffset - oldOffset;
+ } else {
+ amount = AAsset_read(mAAsset, buffer, size);
+ }
+
+ if (amount < 0) {
+ amount = 0;
+ }
+ return amount;
+}
+
+const void* AAssetStreamAdaptor::getMemoryBase() {
+ return AAsset_getBuffer(mAAsset);
+}
+
diff --git a/native/graphics/jni/aassetstreamadaptor.h b/native/graphics/jni/aassetstreamadaptor.h
new file mode 100644
index 0000000..c1fddb0
--- /dev/null
+++ b/native/graphics/jni/aassetstreamadaptor.h
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "SkStream.h"
+
+#include <android/asset_manager.h>
+
+// Like AssetStreamAdaptor, but operates on AAsset, a public NDK API.
+class AAssetStreamAdaptor : public SkStreamRewindable {
+public:
+ /**
+ * Create an SkStream that reads from an AAsset.
+ *
+ * The AAsset must remain open for the lifetime of the Adaptor. The Adaptor
+ * does *not* close the AAsset.
+ */
+ explicit AAssetStreamAdaptor(AAsset*);
+
+ bool rewind() override;
+ size_t read(void* buffer, size_t size) override;
+ bool hasLength() const override { return true; }
+ size_t getLength() const override;
+ bool hasPosition() const override;
+ size_t getPosition() const override;
+ bool seek(size_t position) override;
+ bool move(long offset) override;
+ bool isAtEnd() const override;
+ const void* getMemoryBase() override;
+protected:
+ SkStreamRewindable* onDuplicate() const override;
+
+private:
+ AAsset* mAAsset;
+};
+
diff --git a/native/graphics/jni/bitmap.cpp b/native/graphics/jni/bitmap.cpp
index 1aebeaf..26c7f8d 100644
--- a/native/graphics/jni/bitmap.cpp
+++ b/native/graphics/jni/bitmap.cpp
@@ -16,6 +16,7 @@
#include <android/bitmap.h>
#include <android/graphics/bitmap.h>
+#include <android/data_space.h>
int AndroidBitmap_getInfo(JNIEnv* env, jobject jbitmap,
AndroidBitmapInfo* info) {
@@ -29,6 +30,15 @@
return ANDROID_BITMAP_RESULT_SUCCESS;
}
+int32_t AndroidBitmap_getDataSpace(JNIEnv* env, jobject jbitmap) {
+ if (NULL == env || NULL == jbitmap) {
+ return ADATASPACE_UNKNOWN; // Or return a real error?
+ }
+
+ android::graphics::Bitmap bitmap(env, jbitmap);
+ return bitmap.getDataSpace();
+}
+
int AndroidBitmap_lockPixels(JNIEnv* env, jobject jbitmap, void** addrPtr) {
if (NULL == env || NULL == jbitmap) {
return ANDROID_BITMAP_RESULT_BAD_PARAMETER;
diff --git a/native/graphics/jni/imagedecoder.cpp b/native/graphics/jni/imagedecoder.cpp
new file mode 100644
index 0000000..2ef203d
--- /dev/null
+++ b/native/graphics/jni/imagedecoder.cpp
@@ -0,0 +1,320 @@
+/*
+ * 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.
+ */
+
+#include "aassetstreamadaptor.h"
+
+#include <android/asset_manager.h>
+#include <android/bitmap.h>
+#include <android/imagedecoder.h>
+#include <android/graphics/MimeType.h>
+#include <android/rect.h>
+#include <hwui/ImageDecoder.h>
+#include <log/log.h>
+#include <SkAndroidCodec.h>
+
+#include <fcntl.h>
+#include <optional>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+using namespace android;
+
+int ResultToErrorCode(SkCodec::Result result) {
+ switch (result) {
+ case SkCodec::kIncompleteInput:
+ return ANDROID_IMAGE_DECODER_INCOMPLETE;
+ case SkCodec::kErrorInInput:
+ return ANDROID_IMAGE_DECODER_ERROR;
+ case SkCodec::kInvalidInput:
+ return ANDROID_IMAGE_DECODER_INVALID_INPUT;
+ case SkCodec::kCouldNotRewind:
+ return ANDROID_IMAGE_DECODER_SEEK_ERROR;
+ case SkCodec::kUnimplemented:
+ return ANDROID_IMAGE_DECODER_UNSUPPORTED_FORMAT;
+ case SkCodec::kInvalidConversion:
+ return ANDROID_IMAGE_DECODER_INVALID_CONVERSION;
+ case SkCodec::kInvalidParameters:
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ case SkCodec::kSuccess:
+ return ANDROID_IMAGE_DECODER_SUCCESS;
+ case SkCodec::kInvalidScale:
+ return ANDROID_IMAGE_DECODER_INVALID_SCALE;
+ case SkCodec::kInternalError:
+ return ANDROID_IMAGE_DECODER_INTERNAL_ERROR;
+ }
+}
+
+static int createFromStream(std::unique_ptr<SkStreamRewindable> stream, AImageDecoder** outDecoder) {
+ SkCodec::Result result;
+ auto codec = SkCodec::MakeFromStream(std::move(stream), &result, nullptr,
+ SkCodec::SelectionPolicy::kPreferAnimation);
+ auto androidCodec = SkAndroidCodec::MakeFromCodec(std::move(codec),
+ SkAndroidCodec::ExifOrientationBehavior::kRespect);
+ if (!androidCodec) {
+ return ResultToErrorCode(result);
+ }
+
+ *outDecoder = reinterpret_cast<AImageDecoder*>(new ImageDecoder(std::move(androidCodec)));
+ return ANDROID_IMAGE_DECODER_SUCCESS;
+}
+
+int AImageDecoder_createFromAAsset(AAsset* asset, AImageDecoder** outDecoder) {
+ if (!asset || !outDecoder) {
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ }
+ *outDecoder = nullptr;
+
+ auto stream = std::make_unique<AAssetStreamAdaptor>(asset);
+ return createFromStream(std::move(stream), outDecoder);
+}
+
+static bool isSeekable(int descriptor) {
+ return ::lseek64(descriptor, 0, SEEK_CUR) != -1;
+}
+
+int AImageDecoder_createFromFd(int fd, AImageDecoder** outDecoder) {
+ if (fd <= 0 || !outDecoder) {
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ }
+
+ struct stat fdStat;
+ if (fstat(fd, &fdStat) == -1) {
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ }
+
+ if (!isSeekable(fd)) {
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ }
+
+ // SkFILEStream will close its descriptor. Duplicate it so the client will
+ // still be responsible for closing the original.
+ int dupDescriptor = fcntl(fd, F_DUPFD_CLOEXEC, 0);
+ FILE* file = fdopen(dupDescriptor, "r");
+ if (!file) {
+ close(dupDescriptor);
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ }
+
+ auto stream = std::unique_ptr<SkStreamRewindable>(new SkFILEStream(file));
+ return createFromStream(std::move(stream), outDecoder);
+}
+
+int AImageDecoder_createFromBuffer(const void* buffer, size_t length,
+ AImageDecoder** outDecoder) {
+ if (!buffer || !length || !outDecoder) {
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ }
+ *outDecoder = nullptr;
+
+ // The client is expected to keep the buffer alive as long as the
+ // AImageDecoder, so we do not need to copy the buffer.
+ auto stream = std::unique_ptr<SkStreamRewindable>(
+ new SkMemoryStream(buffer, length, false /* copyData */));
+ return createFromStream(std::move(stream), outDecoder);
+}
+
+static ImageDecoder* toDecoder(AImageDecoder* d) {
+ return reinterpret_cast<ImageDecoder*>(d);
+}
+
+// Note: This differs from the version in android_bitmap.cpp in that this
+// version returns kGray_8_SkColorType for ANDROID_BITMAP_FORMAT_A_8. SkCodec
+// allows decoding single channel images to gray, which Android then treats
+// as A_8/ALPHA_8.
+static SkColorType getColorType(AndroidBitmapFormat format) {
+ switch (format) {
+ case ANDROID_BITMAP_FORMAT_RGBA_8888:
+ return kN32_SkColorType;
+ case ANDROID_BITMAP_FORMAT_RGB_565:
+ return kRGB_565_SkColorType;
+ case ANDROID_BITMAP_FORMAT_RGBA_4444:
+ return kARGB_4444_SkColorType;
+ case ANDROID_BITMAP_FORMAT_A_8:
+ return kGray_8_SkColorType;
+ case ANDROID_BITMAP_FORMAT_RGBA_F16:
+ return kRGBA_F16_SkColorType;
+ default:
+ return kUnknown_SkColorType;
+ }
+}
+
+int AImageDecoder_setAndroidBitmapFormat(AImageDecoder* decoder, int32_t format) {
+ if (!decoder || format < ANDROID_BITMAP_FORMAT_NONE
+ || format > ANDROID_BITMAP_FORMAT_RGBA_F16) {
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ }
+ return toDecoder(decoder)->setOutColorType(getColorType((AndroidBitmapFormat) format))
+ ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_INVALID_CONVERSION;
+}
+
+const AImageDecoderHeaderInfo* AImageDecoder_getHeaderInfo(const AImageDecoder* decoder) {
+ return reinterpret_cast<const AImageDecoderHeaderInfo*>(decoder);
+}
+
+static const ImageDecoder* toDecoder(const AImageDecoderHeaderInfo* info) {
+ return reinterpret_cast<const ImageDecoder*>(info);
+}
+
+int32_t AImageDecoderHeaderInfo_getWidth(const AImageDecoderHeaderInfo* info) {
+ if (!info) {
+ return 0;
+ }
+ return toDecoder(info)->mCodec->getInfo().width();
+}
+
+int32_t AImageDecoderHeaderInfo_getHeight(const AImageDecoderHeaderInfo* info) {
+ if (!info) {
+ return 0;
+ }
+ return toDecoder(info)->mCodec->getInfo().height();
+}
+
+const char* AImageDecoderHeaderInfo_getMimeType(const AImageDecoderHeaderInfo* info) {
+ if (!info) {
+ return nullptr;
+ }
+ return getMimeType(toDecoder(info)->mCodec->getEncodedFormat());
+}
+
+bool AImageDecoderHeaderInfo_isAnimated(const AImageDecoderHeaderInfo* info) {
+ if (!info) {
+ return false;
+ }
+ return toDecoder(info)->mCodec->codec()->getFrameCount() > 1;
+}
+
+// FIXME: Share with getFormat in android_bitmap.cpp?
+static AndroidBitmapFormat getFormat(SkColorType colorType) {
+ switch (colorType) {
+ case kN32_SkColorType:
+ return ANDROID_BITMAP_FORMAT_RGBA_8888;
+ case kRGB_565_SkColorType:
+ return ANDROID_BITMAP_FORMAT_RGB_565;
+ case kARGB_4444_SkColorType:
+ return ANDROID_BITMAP_FORMAT_RGBA_4444;
+ case kAlpha_8_SkColorType:
+ return ANDROID_BITMAP_FORMAT_A_8;
+ case kRGBA_F16_SkColorType:
+ return ANDROID_BITMAP_FORMAT_RGBA_F16;
+ default:
+ return ANDROID_BITMAP_FORMAT_NONE;
+ }
+}
+
+AndroidBitmapFormat AImageDecoderHeaderInfo_getAndroidBitmapFormat(
+ const AImageDecoderHeaderInfo* info) {
+ if (!info) {
+ return ANDROID_BITMAP_FORMAT_NONE;
+ }
+ return getFormat(toDecoder(info)->mCodec->computeOutputColorType(kN32_SkColorType));
+}
+
+int AImageDecoderHeaderInfo_getAlphaFlags(const AImageDecoderHeaderInfo* info) {
+ if (!info) {
+ // FIXME: Better invalid?
+ return -1;
+ }
+ switch (toDecoder(info)->mCodec->getInfo().alphaType()) {
+ case kUnknown_SkAlphaType:
+ LOG_ALWAYS_FATAL("Invalid alpha type");
+ return -1;
+ case kUnpremul_SkAlphaType:
+ // fall through. premul is the default.
+ case kPremul_SkAlphaType:
+ return ANDROID_BITMAP_FLAGS_ALPHA_PREMUL;
+ case kOpaque_SkAlphaType:
+ return ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE;
+ }
+}
+
+SkAlphaType toAlphaType(int androidBitmapFlags) {
+ switch (androidBitmapFlags) {
+ case ANDROID_BITMAP_FLAGS_ALPHA_PREMUL:
+ return kPremul_SkAlphaType;
+ case ANDROID_BITMAP_FLAGS_ALPHA_UNPREMUL:
+ return kUnpremul_SkAlphaType;
+ case ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE:
+ return kOpaque_SkAlphaType;
+ default:
+ return kUnknown_SkAlphaType;
+ }
+}
+
+int AImageDecoder_setAlphaFlags(AImageDecoder* decoder, int alphaFlag) {
+ if (!decoder || alphaFlag < ANDROID_BITMAP_FLAGS_ALPHA_PREMUL
+ || alphaFlag > ANDROID_BITMAP_FLAGS_ALPHA_UNPREMUL) {
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ }
+
+ return toDecoder(decoder)->setOutAlphaType(toAlphaType(alphaFlag))
+ ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_INVALID_CONVERSION;
+}
+
+int AImageDecoder_setTargetSize(AImageDecoder* decoder, int width, int height) {
+ if (!decoder) {
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ }
+
+ return toDecoder(decoder)->setTargetSize(width, height)
+ ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_INVALID_SCALE;
+}
+
+int AImageDecoder_setCrop(AImageDecoder* decoder, ARect crop) {
+ if (!decoder) {
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ }
+
+ SkIRect cropIRect;
+ cropIRect.setLTRB(crop.left, crop.top, crop.right, crop.bottom);
+ SkIRect* cropPtr = cropIRect == SkIRect::MakeEmpty() ? nullptr : &cropIRect;
+ return toDecoder(decoder)->setCropRect(cropPtr)
+ ? ANDROID_IMAGE_DECODER_SUCCESS : ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+}
+
+
+size_t AImageDecoder_getMinimumStride(AImageDecoder* decoder) {
+ if (!decoder) {
+ return 0;
+ }
+
+ SkImageInfo info = toDecoder(decoder)->getOutputInfo();
+ return info.minRowBytes();
+}
+
+int AImageDecoder_decodeImage(AImageDecoder* decoder,
+ void* pixels, size_t stride,
+ size_t size) {
+ if (!decoder || !pixels || !stride) {
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ }
+
+ ImageDecoder* imageDecoder = toDecoder(decoder);
+
+ const int height = imageDecoder->getOutputInfo().height();
+ const size_t minStride = AImageDecoder_getMinimumStride(decoder);
+ // If this calculation were to overflow, it would have been caught in
+ // setTargetSize.
+ if (stride < minStride || size < stride * (height - 1) + minStride) {
+ return ANDROID_IMAGE_DECODER_BAD_PARAMETER;
+ }
+
+ return ResultToErrorCode(imageDecoder->decode(pixels, stride));
+}
+
+void AImageDecoder_delete(AImageDecoder* decoder) {
+ delete toDecoder(decoder);
+}
diff --git a/native/graphics/jni/libjnigraphics.map.txt b/native/graphics/jni/libjnigraphics.map.txt
index a601d8a..832770f 100644
--- a/native/graphics/jni/libjnigraphics.map.txt
+++ b/native/graphics/jni/libjnigraphics.map.txt
@@ -1,6 +1,24 @@
LIBJNIGRAPHICS {
global:
+ AImageDecoder_createFromAAsset;
+ AImageDecoder_createFromFd;
+ AImageDecoder_createFromBuffer;
+ AImageDecoder_delete;
+ AImageDecoder_setAndroidBitmapFormat;
+ AImageDecoder_setAlphaFlags;
+ AImageDecoder_getHeaderInfo;
+ AImageDecoder_getMinimumStride;
+ AImageDecoder_decodeImage;
+ AImageDecoder_setTargetSize;
+ AImageDecoder_setCrop;
+ AImageDecoderHeaderInfo_getWidth;
+ AImageDecoderHeaderInfo_getHeight;
+ AImageDecoderHeaderInfo_getMimeType;
+ AImageDecoderHeaderInfo_getAlphaFlags;
+ AImageDecoderHeaderInfo_isAnimated;
+ AImageDecoderHeaderInfo_getAndroidBitmapFormat;
AndroidBitmap_getInfo;
+ AndroidBitmap_getDataSpace;
AndroidBitmap_lockPixels;
AndroidBitmap_unlockPixels;
local:
diff --git a/opengl/java/android/opengl/EGL14.java b/opengl/java/android/opengl/EGL14.java
index 728e6e1..90b46fd 100644
--- a/opengl/java/android/opengl/EGL14.java
+++ b/opengl/java/android/opengl/EGL14.java
@@ -18,11 +18,11 @@
package android.opengl;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.SurfaceTexture;
import android.view.Surface;
-import android.view.SurfaceView;
import android.view.SurfaceHolder;
+import android.view.SurfaceView;
/**
* EGL 1.4
diff --git a/opengl/java/android/opengl/GLES20.java b/opengl/java/android/opengl/GLES20.java
index d66e7ac..e853e44 100644
--- a/opengl/java/android/opengl/GLES20.java
+++ b/opengl/java/android/opengl/GLES20.java
@@ -19,7 +19,7 @@
package android.opengl;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/** OpenGL ES 2.0
*/
diff --git a/opengl/java/android/opengl/GLSurfaceView.java b/opengl/java/android/opengl/GLSurfaceView.java
index 8a3e6a0..75131b0 100644
--- a/opengl/java/android/opengl/GLSurfaceView.java
+++ b/opengl/java/android/opengl/GLSurfaceView.java
@@ -16,7 +16,7 @@
package android.opengl;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.Trace;
import android.util.AttributeSet;
diff --git a/opengl/java/javax/microedition/khronos/egl/EGL10.java b/opengl/java/javax/microedition/khronos/egl/EGL10.java
index 8a25170..ea571c7 100644
--- a/opengl/java/javax/microedition/khronos/egl/EGL10.java
+++ b/opengl/java/javax/microedition/khronos/egl/EGL10.java
@@ -16,8 +16,7 @@
package javax.microedition.khronos.egl;
-import android.annotation.UnsupportedAppUsage;
-import java.lang.String;
+import android.compat.annotation.UnsupportedAppUsage;
public interface EGL10 extends EGL {
int EGL_SUCCESS = 0x3000;
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/CryptoSettings.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/CryptoSettings.java
index 033f1b1..bb1336f 100644
--- a/packages/BackupEncryption/src/com/android/server/backup/encryption/CryptoSettings.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/CryptoSettings.java
@@ -16,7 +16,6 @@
package com.android.server.backup.encryption;
-import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.internal.util.Preconditions.checkState;
import android.content.Context;
@@ -29,6 +28,7 @@
import com.android.internal.annotations.VisibleForTesting;
import java.security.KeyStoreException;
+import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
@@ -88,8 +88,8 @@
}
private CryptoSettings(SharedPreferences sharedPreferences, Context context) {
- mSharedPreferences = checkNotNull(sharedPreferences);
- mContext = checkNotNull(context);
+ mSharedPreferences = Objects.requireNonNull(sharedPreferences);
+ mContext = Objects.requireNonNull(context);
}
/**
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/BackupFileBuilder.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/BackupFileBuilder.java
index 3d3fb55..4010bfd 100644
--- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/BackupFileBuilder.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/BackupFileBuilder.java
@@ -17,7 +17,6 @@
package com.android.server.backup.encryption.chunking;
import static com.android.internal.util.Preconditions.checkArgument;
-import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.internal.util.Preconditions.checkState;
import android.annotation.Nullable;
@@ -35,6 +34,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
/**
* Writes batches of {@link EncryptedChunk} to a diff script, and generates the associated {@link
@@ -174,7 +174,7 @@
* IllegalStateException}.
*/
public void finish(ChunksMetadataProto.ChunksMetadata metadata) throws IOException {
- checkNotNull(metadata, "Metadata cannot be null");
+ Objects.requireNonNull(metadata, "Metadata cannot be null");
long startOfMetadata = mBackupWriter.getBytesWritten();
mBackupWriter.writeBytes(ChunksMetadataProto.ChunksMetadata.toByteArray(metadata));
@@ -190,7 +190,7 @@
*/
private void writeChunkToFileAndListing(
ChunkHash chunkHash, Map<ChunkHash, EncryptedChunk> newChunks) throws IOException {
- checkNotNull(chunkHash, "Hash cannot be null");
+ Objects.requireNonNull(chunkHash, "Hash cannot be null");
if (mOldChunkListing.hasChunk(chunkHash)) {
ChunkListingMap.Entry oldChunk = mOldChunkListing.getChunkEntry(chunkHash);
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/ProtoStore.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/ProtoStore.java
index 3ba5f2b..b0a562c 100644
--- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/ProtoStore.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/ProtoStore.java
@@ -16,8 +16,6 @@
package com.android.server.backup.encryption.chunking;
-import static com.android.internal.util.Preconditions.checkNotNull;
-
import android.content.Context;
import android.text.TextUtils;
import android.util.AtomicFile;
@@ -34,6 +32,7 @@
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
+import java.util.Objects;
import java.util.Optional;
/**
@@ -75,7 +74,7 @@
*/
@VisibleForTesting
ProtoStore(Class<T> clazz, File storeFolder) throws IOException {
- mClazz = checkNotNull(clazz);
+ mClazz = Objects.requireNonNull(clazz);
mStoreFolder = ensureDirectoryExistsOrThrow(storeFolder);
}
@@ -118,7 +117,7 @@
/** Saves a proto to disk, associating it with the given package. */
public void saveProto(String packageName, T proto) throws IOException {
- checkNotNull(proto);
+ Objects.requireNonNull(proto);
File file = getFileForPackage(packageName);
try (FileOutputStream os = new FileOutputStream(file)) {
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/Hkdf.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/Hkdf.java
index 6f4f549..c7af8c8 100644
--- a/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/Hkdf.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/chunking/cdc/Hkdf.java
@@ -16,10 +16,9 @@
package com.android.server.backup.encryption.chunking.cdc;
-import static com.android.internal.util.Preconditions.checkNotNull;
-
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
+import java.util.Objects;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
@@ -49,9 +48,9 @@
* @throws InvalidKeyException If the salt can not be used as a valid key.
*/
static byte[] hkdf(byte[] masterKey, byte[] salt, byte[] data) throws InvalidKeyException {
- checkNotNull(masterKey, "HKDF requires master key to be set.");
- checkNotNull(salt, "HKDF requires a salt.");
- checkNotNull(data, "No data provided to HKDF.");
+ Objects.requireNonNull(masterKey, "HKDF requires master key to be set.");
+ Objects.requireNonNull(salt, "HKDF requires a salt.");
+ Objects.requireNonNull(data, "No data provided to HKDF.");
return hkdfSha256Expand(hkdfSha256Extract(masterKey, salt), data);
}
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKey.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKey.java
index f356b4f..436c6de8 100644
--- a/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKey.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/keys/RecoverableKeyStoreSecondaryKey.java
@@ -16,14 +16,14 @@
package com.android.server.backup.encryption.keys;
-import static com.android.internal.util.Preconditions.checkNotNull;
-
import android.annotation.IntDef;
import android.content.Context;
import android.security.keystore.recovery.InternalRecoveryServiceException;
import android.security.keystore.recovery.RecoveryController;
import android.util.Slog;
+import java.util.Objects;
+
import javax.crypto.SecretKey;
/**
@@ -46,8 +46,8 @@
* @param secretKey The key.
*/
public RecoverableKeyStoreSecondaryKey(String alias, SecretKey secretKey) {
- mAlias = checkNotNull(alias);
- mSecretKey = checkNotNull(secretKey);
+ mAlias = Objects.requireNonNull(alias);
+ mSecretKey = Objects.requireNonNull(secretKey);
}
/**
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/kv/KeyValueListingBuilder.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/kv/KeyValueListingBuilder.java
index b3518e1..217304c 100644
--- a/packages/BackupEncryption/src/com/android/server/backup/encryption/kv/KeyValueListingBuilder.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/kv/KeyValueListingBuilder.java
@@ -17,7 +17,6 @@
package com.android.server.backup.encryption.kv;
import static com.android.internal.util.Preconditions.checkArgument;
-import static com.android.internal.util.Preconditions.checkNotNull;
import com.android.server.backup.encryption.chunk.ChunkHash;
import com.android.server.backup.encryption.protos.nano.KeyValueListingProto;
@@ -26,6 +25,7 @@
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.Objects;
/**
* Builds a {@link KeyValueListingProto.KeyValueListing}, which is a nano proto and so has no
@@ -37,7 +37,7 @@
/** Adds a new pair entry to the listing. */
public KeyValueListingBuilder addPair(String key, ChunkHash hash) {
checkArgument(key.length() != 0, "Key must have non-zero length");
- checkNotNull(hash, "Hash must not be null");
+ Objects.requireNonNull(hash, "Hash must not be null");
KeyValueListingProto.KeyValueEntry entry = new KeyValueListingProto.KeyValueEntry();
entry.key = key;
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedFullBackupDataProcessor.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedFullBackupDataProcessor.java
index 0baec8b..71588f6 100644
--- a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedFullBackupDataProcessor.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/EncryptedFullBackupDataProcessor.java
@@ -16,7 +16,6 @@
package com.android.server.backup.encryption.tasks;
-import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.internal.util.Preconditions.checkState;
import android.annotation.Nullable;
@@ -34,6 +33,7 @@
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.security.SecureRandom;
+import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
@@ -67,12 +67,12 @@
SecureRandom secureRandom,
RecoverableKeyStoreSecondaryKey secondaryKey,
String packageName) {
- mContext = checkNotNull(context);
- mExecutorService = checkNotNull(executorService);
- mCryptoBackupServer = checkNotNull(cryptoBackupServer);
- mSecureRandom = checkNotNull(secureRandom);
- mSecondaryKey = checkNotNull(secondaryKey);
- mPackageName = checkNotNull(packageName);
+ mContext = Objects.requireNonNull(context);
+ mExecutorService = Objects.requireNonNull(executorService);
+ mCryptoBackupServer = Objects.requireNonNull(cryptoBackupServer);
+ mSecureRandom = Objects.requireNonNull(secureRandom);
+ mSecondaryKey = Objects.requireNonNull(secondaryKey);
+ mPackageName = Objects.requireNonNull(packageName);
}
@Override
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/RotateSecondaryKeyTask.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/RotateSecondaryKeyTask.java
index d58cb66..e5e2c1c 100644
--- a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/RotateSecondaryKeyTask.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/RotateSecondaryKeyTask.java
@@ -18,8 +18,6 @@
import static android.os.Build.VERSION_CODES.P;
-import static com.android.internal.util.Preconditions.checkNotNull;
-
import android.content.Context;
import android.security.keystore.recovery.InternalRecoveryServiceException;
import android.security.keystore.recovery.RecoveryController;
@@ -42,6 +40,7 @@
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
+import java.util.Objects;
import java.util.Optional;
import javax.crypto.IllegalBlockSizeException;
@@ -77,10 +76,10 @@
CryptoSettings cryptoSettings,
RecoveryController recoveryController) {
mContext = context;
- mSecondaryKeyManager = checkNotNull(secondaryKeyManager);
- mCryptoSettings = checkNotNull(cryptoSettings);
- mBackupServer = checkNotNull(backupServer);
- mRecoveryController = checkNotNull(recoveryController);
+ mSecondaryKeyManager = Objects.requireNonNull(secondaryKeyManager);
+ mCryptoSettings = Objects.requireNonNull(cryptoSettings);
+ mBackupServer = Objects.requireNonNull(backupServer);
+ mRecoveryController = Objects.requireNonNull(recoveryController);
}
/** Runs the task. */
diff --git a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/StartSecondaryKeyRotationTask.java b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/StartSecondaryKeyRotationTask.java
index 77cfded..81169e2 100644
--- a/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/StartSecondaryKeyRotationTask.java
+++ b/packages/BackupEncryption/src/com/android/server/backup/encryption/tasks/StartSecondaryKeyRotationTask.java
@@ -26,6 +26,7 @@
import com.android.server.backup.encryption.keys.RecoverableKeyStoreSecondaryKeyManager;
import java.security.UnrecoverableKeyException;
+import java.util.Objects;
import java.util.Optional;
/**
@@ -41,8 +42,8 @@
public StartSecondaryKeyRotationTask(
CryptoSettings cryptoSettings,
RecoverableKeyStoreSecondaryKeyManager secondaryKeyManager) {
- mCryptoSettings = Preconditions.checkNotNull(cryptoSettings);
- mSecondaryKeyManager = Preconditions.checkNotNull(secondaryKeyManager);
+ mCryptoSettings = Objects.requireNonNull(cryptoSettings);
+ mSecondaryKeyManager = Objects.requireNonNull(secondaryKeyManager);
}
/** Begin the key rotation */
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/testing/DiffScriptProcessor.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/testing/DiffScriptProcessor.java
index faddb6c..7e97924 100644
--- a/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/testing/DiffScriptProcessor.java
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/backup/encryption/testing/DiffScriptProcessor.java
@@ -17,7 +17,6 @@
package com.android.server.backup.encryption.testing;
import static com.android.internal.util.Preconditions.checkArgument;
-import static com.android.internal.util.Preconditions.checkNotNull;
import static java.nio.charset.StandardCharsets.UTF_8;
@@ -29,6 +28,7 @@
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.util.Locale;
+import java.util.Objects;
import java.util.Optional;
import java.util.Scanner;
import java.util.regex.Pattern;
@@ -69,7 +69,7 @@
checkArgument(input.exists(), "input file did not exist.");
mInput = input;
mInputLength = input.length();
- mOutput = checkNotNull(output);
+ mOutput = Objects.requireNonNull(output);
}
public void process(InputStream diffScript) throws IOException, MalformedDiffScriptException {
diff --git a/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/shadows/DataEntity.java b/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/shadows/DataEntity.java
index 6d3b5e9..06f4859 100644
--- a/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/shadows/DataEntity.java
+++ b/packages/BackupEncryption/test/robolectric/src/com/android/server/testing/shadows/DataEntity.java
@@ -16,10 +16,9 @@
package com.android.server.testing.shadows;
-import static com.google.common.base.Preconditions.checkNotNull;
-
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
+import java.util.Objects;
/**
* Represents a key value pair in {@link ShadowBackupDataInput} and {@link ShadowBackupDataOutput}.
@@ -34,7 +33,7 @@
* StandardCharsets#UTF_8}.
*/
public DataEntity(String key, String value) {
- this.mKey = checkNotNull(key);
+ this.mKey = Objects.requireNonNull(key);
this.mValue = value.getBytes(StandardCharsets.UTF_8);
mSize = this.mValue.length;
}
@@ -44,7 +43,7 @@
* pair.
*/
public DataEntity(String key) {
- this.mKey = checkNotNull(key);
+ this.mKey = Objects.requireNonNull(key);
mSize = -1;
mValue = null;
}
@@ -62,7 +61,7 @@
* @param size the length of the value in bytes
*/
public DataEntity(String key, byte[] data, int size) {
- this.mKey = checkNotNull(key);
+ this.mKey = Objects.requireNonNull(key);
this.mSize = size;
mValue = new byte[size];
for (int i = 0; i < size; i++) {
diff --git a/packages/CarSystemUI/res/drawable/car_ic_notification_selected.xml b/packages/CarSystemUI/res/drawable/car_ic_notification_selected.xml
deleted file mode 100644
index dd22545..0000000
--- a/packages/CarSystemUI/res/drawable/car_ic_notification_selected.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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
- -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:viewportWidth="44"
- android:viewportHeight="44"
- android:width="44dp"
- android:height="44dp">
- <path
- android:pathData="M22 39.125C23.925 39.125 25.5 37.55 25.5 35.625L18.5 35.625C18.5 37.55 20.0575 39.125 22 39.125ZM32.5 28.625L32.5 19.875C32.5 14.5025 29.63 10.005 24.625 8.815L24.625 7.625C24.625 6.1725 23.4525 5 22 5C20.5475 5 19.375 6.1725 19.375 7.625L19.375 8.815C14.3525 10.005 11.5 14.485 11.5 19.875L11.5 28.625L8 32.125L8 33.875L36 33.875L36 32.125L32.5 28.625Z"
- android:fillColor="@color/car_nav_icon_fill_color_selected" />
-</vector>
\ No newline at end of file
diff --git a/packages/CarSystemUI/res/drawable/car_ic_notification_selected_unseen.xml b/packages/CarSystemUI/res/drawable/car_ic_notification_selected_unseen.xml
deleted file mode 100644
index c5d7728..0000000
--- a/packages/CarSystemUI/res/drawable/car_ic_notification_selected_unseen.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?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
- -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:viewportWidth="44"
- android:viewportHeight="44"
- android:width="44dp"
- android:height="44dp">
- <path
- android:pathData="M22 39.125C23.925 39.125 25.5 37.55 25.5 35.625L18.5 35.625C18.5 37.55 20.0575 39.125 22 39.125ZM32.5 28.625L32.5 19.875C32.5 14.5025 29.63 10.005 24.625 8.815L24.625 7.625C24.625 6.1725 23.4525 5 22 5C20.5475 5 19.375 6.1725 19.375 7.625L19.375 8.815C14.3525 10.005 11.5 14.485 11.5 19.875L11.5 28.625L8 32.125L8 33.875L36 33.875L36 32.125L32.5 28.625Z"
- android:fillColor="@color/car_nav_icon_fill_color_selected" />
- <group
- android:translateX="30"
- android:translateY="2">
- <path
- android:fillColor="@color/car_nav_notification_unseen_indicator_color"
- android:strokeWidth="1"
- android:pathData="M 6 0 C 9.31370849898 0 12 2.68629150102 12 6 C 12 9.31370849898 9.31370849898 12 6 12 C 2.68629150102 12 0 9.31370849898 0 6 C 0 2.68629150102 2.68629150102 0 6 0 Z" />
- </group>
-</vector>
-
diff --git a/packages/CarSystemUI/res/drawable/car_ic_notification_unseen.xml b/packages/CarSystemUI/res/drawable/car_ic_unseen_indicator.xml
similarity index 69%
rename from packages/CarSystemUI/res/drawable/car_ic_notification_unseen.xml
rename to packages/CarSystemUI/res/drawable/car_ic_unseen_indicator.xml
index 25d1af3..025fc9c 100644
--- a/packages/CarSystemUI/res/drawable/car_ic_notification_unseen.xml
+++ b/packages/CarSystemUI/res/drawable/car_ic_unseen_indicator.xml
@@ -19,14 +19,11 @@
android:viewportHeight="44"
android:width="44dp"
android:height="44dp">
- <path
- android:pathData="M22 39.125C23.925 39.125 25.5 37.55 25.5 35.625L18.5 35.625C18.5 37.55 20.0575 39.125 22 39.125ZM32.5 28.625L32.5 19.875C32.5 14.5025 29.63 10.005 24.625 8.815L24.625 7.625C24.625 6.1725 23.4525 5 22 5C20.5475 5 19.375 6.1725 19.375 7.625L19.375 8.815C14.3525 10.005 11.5 14.485 11.5 19.875L11.5 28.625L8 32.125L8 33.875L36 33.875L36 32.125L32.5 28.625Z"
- android:fillColor="@color/car_nav_icon_fill_color" />
<group
android:translateX="30"
android:translateY="2">
<path
- android:fillColor="@color/car_nav_notification_unseen_indicator_color"
+ android:fillColor="@color/car_nav_unseen_indicator_color"
android:strokeWidth="1"
android:pathData="M 6 0 C 9.31370849898 0 12 2.68629150102 12 6 C 12 9.31370849898 9.31370849898 12 6 12 C 2.68629150102 12 0 9.31370849898 0 6 C 0 2.68629150102 2.68629150102 0 6 0 Z" />
</group>
diff --git a/packages/CarSystemUI/res/layout/car_facet_button.xml b/packages/CarSystemUI/res/layout/car_facet_button.xml
deleted file mode 100644
index 8e7ebad..0000000
--- a/packages/CarSystemUI/res/layout/car_facet_button.xml
+++ /dev/null
@@ -1,51 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-**
-** Copyright 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.
-*/
--->
-
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
- <LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/car_facet_button"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:gravity="center"
- android:animateLayoutChanges="true"
- android:orientation="vertical">
-
- <com.android.keyguard.AlphaOptimizedImageButton
- android:id="@+id/car_nav_button_icon"
- android:layout_height="wrap_content"
- android:layout_width="match_parent"
- android:animateLayoutChanges="true"
- android:background="@android:color/transparent"
- android:scaleType="fitCenter">
- </com.android.keyguard.AlphaOptimizedImageButton>
-
- <com.android.keyguard.AlphaOptimizedImageButton
- android:id="@+id/car_nav_button_more_icon"
- android:layout_height="wrap_content"
- android:layout_width="match_parent"
- android:animateLayoutChanges="true"
- android:src="@drawable/car_ic_arrow"
- android:background="@android:color/transparent"
- android:scaleType="fitCenter">
- </com.android.keyguard.AlphaOptimizedImageButton>
-
- </LinearLayout>
-</merge>
diff --git a/packages/CarSystemUI/res/layout/car_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
index 6c7a04f..e2e9a33 100644
--- a/packages/CarSystemUI/res/layout/car_navigation_bar.xml
+++ b/packages/CarSystemUI/res/layout/car_navigation_bar.xml
@@ -33,14 +33,14 @@
android:paddingEnd="20dp"
android:gravity="center">
- <com.android.systemui.navigationbar.car.CarFacetButton
+ <com.android.systemui.navigationbar.car.CarNavigationButton
android:id="@+id/home"
style="@style/NavigationBarButton"
systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
systemui:icon="@drawable/car_ic_overview"
systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
systemui:selectedIcon="@drawable/car_ic_overview_selected"
- systemui:useMoreIcon="false"
+ systemui:highlightWhenSelected="true"
/>
<Space
@@ -48,14 +48,14 @@
android:layout_height="match_parent"
android:layout_weight="1"/>
- <com.android.systemui.navigationbar.car.CarFacetButton
+ <com.android.systemui.navigationbar.car.CarNavigationButton
android:id="@+id/maps_nav"
style="@style/NavigationBarButton"
systemui:categories="android.intent.category.APP_MAPS"
systemui:icon="@drawable/car_ic_navigation"
systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MAPS;launchFlags=0x14000000;end"
systemui:selectedIcon="@drawable/car_ic_navigation_selected"
- systemui:useMoreIcon="false"
+ systemui:highlightWhenSelected="true"
/>
<Space
@@ -63,7 +63,7 @@
android:layout_height="match_parent"
android:layout_weight="1"/>
- <com.android.systemui.navigationbar.car.CarFacetButton
+ <com.android.systemui.navigationbar.car.CarNavigationButton
android:id="@+id/music_nav"
style="@style/NavigationBarButton"
systemui:categories="android.intent.category.APP_MUSIC"
@@ -71,7 +71,7 @@
systemui:intent="intent:#Intent;action=android.car.intent.action.MEDIA_TEMPLATE;launchFlags=0x10000000;end"
systemui:packages="com.android.car.media"
systemui:selectedIcon="@drawable/car_ic_music_selected"
- systemui:useMoreIcon="false"
+ systemui:highlightWhenSelected="true"
/>
<Space
@@ -79,14 +79,14 @@
android:layout_height="match_parent"
android:layout_weight="1"/>
- <com.android.systemui.navigationbar.car.CarFacetButton
+ <com.android.systemui.navigationbar.car.CarNavigationButton
android:id="@+id/phone_nav"
style="@style/NavigationBarButton"
systemui:icon="@drawable/car_ic_phone"
systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;package=com.android.car.dialer;launchFlags=0x10000000;end"
systemui:packages="com.android.car.dialer"
systemui:selectedIcon="@drawable/car_ic_phone_selected"
- systemui:useMoreIcon="false"
+ systemui:highlightWhenSelected="true"
/>
<Space
@@ -94,14 +94,14 @@
android:layout_height="match_parent"
android:layout_weight="1"/>
- <com.android.systemui.navigationbar.car.CarFacetButton
+ <com.android.systemui.navigationbar.car.CarNavigationButton
android:id="@+id/grid_nav"
style="@style/NavigationBarButton"
systemui:componentNames="com.android.car.carlauncher/.AppGridActivity"
systemui:icon="@drawable/car_ic_apps"
systemui:intent="intent:#Intent;component=com.android.car.carlauncher/.AppGridActivity;launchFlags=0x24000000;end"
systemui:selectedIcon="@drawable/car_ic_apps_selected"
- systemui:useMoreIcon="false"
+ systemui:highlightWhenSelected="true"
/>
<Space
@@ -114,8 +114,6 @@
style="@style/NavigationBarButton"
systemui:icon="@drawable/car_ic_notification"
systemui:longIntent="intent:#Intent;component=com.android.car.bugreport/.BugReportActivity;end"
- systemui:selectedIcon="@drawable/car_ic_notification_selected"
- systemui:useMoreIcon="false"
/>
<Space
@@ -127,7 +125,6 @@
android:id="@+id/assist"
style="@style/NavigationBarButton"
systemui:icon="@drawable/ic_mic_white"
- systemui:useMoreIcon="false"
/>
</LinearLayout>
@@ -140,8 +137,7 @@
android:paddingStart="@dimen/car_keyline_1"
android:paddingEnd="@dimen/car_keyline_1"
android:gravity="center"
- android:visibility="gone">
-
- </LinearLayout>
+ android:visibility="gone"
+ />
</com.android.systemui.navigationbar.car.CarNavigationBarView>
\ No newline at end of file
diff --git a/packages/CarSystemUI/res/layout/car_navigation_bar_unprovisioned.xml b/packages/CarSystemUI/res/layout/car_navigation_bar_unprovisioned.xml
index 0f964fd..1c5d37f 100644
--- a/packages/CarSystemUI/res/layout/car_navigation_bar_unprovisioned.xml
+++ b/packages/CarSystemUI/res/layout/car_navigation_bar_unprovisioned.xml
@@ -31,7 +31,7 @@
android:paddingStart="@*android:dimen/car_padding_5"
android:paddingEnd="@*android:dimen/car_padding_5">
- <com.android.systemui.navigationbar.car.CarFacetButton
+ <com.android.systemui.navigationbar.car.CarNavigationButton
android:id="@+id/home"
android:layout_width="@*android:dimen/car_touch_target_size"
android:layout_height="match_parent"
@@ -39,7 +39,8 @@
systemui:icon="@drawable/car_ic_overview"
systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
systemui:selectedIcon="@drawable/car_ic_overview_selected"
- systemui:useMoreIcon="false"/>
+ systemui:highlightWhenSelected="true"
+ />
</LinearLayout>
</com.android.systemui.navigationbar.car.CarNavigationBarView>
diff --git a/packages/CarSystemUI/res/layout/car_navigation_button.xml b/packages/CarSystemUI/res/layout/car_navigation_button.xml
index 6d8cca9..837252b 100644
--- a/packages/CarSystemUI/res/layout/car_navigation_button.xml
+++ b/packages/CarSystemUI/res/layout/car_navigation_button.xml
@@ -18,12 +18,48 @@
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
- <com.android.keyguard.AlphaOptimizedImageButton
+ <FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/car_nav_button_icon"
- android:layout_height="wrap_content"
- android:layout_width="@dimen/car_navigation_button_width"
- android:layout_centerInParent="true"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
android:animateLayoutChanges="true"
- android:scaleType="fitCenter">
- </com.android.keyguard.AlphaOptimizedImageButton>
+ android:orientation="vertical">
+
+ <com.android.keyguard.AlphaOptimizedImageButton
+ android:id="@+id/car_nav_button_icon_image"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:layout_gravity="center"
+ android:animateLayoutChanges="true"
+ android:background="@android:color/transparent"
+ android:scaleType="fitCenter"
+ android:clickable="false"
+ />
+
+ <com.android.keyguard.AlphaOptimizedImageButton
+ android:id="@+id/car_nav_button_more_icon"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:layout_gravity="center"
+ android:animateLayoutChanges="true"
+ android:src="@drawable/car_ic_arrow"
+ android:background="@android:color/transparent"
+ android:scaleType="fitCenter"
+ android:clickable="false"
+ />
+
+ <ImageView
+ android:id="@+id/car_nav_button_unseen_icon"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:layout_gravity="center"
+ android:src="@drawable/car_ic_unseen_indicator"
+ android:background="@android:color/transparent"
+ android:scaleType="fitCenter"
+ android:clickable="false"
+ />
+
+ </FrameLayout>
</merge>
diff --git a/packages/CarSystemUI/res/layout/super_status_bar.xml b/packages/CarSystemUI/res/layout/super_status_bar.xml
index 37cd1d4..0b34626 100644
--- a/packages/CarSystemUI/res/layout/super_status_bar.xml
+++ b/packages/CarSystemUI/res/layout/super_status_bar.xml
@@ -93,7 +93,7 @@
android:layout_height="match_parent"
android:visibility="invisible"/>
- <ViewStub android:id="@+id/status_bar_expanded"
+ <include layout="@layout/status_bar_expanded"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible"/>
diff --git a/packages/CarSystemUI/res/layout/sysui_primary_window.xml b/packages/CarSystemUI/res/layout/sysui_primary_window.xml
new file mode 100644
index 0000000..cc36e87
--- /dev/null
+++ b/packages/CarSystemUI/res/layout/sysui_primary_window.xml
@@ -0,0 +1,30 @@
+<?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.
+ -->
+
+<!-- Fullscreen views in sysui should be listed here in increasing Z order. -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:background="@android:color/transparent"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <ViewStub android:id="@+id/fullscreen_user_switcher_stub"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout="@layout/car_fullscreen_user_switcher"/>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/CarSystemUI/res/values/attrs.xml b/packages/CarSystemUI/res/values/attrs.xml
index 6178738..54026af 100644
--- a/packages/CarSystemUI/res/values/attrs.xml
+++ b/packages/CarSystemUI/res/values/attrs.xml
@@ -16,6 +16,12 @@
-->
<resources>
+ <attr name="icon" format="reference"/>
+ <attr name="selectedIcon" format="reference"/>
+ <attr name="intent" format="string"/>
+ <attr name="longIntent" format="string"/>
+ <attr name="selectedAlpha" format="float" />
+ <attr name="unselectedAlpha" format="float" />
<!-- Custom attributes to configure hvac values -->
<declare-styleable name="AnimatedTemperatureView">
@@ -32,4 +38,67 @@
<attr name="android:minEms"/>
<attr name="android:textAppearance"/>
</declare-styleable>
+
+ <!-- Allow for custom attribs to be added to a nav button -->
+ <declare-styleable name="CarNavigationButton">
+ <!-- intent to start when button is click -->
+ <attr name="intent" />
+ <!-- intent to start when a long press has happened -->
+ <attr name="longIntent" />
+ <!-- start the intent as a broad cast instead of an activity if true-->
+ <attr name="broadcast" format="boolean"/>
+ <!-- Alpha value to used when in selected state. Defaults 1f -->
+ <attr name="selectedAlpha" />
+ <!-- Alpha value to used when in un-selected state. Defaults 0.7f -->
+ <attr name="unselectedAlpha" />
+ <!-- icon to be rendered when in selected state -->
+ <attr name="selectedIcon" />
+ <!-- icon to be rendered (drawable) -->
+ <attr name="icon"/>
+ <!-- categories that will be added as extras to the fired intents -->
+ <attr name="categories" format="string"/>
+ <!-- package names that will be added as extras to the fired intents -->
+ <attr name="packages" format="string" />
+ <!-- componentName names that will be used for detecting selected state -->
+ <attr name="componentNames" format="string" />
+ <!-- whether to highlight the button when selected. Defaults false -->
+ <attr name="showMoreWhenSelected" format="boolean" />
+ <!-- whether to highlight the button when selected. Defaults false -->
+ <attr name="highlightWhenSelected" format="boolean" />
+ </declare-styleable>
+
+ <!-- Custom attributes to configure hvac values -->
+ <declare-styleable name="TemperatureView">
+ <attr name="hvacAreaId" format="integer"/>
+ <attr name="hvacPropertyId" format="integer"/>
+ <attr name="hvacTempFormat" format="string"/>
+ </declare-styleable>
+
+ <declare-styleable name="carVolumeItems"/>
+ <declare-styleable name="carVolumeItems_item">
+ <!-- Align with AudioAttributes.USAGE_* -->
+ <attr name="usage">
+ <enum name="unknown" value="0"/>
+ <enum name="media" value="1"/>
+ <enum name="voice_communication" value="2"/>
+ <enum name="voice_communication_signalling" value="3"/>
+ <enum name="alarm" value="4"/>
+ <enum name="notification" value="5"/>
+ <enum name="notification_ringtone" value="6"/>
+ <enum name="notification_communication_request" value="7"/>
+ <enum name="notification_communication_instant" value="8"/>
+ <enum name="notification_communication_delayed" value="9"/>
+ <enum name="notification_event" value="10"/>
+ <enum name="assistance_accessibility" value="11"/>
+ <enum name="assistance_navigation_guidance" value="12"/>
+ <enum name="assistance_sonification" value="13"/>
+ <enum name="game" value="14"/>
+ <!-- hidden, do not use -->
+ <!-- enum name="virtual_source" value="15"/ -->
+ <enum name="assistant" value="16"/>
+ </attr>
+
+ <!-- Icon resource ids to render on UI -->
+ <attr name="icon" />
+ </declare-styleable>
</resources>
diff --git a/packages/CarSystemUI/res/values/colors.xml b/packages/CarSystemUI/res/values/colors.xml
index 5fcf38fc..7972e09 100644
--- a/packages/CarSystemUI/res/values/colors.xml
+++ b/packages/CarSystemUI/res/values/colors.xml
@@ -43,8 +43,8 @@
<!-- The color of the dividing line between grouped notifications. -->
<color name="notification_divider_color">@*android:color/notification_action_list</color>
- <!-- The color for the unseen notification indicator. -->
- <color name="car_nav_notification_unseen_indicator_color">#e25142</color>
+ <!-- The color for the unseen indicator. -->
+ <color name="car_nav_unseen_indicator_color">#e25142</color>
<!-- The color of the ripples on the untinted notifications -->
<color name="notification_ripple_untinted_color">@color/ripple_material_light</color>
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
index ed945e7..cfe1c70 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/CarNotificationEntryManager.java
@@ -18,14 +18,20 @@
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
+import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
import com.android.systemui.statusbar.notification.logging.NotifLog;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
+import com.android.systemui.util.leak.LeakDetector;
import javax.inject.Inject;
import javax.inject.Singleton;
+import dagger.Lazy;
+
/**
* Car specific notification entry manager that does nothing when adding a notification.
*
@@ -40,8 +46,13 @@
NotifLog notifLog,
NotificationGroupManager groupManager,
NotificationRankingManager rankingManager,
- KeyguardEnvironment keyguardEnvironment) {
- super(notifLog, groupManager, rankingManager, keyguardEnvironment);
+ KeyguardEnvironment keyguardEnvironment,
+ FeatureFlags featureFlags,
+ Lazy<NotificationRowBinder> notificationRowBinderLazy,
+ Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy,
+ LeakDetector leakDetector) {
+ super(notifLog, groupManager, rankingManager, keyguardEnvironment, featureFlags,
+ notificationRowBinderLazy, notificationRemoteInputManagerLazy, leakDetector);
}
@Override
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/SystemUIPrimaryWindowController.java b/packages/CarSystemUI/src/com/android/systemui/car/SystemUIPrimaryWindowController.java
new file mode 100644
index 0000000..c7e14d6
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/car/SystemUIPrimaryWindowController.java
@@ -0,0 +1,153 @@
+/*
+ * 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.systemui.car;
+
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.os.Binder;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Controls the expansion state of the primary window which will contain all of the fullscreen sysui
+ * behavior. This window still has a collapsed state in order to watch for swipe events to expand
+ * this window for the notification panel.
+ */
+@Singleton
+public class SystemUIPrimaryWindowController implements
+ ConfigurationController.ConfigurationListener {
+
+ private final Context mContext;
+ private final Resources mResources;
+ private final WindowManager mWindowManager;
+
+ private final int mStatusBarHeight;
+ private final int mNavBarHeight;
+ private final int mDisplayHeight;
+ private ViewGroup mBaseLayout;
+ private WindowManager.LayoutParams mLp;
+ private WindowManager.LayoutParams mLpChanged;
+ private boolean mIsAttached = false;
+
+ @Inject
+ public SystemUIPrimaryWindowController(
+ Context context,
+ @Main Resources resources,
+ WindowManager windowManager,
+ ConfigurationController configurationController
+ ) {
+ mContext = context;
+ mResources = resources;
+ mWindowManager = windowManager;
+
+ Point display = new Point();
+ mWindowManager.getDefaultDisplay().getSize(display);
+ mDisplayHeight = display.y;
+
+ mStatusBarHeight = mResources.getDimensionPixelSize(
+ com.android.internal.R.dimen.status_bar_height);
+ mNavBarHeight = mResources.getDimensionPixelSize(R.dimen.navigation_bar_height);
+
+ mLpChanged = new WindowManager.LayoutParams();
+ mBaseLayout = (ViewGroup) LayoutInflater.from(context)
+ .inflate(R.layout.sysui_primary_window, /* root= */ null, false);
+
+ configurationController.addCallback(this);
+ }
+
+ /** Returns the base view of the primary window. */
+ public ViewGroup getBaseLayout() {
+ return mBaseLayout;
+ }
+
+ /** Returns {@code true} if the window is already attached. */
+ public boolean isAttached() {
+ return mIsAttached;
+ }
+
+ /** Attaches the window to the window manager. */
+ public void attach() {
+ if (mIsAttached) {
+ return;
+ }
+ mIsAttached = true;
+ // Now that the status bar window encompasses the sliding panel and its
+ // translucent backdrop, the entire thing is made TRANSLUCENT and is
+ // hardware-accelerated.
+ mLp = new WindowManager.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ mStatusBarHeight,
+ WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
+ | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
+ PixelFormat.TRANSLUCENT);
+ mLp.token = new Binder();
+ mLp.gravity = Gravity.TOP;
+ mLp.setFitWindowInsetsTypes(/* types= */ 0);
+ mLp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+ mLp.setTitle("SystemUIPrimaryWindow");
+ mLp.packageName = mContext.getPackageName();
+ mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+ mWindowManager.addView(mBaseLayout, mLp);
+ mLpChanged.copyFrom(mLp);
+ }
+
+ /** Sets the window to the expanded state. */
+ public void setWindowExpanded(boolean expanded) {
+ if (expanded) {
+ // TODO: Update this so that the windowing type gets the full height of the display
+ // when we use MATCH_PARENT.
+ mLpChanged.height = mDisplayHeight + mNavBarHeight;
+ mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+ } else {
+ mLpChanged.height = mStatusBarHeight;
+ // TODO: Allow touches to go through to the status bar to handle notification panel.
+ mLpChanged.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+ }
+ updateWindow();
+ }
+
+ /** Returns {@code true} if the window is expanded */
+ public boolean isWindowExpanded() {
+ return mLp.height != mStatusBarHeight;
+ }
+
+ private void updateWindow() {
+ if (mLp != null && mLp.copyFrom(mLpChanged) != 0) {
+ if (isAttached()) {
+ mWindowManager.updateViewLayout(mBaseLayout, mLp);
+ }
+ }
+ }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/AssitantButton.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/AssitantButton.java
index c50de22..98cc00e 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/AssitantButton.java
+++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/AssitantButton.java
@@ -30,9 +30,9 @@
/**
* AssitantButton is a ui component that will trigger the Voice Interaction Service.
*/
-public class AssitantButton extends CarFacetButton {
+public class AssitantButton extends CarNavigationButton {
- private static final String TAG = "CarFacetButton";
+ private static final String TAG = "AssistantButton";
private final AssistUtils mAssistUtils;
private IVoiceInteractionSessionShowCallback mShowCallback =
new IVoiceInteractionSessionShowCallback.Stub() {
@@ -62,7 +62,7 @@
}
@Override
- protected void setupIntents(TypedArray typedArray) {
+ protected void setUpIntents(TypedArray typedArray) {
// left blank because for the assistant button Intent will not be passed from the layout.
}
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/ButtonSelectionStateController.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/ButtonSelectionStateController.java
new file mode 100644
index 0000000..c36aaa0
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/ButtonSelectionStateController.java
@@ -0,0 +1,232 @@
+/*
+ * 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 com.android.systemui.navigationbar.car;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.view.Display;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * CarNavigationButtons can optionally have selection state that toggles certain visual indications
+ * based on whether the active application on screen is associated with it. This is basically a
+ * similar concept to a radio button group.
+ *
+ * This class controls the selection state of CarNavigationButtons that have opted in to have such
+ * selection state-dependent visual indications.
+ */
+@Singleton
+public class ButtonSelectionStateController {
+
+ private final Set<CarNavigationButton> mRegisteredViews = new HashSet<>();
+
+ protected ButtonMap mButtonsByCategory = new ButtonMap();
+ protected ButtonMap mButtonsByPackage = new ButtonMap();
+ protected ButtonMap mButtonsByComponentName = new ButtonMap();
+ protected HashSet<CarNavigationButton> mSelectedButtons;
+ protected Context mContext;
+
+ @Inject
+ public ButtonSelectionStateController(Context context) {
+ mContext = context;
+ mSelectedButtons = new HashSet<>();
+ }
+
+ /**
+ * Iterate through a view looking for CarNavigationButton and add it to the controller if it
+ * opted in to be highlighted when the active application is associated with it.
+ *
+ * @param v the View that may contain CarFacetButtons
+ */
+ protected void addAllButtonsWithSelectionState(View v) {
+ if (v instanceof CarNavigationButton) {
+ if (((CarNavigationButton) v).hasSelectionState()) {
+ addButtonWithSelectionState((CarNavigationButton) v);
+ }
+ } else if (v instanceof ViewGroup) {
+ ViewGroup viewGroup = (ViewGroup) v;
+ for (int i = 0; i < viewGroup.getChildCount(); i++) {
+ addAllButtonsWithSelectionState(viewGroup.getChildAt(i));
+ }
+ }
+ }
+
+ /** Removes all buttons from the button maps. */
+ protected void removeAll() {
+ mButtonsByCategory.clear();
+ mButtonsByPackage.clear();
+ mButtonsByComponentName.clear();
+ mSelectedButtons.clear();
+ mRegisteredViews.clear();
+ }
+
+ /**
+ * This will unselect the currently selected CarNavigationButton and determine which one should
+ * be selected next. It does this by reading the properties on the CarNavigationButton and
+ * seeing if they are a match with the supplied StackInfo list.
+ * The order of selection detection is ComponentName, PackageName then Category
+ * They will then be compared with the supplied StackInfo list.
+ * The StackInfo is expected to be supplied in order of recency and StackInfo will only be used
+ * for consideration if it has the same displayId as the CarNavigationButton.
+ *
+ * @param stackInfoList of the currently running application
+ * @param validDisplay index of the valid display
+ */
+
+ protected void taskChanged(List<ActivityManager.StackInfo> stackInfoList, int validDisplay) {
+ ActivityManager.StackInfo validStackInfo = null;
+ for (ActivityManager.StackInfo stackInfo : stackInfoList) {
+ // Find the first stack info with a topActivity in the primary display.
+ // TODO: We assume that CarFacetButton will launch an app only in the primary display.
+ // We need to extend the functionality to handle the multiple display properly.
+ if (stackInfo.topActivity != null && stackInfo.displayId == validDisplay) {
+ validStackInfo = stackInfo;
+ break;
+ }
+ }
+
+ if (validStackInfo == null) {
+ // No stack was found that was on the same display as the buttons thus return
+ return;
+ }
+ int displayId = validStackInfo.displayId;
+
+ mSelectedButtons.forEach(carNavigationButton -> {
+ if (carNavigationButton.getDisplayId() == displayId) {
+ carNavigationButton.setSelected(false);
+ }
+ });
+ mSelectedButtons.clear();
+
+ HashSet<CarNavigationButton> selectedButtons = findSelectedButtons(validStackInfo);
+
+ if (selectedButtons != null) {
+ selectedButtons.forEach(carNavigationButton -> {
+ if (carNavigationButton.getDisplayId() == displayId) {
+ carNavigationButton.setSelected(true);
+ mSelectedButtons.add(carNavigationButton);
+ }
+ });
+ }
+ }
+
+ /**
+ * Defaults to Display.DEFAULT_DISPLAY when no parameter is provided for the validDisplay.
+ *
+ * @param stackInfoList
+ */
+ protected void taskChanged(List<ActivityManager.StackInfo> stackInfoList) {
+ taskChanged(stackInfoList, Display.DEFAULT_DISPLAY);
+ }
+
+ /**
+ * Add navigation button to this controller if it uses selection state.
+ */
+ private void addButtonWithSelectionState(CarNavigationButton carNavigationButton) {
+ if (mRegisteredViews.contains(carNavigationButton)) {
+ return;
+ }
+ String[] categories = carNavigationButton.getCategories();
+ for (int i = 0; i < categories.length; i++) {
+ mButtonsByCategory.add(categories[i], carNavigationButton);
+ }
+
+ String[] packages = carNavigationButton.getPackages();
+ for (int i = 0; i < packages.length; i++) {
+ mButtonsByPackage.add(packages[i], carNavigationButton);
+ }
+ String[] componentNames = carNavigationButton.getComponentName();
+ for (int i = 0; i < componentNames.length; i++) {
+ mButtonsByComponentName.add(componentNames[i], carNavigationButton);
+ }
+
+ mRegisteredViews.add(carNavigationButton);
+ }
+
+ private HashSet<CarNavigationButton> findSelectedButtons(
+ ActivityManager.StackInfo validStackInfo) {
+ String packageName = validStackInfo.topActivity.getPackageName();
+
+ HashSet<CarNavigationButton> selectedButtons =
+ findButtonsByComponentName(validStackInfo.topActivity);
+ if (selectedButtons == null) {
+ selectedButtons = mButtonsByPackage.get(packageName);
+ }
+ if (selectedButtons == null) {
+ String category = getPackageCategory(packageName);
+ if (category != null) {
+ selectedButtons = mButtonsByCategory.get(category);
+ }
+ }
+
+ return selectedButtons;
+ }
+
+ private HashSet<CarNavigationButton> findButtonsByComponentName(
+ ComponentName componentName) {
+ HashSet<CarNavigationButton> buttons =
+ mButtonsByComponentName.get(componentName.flattenToShortString());
+ return (buttons != null) ? buttons :
+ mButtonsByComponentName.get(componentName.flattenToString());
+ }
+
+ private String getPackageCategory(String packageName) {
+ PackageManager pm = mContext.getPackageManager();
+ Set<String> supportedCategories = mButtonsByCategory.keySet();
+ for (String category : supportedCategories) {
+ Intent intent = new Intent();
+ intent.setPackage(packageName);
+ intent.setAction(Intent.ACTION_MAIN);
+ intent.addCategory(category);
+ List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
+ if (list.size() > 0) {
+ // Cache this package name into ButtonsByPackage map, so we won't have to query
+ // all categories next time this package name shows up.
+ mButtonsByPackage.put(packageName, mButtonsByCategory.get(category));
+ return category;
+ }
+ }
+ return null;
+ }
+
+ // simple multi-map
+ private static class ButtonMap extends HashMap<String, HashSet<CarNavigationButton>> {
+
+ public boolean add(String key, CarNavigationButton value) {
+ if (containsKey(key)) {
+ return get(key).add(value);
+ }
+ HashSet<CarNavigationButton> set = new HashSet<>();
+ set.add(value);
+ put(key, set);
+ return true;
+ }
+ }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/FacetButtonTaskStackListener.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/ButtonSelectionStateListener.java
similarity index 75%
rename from packages/CarSystemUI/src/com/android/systemui/navigationbar/car/FacetButtonTaskStackListener.java
rename to packages/CarSystemUI/src/com/android/systemui/navigationbar/car/ButtonSelectionStateListener.java
index 4925220..9da4121 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/FacetButtonTaskStackListener.java
+++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/ButtonSelectionStateListener.java
@@ -24,28 +24,25 @@
import javax.inject.Inject;
import javax.inject.Singleton;
-import dagger.Lazy;
-
/**
* An implementation of TaskStackChangeListener, that listens for changes in the system
* task stack and notifies the navigation bar.
*/
@Singleton
-class FacetButtonTaskStackListener extends TaskStackChangeListener {
- private static final String TAG = FacetButtonTaskStackListener.class.getSimpleName();
+class ButtonSelectionStateListener extends TaskStackChangeListener {
+ private static final String TAG = ButtonSelectionStateListener.class.getSimpleName();
- private final Lazy<CarFacetButtonController> mFacetButtonControllerLazy;
+ private final ButtonSelectionStateController mButtonSelectionStateController;
@Inject
- FacetButtonTaskStackListener(
- Lazy<CarFacetButtonController> carFacetButtonControllerLazy) {
- mFacetButtonControllerLazy = carFacetButtonControllerLazy;
+ ButtonSelectionStateListener(ButtonSelectionStateController carNavigationButtonController) {
+ mButtonSelectionStateController = carNavigationButtonController;
}
@Override
public void onTaskStackChanged() {
try {
- mFacetButtonControllerLazy.get().taskChanged(
+ mButtonSelectionStateController.taskChanged(
ActivityTaskManager.getService().getAllStackInfos());
} catch (Exception e) {
Log.e(TAG, "Getting StackInfo from activity manager failed", e);
@@ -55,7 +52,7 @@
@Override
public void onTaskDisplayChanged(int taskId, int newDisplayId) {
try {
- mFacetButtonControllerLazy.get().taskChanged(
+ mButtonSelectionStateController.taskChanged(
ActivityTaskManager.getService().getAllStackInfos());
} catch (Exception e) {
Log.e(TAG, "Getting StackInfo from activity manager failed", e);
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarFacetButton.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarFacetButton.java
deleted file mode 100644
index 0b89992..0000000
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarFacetButton.java
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * 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 com.android.systemui.navigationbar.car;
-
-import android.app.ActivityOptions;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.TypedArray;
-import android.os.Build;
-import android.os.UserHandle;
-import android.util.AttributeSet;
-import android.view.Display;
-import android.view.View;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-
-import com.android.keyguard.AlphaOptimizedImageButton;
-import com.android.systemui.R;
-
-/**
- * CarFacetButton is a ui component designed to be used as a shortcut for an app of a defined
- * category. It can also render a indicator implying that there are more options of apps to launch
- * using this component. This is done with a "More icon" currently an arrow as defined in the layout
- * file. The class is to serve as an example.
- *
- * New activity will be launched on the same display as the button is on.
- * Usage example: A button that allows a user to select a music app and indicate that there are
- * other music apps installed.
- */
-public class CarFacetButton extends LinearLayout {
- private static final String FACET_FILTER_DELIMITER = ";";
- /**
- * Extra information to be sent to a helper to make the decision of what app to launch when
- * clicked.
- */
- private static final String EXTRA_FACET_CATEGORIES = "categories";
- private static final String EXTRA_FACET_PACKAGES = "packages";
- private static final String EXTRA_FACET_ID = "filter_id";
- private static final String EXTRA_FACET_LAUNCH_PICKER = "launch_picker";
- private static final String TAG = "CarFacetButton";
-
- private Context mContext;
- private AlphaOptimizedImageButton mIcon;
- private AlphaOptimizedImageButton mMoreIcon;
- private boolean mSelected = false;
- private String[] mComponentNames;
- /** App categories that are to be used with this widget */
- private String[] mFacetCategories;
- /** App packages that are allowed to be used with this widget */
- private String[] mFacetPackages;
- private int mIconResourceId;
- /**
- * If defined in the xml this will be the icon that's rendered when the button is marked as
- * selected
- */
- private int mSelectedIconResourceId;
- private boolean mUseMoreIcon = true;
- private float mSelectedAlpha = 1f;
- private float mUnselectedAlpha = 1f;
-
- public CarFacetButton(Context context, AttributeSet attrs) {
- super(context, attrs);
- mContext = context;
- View.inflate(context, R.layout.car_facet_button, this);
- // extract custom attributes
- TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CarFacetButton);
- setupIntents(typedArray);
- setupIcons(typedArray);
- }
-
- /**
- * Reads the custom attributes to setup click handlers for this component.
- */
- protected void setupIntents(TypedArray typedArray) {
- String intentString = typedArray.getString(R.styleable.CarFacetButton_intent);
- String longPressIntentString = typedArray.getString(R.styleable.CarFacetButton_longIntent);
- String categoryString = typedArray.getString(R.styleable.CarFacetButton_categories);
- String packageString = typedArray.getString(R.styleable.CarFacetButton_packages);
- String componentNameString =
- typedArray.getString(R.styleable.CarFacetButton_componentNames);
- try {
- final Intent intent = Intent.parseUri(intentString, Intent.URI_INTENT_SCHEME);
- intent.putExtra(EXTRA_FACET_ID, Integer.toString(getId()));
-
- if (packageString != null) {
- mFacetPackages = packageString.split(FACET_FILTER_DELIMITER);
- intent.putExtra(EXTRA_FACET_PACKAGES, mFacetPackages);
- }
- if (categoryString != null) {
- mFacetCategories = categoryString.split(FACET_FILTER_DELIMITER);
- intent.putExtra(EXTRA_FACET_CATEGORIES, mFacetCategories);
- }
- if (componentNameString != null) {
- mComponentNames = componentNameString.split(FACET_FILTER_DELIMITER);
- }
-
- setOnClickListener(v -> {
- ActivityOptions options = ActivityOptions.makeBasic();
- options.setLaunchDisplayId(mContext.getDisplayId());
- intent.putExtra(EXTRA_FACET_LAUNCH_PICKER, mSelected);
- mContext.startActivityAsUser(intent, options.toBundle(), UserHandle.CURRENT);
- mContext.sendBroadcastAsUser(
- new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), UserHandle.CURRENT);
- });
-
- if (longPressIntentString != null && (Build.IS_ENG || Build.IS_USERDEBUG)) {
- final Intent longPressIntent = Intent.parseUri(longPressIntentString,
- Intent.URI_INTENT_SCHEME);
- setOnLongClickListener(v -> {
- ActivityOptions options = ActivityOptions.makeBasic();
- options.setLaunchDisplayId(mContext.getDisplayId());
- mContext.startActivityAsUser(longPressIntent, options.toBundle(),
- UserHandle.CURRENT);
- mContext.sendBroadcastAsUser(
- new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS), UserHandle.CURRENT);
- return true;
- });
- }
- } catch (Exception e) {
- throw new RuntimeException("Failed to attach intent", e);
- }
- }
-
- private void setupIcons(TypedArray styledAttributes) {
- mSelectedAlpha = styledAttributes.getFloat(
- R.styleable.CarFacetButton_selectedAlpha, mSelectedAlpha);
- mUnselectedAlpha = styledAttributes.getFloat(
- R.styleable.CarFacetButton_unselectedAlpha, mUnselectedAlpha);
- mIcon = findViewById(R.id.car_nav_button_icon);
- mIcon.setScaleType(ImageView.ScaleType.CENTER);
- mIcon.setClickable(false);
- mIcon.setAlpha(mUnselectedAlpha);
- mIconResourceId = styledAttributes.getResourceId(R.styleable.CarFacetButton_icon, 0);
- mIcon.setImageResource(mIconResourceId);
- mSelectedIconResourceId = styledAttributes.getResourceId(
- R.styleable.CarFacetButton_selectedIcon, mIconResourceId);
-
- mMoreIcon = findViewById(R.id.car_nav_button_more_icon);
- mMoreIcon.setClickable(false);
- mMoreIcon.setAlpha(mSelectedAlpha);
- mMoreIcon.setVisibility(GONE);
- mUseMoreIcon = styledAttributes.getBoolean(R.styleable.CarFacetButton_useMoreIcon, true);
- }
-
- /**
- * @return The app categories the component represents
- */
- public String[] getCategories() {
- if (mFacetCategories == null) {
- return new String[0];
- }
- return mFacetCategories;
- }
-
- /**
- * @return The valid packages that should be considered.
- */
- public String[] getFacetPackages() {
- if (mFacetPackages == null) {
- return new String[0];
- }
- return mFacetPackages;
- }
-
- /**
- * @return The list of component names.
- */
- public String[] getComponentName() {
- if (mComponentNames == null) {
- return new String[0];
- }
- return mComponentNames;
- }
-
- /**
- * Updates the alpha of the icons to "selected" and shows the "More icon"
- *
- * @param selected true if the view must be selected, false otherwise
- */
- public void setSelected(boolean selected) {
- super.setSelected(selected);
- setSelected(selected, selected);
- }
-
- /**
- * Updates the visual state to let the user know if it's been selected.
- *
- * @param selected true if should update the alpha of the icon to selected, false otherwise
- * @param showMoreIcon true if the "more icon" should be shown, false otherwise. Note this
- * is ignored if the attribute useMoreIcon is set to false
- */
- public void setSelected(boolean selected, boolean showMoreIcon) {
- mSelected = selected;
- mIcon.setAlpha(mSelected ? mSelectedAlpha : mUnselectedAlpha);
- mIcon.setImageResource(mSelected ? mSelectedIconResourceId : mIconResourceId);
- if (mUseMoreIcon) {
- mMoreIcon.setVisibility(showMoreIcon ? VISIBLE : GONE);
- }
- }
-
- /**
- * @return The id of the display the button is on or Display.INVALID_DISPLAY if it's not yet on
- * a display.
- */
- public int getDisplayId() {
- Display display = getDisplay();
- if (display == null) {
- return Display.INVALID_DISPLAY;
- }
- return display.getDisplayId();
- }
-}
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarFacetButtonController.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarFacetButtonController.java
deleted file mode 100644
index f66e828..0000000
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarFacetButtonController.java
+++ /dev/null
@@ -1,215 +0,0 @@
-/*
- * 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 com.android.systemui.navigationbar.car;
-
-import android.app.ActivityManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.view.Display;
-import android.view.View;
-import android.view.ViewGroup;
-
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Set;
-
-import javax.inject.Inject;
-import javax.inject.Singleton;
-
-/**
- * CarFacetButtons placed on the nav bar are designed to have visual indication that the active
- * application on screen is associated with it. This is basically a similar concept to a radio
- * button group.
- */
-@Singleton
-public class CarFacetButtonController {
-
- private final Set<CarFacetButton> mRegisteredViews = new HashSet<>();
-
- protected ButtonMap mButtonsByCategory = new ButtonMap();
- protected ButtonMap mButtonsByPackage = new ButtonMap();
- protected ButtonMap mButtonsByComponentName = new ButtonMap();
- protected HashSet<CarFacetButton> mSelectedFacetButtons;
- protected Context mContext;
-
- @Inject
- public CarFacetButtonController(Context context) {
- mContext = context;
- mSelectedFacetButtons = new HashSet<>();
- }
-
- /**
- * Add facet button to this controller. The expected use is for the facet button
- * to get a reference to this controller via {@link com.android.systemui.Dependency}
- * and self add.
- */
- private void addFacetButton(CarFacetButton facetButton) {
- if (mRegisteredViews.contains(facetButton)) {
- return;
- }
-
- String[] categories = facetButton.getCategories();
- for (int i = 0; i < categories.length; i++) {
- mButtonsByCategory.add(categories[i], facetButton);
- }
-
- String[] facetPackages = facetButton.getFacetPackages();
- for (int i = 0; i < facetPackages.length; i++) {
- mButtonsByPackage.add(facetPackages[i], facetButton);
- }
- String[] componentNames = facetButton.getComponentName();
- for (int i = 0; i < componentNames.length; i++) {
- mButtonsByComponentName.add(componentNames[i], facetButton);
- }
-
- mRegisteredViews.add(facetButton);
- }
-
- /** Removes all buttons from the button maps. */
- public void removeAll() {
- mButtonsByCategory.clear();
- mButtonsByPackage.clear();
- mButtonsByComponentName.clear();
- mSelectedFacetButtons.clear();
- mRegisteredViews.clear();
- }
-
- /**
- * Iterate through a view looking for CarFacetButtons and adding them to the controller if found
- *
- * @param v the View that may contain CarFacetButtons
- */
- public void addAllFacetButtons(View v) {
- if (v instanceof CarFacetButton) {
- addFacetButton((CarFacetButton) v);
- } else if (v instanceof ViewGroup) {
- ViewGroup viewGroup = (ViewGroup) v;
- for (int i = 0; i < viewGroup.getChildCount(); i++) {
- addAllFacetButtons(viewGroup.getChildAt(i));
- }
- }
- }
-
- /**
- * This will unselect the currently selected CarFacetButton and determine which one should be
- * selected next. It does this by reading the properties on the CarFacetButton and seeing if
- * they are a match with the supplied StackInfo list.
- * The order of selection detection is ComponentName, PackageName then Category
- * They will then be compared with the supplied StackInfo list.
- * The StackInfo is expected to be supplied in order of recency and StackInfo will only be used
- * for consideration if it has the same displayId as the CarFacetButtons.
- *
- * @param stackInfoList of the currently running application
- */
- public void taskChanged(List<ActivityManager.StackInfo> stackInfoList) {
- ActivityManager.StackInfo validStackInfo = null;
- for (ActivityManager.StackInfo stackInfo : stackInfoList) {
- // Find the first stack info with a topActivity in the primary display.
- // TODO: We assume that CarFacetButton will launch an app only in the primary display.
- // We need to extend the functionality to handle the mutliple display properly.
- if (stackInfo.topActivity != null && stackInfo.displayId == Display.DEFAULT_DISPLAY) {
- validStackInfo = stackInfo;
- break;
- }
- }
-
- if (validStackInfo == null) {
- // No stack was found that was on the same display as the facet buttons thus return
- return;
- }
-
- if (mSelectedFacetButtons != null) {
- Iterator<CarFacetButton> iterator = mSelectedFacetButtons.iterator();
- while (iterator.hasNext()) {
- CarFacetButton carFacetButton = iterator.next();
- if (carFacetButton.getDisplayId() == validStackInfo.displayId) {
- carFacetButton.setSelected(false);
- iterator.remove();
- }
- }
- }
-
- String packageName = validStackInfo.topActivity.getPackageName();
- HashSet<CarFacetButton> facetButton =
- findFacetButtonByComponentName(validStackInfo.topActivity);
- if (facetButton == null) {
- facetButton = mButtonsByPackage.get(packageName);
- }
-
- if (facetButton == null) {
- String category = getPackageCategory(packageName);
- if (category != null) {
- facetButton = mButtonsByCategory.get(category);
- }
- }
-
- if (facetButton != null) {
- for (CarFacetButton carFacetButton : facetButton) {
- if (carFacetButton.getDisplayId() == validStackInfo.displayId) {
- carFacetButton.setSelected(true);
- mSelectedFacetButtons.add(carFacetButton);
- }
- }
- }
-
- }
-
- private HashSet<CarFacetButton> findFacetButtonByComponentName(ComponentName componentName) {
- HashSet<CarFacetButton> buttons =
- mButtonsByComponentName.get(componentName.flattenToShortString());
- return (buttons != null) ? buttons :
- mButtonsByComponentName.get(componentName.flattenToString());
- }
-
- protected String getPackageCategory(String packageName) {
- PackageManager pm = mContext.getPackageManager();
- Set<String> supportedCategories = mButtonsByCategory.keySet();
- for (String category : supportedCategories) {
- Intent intent = new Intent();
- intent.setPackage(packageName);
- intent.setAction(Intent.ACTION_MAIN);
- intent.addCategory(category);
- List<ResolveInfo> list = pm.queryIntentActivities(intent, 0);
- if (list.size() > 0) {
- // Cache this package name into facetPackageMap, so we won't have to query
- // all categories next time this package name shows up.
- mButtonsByPackage.put(packageName, mButtonsByCategory.get(category));
- return category;
- }
- }
- return null;
- }
-
- // simple multi-map
- private static class ButtonMap extends HashMap<String, HashSet<CarFacetButton>> {
-
- public boolean add(String key, CarFacetButton value) {
- if (containsKey(key)) {
- return get(key).add(value);
- }
- HashSet<CarFacetButton> set = new HashSet<>();
- set.add(value);
- put(key, set);
- return true;
- }
- }
-}
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
index 59a084e..d8c9d17 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
@@ -57,12 +57,12 @@
private final WindowManager mWindowManager;
private final CarDeviceProvisionedController mCarDeviceProvisionedController;
private final CommandQueue mCommandQueue;
- private final Lazy<FacetButtonTaskStackListener> mFacetButtonTaskStackListenerLazy;
+ private final ButtonSelectionStateListener mButtonSelectionStateListener;
private final Handler mMainHandler;
private final Lazy<KeyguardStateController> mKeyguardStateControllerLazy;
private final Lazy<NavigationBarController> mNavigationBarControllerLazy;
private final SuperStatusBarViewFactory mSuperStatusBarViewFactory;
- private final Lazy<CarFacetButtonController> mCarFacetButtonControllerLazy;
+ private final ButtonSelectionStateController mButtonSelectionStateController;
private IStatusBarService mBarService;
private ActivityManagerWrapper mActivityManagerWrapper;
@@ -92,24 +92,24 @@
WindowManager windowManager,
DeviceProvisionedController deviceProvisionedController,
CommandQueue commandQueue,
- Lazy<FacetButtonTaskStackListener> facetButtonTaskStackListenerLazy,
+ ButtonSelectionStateListener buttonSelectionStateListener,
@Main Handler mainHandler,
Lazy<KeyguardStateController> keyguardStateControllerLazy,
Lazy<NavigationBarController> navigationBarControllerLazy,
SuperStatusBarViewFactory superStatusBarViewFactory,
- Lazy<CarFacetButtonController> carFacetButtonControllerLazy) {
+ ButtonSelectionStateController buttonSelectionStateController) {
super(context);
mCarNavigationBarController = carNavigationBarController;
mWindowManager = windowManager;
mCarDeviceProvisionedController = (CarDeviceProvisionedController)
deviceProvisionedController;
mCommandQueue = commandQueue;
- mFacetButtonTaskStackListenerLazy = facetButtonTaskStackListenerLazy;
+ mButtonSelectionStateListener = buttonSelectionStateListener;
mMainHandler = mainHandler;
mKeyguardStateControllerLazy = keyguardStateControllerLazy;
mNavigationBarControllerLazy = navigationBarControllerLazy;
mSuperStatusBarViewFactory = superStatusBarViewFactory;
- mCarFacetButtonControllerLazy = carFacetButtonControllerLazy;
+ mButtonSelectionStateController = buttonSelectionStateController;
}
@Override
@@ -156,7 +156,7 @@
createNavigationBar(result);
mActivityManagerWrapper = ActivityManagerWrapper.getInstance();
- mActivityManagerWrapper.registerTaskStackListener(mFacetButtonTaskStackListenerLazy.get());
+ mActivityManagerWrapper.registerTaskStackListener(mButtonSelectionStateListener);
mCarNavigationBarController.connectToHvac();
}
@@ -181,7 +181,7 @@
// remove and reattach all hvac components such that we don't keep a reference to unused
// ui elements
mCarNavigationBarController.removeAllFromHvac();
- mCarFacetButtonControllerLazy.get().removeAll();
+ mButtonSelectionStateController.removeAll();
if (mTopNavigationBarWindow != null) {
mTopNavigationBarWindow.removeAllViews();
@@ -343,7 +343,7 @@
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
pw.print(" mTaskStackListener=");
- pw.println(mFacetButtonTaskStackListenerLazy.get());
+ pw.println(mButtonSelectionStateListener);
pw.print(" mBottomNavigationBarView=");
pw.println(mBottomNavigationBarView);
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarController.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarController.java
index 6f28843..a56c4ed 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarController.java
@@ -37,7 +37,7 @@
private final Context mContext;
private final NavigationBarViewFactory mNavigationBarViewFactory;
- private final Lazy<CarFacetButtonController> mCarFacetButtonControllerLazy;
+ private final ButtonSelectionStateController mButtonSelectionStateController;
private final Lazy<HvacController> mHvacControllerLazy;
private boolean mShowBottom;
@@ -58,11 +58,11 @@
@Inject
public CarNavigationBarController(Context context,
NavigationBarViewFactory navigationBarViewFactory,
- Lazy<CarFacetButtonController> carFacetButtonControllerLazy,
+ ButtonSelectionStateController buttonSelectionStateController,
Lazy<HvacController> hvacControllerLazy) {
mContext = context;
mNavigationBarViewFactory = navigationBarViewFactory;
- mCarFacetButtonControllerLazy = carFacetButtonControllerLazy;
+ mButtonSelectionStateController = buttonSelectionStateController;
mHvacControllerLazy = hvacControllerLazy;
// Read configuration.
@@ -175,7 +175,7 @@
NotificationsShadeController notifShadeController) {
view.setStatusBarWindowTouchListener(statusBarTouchListener);
view.setNotificationsPanelController(notifShadeController);
- mCarFacetButtonControllerLazy.get().addAllFacetButtons(view);
+ mButtonSelectionStateController.addAllButtonsWithSelectionState(view);
mHvacControllerLazy.get().addTemperatureViewToController(view);
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationButton.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationButton.java
index 922bfff..b4d4785 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationButton.java
+++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationButton.java
@@ -24,8 +24,12 @@
import android.os.UserHandle;
import android.util.AttributeSet;
import android.util.Log;
+import android.view.Display;
+import android.view.View;
import android.widget.ImageView;
+import android.widget.LinearLayout;
+import com.android.keyguard.AlphaOptimizedImageButton;
import com.android.systemui.R;
import java.net.URISyntaxException;
@@ -35,110 +39,64 @@
* xml file level. This allows for more control via overlays instead of having to update
* code.
*/
-public class CarNavigationButton extends com.android.keyguard.AlphaOptimizedImageButton {
- private static final String TAG = "CarNavigationButton";
+public class CarNavigationButton extends LinearLayout {
- private static final int UNSEEN_ICON_RESOURCE_ID = R.drawable.car_ic_notification_unseen;
- private static final int UNSEEN_SELECTED_ICON_RESOURCE_ID =
- R.drawable.car_ic_notification_selected_unseen;
+ protected static final float DEFAULT_SELECTED_ALPHA = 1f;
+ protected static final float DEFAULT_UNSELECTED_ALPHA = 0.75f;
+
+ private static final String TAG = "CarNavigationButton";
+ private static final String BUTTON_FILTER_DELIMITER = ";";
+ private static final String EXTRA_BUTTON_CATEGORIES = "categories";
+ private static final String EXTRA_BUTTON_PACKAGES = "packages";
private Context mContext;
+ private AlphaOptimizedImageButton mIcon;
+ private AlphaOptimizedImageButton mMoreIcon;
+ private ImageView mUnseenIcon;
private String mIntent;
private String mLongIntent;
private boolean mBroadcastIntent;
private boolean mHasUnseen = false;
private boolean mSelected = false;
- private float mSelectedAlpha = 1f;
- private float mUnselectedAlpha = 1f;
+ private float mSelectedAlpha;
+ private float mUnselectedAlpha;
private int mSelectedIconResourceId;
private int mIconResourceId;
-
+ private String[] mComponentNames;
+ /** App categories that are to be used with this widget */
+ private String[] mButtonCategories;
+ /** App packages that are allowed to be used with this widget */
+ private String[] mButtonPackages;
+ /** Whether to display more icon beneath the primary icon when the button is selected */
+ private boolean mShowMoreWhenSelected = false;
+ /** Whether to highlight the button if the active application is associated with it */
+ private boolean mHighlightWhenSelected = false;
public CarNavigationButton(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
-
+ View.inflate(mContext, R.layout.car_navigation_button, /* root= */ this);
// CarNavigationButton attrs
- TypedArray typedArray = context.obtainStyledAttributes(
- attrs, R.styleable.CarNavigationButton);
- mIntent = typedArray.getString(R.styleable.CarNavigationButton_intent);
- mLongIntent = typedArray.getString(R.styleable.CarNavigationButton_longIntent);
- mBroadcastIntent = typedArray.getBoolean(R.styleable.CarNavigationButton_broadcast, false);
- mSelectedAlpha = typedArray.getFloat(
- R.styleable.CarNavigationButton_selectedAlpha, mSelectedAlpha);
- mUnselectedAlpha = typedArray.getFloat(
- R.styleable.CarNavigationButton_unselectedAlpha, mUnselectedAlpha);
- mSelectedIconResourceId = typedArray.getResourceId(
- R.styleable.CarNavigationButton_selectedIcon, mIconResourceId);
- mIconResourceId = typedArray.getResourceId(
- R.styleable.CarNavigationButton_icon, 0);
+ TypedArray typedArray = context.obtainStyledAttributes(attrs,
+ R.styleable.CarNavigationButton);
+
+ setUpIntents(typedArray);
+ setUpIcons(typedArray);
typedArray.recycle();
}
-
- /**
- * After the standard inflate this then adds the xml defined intents to click and long click
- * actions if defined.
- */
- @Override
- public void onFinishInflate() {
- super.onFinishInflate();
- setScaleType(ImageView.ScaleType.CENTER);
- setAlpha(mUnselectedAlpha);
- setImageResource(mIconResourceId);
- try {
- if (mIntent != null) {
- final Intent intent = Intent.parseUri(mIntent, Intent.URI_INTENT_SCHEME);
- setOnClickListener(v -> {
- try {
- if (mBroadcastIntent) {
- mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
- mContext.sendBroadcastAsUser(
- new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS),
- UserHandle.CURRENT);
- return;
- }
- ActivityOptions options = ActivityOptions.makeBasic();
- options.setLaunchDisplayId(mContext.getDisplayId());
- mContext.startActivityAsUser(intent, options.toBundle(),
- UserHandle.CURRENT);
- } catch (Exception e) {
- Log.e(TAG, "Failed to launch intent", e);
- }
- });
- }
- } catch (URISyntaxException e) {
- throw new RuntimeException("Failed to attach intent", e);
- }
-
- try {
- if (mLongIntent != null && (Build.IS_ENG || Build.IS_USERDEBUG)) {
- final Intent intent = Intent.parseUri(mLongIntent, Intent.URI_INTENT_SCHEME);
- setOnLongClickListener(v -> {
- try {
- ActivityOptions options = ActivityOptions.makeBasic();
- options.setLaunchDisplayId(mContext.getDisplayId());
- mContext.startActivityAsUser(intent, options.toBundle(),
- UserHandle.CURRENT);
- } catch (Exception e) {
- Log.e(TAG, "Failed to launch intent", e);
- }
- // consume event either way
- return true;
- });
- }
- } catch (URISyntaxException e) {
- throw new RuntimeException("Failed to attach long press intent", e);
- }
- }
-
/**
* @param selected true if should indicate if this is a selected state, false otherwise
*/
public void setSelected(boolean selected) {
super.setSelected(selected);
mSelected = selected;
- setAlpha(mSelected ? mSelectedAlpha : mUnselectedAlpha);
+ if (mHighlightWhenSelected) {
+ setAlpha(mSelected ? mSelectedAlpha : mUnselectedAlpha);
+ }
+ if (mShowMoreWhenSelected && mMoreIcon != null) {
+ mMoreIcon.setVisibility(selected ? VISIBLE : GONE);
+ }
updateImage();
}
@@ -155,12 +113,172 @@
return mHasUnseen;
}
- private void updateImage() {
- if (mHasUnseen) {
- setImageResource(mSelected ? UNSEEN_SELECTED_ICON_RESOURCE_ID
- : UNSEEN_ICON_RESOURCE_ID);
- } else {
- setImageResource(mSelected ? mSelectedIconResourceId : mIconResourceId);
+ /**
+ * @return The app categories the component represents
+ */
+ public String[] getCategories() {
+ if (mButtonCategories == null) {
+ return new String[0];
+ }
+ return mButtonCategories;
+ }
+
+ /**
+ * @return The valid packages that should be considered.
+ */
+ public String[] getPackages() {
+ if (mButtonPackages == null) {
+ return new String[0];
+ }
+ return mButtonPackages;
+ }
+
+ /**
+ * @return The list of component names.
+ */
+ public String[] getComponentName() {
+ if (mComponentNames == null) {
+ return new String[0];
+ }
+ return mComponentNames;
+ }
+
+ /**
+ * @return The id of the display the button is on or Display.INVALID_DISPLAY if it's not yet on
+ * a display.
+ */
+ protected int getDisplayId() {
+ Display display = getDisplay();
+ if (display == null) {
+ return Display.INVALID_DISPLAY;
+ }
+ return display.getDisplayId();
+ }
+
+ protected boolean hasSelectionState() {
+ return mHighlightWhenSelected || mShowMoreWhenSelected;
+ }
+
+ /**
+ * Sets up intents for click, long touch, and broadcast.
+ */
+ protected void setUpIntents(TypedArray typedArray) {
+ mIntent = typedArray.getString(R.styleable.CarNavigationButton_intent);
+ mLongIntent = typedArray.getString(R.styleable.CarNavigationButton_longIntent);
+ mBroadcastIntent = typedArray.getBoolean(R.styleable.CarNavigationButton_broadcast, false);
+
+ String categoryString = typedArray.getString(R.styleable.CarNavigationButton_categories);
+ String packageString = typedArray.getString(R.styleable.CarNavigationButton_packages);
+ String componentNameString =
+ typedArray.getString(R.styleable.CarNavigationButton_componentNames);
+
+ try {
+ if (mIntent != null) {
+ final Intent intent = Intent.parseUri(mIntent, Intent.URI_INTENT_SCHEME);
+ setOnClickListener(getButtonClickListener(intent));
+ if (packageString != null) {
+ mButtonPackages = packageString.split(BUTTON_FILTER_DELIMITER);
+ intent.putExtra(EXTRA_BUTTON_PACKAGES, mButtonPackages);
+ }
+ if (categoryString != null) {
+ mButtonCategories = categoryString.split(BUTTON_FILTER_DELIMITER);
+ intent.putExtra(EXTRA_BUTTON_CATEGORIES, mButtonCategories);
+ }
+ if (componentNameString != null) {
+ mComponentNames = componentNameString.split(BUTTON_FILTER_DELIMITER);
+ }
+ }
+ } catch (URISyntaxException e) {
+ throw new RuntimeException("Failed to attach intent", e);
+ }
+
+ try {
+ if (mLongIntent != null && (Build.IS_ENG || Build.IS_USERDEBUG)) {
+ final Intent intent = Intent.parseUri(mLongIntent, Intent.URI_INTENT_SCHEME);
+ setOnLongClickListener(getButtonLongClickListener(intent));
+ }
+ } catch (URISyntaxException e) {
+ throw new RuntimeException("Failed to attach long press intent", e);
}
}
+
+ /** Defines the behavior of a button click. */
+ protected OnClickListener getButtonClickListener(Intent toSend) {
+ return v -> {
+ mContext.sendBroadcastAsUser(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS),
+ UserHandle.CURRENT);
+ try {
+ if (mBroadcastIntent) {
+ mContext.sendBroadcastAsUser(toSend, UserHandle.CURRENT);
+ return;
+ }
+ ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(mContext.getDisplayId());
+ mContext.startActivityAsUser(toSend, options.toBundle(),
+ UserHandle.CURRENT);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to launch intent", e);
+ }
+ };
+ }
+
+ /** Defines the behavior of a long click. */
+ protected OnLongClickListener getButtonLongClickListener(Intent toSend) {
+ return v -> {
+ mContext.sendBroadcastAsUser(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS),
+ UserHandle.CURRENT);
+ try {
+ ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(mContext.getDisplayId());
+ mContext.startActivityAsUser(toSend, options.toBundle(),
+ UserHandle.CURRENT);
+ } catch (Exception e) {
+ Log.e(TAG, "Failed to launch intent", e);
+ }
+ // consume event either way
+ return true;
+ };
+ }
+
+
+ /**
+ * Initializes view-related aspects of the button.
+ */
+ private void setUpIcons(TypedArray typedArray) {
+ mSelectedAlpha = typedArray.getFloat(
+ R.styleable.CarNavigationButton_selectedAlpha, DEFAULT_SELECTED_ALPHA);
+ mUnselectedAlpha = typedArray.getFloat(
+ R.styleable.CarNavigationButton_unselectedAlpha, DEFAULT_UNSELECTED_ALPHA);
+ mHighlightWhenSelected = typedArray.getBoolean(
+ R.styleable.CarNavigationButton_highlightWhenSelected,
+ mHighlightWhenSelected);
+ mShowMoreWhenSelected = typedArray.getBoolean(
+ R.styleable.CarNavigationButton_showMoreWhenSelected,
+ mShowMoreWhenSelected);
+
+ mSelectedIconResourceId = typedArray.getResourceId(
+ R.styleable.CarNavigationButton_selectedIcon, mIconResourceId);
+ mIconResourceId = typedArray.getResourceId(
+ R.styleable.CarNavigationButton_icon, 0);
+
+ mIcon = findViewById(R.id.car_nav_button_icon_image);
+ mIcon.setScaleType(ImageView.ScaleType.CENTER);
+ // Always apply selected alpha if the button does not toggle alpha based on selection state.
+ mIcon.setAlpha(mHighlightWhenSelected ? mUnselectedAlpha : mSelectedAlpha);
+ mIcon.setImageResource(mIconResourceId);
+
+ mMoreIcon = findViewById(R.id.car_nav_button_more_icon);
+ mMoreIcon.setAlpha(mSelectedAlpha);
+ mMoreIcon.setVisibility(GONE);
+
+ mUnseenIcon = findViewById(R.id.car_nav_button_unseen_icon);
+
+ mUnseenIcon.setVisibility(mHasUnseen ? VISIBLE : GONE);
+ }
+
+ private void updateImage() {
+ mIcon.setImageResource(mSelected ? mSelectedIconResourceId : mIconResourceId);
+ mUnseenIcon.setVisibility(mHasUnseen ? VISIBLE : GONE);
+ }
+
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 77db54c..18485dc 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.car;
-import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
import android.animation.Animator;
@@ -68,6 +67,7 @@
import com.android.systemui.car.CarDeviceProvisionedController;
import com.android.systemui.car.CarDeviceProvisionedListener;
import com.android.systemui.car.CarServiceProvider;
+import com.android.systemui.car.SystemUIPrimaryWindowController;
import com.android.systemui.classifier.FalsingLog;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.qualifiers.UiBackground;
@@ -107,7 +107,8 @@
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
-import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.phone.AutoHideController;
@@ -128,11 +129,11 @@
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.phone.StatusBarComponent;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
import com.android.systemui.statusbar.phone.StatusBarWindowController;
+import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -168,6 +169,7 @@
// acceleration rate for the fling animation
private static final float FLING_SPEED_UP_FACTOR = 0.6f;
+ private final UserSwitcherController mUserSwitcherController;
private final ScrimController mScrimController;
private final LockscreenLockIconController mLockscreenLockIconController;
@@ -177,17 +179,16 @@
private float mBackgroundAlphaDiff;
private float mInitialBackgroundAlpha;
- private final Lazy<FullscreenUserSwitcher> mFullscreenUserSwitcherLazy;
- private FullscreenUserSwitcher mFullscreenUserSwitcher;
-
private CarBatteryController mCarBatteryController;
private BatteryMeterView mBatteryMeterView;
private Drawable mNotificationPanelBackground;
private final Object mQueueLock = new Object();
+ private final SystemUIPrimaryWindowController mSystemUIPrimaryWindowController;
private final CarNavigationBarController mCarNavigationBarController;
private final FlingAnimationUtils.Builder mFlingAnimationUtilsBuilder;
private final Lazy<PowerManagerHelper> mPowerManagerHelperLazy;
+ private final FullscreenUserSwitcher mFullscreenUserSwitcher;
private final ShadeController mShadeController;
private final CarServiceProvider mCarServiceProvider;
private final CarDeviceProvisionedController mCarDeviceProvisionedController;
@@ -268,8 +269,7 @@
HeadsUpManagerPhone headsUpManagerPhone,
DynamicPrivacyController dynamicPrivacyController,
BypassHeadsUpNotifier bypassHeadsUpNotifier,
- @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowNotificationLongPress,
- Lazy<NewNotifPipeline> newNotifPipeline,
+ Lazy<NotifPipelineInitializer> notifPipelineInitializer,
FalsingManager falsingManager,
BroadcastDispatcher broadcastDispatcher,
RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler,
@@ -334,11 +334,13 @@
KeyguardDismissUtil keyguardDismissUtil,
ExtensionController extensionController,
UserInfoControllerImpl userInfoControllerImpl,
+ NotificationRowBinderImpl notificationRowBinder,
DismissCallbackRegistry dismissCallbackRegistry,
/* Car Settings injected components. */
CarServiceProvider carServiceProvider,
Lazy<PowerManagerHelper> powerManagerHelperLazy,
- Lazy<FullscreenUserSwitcher> fullscreenUserSwitcherLazy,
+ FullscreenUserSwitcher fullscreenUserSwitcher,
+ SystemUIPrimaryWindowController systemUIPrimaryWindowController,
CarNavigationBarController carNavigationBarController,
FlingAnimationUtils.Builder flingAnimationUtilsBuilder) {
super(
@@ -355,8 +357,7 @@
headsUpManagerPhone,
dynamicPrivacyController,
bypassHeadsUpNotifier,
- allowNotificationLongPress,
- newNotifPipeline,
+ notifPipelineInitializer,
falsingManager,
broadcastDispatcher,
remoteInputQuickSettingsDisabler,
@@ -421,7 +422,9 @@
keyguardDismissUtil,
extensionController,
userInfoControllerImpl,
+ notificationRowBinder,
dismissCallbackRegistry);
+ mUserSwitcherController = userSwitcherController;
mScrimController = scrimController;
mLockscreenLockIconController = lockscreenLockIconController;
mCarDeviceProvisionedController =
@@ -429,7 +432,8 @@
mShadeController = shadeController;
mCarServiceProvider = carServiceProvider;
mPowerManagerHelperLazy = powerManagerHelperLazy;
- mFullscreenUserSwitcherLazy = fullscreenUserSwitcherLazy;
+ mFullscreenUserSwitcher = fullscreenUserSwitcher;
+ mSystemUIPrimaryWindowController = systemUIPrimaryWindowController;
mCarNavigationBarController = carNavigationBarController;
mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilder;
}
@@ -444,6 +448,13 @@
mScreenLifecycle = Dependency.get(ScreenLifecycle.class);
mScreenLifecycle.addObserver(mScreenObserver);
+ // TODO: Remove the setup of user switcher from Car Status Bar.
+ mSystemUIPrimaryWindowController.attach();
+ mFullscreenUserSwitcher.setStatusBar(this);
+ mFullscreenUserSwitcher.setContainer(
+ mSystemUIPrimaryWindowController.getBaseLayout().findViewById(
+ R.id.fullscreen_user_switcher_stub));
+
// Notification bar related setup.
mInitialBackgroundAlpha = (float) mContext.getResources().getInteger(
R.integer.config_initialNotificationBackgroundAlpha) / 100;
@@ -465,7 +476,7 @@
super.start();
- mNotificationPanel.setScrollingEnabled(true);
+ mNotificationPanelViewController.setScrollingEnabled(true);
mSettleOpenPercentage = mContext.getResources().getInteger(
R.integer.notification_settle_open_percentage);
mSettleClosePercentage = mContext.getResources().getInteger(
@@ -510,16 +521,6 @@
});
}
- /**
- * Allows for showing or hiding just the navigation bars. This is indented to be used when
- * the full screen user selector is shown.
- */
- void setNavBarVisibility(@View.Visibility int visibility) {
- mCarNavigationBarController.setBottomWindowVisibility(visibility);
- mCarNavigationBarController.setLeftWindowVisibility(visibility);
- mCarNavigationBarController.setRightWindowVisibility(visibility);
- }
-
@Override
public boolean hideKeyguard() {
boolean result = super.hideKeyguard();
@@ -924,9 +925,6 @@
+ " scroll " + mStackScroller.getScrollX()
+ "," + mStackScroller.getScrollY());
}
-
- pw.print(" mFullscreenUserSwitcher=");
- pw.println(mFullscreenUserSwitcher);
pw.print(" mCarBatteryController=");
pw.println(mCarBatteryController);
pw.print(" mBatteryMeterView=");
@@ -972,14 +970,7 @@
@Override
protected void createUserSwitcher() {
- UserSwitcherController userSwitcherController =
- Dependency.get(UserSwitcherController.class);
- if (userSwitcherController.useFullscreenUserSwitcher()) {
- mFullscreenUserSwitcher = mFullscreenUserSwitcherLazy.get();
- mFullscreenUserSwitcher.setStatusBar(this);
- mFullscreenUserSwitcher.setContainer(
- mStatusBarWindow.findViewById(R.id.fullscreen_user_switcher_stub));
- } else {
+ if (!mUserSwitcherController.useFullscreenUserSwitcher()) {
super.createUserSwitcher();
}
}
@@ -996,25 +987,12 @@
super.onStateChanged(newState);
if (newState != StatusBarState.FULLSCREEN_USER_SWITCHER) {
- hideUserSwitcher();
+ mFullscreenUserSwitcher.hide();
} else {
dismissKeyguardWhenUserSwitcherNotDisplayed();
}
}
- /** Makes the full screen user switcher visible, if applicable. */
- public void showUserSwitcher() {
- if (mFullscreenUserSwitcher != null && mState == StatusBarState.FULLSCREEN_USER_SWITCHER) {
- mFullscreenUserSwitcher.show(); // Makes the switcher visible.
- }
- }
-
- private void hideUserSwitcher() {
- if (mFullscreenUserSwitcher != null) {
- mFullscreenUserSwitcher.hide();
- }
- }
-
final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
@Override
public void onScreenTurnedOn() {
@@ -1024,7 +1002,7 @@
// We automatically dismiss keyguard unless user switcher is being shown on the keyguard.
private void dismissKeyguardWhenUserSwitcherNotDisplayed() {
- if (mFullscreenUserSwitcher == null) {
+ if (!mUserSwitcherController.useFullscreenUserSwitcher()) {
return; // Not using the full screen user switcher.
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarKeyguardViewManager.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarKeyguardViewManager.java
index 0ad0992..2a2eb69 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarKeyguardViewManager.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarKeyguardViewManager.java
@@ -24,6 +24,7 @@
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.R;
import com.android.systemui.dock.DockManager;
+import com.android.systemui.navigationbar.car.CarNavigationBarController;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.phone.NavigationModeController;
@@ -40,6 +41,8 @@
public class CarStatusBarKeyguardViewManager extends StatusBarKeyguardViewManager {
protected boolean mShouldHideNavBar;
+ private final CarNavigationBarController mCarNavigationBarController;
+ private final FullscreenUserSwitcher mFullscreenUserSwitcher;
@Inject
public CarStatusBarKeyguardViewManager(Context context,
@@ -52,13 +55,17 @@
DockManager dockManager,
StatusBarWindowController statusBarWindowController,
KeyguardStateController keyguardStateController,
- NotificationMediaManager notificationMediaManager) {
+ NotificationMediaManager notificationMediaManager,
+ CarNavigationBarController carNavigationBarController,
+ FullscreenUserSwitcher fullscreenUserSwitcher) {
super(context, callback, lockPatternUtils, sysuiStatusBarStateController,
configurationController, keyguardUpdateMonitor, navigationModeController,
dockManager, statusBarWindowController, keyguardStateController,
notificationMediaManager);
mShouldHideNavBar = context.getResources()
.getBoolean(R.bool.config_hideNavWhenKeyguardBouncerShown);
+ mCarNavigationBarController = carNavigationBarController;
+ mFullscreenUserSwitcher = fullscreenUserSwitcher;
}
@Override
@@ -66,8 +73,10 @@
if (!mShouldHideNavBar) {
return;
}
- CarStatusBar statusBar = (CarStatusBar) mStatusBar;
- statusBar.setNavBarVisibility(navBarVisible ? View.VISIBLE : View.GONE);
+ int visibility = navBarVisible ? View.VISIBLE : View.GONE;
+ mCarNavigationBarController.setBottomWindowVisibility(visibility);
+ mCarNavigationBarController.setLeftWindowVisibility(visibility);
+ mCarNavigationBarController.setRightWindowVisibility(visibility);
}
/**
@@ -86,8 +95,7 @@
*/
@Override
public void onCancelClicked() {
- CarStatusBar statusBar = (CarStatusBar) mStatusBar;
- statusBar.showUserSwitcher();
+ mFullscreenUserSwitcher.show();
}
/**
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
index 1ebaef7..3abbe32 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.car;
-import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
import android.content.Context;
@@ -32,6 +31,7 @@
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.car.CarServiceProvider;
+import com.android.systemui.car.SystemUIPrimaryWindowController;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.keyguard.DismissCallbackRegistry;
@@ -67,7 +67,8 @@
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
-import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.phone.AutoHideController;
@@ -86,11 +87,11 @@
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.StatusBarComponent;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarter;
import com.android.systemui.statusbar.phone.StatusBarWindowController;
+import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -138,8 +139,7 @@
HeadsUpManagerPhone headsUpManagerPhone,
DynamicPrivacyController dynamicPrivacyController,
BypassHeadsUpNotifier bypassHeadsUpNotifier,
- @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowNotificationLongPress,
- Lazy<NewNotifPipeline> newNotifPipeline,
+ Lazy<NotifPipelineInitializer> notifPipelineInitializer,
FalsingManager falsingManager,
BroadcastDispatcher broadcastDispatcher,
RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler,
@@ -204,10 +204,12 @@
KeyguardDismissUtil keyguardDismissUtil,
ExtensionController extensionController,
UserInfoControllerImpl userInfoControllerImpl,
+ NotificationRowBinderImpl notificationRowBinder,
DismissCallbackRegistry dismissCallbackRegistry,
CarServiceProvider carServiceProvider,
Lazy<PowerManagerHelper> powerManagerHelperLazy,
- Lazy<FullscreenUserSwitcher> fullscreenUserSwitcherLazy,
+ FullscreenUserSwitcher fullscreenUserSwitcher,
+ SystemUIPrimaryWindowController systemUIPrimaryWindowController,
CarNavigationBarController carNavigationBarController,
FlingAnimationUtils.Builder flingAnimationUtilsBuilder) {
return new CarStatusBar(
@@ -224,8 +226,7 @@
headsUpManagerPhone,
dynamicPrivacyController,
bypassHeadsUpNotifier,
- allowNotificationLongPress,
- newNotifPipeline,
+ notifPipelineInitializer,
falsingManager,
broadcastDispatcher,
remoteInputQuickSettingsDisabler,
@@ -289,10 +290,12 @@
keyguardDismissUtil,
extensionController,
userInfoControllerImpl,
+ notificationRowBinder,
dismissCallbackRegistry,
carServiceProvider,
powerManagerHelperLazy,
- fullscreenUserSwitcherLazy,
+ fullscreenUserSwitcher,
+ systemUIPrimaryWindowController,
carNavigationBarController,
flingAnimationUtilsBuilder);
}
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
index f8fc3bb..3cd66c2 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/FullscreenUserSwitcher.java
@@ -38,6 +38,7 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.systemui.R;
import com.android.systemui.car.CarServiceProvider;
+import com.android.systemui.car.SystemUIPrimaryWindowController;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.statusbar.car.CarTrustAgentUnlockDialogHelper.OnHideListener;
import com.android.systemui.statusbar.car.UserGridRecyclerView.UserRecord;
@@ -56,9 +57,10 @@
private final UserManager mUserManager;
private final CarServiceProvider mCarServiceProvider;
private final CarTrustAgentUnlockDialogHelper mUnlockDialogHelper;
+ private final SystemUIPrimaryWindowController mSystemUIPrimaryWindowController;
+ private CarStatusBar mCarStatusBar;
private final int mShortAnimDuration;
- private CarStatusBar mStatusBar;
private View mParent;
private UserGridRecyclerView mUserGridView;
private CarTrustAgentEnrollmentManager mEnrollmentManager;
@@ -81,23 +83,35 @@
@Main Resources resources,
UserManager userManager,
CarServiceProvider carServiceProvider,
- CarTrustAgentUnlockDialogHelper carTrustAgentUnlockDialogHelper) {
+ CarTrustAgentUnlockDialogHelper carTrustAgentUnlockDialogHelper,
+ SystemUIPrimaryWindowController systemUIPrimaryWindowController) {
mContext = context;
mResources = resources;
mUserManager = userManager;
mCarServiceProvider = carServiceProvider;
mUnlockDialogHelper = carTrustAgentUnlockDialogHelper;
+ mSystemUIPrimaryWindowController = systemUIPrimaryWindowController;
mShortAnimDuration = mResources.getInteger(android.R.integer.config_shortAnimTime);
}
- /** Sets the status bar which controls the keyguard. */
+ /** Sets the status bar which gives an entry point to dismiss the keyguard. */
+ // TODO: Remove this in favor of a keyguard controller.
public void setStatusBar(CarStatusBar statusBar) {
- mStatusBar = statusBar;
+ mCarStatusBar = statusBar;
+ }
+
+ /** Returns {@code true} if the user switcher already has a parent view. */
+ public boolean isAttached() {
+ return mParent != null;
}
/** Sets the {@link ViewStub} to show the user switcher. */
public void setContainer(ViewStub containerStub) {
+ if (isAttached()) {
+ return;
+ }
+
mParent = containerStub.inflate();
View container = mParent.findViewById(R.id.container);
@@ -148,20 +162,31 @@
* Makes user grid visible.
*/
public void show() {
+ if (!isAttached()) {
+ return;
+ }
mParent.setVisibility(View.VISIBLE);
+ mSystemUIPrimaryWindowController.setWindowExpanded(true);
}
/**
* Hides the user grid.
*/
public void hide() {
+ if (!isAttached()) {
+ return;
+ }
mParent.setVisibility(View.INVISIBLE);
+ mSystemUIPrimaryWindowController.setWindowExpanded(false);
}
/**
* @return {@code true} if user grid is visible, {@code false} otherwise.
*/
public boolean isVisible() {
+ if (!isAttached()) {
+ return false;
+ }
return mParent.getVisibility() == View.VISIBLE;
}
@@ -196,7 +221,7 @@
}
if (mSelectedUser.mType == UserRecord.FOREGROUND_USER) {
hide();
- mStatusBar.dismissKeyguard();
+ mCarStatusBar.dismissKeyguard();
return;
}
// Switching is about to happen, since it takes time, fade out the switcher gradually.
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
index cdabeeb..8c756ec 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/UserGridRecyclerView.java
@@ -43,6 +43,9 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowInsets;
+import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.TextView;
@@ -53,7 +56,6 @@
import com.android.internal.util.UserIcons;
import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.SystemUIDialog;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -337,7 +339,7 @@
.setPositiveButton(android.R.string.ok, null)
.create();
// Sets window flags for the SysUI dialog
- SystemUIDialog.applyFlags(maxUsersDialog);
+ applyCarSysUIDialogFlags(maxUsersDialog);
maxUsersDialog.show();
}
@@ -356,10 +358,19 @@
.setOnCancelListener(this)
.create();
// Sets window flags for the SysUI dialog
- SystemUIDialog.applyFlags(addUserDialog);
+ applyCarSysUIDialogFlags(addUserDialog);
addUserDialog.show();
}
+ private void applyCarSysUIDialogFlags(AlertDialog dialog) {
+ final Window window = dialog.getWindow();
+ window.setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
+ window.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM
+ | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+ window.setFitWindowInsetsTypes(
+ window.getFitWindowInsetsTypes() & ~WindowInsets.Type.statusBars());
+ }
+
private void notifyUserSelected(UserRecord userRecord) {
// Notify the listener which user was selected
if (mUserSelectionListener != null) {
diff --git a/packages/CarSystemUI/tests/res/layout/car_button_selection_state_controller_test.xml b/packages/CarSystemUI/tests/res/layout/car_button_selection_state_controller_test.xml
new file mode 100644
index 0000000..f0e0216
--- /dev/null
+++ b/packages/CarSystemUI/tests/res/layout/car_button_selection_state_controller_test.xml
@@ -0,0 +1,55 @@
+<!--
+ ~ 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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:id="@id/nav_buttons"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingStart="20dp"
+ android:paddingEnd="20dp"
+ android:gravity="center">
+
+ <com.android.systemui.navigationbar.car.CarNavigationButton
+ android:id="@+id/detectable_by_component_name"
+ style="@style/NavigationBarButton"
+ systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
+ systemui:icon="@drawable/car_ic_overview"
+ systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
+ systemui:highlightWhenSelected="true"
+ />
+
+ <com.android.systemui.navigationbar.car.CarNavigationButton
+ android:id="@+id/detectable_by_category"
+ style="@style/NavigationBarButton"
+ systemui:categories="android.intent.category.APP_MAPS"
+ systemui:icon="@drawable/car_ic_navigation"
+ systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.APP_MAPS;launchFlags=0x14000000;end"
+ systemui:highlightWhenSelected="true"
+ />
+
+ <com.android.systemui.navigationbar.car.CarNavigationButton
+ android:id="@+id/detectable_by_package"
+ style="@style/NavigationBarButton"
+ systemui:icon="@drawable/car_ic_phone"
+ systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;package=com.android.car.dialer;launchFlags=0x10000000;end"
+ systemui:packages="com.android.car.dialer"
+ systemui:highlightWhenSelected="true"
+ />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/CarSystemUI/tests/res/layout/car_navigation_button_test.xml b/packages/CarSystemUI/tests/res/layout/car_navigation_button_test.xml
new file mode 100644
index 0000000..576928c
--- /dev/null
+++ b/packages/CarSystemUI/tests/res/layout/car_navigation_button_test.xml
@@ -0,0 +1,115 @@
+<!--
+ ~ 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.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:id="@id/nav_buttons"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:paddingStart="20dp"
+ android:paddingEnd="20dp"
+ android:gravity="center">
+
+ <com.android.systemui.navigationbar.car.CarNavigationButton
+ android:id="@+id/default_no_selection_state"
+ style="@style/NavigationBarButton"
+ systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
+ systemui:icon="@drawable/car_ic_overview"
+ systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
+ systemui:selectedIcon="@drawable/car_ic_overview_selected"
+ />
+
+ <com.android.systemui.navigationbar.car.CarNavigationButton
+ android:id="@+id/app_grid_activity"
+ style="@style/NavigationBarButton"
+ systemui:componentNames="com.android.car.carlauncher/.AppGridActivity"
+ systemui:icon="@drawable/car_ic_apps"
+ systemui:intent="intent:#Intent;component=com.android.car.carlauncher/.AppGridActivity;launchFlags=0x24000000;end"
+ systemui:selectedIcon="@drawable/car_ic_apps_selected"
+ systemui:highlightWhenSelected="true"
+ />
+
+ <com.android.systemui.navigationbar.car.CarNavigationButton
+ android:id="@+id/long_click_app_grid_activity"
+ style="@style/NavigationBarButton"
+ systemui:componentNames="com.android.car.carlauncher/.AppGridActivity"
+ systemui:icon="@drawable/car_ic_apps"
+ systemui:longIntent="intent:#Intent;component=com.android.car.carlauncher/.AppGridActivity;launchFlags=0x24000000;end"
+ systemui:selectedIcon="@drawable/car_ic_apps_selected"
+ systemui:highlightWhenSelected="true"
+ />
+
+ <com.android.systemui.navigationbar.car.CarNavigationButton
+ android:id="@+id/broadcast"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@null"
+ systemui:broadcast="true"
+ systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
+ />
+
+ <com.android.systemui.navigationbar.car.CarNavigationButton
+ android:id="@+id/selected_icon_undefined"
+ style="@style/NavigationBarButton"
+ systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
+ systemui:icon="@drawable/car_ic_overview"
+ systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
+ />
+
+ <com.android.systemui.navigationbar.car.CarNavigationButton
+ android:id="@+id/highlightable_no_more_button"
+ style="@style/NavigationBarButton"
+ systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
+ systemui:icon="@drawable/car_ic_overview"
+ systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
+ systemui:selectedIcon="@drawable/car_ic_overview_selected"
+ systemui:highlightWhenSelected="true"
+ />
+
+ <com.android.systemui.navigationbar.car.CarNavigationButton
+ android:id="@+id/not_highlightable_more_button"
+ style="@style/NavigationBarButton"
+ systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
+ systemui:icon="@drawable/car_ic_overview"
+ systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
+ systemui:selectedIcon="@drawable/car_ic_overview_selected"
+ systemui:showMoreWhenSelected="true"
+ />
+
+ <com.android.systemui.navigationbar.car.CarNavigationButton
+ android:id="@+id/highlightable_more_button"
+ style="@style/NavigationBarButton"
+ systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
+ systemui:icon="@drawable/car_ic_overview"
+ systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
+ systemui:selectedIcon="@drawable/car_ic_overview_selected"
+ systemui:highlightWhenSelected="true"
+ systemui:showMoreWhenSelected="true"
+ />
+
+ <com.android.systemui.navigationbar.car.CarNavigationButton
+ android:id="@+id/broadcast"
+ style="@style/NavigationBarButton"
+ systemui:componentNames="com.android.car.carlauncher/.CarLauncher"
+ systemui:icon="@drawable/car_ic_overview"
+ systemui:intent="intent:#Intent;action=android.intent.action.MAIN;category=android.intent.category.HOME;launchFlags=0x14000000;end"
+ systemui:selectedIcon="@drawable/car_ic_overview_selected"
+ systemui:broadcast="true"
+ />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/ButtonSelectionStateControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/ButtonSelectionStateControllerTest.java
new file mode 100644
index 0000000..f94dd82
--- /dev/null
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/ButtonSelectionStateControllerTest.java
@@ -0,0 +1,136 @@
+/*
+ * 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 com.android.systemui.navigationbar.car;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.LayoutInflater;
+import android.widget.LinearLayout;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.tests.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class ButtonSelectionStateControllerTest extends SysuiTestCase {
+
+ private static final String TEST_COMPONENT_NAME_PACKAGE = "com.android.car.carlauncher";
+ private static final String TEST_COMPONENT_NAME_CLASS = ".CarLauncher";
+ private static final String TEST_CATEGORY = "com.google.android.apps.maps";
+ private static final String TEST_CATEGORY_CLASS = ".APP_MAPS";
+ private static final String TEST_PACKAGE = "com.android.car.dialer";
+ private static final String TEST_PACKAGE_CLASS = ".Dialer";
+
+ // LinearLayout with CarNavigationButtons with different configurations.
+ private LinearLayout mTestView;
+ private ButtonSelectionStateController mButtonSelectionStateController;
+ private ComponentName mComponentName;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mTestView = (LinearLayout) LayoutInflater.from(mContext).inflate(
+ R.layout.car_button_selection_state_controller_test, /* root= */ null);
+ mButtonSelectionStateController = new ButtonSelectionStateController(mContext);
+ mButtonSelectionStateController.addAllButtonsWithSelectionState(mTestView);
+ }
+
+ @Test
+ public void onTaskChanged_buttonDetectableByComponentName_selectsAssociatedButton() {
+ CarNavigationButton testButton = mTestView.findViewById(R.id.detectable_by_component_name);
+ mComponentName = new ComponentName(TEST_COMPONENT_NAME_PACKAGE, TEST_COMPONENT_NAME_CLASS);
+ List<ActivityManager.StackInfo> testStack = createTestStack(mComponentName);
+ testButton.setSelected(false);
+ mButtonSelectionStateController.taskChanged(testStack, /* validDisplay= */ -1);
+
+ assertbuttonSelected(testButton);
+ }
+
+ @Test
+ public void onTaskChanged_buttonDetectableByCategory_selectsAssociatedButton() {
+ CarNavigationButton testButton = mTestView.findViewById(R.id.detectable_by_category);
+ mComponentName = new ComponentName(TEST_CATEGORY, TEST_CATEGORY_CLASS);
+ List<ActivityManager.StackInfo> testStack = createTestStack(mComponentName);
+ testButton.setSelected(false);
+ mButtonSelectionStateController.taskChanged(testStack, /* validDisplay= */ -1);
+
+ assertbuttonSelected(testButton);
+ }
+
+ @Test
+ public void onTaskChanged_buttonDetectableByPackage_selectsAssociatedButton() {
+ CarNavigationButton testButton = mTestView.findViewById(R.id.detectable_by_package);
+ mComponentName = new ComponentName(TEST_PACKAGE, TEST_PACKAGE_CLASS);
+ List<ActivityManager.StackInfo> testStack = createTestStack(mComponentName);
+ testButton.setSelected(false);
+ mButtonSelectionStateController.taskChanged(testStack, /* validDisplay= */ -1);
+
+ assertbuttonSelected(testButton);
+ }
+
+ @Test
+ public void onTaskChanged_deselectsPreviouslySelectedButton() {
+ CarNavigationButton oldButton = mTestView.findViewById(R.id.detectable_by_component_name);
+ mComponentName = new ComponentName(TEST_COMPONENT_NAME_PACKAGE, TEST_COMPONENT_NAME_CLASS);
+ List<ActivityManager.StackInfo> oldStack = createTestStack(mComponentName);
+ oldButton.setSelected(false);
+ mButtonSelectionStateController.taskChanged(oldStack, /* validDisplay= */ -1);
+
+ mComponentName = new ComponentName(TEST_PACKAGE, TEST_PACKAGE_CLASS);
+ List<ActivityManager.StackInfo> newStack = createTestStack(mComponentName);
+ mButtonSelectionStateController.taskChanged(newStack, /* validDisplay= */ -1);
+
+ assertButtonUnselected(oldButton);
+ }
+
+ // Comparing alpha is a valid way to verify button selection state because all test buttons use
+ // highlightWhenSelected = true.
+ private void assertbuttonSelected(CarNavigationButton button) {
+ assertThat(button.getAlpha()).isEqualTo(CarNavigationButton.DEFAULT_SELECTED_ALPHA);
+ }
+
+ private void assertButtonUnselected(CarNavigationButton button) {
+ assertThat(button.getAlpha()).isEqualTo(CarNavigationButton.DEFAULT_UNSELECTED_ALPHA);
+ }
+
+ private List<ActivityManager.StackInfo> createTestStack(ComponentName componentName) {
+ ActivityManager.StackInfo validStackInfo = new ActivityManager.StackInfo();
+ validStackInfo.displayId = -1; // No display is assigned to this test view
+ validStackInfo.topActivity = componentName;
+
+ List<ActivityManager.StackInfo> testStack = new ArrayList<>();
+ testStack.add(validStackInfo);
+
+ return testStack;
+ }
+}
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarControllerTest.java
index 642b114..e0c13ed 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarControllerTest.java
@@ -51,7 +51,7 @@
private TestableResources mTestableResources;
@Mock
- private CarFacetButtonController mCarFacetButtonController;
+ private ButtonSelectionStateController mButtonSelectionStateController;
@Mock
private HvacController mHvacController;
@@ -69,7 +69,7 @@
@Test
public void testConnectToHvac_callsConnect() {
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
mCarNavigationBar.connectToHvac();
@@ -79,7 +79,7 @@
@Test
public void testRemoveAllFromHvac_callsRemoveAll() {
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
mCarNavigationBar.removeAllFromHvac();
@@ -90,7 +90,7 @@
public void testGetBottomWindow_bottomDisabled_returnsNull() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, false);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
ViewGroup window = mCarNavigationBar.getBottomWindow();
@@ -101,7 +101,7 @@
public void testGetBottomWindow_bottomEnabled_returnsWindow() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
ViewGroup window = mCarNavigationBar.getBottomWindow();
@@ -112,7 +112,7 @@
public void testGetBottomWindow_bottomEnabled_calledTwice_returnsSameWindow() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
ViewGroup window1 = mCarNavigationBar.getBottomWindow();
ViewGroup window2 = mCarNavigationBar.getBottomWindow();
@@ -124,7 +124,7 @@
public void testGetLeftWindow_leftDisabled_returnsNull() {
mTestableResources.addOverride(R.bool.config_enableLeftNavigationBar, false);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
ViewGroup window = mCarNavigationBar.getLeftWindow();
assertThat(window).isNull();
}
@@ -133,7 +133,7 @@
public void testGetLeftWindow_leftEnabled_returnsWindow() {
mTestableResources.addOverride(R.bool.config_enableLeftNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
ViewGroup window = mCarNavigationBar.getLeftWindow();
@@ -144,7 +144,7 @@
public void testGetLeftWindow_leftEnabled_calledTwice_returnsSameWindow() {
mTestableResources.addOverride(R.bool.config_enableLeftNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
ViewGroup window1 = mCarNavigationBar.getLeftWindow();
ViewGroup window2 = mCarNavigationBar.getLeftWindow();
@@ -156,7 +156,7 @@
public void testGetRightWindow_rightDisabled_returnsNull() {
mTestableResources.addOverride(R.bool.config_enableRightNavigationBar, false);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
ViewGroup window = mCarNavigationBar.getRightWindow();
@@ -167,7 +167,7 @@
public void testGetRightWindow_rightEnabled_returnsWindow() {
mTestableResources.addOverride(R.bool.config_enableRightNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
ViewGroup window = mCarNavigationBar.getRightWindow();
@@ -178,7 +178,7 @@
public void testGetRightWindow_rightEnabled_calledTwice_returnsSameWindow() {
mTestableResources.addOverride(R.bool.config_enableRightNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
ViewGroup window1 = mCarNavigationBar.getRightWindow();
ViewGroup window2 = mCarNavigationBar.getRightWindow();
@@ -190,7 +190,7 @@
public void testSetBottomWindowVisibility_setTrue_isVisible() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
ViewGroup window = mCarNavigationBar.getBottomWindow();
mCarNavigationBar.setBottomWindowVisibility(View.VISIBLE);
@@ -202,7 +202,7 @@
public void testSetBottomWindowVisibility_setFalse_isGone() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
ViewGroup window = mCarNavigationBar.getBottomWindow();
mCarNavigationBar.setBottomWindowVisibility(View.GONE);
@@ -214,7 +214,7 @@
public void testSetLeftWindowVisibility_setTrue_isVisible() {
mTestableResources.addOverride(R.bool.config_enableLeftNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
ViewGroup window = mCarNavigationBar.getLeftWindow();
mCarNavigationBar.setLeftWindowVisibility(View.VISIBLE);
@@ -226,7 +226,7 @@
public void testSetLeftWindowVisibility_setFalse_isGone() {
mTestableResources.addOverride(R.bool.config_enableLeftNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
ViewGroup window = mCarNavigationBar.getLeftWindow();
mCarNavigationBar.setLeftWindowVisibility(View.GONE);
@@ -238,7 +238,7 @@
public void testSetRightWindowVisibility_setTrue_isVisible() {
mTestableResources.addOverride(R.bool.config_enableRightNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
ViewGroup window = mCarNavigationBar.getRightWindow();
mCarNavigationBar.setRightWindowVisibility(View.VISIBLE);
@@ -250,7 +250,7 @@
public void testSetRightWindowVisibility_setFalse_isGone() {
mTestableResources.addOverride(R.bool.config_enableRightNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
ViewGroup window = mCarNavigationBar.getRightWindow();
mCarNavigationBar.setRightWindowVisibility(View.GONE);
@@ -262,7 +262,7 @@
public void testRegisterBottomBarTouchListener_createViewFirst_registrationSuccessful() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
View.OnTouchListener controller = bottomBar.getStatusBarWindowTouchListener();
@@ -277,7 +277,7 @@
public void testRegisterBottomBarTouchListener_registerFirst_registrationSuccessful() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
mCarNavigationBar.registerBottomBarTouchListener(mock(View.OnTouchListener.class));
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
@@ -290,7 +290,7 @@
public void testRegisterNotificationController_createViewFirst_registrationSuccessful() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
CarNavigationBarController.NotificationsShadeController controller =
@@ -307,7 +307,7 @@
public void testRegisterNotificationController_registerFirst_registrationSuccessful() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
mCarNavigationBar.registerNotificationController(
mock(CarNavigationBarController.NotificationsShadeController.class));
@@ -322,7 +322,7 @@
public void testShowAllKeyguardButtons_bottomEnabled_bottomKeyguardButtonsVisible() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
View bottomKeyguardButtons = bottomBar.findViewById(R.id.lock_screen_nav_buttons);
@@ -335,7 +335,7 @@
public void testShowAllKeyguardButtons_bottomEnabled_bottomNavButtonsGone() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
View bottomButtons = bottomBar.findViewById(R.id.nav_buttons);
@@ -348,7 +348,7 @@
public void testHideAllKeyguardButtons_bottomEnabled_bottomKeyguardButtonsGone() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
View bottomKeyguardButtons = bottomBar.findViewById(R.id.lock_screen_nav_buttons);
@@ -363,7 +363,7 @@
public void testHideAllKeyguardButtons_bottomEnabled_bottomNavButtonsVisible() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
View bottomButtons = bottomBar.findViewById(R.id.nav_buttons);
@@ -378,7 +378,7 @@
public void testToggleAllNotificationsUnseenIndicator_bottomEnabled_hasUnseen_setCorrectly() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
CarNavigationButton notifications = bottomBar.findViewById(R.id.notifications);
@@ -393,7 +393,7 @@
public void testToggleAllNotificationsUnseenIndicator_bottomEnabled_noUnseen_setCorrectly() {
mTestableResources.addOverride(R.bool.config_enableBottomNavigationBar, true);
mCarNavigationBar = new CarNavigationBarController(mContext, mNavigationBarViewFactory,
- () -> mCarFacetButtonController, () -> mHvacController);
+ mButtonSelectionStateController, () -> mHvacController);
CarNavigationBarView bottomBar = mCarNavigationBar.getBottomBar(/* isSetUp= */ true);
CarNavigationButton notifications = bottomBar.findViewById(R.id.notifications);
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationButtonTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationButtonTest.java
new file mode 100644
index 0000000..96d567d
--- /dev/null
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationButtonTest.java
@@ -0,0 +1,239 @@
+/*
+ * 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 com.android.systemui.navigationbar.car;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.keyguard.AlphaOptimizedImageButton;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.tests.R;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+@SmallTest
+public class CarNavigationButtonTest extends SysuiTestCase {
+
+ private static final String DEFAULT_BUTTON_ACTIVITY_NAME =
+ "com.android.car.carlauncher/.CarLauncher";
+ private static final String APP_GRID_BUTTON_ACTIVITY_NAME =
+ "com.android.car.carlauncher/.AppGridActivity";
+ private static final String BROADCAST_ACTION_NAME =
+ "android.car.intent.action.TOGGLE_HVAC_CONTROLS";
+
+ private ActivityManager mActivityManager;
+ // LinearLayout with CarNavigationButtons with different configurations.
+ private LinearLayout mTestView;
+ // Does not have any selection state which is the default configuration.
+ private CarNavigationButton mDefaultButton;
+
+ @Before
+ public void setUp() {
+ mContext = spy(mContext);
+ mTestView = (LinearLayout) LayoutInflater.from(mContext).inflate(
+ R.layout.car_navigation_button_test, /* root= */ null);
+ mDefaultButton = mTestView.findViewById(R.id.default_no_selection_state);
+ mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
+ }
+
+ @Test
+ public void onCreate_iconIsVisible() {
+ AlphaOptimizedImageButton icon = mDefaultButton.findViewById(
+ R.id.car_nav_button_icon_image);
+
+ assertThat(icon.getDrawable()).isNotNull();
+ }
+
+ @Test
+ public void onSelected_selectedIconDefined_togglesIcon() {
+ mDefaultButton.setSelected(true);
+ Drawable selectedIconDrawable = ((AlphaOptimizedImageButton) mDefaultButton.findViewById(
+ R.id.car_nav_button_icon_image)).getDrawable();
+
+
+ mDefaultButton.setSelected(false);
+ Drawable unselectedIconDrawable = ((AlphaOptimizedImageButton) mDefaultButton.findViewById(
+ R.id.car_nav_button_icon_image)).getDrawable();
+
+ assertThat(selectedIconDrawable).isNotEqualTo(unselectedIconDrawable);
+ }
+
+ @Test
+ public void onSelected_selectedIconUndefined_displaysSameIcon() {
+ CarNavigationButton selectedIconUndefinedButton = mTestView.findViewById(
+ R.id.selected_icon_undefined);
+
+ selectedIconUndefinedButton.setSelected(true);
+ Drawable selectedIconDrawable = ((AlphaOptimizedImageButton) mDefaultButton.findViewById(
+ R.id.car_nav_button_icon_image)).getDrawable();
+
+
+ selectedIconUndefinedButton.setSelected(false);
+ Drawable unselectedIconDrawable = ((AlphaOptimizedImageButton) mDefaultButton.findViewById(
+ R.id.car_nav_button_icon_image)).getDrawable();
+
+ assertThat(selectedIconDrawable).isEqualTo(unselectedIconDrawable);
+ }
+
+ @Test
+ public void onUnselected_doesNotHighlightWhenSelected_applySelectedAlpha() {
+ mDefaultButton.setSelected(false);
+
+ assertThat(mDefaultButton.getAlpha()).isEqualTo(
+ CarNavigationButton.DEFAULT_SELECTED_ALPHA);
+ }
+
+ @Test
+ public void onSelected_doesNotHighlightWhenSelected_applySelectedAlpha() {
+ mDefaultButton.setSelected(true);
+
+ assertThat(mDefaultButton.getAlpha()).isEqualTo(
+ CarNavigationButton.DEFAULT_SELECTED_ALPHA);
+ }
+
+ @Test
+ public void onUnselected_highlightWhenSelected_applyDefaultUnselectedAlpha() {
+ CarNavigationButton highlightWhenSelectedButton = mTestView.findViewById(
+ R.id.highlightable_no_more_button);
+ highlightWhenSelectedButton.setSelected(false);
+
+ assertThat(highlightWhenSelectedButton.getAlpha()).isEqualTo(
+ CarNavigationButton.DEFAULT_UNSELECTED_ALPHA);
+ }
+
+ @Test
+ public void onSelected_highlightWhenSelected_applyDefaultSelectedAlpha() {
+ CarNavigationButton highlightWhenSelectedButton = mTestView.findViewById(
+ R.id.highlightable_no_more_button);
+ highlightWhenSelectedButton.setSelected(true);
+
+ assertThat(highlightWhenSelectedButton.getAlpha()).isEqualTo(
+ CarNavigationButton.DEFAULT_SELECTED_ALPHA);
+ }
+
+ @Test
+ public void onSelected_doesNotShowMoreWhenSelected_doesNotShowMoreIcon() {
+ mDefaultButton.setSelected(true);
+ AlphaOptimizedImageButton moreIcon = mDefaultButton.findViewById(
+ R.id.car_nav_button_more_icon);
+
+ assertThat(moreIcon.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void onSelected_showMoreWhenSelected_showsMoreIcon() {
+ CarNavigationButton showMoreWhenSelected = mTestView.findViewById(
+ R.id.not_highlightable_more_button);
+ showMoreWhenSelected.setSelected(true);
+ AlphaOptimizedImageButton moreIcon = showMoreWhenSelected.findViewById(
+ R.id.car_nav_button_more_icon);
+
+ assertThat(moreIcon.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void onUnselected_showMoreWhenSelected_doesNotShowMoreIcon() {
+ CarNavigationButton showMoreWhenSelected = mTestView.findViewById(
+ R.id.highlightable_no_more_button);
+ showMoreWhenSelected.setSelected(true);
+ showMoreWhenSelected.setSelected(false);
+ AlphaOptimizedImageButton moreIcon = showMoreWhenSelected.findViewById(
+ R.id.car_nav_button_more_icon);
+
+ assertThat(moreIcon.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void onClick_launchesIntentActivity() {
+ mDefaultButton.performClick();
+
+ assertThat(getCurrentActivityName()).isEqualTo(DEFAULT_BUTTON_ACTIVITY_NAME);
+
+ CarNavigationButton appGridButton = mTestView.findViewById(R.id.app_grid_activity);
+ appGridButton.performClick();
+
+ assertThat(getCurrentActivityName()).isEqualTo(APP_GRID_BUTTON_ACTIVITY_NAME);
+ }
+
+ @Test
+ public void onLongClick_longIntentDefined_launchesLongIntentActivity() {
+ mDefaultButton.performClick();
+
+ assertThat(getCurrentActivityName()).isEqualTo(DEFAULT_BUTTON_ACTIVITY_NAME);
+
+ CarNavigationButton appGridButton = mTestView.findViewById(
+ R.id.long_click_app_grid_activity);
+ appGridButton.performLongClick();
+
+ assertThat(getCurrentActivityName()).isEqualTo(APP_GRID_BUTTON_ACTIVITY_NAME);
+ }
+
+ @Test
+ public void onClick_useBroadcast_broadcastsIntent() {
+ CarNavigationButton appGridButton = mTestView.findViewById(R.id.broadcast);
+ appGridButton.performClick();
+
+ verify(mContext).sendBroadcastAsUser(argThat(new ArgumentMatcher<Intent>() {
+ @Override
+ public boolean matches(Intent argument) {
+ return argument.getAction().equals(BROADCAST_ACTION_NAME);
+ }
+ }), any());
+ }
+
+ @Test
+ public void onSetUnseen_hasUnseen_showsUnseenIndicator() {
+ mDefaultButton.setUnseen(true);
+ ImageView hasUnseenIndicator = mDefaultButton.findViewById(R.id.car_nav_button_unseen_icon);
+
+ assertThat(hasUnseenIndicator.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void onSetUnseen_doesNotHaveUnseen_hidesUnseenIndicator() {
+ mDefaultButton.setUnseen(false);
+ ImageView hasUnseenIndicator = mDefaultButton.findViewById(R.id.car_nav_button_unseen_icon);
+
+ assertThat(hasUnseenIndicator.getVisibility()).isEqualTo(View.GONE);
+ }
+
+ private String getCurrentActivityName() {
+ return mActivityManager.getRunningTasks(1).get(0).topActivity.flattenToShortString();
+ }
+}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
index 642dc82..5054281 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CaptivePortalLoginActivity.java
@@ -36,6 +36,7 @@
import android.os.Bundle;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
@@ -48,8 +49,6 @@
import android.widget.ProgressBar;
import android.widget.TextView;
-import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.TrafficStatsConstants;
@@ -203,11 +202,11 @@
}
private URL getUrlForCaptivePortal() {
- String url = getIntent().getStringExtra(TelephonyIntents.EXTRA_REDIRECTION_URL_KEY);
+ String url = getIntent().getStringExtra(TelephonyManager.EXTRA_REDIRECTION_URL);
if (TextUtils.isEmpty(url)) url = mCm.getCaptivePortalServerUrl();
final CarrierConfigManager configManager = getApplicationContext()
.getSystemService(CarrierConfigManager.class);
- final int subId = getIntent().getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+ final int subId = getIntent().getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.getDefaultVoiceSubscriptionId());
final String[] portalURLs = configManager.getConfigForSubId(subId).getStringArray(
CarrierConfigManager.KEY_CARRIER_DEFAULT_REDIRECTION_URL_STRING_ARRAY);
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
index 2697a10..cb062a6 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CarrierActionUtils.java
@@ -30,8 +30,6 @@
import android.text.TextUtils;
import android.util.Log;
-import com.android.internal.telephony.PhoneConstants;
-
/**
* This util class provides common logic for carrier actions
*/
@@ -103,7 +101,7 @@
}
private static void onDisableAllMeteredApns(Intent intent, Context context) {
- int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+ int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.getDefaultVoiceSubscriptionId());
logd("onDisableAllMeteredApns subId: " + subId);
final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
@@ -111,7 +109,7 @@
}
private static void onEnableAllMeteredApns(Intent intent, Context context) {
- int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+ int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.getDefaultVoiceSubscriptionId());
logd("onEnableAllMeteredApns subId: " + subId);
final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
@@ -135,7 +133,7 @@
}
private static void onRegisterDefaultNetworkAvail(Intent intent, Context context) {
- int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+ int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.getDefaultVoiceSubscriptionId());
logd("onRegisterDefaultNetworkAvail subId: " + subId);
final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
@@ -143,7 +141,7 @@
}
private static void onDeregisterDefaultNetworkAvail(Intent intent, Context context) {
- int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+ int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.getDefaultVoiceSubscriptionId());
logd("onDeregisterDefaultNetworkAvail subId: " + subId);
final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
@@ -151,7 +149,7 @@
}
private static void onDisableRadio(Intent intent, Context context) {
- int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+ int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.getDefaultVoiceSubscriptionId());
logd("onDisableRadio subId: " + subId);
final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
@@ -159,7 +157,7 @@
}
private static void onEnableRadio(Intent intent, Context context) {
- int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+ int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.getDefaultVoiceSubscriptionId());
logd("onEnableRadio subId: " + subId);
final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
@@ -202,7 +200,7 @@
}
private static void onResetAllCarrierActions(Intent intent, Context context) {
- int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+ int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.getDefaultVoiceSubscriptionId());
logd("onResetAllCarrierActions subId: " + subId);
final TelephonyManager telephonyMgr = context.getSystemService(TelephonyManager.class);
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java
index 46b1d5f..c7f5e9a 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/CustomConfigLoader.java
@@ -19,10 +19,10 @@
import android.content.Intent;
import android.os.PersistableBundle;
import android.telephony.CarrierConfigManager;
+import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
-import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.util.ArrayUtils;
import java.util.ArrayList;
@@ -50,7 +50,7 @@
* @param intent passing signal for config match
* @return a list of carrier action for the given signal based on the carrier config.
*
- * Example: input intent TelephonyIntent.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED
+ * Example: input intent TelephonyManager.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED
* This intent allows fined-grained matching based on both intent type & extra values:
* apnType and errorCode.
* apnType read from passing intent is "default" and errorCode is 0x26 for example and
@@ -78,25 +78,25 @@
String arg1 = null;
String arg2 = null;
switch (intent.getAction()) {
- case TelephonyIntents.ACTION_CARRIER_SIGNAL_REDIRECTED:
+ case TelephonyManager.ACTION_CARRIER_SIGNAL_REDIRECTED:
configs = b.getStringArray(CarrierConfigManager
.KEY_CARRIER_DEFAULT_ACTIONS_ON_REDIRECTION_STRING_ARRAY);
break;
- case TelephonyIntents.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED:
+ case TelephonyManager.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED:
configs = b.getStringArray(CarrierConfigManager
.KEY_CARRIER_DEFAULT_ACTIONS_ON_DCFAILURE_STRING_ARRAY);
- arg1 = intent.getStringExtra(TelephonyIntents.EXTRA_APN_TYPE_KEY);
- arg2 = intent.getStringExtra(TelephonyIntents.EXTRA_ERROR_CODE_KEY);
+ arg1 = intent.getStringExtra(TelephonyManager.EXTRA_APN_TYPE);
+ arg2 = intent.getStringExtra(TelephonyManager.EXTRA_ERROR_CODE);
break;
- case TelephonyIntents.ACTION_CARRIER_SIGNAL_RESET:
+ case TelephonyManager.ACTION_CARRIER_SIGNAL_RESET:
configs = b.getStringArray(CarrierConfigManager
.KEY_CARRIER_DEFAULT_ACTIONS_ON_RESET);
break;
- case TelephonyIntents.ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE:
+ case TelephonyManager.ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE:
configs = b.getStringArray(CarrierConfigManager
.KEY_CARRIER_DEFAULT_ACTIONS_ON_DEFAULT_NETWORK_AVAILABLE);
- arg1 = String.valueOf(intent.getBooleanExtra(TelephonyIntents
- .EXTRA_DEFAULT_NETWORK_AVAILABLE_KEY, false));
+ arg1 = String.valueOf(intent.getBooleanExtra(TelephonyManager
+ .EXTRA_DEFAULT_NETWORK_AVAILABLE, false));
break;
default:
Log.e(TAG, "load carrier config failure with un-configured key: "
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java
index 3e34f0a..78a02d7 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/ProvisionObserver.java
@@ -27,10 +27,9 @@
import android.net.Network;
import android.net.NetworkCapabilities;
import android.provider.Settings;
+import android.telephony.TelephonyManager;
import android.util.Log;
-import com.android.internal.telephony.TelephonyIntents;
-
/**
* Service to run {@link android.app.job.JobScheduler} job.
* Service to monitor when there is a change to conent URI
@@ -93,7 +92,7 @@
}
int jobId;
switch(intent.getAction()) {
- case TelephonyIntents.ACTION_CARRIER_SIGNAL_REDIRECTED:
+ case TelephonyManager.ACTION_CARRIER_SIGNAL_REDIRECTED:
jobId = PROVISION_OBSERVER_REEVALUATION_JOB_ID;
break;
default:
diff --git a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java
index 1928ad9..6229434 100644
--- a/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java
+++ b/packages/CarrierDefaultApp/tests/unit/src/com/android/carrierdefaultapp/CarrierDefaultReceiverTest.java
@@ -15,18 +15,22 @@
*/
package com.android.carrierdefaultapp;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.os.PersistableBundle;
import android.telephony.CarrierConfigManager;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.test.InstrumentationTestCase;
-import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.telephony.TelephonyIntents;
-
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -34,10 +38,6 @@
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
public class CarrierDefaultReceiverTest extends InstrumentationTestCase {
@Mock
@@ -69,6 +69,7 @@
mContext.injectSystemService(NotificationManager.class, mNotificationMgr);
mContext.injectSystemService(TelephonyManager.class, mTelephonyMgr);
mContext.injectSystemService(CarrierConfigManager.class, mCarrierConfigMgr);
+ doReturn(mTelephonyMgr).when(mTelephonyMgr).createForSubscriptionId(anyInt());
mReceiver = new CarrierDefaultBroadcastReceiver();
}
@@ -87,8 +88,8 @@
.KEY_CARRIER_DEFAULT_ACTIONS_ON_REDIRECTION_STRING_ARRAY, new String[]{"4,1"});
doReturn(b).when(mCarrierConfigMgr).getConfig();
- Intent intent = new Intent(TelephonyIntents.ACTION_CARRIER_SIGNAL_REDIRECTED);
- intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
+ Intent intent = new Intent(TelephonyManager.ACTION_CARRIER_SIGNAL_REDIRECTED);
+ intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
mReceiver.onReceive(mContext, intent);
mContext.waitForMs(100);
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
index 9e49826..9ccb837 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
@@ -56,6 +56,7 @@
import android.os.RemoteException;
import android.os.image.DynamicSystemClient;
import android.os.image.DynamicSystemManager;
+import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
@@ -74,6 +75,8 @@
// TODO (b/131866826): This is currently for test only. Will move this to System API.
static final String KEY_ENABLE_WHEN_COMPLETED = "KEY_ENABLE_WHEN_COMPLETED";
+ static final String KEY_DSU_SLOT = "KEY_DSU_SLOT";
+ static final String DEFAULT_DSU_SLOT = "dsu";
/*
* Intent actions
@@ -244,10 +247,15 @@
long systemSize = intent.getLongExtra(DynamicSystemClient.KEY_SYSTEM_SIZE, 0);
long userdataSize = intent.getLongExtra(DynamicSystemClient.KEY_USERDATA_SIZE, 0);
mEnableWhenCompleted = intent.getBooleanExtra(KEY_ENABLE_WHEN_COMPLETED, false);
+ String dsuSlot = intent.getStringExtra(KEY_DSU_SLOT);
+ if (TextUtils.isEmpty(dsuSlot)) {
+ dsuSlot = DEFAULT_DSU_SLOT;
+ }
// TODO: better constructor or builder
- mInstallTask = new InstallationAsyncTask(
- url, systemSize, userdataSize, this, mDynSystem, this);
+ mInstallTask =
+ new InstallationAsyncTask(
+ url, dsuSlot, systemSize, userdataSize, this, mDynSystem, this);
mInstallTask.execute();
@@ -262,10 +270,11 @@
return;
}
+ stopForeground(true);
mJustCancelledByUser = true;
if (mInstallTask.cancel(false)) {
- // Will cleanup and post status in onResult()
+ // Will stopSelf() in onResult()
Log.d(TAG, "Cancel request filed successfully");
} else {
Log.e(TAG, "Trying to cancel installation while it's already completed.");
@@ -408,7 +417,9 @@
break;
case STATUS_READY:
- builder.setContentText(getString(R.string.notification_install_completed));
+ String msgCompleted = getString(R.string.notification_install_completed);
+ builder.setContentText(msgCompleted)
+ .setStyle(new Notification.BigTextStyle().bigText(msgCompleted));
builder.addAction(new Notification.Action.Builder(
null, getString(R.string.notification_action_discard),
@@ -421,7 +432,9 @@
break;
case STATUS_IN_USE:
- builder.setContentText(getString(R.string.notification_dynsystem_in_use));
+ String msgInUse = getString(R.string.notification_dynsystem_in_use);
+ builder.setContentText(msgInUse)
+ .setStyle(new Notification.BigTextStyle().bigText(msgInUse));
builder.addAction(new Notification.Action.Builder(
null, getString(R.string.notification_action_uninstall),
@@ -451,7 +464,49 @@
}
private void postStatus(int status, int cause, Throwable detail) {
- Log.d(TAG, "postStatus(): statusCode=" + status + ", causeCode=" + cause);
+ String statusString;
+ String causeString;
+
+ switch (status) {
+ case STATUS_NOT_STARTED:
+ statusString = "NOT_STARTED";
+ break;
+ case STATUS_IN_PROGRESS:
+ statusString = "IN_PROGRESS";
+ break;
+ case STATUS_READY:
+ statusString = "READY";
+ break;
+ case STATUS_IN_USE:
+ statusString = "IN_USE";
+ break;
+ default:
+ statusString = "UNKNOWN";
+ break;
+ }
+
+ switch (cause) {
+ case CAUSE_INSTALL_COMPLETED:
+ causeString = "INSTALL_COMPLETED";
+ break;
+ case CAUSE_INSTALL_CANCELLED:
+ causeString = "INSTALL_CANCELLED";
+ break;
+ case CAUSE_ERROR_IO:
+ causeString = "ERROR_IO";
+ break;
+ case CAUSE_ERROR_INVALID_URL:
+ causeString = "ERROR_INVALID_URL";
+ break;
+ case CAUSE_ERROR_EXCEPTION:
+ causeString = "ERROR_EXCEPTION";
+ break;
+ default:
+ causeString = "CAUSE_NOT_SPECIFIED";
+ break;
+ }
+
+ Log.d(TAG, "status=" + statusString + ", cause=" + causeString);
boolean notifyOnNotificationBar = true;
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
index b206a1f..9aea0e7 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
@@ -89,10 +89,12 @@
interface ProgressListener {
void onProgressUpdate(Progress progress);
+
void onResult(int resultCode, Throwable detail);
}
private final String mUrl;
+ private final String mDsuSlot;
private final long mSystemSize;
private final long mUserdataSize;
private final Context mContext;
@@ -106,9 +108,16 @@
private InputStream mStream;
private ZipFile mZipFile;
- InstallationAsyncTask(String url, long systemSize, long userdataSize, Context context,
- DynamicSystemManager dynSystem, ProgressListener listener) {
+ InstallationAsyncTask(
+ String url,
+ String dsuSlot,
+ long systemSize,
+ long userdataSize,
+ Context context,
+ DynamicSystemManager dynSystem,
+ ProgressListener listener) {
mUrl = url;
+ mDsuSlot = dsuSlot;
mSystemSize = systemSize;
mUserdataSize = userdataSize;
mContext = context;
@@ -126,14 +135,17 @@
verifyAndPrepare();
- mDynSystem.startInstallation();
+ mDynSystem.startInstallation(mDsuSlot);
installUserdata();
if (isCancelled()) {
mDynSystem.remove();
return null;
}
-
+ if (mUrl == null) {
+ mDynSystem.finishInstallation();
+ return null;
+ }
installImages();
if (isCancelled()) {
mDynSystem.remove();
@@ -194,6 +206,9 @@
}
private void verifyAndPrepare() throws Exception {
+ if (mUrl == null) {
+ return;
+ }
String extension = mUrl.substring(mUrl.lastIndexOf('.') + 1);
if ("gz".equals(extension) || "gzip".equals(extension)) {
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java
index 3b3933b..e42ded7 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java
@@ -28,11 +28,9 @@
import android.util.FeatureFlagUtils;
import android.util.Log;
-
/**
- * This Activity starts KeyguardManager and ask the user to confirm
- * before any installation request. If the device is not protected by
- * a password, it approves the request by default.
+ * This Activity starts KeyguardManager and ask the user to confirm before any installation request.
+ * If the device is not protected by a password, it approves the request by default.
*/
public class VerificationActivity extends Activity {
@@ -88,11 +86,15 @@
Uri url = callingIntent.getData();
Bundle extras = callingIntent.getExtras();
- sVerifiedUrl = url.toString();
+ if (url != null) {
+ sVerifiedUrl = url.toString();
+ }
// start service
Intent intent = new Intent(this, DynamicSystemInstallationService.class);
- intent.setData(url);
+ if (url != null) {
+ intent.setData(url);
+ }
intent.setAction(DynamicSystemClient.ACTION_START_INSTALL);
intent.putExtras(extras);
@@ -106,6 +108,7 @@
}
static boolean isVerified(String url) {
+ if (url == null) return true;
return sVerifiedUrl != null && sVerifiedUrl.equals(url);
}
}
diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
index 1e19786..ec445d4 100644
--- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
+++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java
@@ -160,7 +160,7 @@
final int userId = UserHandle.myUserId();
final List<VolumeInfo> volumes = mStorageManager.getVolumes();
for (VolumeInfo volume : volumes) {
- if (!volume.isMountedReadable()) continue;
+ if (!volume.isMountedReadable() || volume.getMountUserId() != userId) continue;
final String rootId;
final String title;
@@ -192,9 +192,8 @@
title = mStorageManager.getBestVolumeDescription(privateVol);
storageUuid = StorageManager.convert(privateVol.fsUuid);
}
- } else if ((volume.getType() == VolumeInfo.TYPE_PUBLIC
- || volume.getType() == VolumeInfo.TYPE_STUB)
- && volume.getMountUserId() == userId) {
+ } else if (volume.getType() == VolumeInfo.TYPE_PUBLIC
+ || volume.getType() == VolumeInfo.TYPE_STUB) {
rootId = volume.getFsUuid();
title = mStorageManager.getBestVolumeDescription(volume);
storageUuid = null;
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 3f99d10..5b84b10 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -203,9 +203,9 @@
<string name="vpn_settings_not_available" msgid="2894137119965668920">"Erabiltzaile honek ezin ditu VPN ezarpenak atzitu"</string>
<string name="tethering_settings_not_available" msgid="266821736434699780">"Erabiltzaile honek ezin ditu konexioa partekatzeko ezarpenak atzitu"</string>
<string name="apn_settings_not_available" msgid="1147111671403342300">"Erabiltzaile honek ezin ditu APN ezarpenak atzitu"</string>
- <string name="enable_adb" msgid="8072776357237289039">"USB arazketa"</string>
+ <string name="enable_adb" msgid="8072776357237289039">"USB bidezko arazketa"</string>
<string name="enable_adb_summary" msgid="3711526030096574316">"Gaitu arazketa modua USBa konektatzean"</string>
- <string name="clear_adb_keys" msgid="3010148733140369917">"Baliogabetu USB arazketarako baimenak"</string>
+ <string name="clear_adb_keys" msgid="3010148733140369917">"Baliogabetu USB bidezko arazketarako baimenak"</string>
<string name="bugreport_in_power" msgid="8664089072534638709">"Akatsen txostenerako lasterbidea"</string>
<string name="bugreport_in_power_summary" msgid="1885529649381831775">"Bateriaren menuan, erakutsi akatsen txostena sortzeko botoia"</string>
<string name="keep_screen_on" msgid="1187161672348797558">"Mantendu aktibo"</string>
@@ -267,9 +267,9 @@
<string name="debug_view_attributes" msgid="3539609843984208216">"Gaitu ikuspegiaren atributuak ikuskatzeko aukera"</string>
<string name="mobile_data_always_on_summary" msgid="1112156365594371019">"Mantendu datu-konexioa beti aktibo, baita wifi-konexioa aktibo dagoenean ere (sare batetik bestera bizkor aldatu ahal izateko)."</string>
<string name="tethering_hardware_offload_summary" msgid="7801345335142803029">"Erabilgarri badago, erabili konexioa partekatzeko hardwarearen azelerazioa"</string>
- <string name="adb_warning_title" msgid="7708653449506485728">"USB arazketa onartu?"</string>
- <string name="adb_warning_message" msgid="8145270656419669221">"USB arazketa garapen-xedeetarako soilik dago diseinatuta. Erabil ezazu ordenagailuaren eta gailuaren artean datuak kopiatzeko, aplikazioak gailuan jakinarazi gabe instalatzeko eta erregistro-datuak irakurtzeko."</string>
- <string name="adb_keys_warning_message" msgid="2968555274488101220">"Aurretik baimendutako ordenagailu guztiei USB arazketarako sarbidea baliogabetu nahi diezu?"</string>
+ <string name="adb_warning_title" msgid="7708653449506485728">"USB bidezko arazketa onartu?"</string>
+ <string name="adb_warning_message" msgid="8145270656419669221">"USB bidezko arazketa garapen-xedeetarako soilik dago diseinatuta. Erabil ezazu ordenagailuaren eta gailuaren artean datuak kopiatzeko, aplikazioak gailuan jakinarazi gabe instalatzeko eta erregistro-datuak irakurtzeko."</string>
+ <string name="adb_keys_warning_message" msgid="2968555274488101220">"Aurretik baimendutako ordenagailu guztiei USB bidezko arazketarako sarbidea baliogabetu nahi diezu?"</string>
<string name="dev_settings_warning_title" msgid="8251234890169074553">"Baimendu garapenerako ezarpenak?"</string>
<string name="dev_settings_warning_message" msgid="37741686486073668">"Ezarpen hauek garapen-xedeetarako pentsatu dira soilik. Baliteke ezarpenen eraginez gailua matxuratzea edo funtzionamendu okerra izatea."</string>
<string name="verify_apps_over_usb_title" msgid="6031809675604442636">"Egiaztatu USBko aplikazioak"</string>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index f9ac3a5..d0307ec 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -276,7 +276,7 @@
<string name="verify_apps_over_usb_summary" msgid="1317933737581167839">"नुकसानदेह व्यवहार के लिए ADB/ADT से इंस्टॉल किए गए ऐप्लिकेशन जाँचें."</string>
<string name="bluetooth_show_devices_without_names_summary" msgid="780964354377854507">"बिना नाम वाले ब्लूटूथ डिवाइस (केवल MAC पते वाले) दिखाए जाएंगे"</string>
<string name="bluetooth_disable_absolute_volume_summary" msgid="2006309932135547681">"दूर के डिवाइस पर आवाज़ बहुत बढ़ जाने या उससे नियंत्रण हटने जैसी समस्याएं होने पर, यह ब्लूटूथ के ज़रिए आवाज़ के नियंत्रण की सुविधा रोक देता है."</string>
- <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"Bluetooth Gabeldorsche सुविधा का स्टैक चालू करें."</string>
+ <string name="bluetooth_enable_gabeldorsche_summary" msgid="8472344901097607030">"ब्लूटूथ Gabeldorsche सुविधा का स्टैक चालू करें."</string>
<string name="enable_terminal_title" msgid="3834790541986303654">"स्थानीय टर्मिनल"</string>
<string name="enable_terminal_summary" msgid="2481074834856064500">"लोकल शेल तक पहुंचने की सुविधा देने वाले टर्मिनल ऐप को चालू करें"</string>
<string name="hdcp_checking_title" msgid="3155692785074095986">"एचडीसीपी जाँच"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java
index 69bd0ed..ff00fb3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/DeviceInfoUtils.java
@@ -16,8 +16,6 @@
package com.android.settingslib;
-import static android.content.Context.TELEPHONY_SERVICE;
-
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -172,36 +170,38 @@
}
}
- public static String getFormattedPhoneNumber(Context context, SubscriptionInfo subscriptionInfo) {
+ /**
+ * Format a phone number.
+ * @param subscriptionInfo {@link SubscriptionInfo} subscription information.
+ * @return Returns formatted phone number.
+ */
+ public static String getFormattedPhoneNumber(Context context,
+ SubscriptionInfo subscriptionInfo) {
String formattedNumber = null;
if (subscriptionInfo != null) {
- final TelephonyManager telephonyManager =
- (TelephonyManager) context.getSystemService(TELEPHONY_SERVICE);
- final String rawNumber =
- telephonyManager.getLine1Number(subscriptionInfo.getSubscriptionId());
+ final TelephonyManager telephonyManager = context.getSystemService(
+ TelephonyManager.class);
+ final String rawNumber = telephonyManager.createForSubscriptionId(
+ subscriptionInfo.getSubscriptionId()).getLine1Number();
if (!TextUtils.isEmpty(rawNumber)) {
formattedNumber = PhoneNumberUtils.formatNumber(rawNumber);
}
-
}
return formattedNumber;
}
public static String getFormattedPhoneNumbers(Context context,
- List<SubscriptionInfo> subscriptionInfo) {
+ List<SubscriptionInfo> subscriptionInfoList) {
StringBuilder sb = new StringBuilder();
- if (subscriptionInfo != null) {
- final TelephonyManager telephonyManager =
- (TelephonyManager) context.getSystemService(TELEPHONY_SERVICE);
- final int count = subscriptionInfo.size();
- for (int i = 0; i < count; i++) {
- final String rawNumber = telephonyManager.getLine1Number(
- subscriptionInfo.get(i).getSubscriptionId());
+ if (subscriptionInfoList != null) {
+ final TelephonyManager telephonyManager = context.getSystemService(
+ TelephonyManager.class);
+ final int count = subscriptionInfoList.size();
+ for (SubscriptionInfo subscriptionInfo : subscriptionInfoList) {
+ final String rawNumber = telephonyManager.createForSubscriptionId(
+ subscriptionInfo.getSubscriptionId()).getLine1Number();
if (!TextUtils.isEmpty(rawNumber)) {
- sb.append(PhoneNumberUtils.formatNumber(rawNumber));
- if (i < count - 1) {
- sb.append("\n");
- }
+ sb.append(PhoneNumberUtils.formatNumber(rawNumber)).append("\n");
}
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
index a2bd210..a784e04 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
@@ -16,6 +16,9 @@
package com.android.settingslib.bluetooth;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
import android.bluetooth.BluetoothA2dp;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
@@ -148,17 +151,18 @@
}
public boolean connect(BluetoothDevice device) {
- if (mService == null) return false;
- return mService.connect(device);
+ if (mService == null) {
+ return false;
+ }
+ return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
}
public boolean disconnect(BluetoothDevice device) {
- if (mService == null) return false;
- // Downgrade priority as user is disconnecting the headset.
- if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
- mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ if (mService == null) {
+ return false;
}
- return mService.disconnect(device);
+
+ return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
}
public int getConnectionStatus(BluetoothDevice device) {
@@ -182,12 +186,12 @@
if (mService == null) {
return false;
}
- return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
}
public int getPreferred(BluetoothDevice device) {
if (mService == null) {
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ return CONNECTION_POLICY_FORBIDDEN;
}
return mService.getConnectionPolicy(device);
}
@@ -197,11 +201,11 @@
return;
}
if (preferred) {
- if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
- mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+ mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
}
} else {
- mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+ mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
}
}
boolean isA2dpPlaying() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
index bc03c34..8ca5a74 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
@@ -16,6 +16,9 @@
package com.android.settingslib.bluetooth;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
import android.bluetooth.BluetoothA2dpSink;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
@@ -116,18 +119,15 @@
if (mService == null) {
return false;
}
- return mService.connect(device);
+ return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
}
public boolean disconnect(BluetoothDevice device) {
if (mService == null) {
return false;
}
- // Downgrade priority as user is disconnecting the headset.
- if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
- mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
- }
- return mService.disconnect(device);
+
+ return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
}
public int getConnectionStatus(BluetoothDevice device) {
@@ -141,12 +141,12 @@
if (mService == null) {
return false;
}
- return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
}
public int getPreferred(BluetoothDevice device) {
if (mService == null) {
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ return CONNECTION_POLICY_FORBIDDEN;
}
return mService.getConnectionPolicy(device);
}
@@ -156,11 +156,11 @@
return;
}
if (preferred) {
- if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
- mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+ mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
}
} else {
- mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+ mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 747ceb1..50d3a5d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -262,13 +262,28 @@
}
}
+ /**
+ * Connect this device.
+ *
+ * @param connectAllProfiles {@code true} to connect all profile, {@code false} otherwise.
+ *
+ * @deprecated use {@link #connect()} instead.
+ */
+ @Deprecated
public void connect(boolean connectAllProfiles) {
+ connect();
+ }
+
+ /**
+ * Connect this device.
+ */
+ public void connect() {
if (!ensurePaired()) {
return;
}
mConnectAttempted = SystemClock.elapsedRealtime();
- connectWithoutResettingTimer(connectAllProfiles);
+ connectAllEnabledProfiles();
}
public long getHiSyncId() {
@@ -289,10 +304,10 @@
void onBondingDockConnect() {
// Attempt to connect if UUIDs are available. Otherwise,
// we will connect when the ACTION_UUID intent arrives.
- connect(false);
+ connect();
}
- private void connectWithoutResettingTimer(boolean connectAllProfiles) {
+ private void connectAllEnabledProfiles() {
synchronized (mProfileLock) {
// Try to initialize the profiles if they were not.
if (mProfiles.isEmpty()) {
@@ -307,36 +322,7 @@
return;
}
- int preferredProfiles = 0;
- for (LocalBluetoothProfile profile : mProfiles) {
- if (connectAllProfiles ? profile.accessProfileEnabled()
- : profile.isAutoConnectable()) {
- if (profile.isPreferred(mDevice)) {
- ++preferredProfiles;
- connectInt(profile);
- }
- }
- }
- if (BluetoothUtils.D) Log.d(TAG, "Preferred profiles = " + preferredProfiles);
-
- if (preferredProfiles == 0) {
- connectAutoConnectableProfiles();
- }
- }
- }
-
- private void connectAutoConnectableProfiles() {
- if (!ensurePaired()) {
- return;
- }
-
- synchronized (mProfileLock) {
- for (LocalBluetoothProfile profile : mProfiles) {
- if (profile.isAutoConnectable()) {
- profile.setPreferred(mDevice, true);
- connectInt(profile);
- }
- }
+ mLocalAdapter.connectAllEnabledProfiles(mDevice);
}
}
@@ -703,7 +689,7 @@
*/
if (!mProfiles.isEmpty()
&& ((mConnectAttempted + timeout) > SystemClock.elapsedRealtime())) {
- connectWithoutResettingTimer(false);
+ connectAllEnabledProfiles();
}
dispatchAttributesChanged();
@@ -722,7 +708,7 @@
refresh();
if (bondState == BluetoothDevice.BOND_BONDED && mDevice.isBondingInitiatedLocally()) {
- connect(false);
+ connect();
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
index 560cb3b..d65b5da 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
@@ -16,6 +16,9 @@
package com.android.settingslib.bluetooth;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
@@ -112,18 +115,15 @@
if (mService == null) {
return false;
}
- return mService.connect(device);
+ return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
}
public boolean disconnect(BluetoothDevice device) {
if (mService == null) {
return false;
}
- // Downgrade priority as user is disconnecting the headset.
- if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
- mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
- }
- return mService.disconnect(device);
+
+ return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
}
public int getConnectionStatus(BluetoothDevice device) {
@@ -165,12 +165,12 @@
if (mService == null) {
return false;
}
- return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
}
public int getPreferred(BluetoothDevice device) {
if (mService == null) {
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ return CONNECTION_POLICY_FORBIDDEN;
}
return mService.getConnectionPolicy(device);
}
@@ -180,11 +180,11 @@
return;
}
if (preferred) {
- if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
- mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+ mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
}
} else {
- mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+ mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
index b4b55f3..9f1af66 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
@@ -16,6 +16,9 @@
package com.android.settingslib.bluetooth;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
@@ -146,17 +149,18 @@
}
public boolean connect(BluetoothDevice device) {
- if (mService == null) return false;
- return mService.connect(device);
+ if (mService == null) {
+ return false;
+ }
+ return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
}
public boolean disconnect(BluetoothDevice device) {
- if (mService == null) return false;
- // Downgrade priority as user is disconnecting the hearing aid.
- if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
- mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ if (mService == null) {
+ return false;
}
- return mService.disconnect(device);
+
+ return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
}
public int getConnectionStatus(BluetoothDevice device) {
@@ -180,12 +184,12 @@
if (mService == null) {
return false;
}
- return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
}
public int getPreferred(BluetoothDevice device) {
if (mService == null) {
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ return CONNECTION_POLICY_FORBIDDEN;
}
return mService.getConnectionPolicy(device);
}
@@ -195,11 +199,11 @@
return;
}
if (preferred) {
- if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
- mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+ mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
}
} else {
- mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+ mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
index a372e23..678f2e3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
@@ -16,6 +16,9 @@
package com.android.settingslib.bluetooth;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
@@ -126,7 +129,7 @@
if (mService == null) {
return false;
}
- return mService.connect(device);
+ return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
}
@Override
@@ -134,11 +137,8 @@
if (mService == null) {
return false;
}
- // Downgrade priority as user is disconnecting the headset.
- if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
- mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
- }
- return mService.disconnect(device);
+
+ return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
}
@Override
@@ -154,13 +154,13 @@
if (mService == null) {
return false;
}
- return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
}
@Override
public int getPreferred(BluetoothDevice device) {
if (mService == null) {
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ return CONNECTION_POLICY_FORBIDDEN;
}
return mService.getConnectionPolicy(device);
}
@@ -171,11 +171,11 @@
return;
}
if (preferred) {
- if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
- mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+ mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
}
} else {
- mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+ mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
index 975a1e6..588083e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
@@ -16,6 +16,9 @@
package com.android.settingslib.bluetooth;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
@@ -99,13 +102,17 @@
}
public boolean connect(BluetoothDevice device) {
- if (mService == null) return false;
- return mService.connect(device);
+ if (mService == null) {
+ return false;
+ }
+ return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
}
public boolean disconnect(BluetoothDevice device) {
- if (mService == null) return false;
- return mService.disconnect(device);
+ if (mService == null) {
+ return false;
+ }
+ return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
}
public int getConnectionStatus(BluetoothDevice device) {
@@ -119,12 +126,12 @@
if (mService == null) {
return false;
}
- return mService.getConnectionPolicy(device) != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ return mService.getConnectionPolicy(device) != CONNECTION_POLICY_FORBIDDEN;
}
public int getPreferred(BluetoothDevice device) {
if (mService == null) {
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ return CONNECTION_POLICY_FORBIDDEN;
}
return mService.getConnectionPolicy(device);
}
@@ -132,11 +139,11 @@
public void setPreferred(BluetoothDevice device, boolean preferred) {
if (mService == null) return;
if (preferred) {
- if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
- mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+ mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
}
} else {
- mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+ mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
index 95139a1..7d121aa 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
@@ -16,6 +16,9 @@
package com.android.settingslib.bluetooth;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
@@ -115,18 +118,15 @@
if (mService == null) {
return false;
}
- return mService.connect(device);
+ return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
}
public boolean disconnect(BluetoothDevice device) {
if (mService == null) {
return false;
}
- // Downgrade priority as user is disconnecting.
- if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
- mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
- }
- return mService.disconnect(device);
+
+ return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
}
public int getConnectionStatus(BluetoothDevice device) {
@@ -140,12 +140,12 @@
if (mService == null) {
return false;
}
- return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
}
public int getPreferred(BluetoothDevice device) {
if (mService == null) {
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ return CONNECTION_POLICY_FORBIDDEN;
}
return mService.getConnectionPolicy(device);
}
@@ -155,11 +155,11 @@
return;
}
if (preferred) {
- if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
- mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+ mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
}
} else {
- mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+ mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
index 31a0eea..a96a4e7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
@@ -16,6 +16,8 @@
package com.android.settingslib.bluetooth;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
@@ -119,10 +121,8 @@
if (mService == null) {
return false;
}
- if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
- mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
- }
- return mService.disconnect(device);
+
+ return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
}
public int getConnectionStatus(BluetoothDevice device) {
@@ -136,12 +136,12 @@
if (mService == null) {
return false;
}
- return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
}
public int getPreferred(BluetoothDevice device) {
if (mService == null) {
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ return CONNECTION_POLICY_FORBIDDEN;
}
return mService.getConnectionPolicy(device);
}
@@ -155,7 +155,7 @@
mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
}
} else {
- mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+ mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
index 4ea0df6..56267fc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
@@ -16,6 +16,9 @@
package com.android.settingslib.bluetooth;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
@@ -129,7 +132,7 @@
return false;
}
Log.d(TAG,"PBAPClientProfile attempting to connect to " + device.getAddress());
- return mService.connect(device);
+ return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
}
public boolean disconnect(BluetoothDevice device) {
@@ -137,7 +140,7 @@
if (mService == null) {
return false;
}
- return mService.disconnect(device);
+ return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
}
public int getConnectionStatus(BluetoothDevice device) {
@@ -151,12 +154,12 @@
if (mService == null) {
return false;
}
- return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
}
public int getPreferred(BluetoothDevice device) {
if (mService == null) {
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ return CONNECTION_POLICY_FORBIDDEN;
}
return mService.getConnectionPolicy(device);
}
@@ -166,11 +169,11 @@
return;
}
if (preferred) {
- if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
- mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+ mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
}
} else {
- mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+ mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
index 3f920a8..f7c0bf5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
@@ -16,6 +16,8 @@
package com.android.settingslib.bluetooth;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
@@ -96,8 +98,10 @@
}
public boolean disconnect(BluetoothDevice device) {
- if (mService == null) return false;
- return mService.disconnect(device);
+ if (mService == null) {
+ return false;
+ }
+ return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
}
public int getConnectionStatus(BluetoothDevice device) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
index 0ca4d61..3022c5b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
@@ -16,6 +16,9 @@
package com.android.settingslib.bluetooth;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
@@ -112,17 +115,15 @@
if (mService == null) {
return false;
}
- return mService.connect(device);
+ return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
}
public boolean disconnect(BluetoothDevice device) {
if (mService == null) {
return false;
}
- if (mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
- mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
- }
- return mService.disconnect(device);
+
+ return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
}
public int getConnectionStatus(BluetoothDevice device) {
@@ -136,12 +137,12 @@
if (mService == null) {
return false;
}
- return mService.getConnectionPolicy(device) > BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
}
public int getPreferred(BluetoothDevice device) {
if (mService == null) {
- return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+ return CONNECTION_POLICY_FORBIDDEN;
}
return mService.getConnectionPolicy(device);
}
@@ -151,11 +152,11 @@
return;
}
if (preferred) {
- if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
- mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+ if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+ mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
}
} else {
- mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN);
+ mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java
index 61e47f8..6e7a429 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/VisibilityLoggerMixin.java
@@ -87,8 +87,10 @@
if (mMetricsFeature == null || mMetricsCategory == METRICS_CATEGORY_UNKNOWN) {
return;
}
- final int elapse = (int) (SystemClock.elapsedRealtime() - mCreationTimestamp);
- mMetricsFeature.action(METRICS_CATEGORY_UNKNOWN, action, mMetricsCategory, key, elapse);
+ if (mCreationTimestamp != 0L) {
+ final int elapse = (int) (SystemClock.elapsedRealtime() - mCreationTimestamp);
+ mMetricsFeature.action(METRICS_CATEGORY_UNKNOWN, action, mMetricsCategory, key, elapse);
+ }
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
index 4ab9a9a..b07fc2b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawer/CategoryKey.java
@@ -61,6 +61,8 @@
"com.android.settings.category.ia.my_device_info";
public static final String CATEGORY_BATTERY_SAVER_SETTINGS =
"com.android.settings.category.ia.battery_saver_settings";
+ public static final String CATEGORY_SMART_BATTERY_SETTINGS =
+ "com.android.settings.category.ia.smart_battery_settings";
public static final Map<String, String> KEY_COMPAT_MAP;
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 5b4ef3a..e85a102 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -122,7 +122,7 @@
final CachedBluetoothDevice cachedDevice =
((BluetoothMediaDevice) device).getCachedDevice();
if (!cachedDevice.isConnected() && !cachedDevice.isBusy()) {
- cachedDevice.connect(true);
+ cachedDevice.connect();
return;
}
}
@@ -194,6 +194,14 @@
}
}
+ void dispatchDeviceAttributesChanged() {
+ synchronized (mCallbacks) {
+ for (DeviceCallback callback : mCallbacks) {
+ callback.onDeviceAttributesChanged();
+ }
+ }
+ }
+
/**
* Stop scan MediaDevice
*/
@@ -306,14 +314,12 @@
}
mCurrentConnectedDevice = connectDevice;
updatePhoneMediaDeviceSummary();
- dispatchDeviceListUpdate();
+ dispatchDeviceAttributesChanged();
}
@Override
public void onDeviceAttributesChanged() {
- addPhoneDeviceIfNecessary();
- removePhoneMediaDeviceIfNecessary();
- dispatchDeviceListUpdate();
+ dispatchDeviceAttributesChanged();
}
}
@@ -327,7 +333,7 @@
*
* @param devices MediaDevice list
*/
- void onDeviceListUpdate(List<MediaDevice> devices);
+ default void onDeviceListUpdate(List<MediaDevice> devices) {};
/**
* Callback for notifying the connected device is changed.
@@ -338,6 +344,12 @@
* {@link MediaDeviceState#STATE_CONNECTING},
* {@link MediaDeviceState#STATE_DISCONNECTED}
*/
- void onSelectedDeviceStateChanged(MediaDevice device, @MediaDeviceState int state);
+ default void onSelectedDeviceStateChanged(MediaDevice device,
+ @MediaDeviceState int state) {};
+
+ /**
+ * Callback for notifying the device attributes is changed.
+ */
+ default void onDeviceAttributesChanged() {};
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
index ebca962..05a6ce4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/DataUsageUtils.java
@@ -38,9 +38,9 @@
final SubscriptionManager subscriptionManager = context.getSystemService(
SubscriptionManager.class);
final NetworkTemplate mobileAll = NetworkTemplate.buildTemplateMobileAll(
- telephonyManager.getSubscriberId(subId));
+ telephonyManager.createForSubscriptionId(subId).getSubscriberId());
- if (!subscriptionManager.isActiveSubId(subId)) {
+ if (!subscriptionManager.isActiveSubscriptionId(subId)) {
Log.i(TAG, "Subscription is not active: " + subId);
return mobileAll;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index b13c483..9d4c24e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -211,13 +211,6 @@
private static final int EAP_WPA = 1; // WPA-EAP
private static final int EAP_WPA2_WPA3 = 2; // RSN-EAP
- /**
- * The number of distinct wifi levels.
- *
- * <p>Must keep in sync with {@link R.array.wifi_signal} and {@link WifiManager#RSSI_LEVELS}.
- */
- public static final int SIGNAL_LEVELS = 5;
-
public static final int UNREACHABLE_RSSI = Integer.MIN_VALUE;
public static final String KEY_PREFIX_AP = "AP:";
@@ -453,9 +446,10 @@
return other.getSpeed() - getSpeed();
}
+ WifiManager wifiManager = getWifiManager();
// Sort by signal strength, bucketed by level
- int difference = WifiManager.calculateSignalLevel(other.mRssi, SIGNAL_LEVELS)
- - WifiManager.calculateSignalLevel(mRssi, SIGNAL_LEVELS);
+ int difference = wifiManager.calculateSignalLevel(other.mRssi)
+ - wifiManager.calculateSignalLevel(mRssi);
if (difference != 0) {
return difference;
}
@@ -869,13 +863,14 @@
}
/**
- * Returns the number of levels to show for a Wifi icon, from 0 to {@link #SIGNAL_LEVELS}-1.
+ * Returns the number of levels to show for a Wifi icon, from 0 to
+ * {@link WifiManager#getMaxSignalLevel()}.
*
- * <p>Use {@#isReachable()} to determine if an AccessPoint is in range, as this method will
+ * <p>Use {@link #isReachable()} to determine if an AccessPoint is in range, as this method will
* always return at least 0.
*/
public int getLevel() {
- return WifiManager.calculateSignalLevel(mRssi, SIGNAL_LEVELS);
+ return getWifiManager().calculateSignalLevel(mRssi);
}
public int getRssi() {
@@ -1766,7 +1761,10 @@
if (config.allowedKeyManagement.get(KeyMgmt.OWE)) {
return SECURITY_OWE;
}
- return (config.wepKeys[0] != null) ? SECURITY_WEP : SECURITY_NONE;
+ return (config.wepTxKeyIndex >= 0
+ && config.wepTxKeyIndex < config.wepKeys.length
+ && config.wepKeys[config.wepTxKeyIndex] != null)
+ ? SECURITY_WEP : SECURITY_NONE;
}
public static String securityToString(int security, int pskType) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
index 4a4f0e6..f21e466 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/TestAccessPointBuilder.java
@@ -22,6 +22,7 @@
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.os.Parcelable;
@@ -126,13 +127,15 @@
@Keep
public TestAccessPointBuilder setLevel(int level) {
// Reversal of WifiManager.calculateSignalLevels
+ WifiManager wifiManager = mContext.getSystemService(WifiManager.class);
+ int maxSignalLevel = wifiManager.getMaxSignalLevel();
if (level == 0) {
mRssi = MIN_RSSI;
- } else if (level >= AccessPoint.SIGNAL_LEVELS) {
+ } else if (level > maxSignalLevel) {
mRssi = MAX_RSSI;
} else {
float inputRange = MAX_RSSI - MIN_RSSI;
- float outputRange = AccessPoint.SIGNAL_LEVELS - 1;
+ float outputRange = maxSignalLevel;
mRssi = (int) (level * inputRange / outputRange + MIN_RSSI);
}
return this;
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
index 440315f..2d7d59c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiEntryPreference.java
@@ -118,7 +118,7 @@
notifyChanged();
}
- setSummary(mWifiEntry.getSummary());
+ setSummary(mWifiEntry.getSummary(false /* concise */));
mContentDescription = buildContentDescription();
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index 8591116..3f55cea 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -89,7 +89,7 @@
public void setListening(boolean listening) {
if (listening) {
mNetworkScoreManager.registerNetworkScoreCache(NetworkKey.TYPE_WIFI,
- mWifiNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK);
+ mWifiNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_CURRENT_NETWORK);
mWifiNetworkScoreCache.registerListener(mCacheListener);
mConnectivityManager.registerNetworkCallback(
mNetworkRequest, mNetworkCallback, mHandler);
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
index ba6a8ea..ed4ff08 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiTracker.java
@@ -361,7 +361,7 @@
mNetworkScoreManager.registerNetworkScoreCache(
NetworkKey.TYPE_WIFI,
mScoreCache,
- NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS);
+ NetworkScoreManager.SCORE_FILTER_SCAN_RESULTS);
}
private void requestScoresForNetworkKeys(Collection<NetworkKey> keys) {
diff --git a/packages/SettingsLib/tests/integ/Android.bp b/packages/SettingsLib/tests/integ/Android.bp
index 4600793..2ccff1e 100644
--- a/packages/SettingsLib/tests/integ/Android.bp
+++ b/packages/SettingsLib/tests/integ/Android.bp
@@ -14,7 +14,10 @@
android_test {
name: "SettingsLibTests",
- defaults: ["SettingsLibDefaults"],
+ defaults: [
+ "SettingsLibDefaults",
+ "framework-wifi-test-defaults"
+ ],
certificate: "platform",
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
index 61cbbd3..03201ae 100644
--- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/AccessPointTest.java
@@ -83,7 +83,6 @@
@SmallTest
@RunWith(AndroidJUnit4.class)
public class AccessPointTest {
-
private static final String TEST_SSID = "\"test_ssid\"";
private static final String ROAMING_SSID = "\"roaming_ssid\"";
private static final String OSU_FRIENDLY_NAME = "osu_friendly_name";
@@ -98,6 +97,7 @@
20 * DateUtils.MINUTE_IN_MILLIS;
private Context mContext;
+ private int mMaxSignalLevel;
private WifiInfo mWifiInfo;
@Mock private Context mMockContext;
@Mock private WifiManager mMockWifiManager;
@@ -128,6 +128,7 @@
public void setUp() {
MockitoAnnotations.initMocks(this);
mContext = InstrumentationRegistry.getTargetContext();
+ mMaxSignalLevel = mContext.getSystemService(WifiManager.class).getMaxSignalLevel();
mWifiInfo = new WifiInfo();
mWifiInfo.setSSID(WifiSsid.createFromAsciiEncoded(TEST_SSID));
mWifiInfo.setBSSID(TEST_BSSID);
@@ -180,7 +181,7 @@
@Test
public void testCompareTo_GivesHighLevelBeforeLowLevel() {
- final int highLevel = AccessPoint.SIGNAL_LEVELS - 1;
+ final int highLevel = mMaxSignalLevel;
final int lowLevel = 1;
assertThat(highLevel).isGreaterThan(lowLevel);
@@ -226,7 +227,7 @@
.setReachable(true).build();
AccessPoint saved = new TestAccessPointBuilder(mContext).setSaved(true).build();
AccessPoint highLevelAndReachable = new TestAccessPointBuilder(mContext)
- .setLevel(AccessPoint.SIGNAL_LEVELS - 1).build();
+ .setLevel(mMaxSignalLevel).build();
AccessPoint firstName = new TestAccessPointBuilder(mContext).setSsid("a").build();
AccessPoint lastname = new TestAccessPointBuilder(mContext).setSsid("z").build();
@@ -520,6 +521,8 @@
when(packageManager.getApplicationInfoAsUser(eq(appPackageName), anyInt(), anyInt()))
.thenReturn(applicationInfo);
when(applicationInfo.loadLabel(packageManager)).thenReturn(appLabel);
+ when(context.getSystemService(Context.WIFI_SERVICE)).thenReturn(mMockWifiManager);
+ when(mMockWifiManager.calculateSignalLevel(rssi)).thenReturn(4);
NetworkInfo networkInfo =
new NetworkInfo(ConnectivityManager.TYPE_WIFI, 0 /* subtype */, "WIFI", "");
@@ -636,14 +639,14 @@
public void testBuilder_setLevel() {
AccessPoint testAp;
- for (int i = 0; i < AccessPoint.SIGNAL_LEVELS; i++) {
+ for (int i = 0; i <= mMaxSignalLevel; i++) {
testAp = new TestAccessPointBuilder(mContext).setLevel(i).build();
assertThat(testAp.getLevel()).isEqualTo(i);
}
// numbers larger than the max level should be set to max
- testAp = new TestAccessPointBuilder(mContext).setLevel(AccessPoint.SIGNAL_LEVELS).build();
- assertThat(testAp.getLevel()).isEqualTo(AccessPoint.SIGNAL_LEVELS - 1);
+ testAp = new TestAccessPointBuilder(mContext).setLevel(mMaxSignalLevel + 1).build();
+ assertThat(testAp.getLevel()).isEqualTo(mMaxSignalLevel);
// numbers less than 0 should give level 0
testAp = new TestAccessPointBuilder(mContext).setLevel(-100).build();
@@ -653,7 +656,7 @@
@Test
public void testBuilder_settingReachableAfterLevelDoesNotAffectLevel() {
int level = 1;
- assertThat(level).isLessThan(AccessPoint.SIGNAL_LEVELS - 1);
+ assertThat(level).isLessThan(mMaxSignalLevel);
AccessPoint testAp =
new TestAccessPointBuilder(mContext).setLevel(level).setReachable(true).build();
diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/OWNER b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/OWNER
new file mode 100644
index 0000000..5c2a7b8
--- /dev/null
+++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/wifi/OWNER
@@ -0,0 +1,4 @@
+# People who can approve changes for submission
+arcwang@google.com
+govenliu@google.com
+qal@google.com
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java
index 976445e..ccb6646 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java
@@ -16,6 +16,9 @@
package com.android.settingslib.bluetooth;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.verify;
@@ -67,13 +70,13 @@
@Test
public void connect_shouldConnectBluetoothA2dpSink() {
mProfile.connect(mBluetoothDevice);
- verify(mService).connect(mBluetoothDevice);
+ verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED);
}
@Test
public void disconnect_shouldDisconnectBluetoothA2dpSink() {
mProfile.disconnect(mBluetoothDevice);
- verify(mService).disconnect(mBluetoothDevice);
+ verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN);
}
@Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java
index 69c020d..9180760 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java
@@ -16,6 +16,9 @@
package com.android.settingslib.bluetooth;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.verify;
@@ -67,13 +70,13 @@
@Test
public void connect_shouldConnectBluetoothHeadsetClient() {
mProfile.connect(mBluetoothDevice);
- verify(mService).connect(mBluetoothDevice);
+ verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED);
}
@Test
public void disconnect_shouldDisconnectBluetoothHeadsetClient() {
mProfile.disconnect(mBluetoothDevice);
- verify(mService).disconnect(mBluetoothDevice);
+ verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN);
}
@Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java
index 6f66709..1425c38 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java
@@ -16,6 +16,9 @@
package com.android.settingslib.bluetooth;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.verify;
@@ -67,13 +70,13 @@
@Test
public void connect_shouldConnectBluetoothMapClient() {
mProfile.connect(mBluetoothDevice);
- verify(mService).connect(mBluetoothDevice);
+ verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED);
}
@Test
public void disconnect_shouldDisconnectBluetoothMapClient() {
mProfile.disconnect(mBluetoothDevice);
- verify(mService).disconnect(mBluetoothDevice);
+ verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN);
}
@Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java
index b21ec9c3..15f560b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java
@@ -16,6 +16,9 @@
package com.android.settingslib.bluetooth;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.verify;
@@ -67,13 +70,13 @@
@Test
public void connect_shouldConnectBluetoothPbapClient() {
mProfile.connect(mBluetoothDevice);
- verify(mService).connect(mBluetoothDevice);
+ verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED);
}
@Test
public void disconnect_shouldDisconnectBluetoothPbapClient() {
mProfile.disconnect(mBluetoothDevice);
- verify(mService).disconnect(mBluetoothDevice);
+ verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN);
}
@Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java
index ec88034..4f978a8 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java
@@ -16,6 +16,9 @@
package com.android.settingslib.bluetooth;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.verify;
@@ -66,13 +69,13 @@
@Test
public void connect_shouldConnectBluetoothSap() {
mProfile.connect(mBluetoothDevice);
- verify(mService).connect(mBluetoothDevice);
+ verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED);
}
@Test
public void disconnect_shouldDisconnectBluetoothSap() {
mProfile.disconnect(mBluetoothDevice);
- verify(mService).disconnect(mBluetoothDevice);
+ verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN);
}
@Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java
index 605c861..340a6c7 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/CategoryKeyTest.java
@@ -59,8 +59,9 @@
allKeys.add(CategoryKey.CATEGORY_SYSTEM_DEVELOPMENT);
allKeys.add(CategoryKey.CATEGORY_GESTURES);
allKeys.add(CategoryKey.CATEGORY_NIGHT_DISPLAY);
+ allKeys.add(CategoryKey.CATEGORY_SMART_BATTERY_SETTINGS);
// DO NOT REMOVE ANYTHING ABOVE
- assertThat(allKeys.size()).isEqualTo(18);
+ assertThat(allKeys.size()).isEqualTo(19);
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index 98bb74a..c780a64 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -127,7 +127,7 @@
mLocalMediaManager.registerCallback(mCallback);
mLocalMediaManager.connectDevice(device);
- verify(cachedDevice).connect(true);
+ verify(cachedDevice).connect();
}
@Test
@@ -355,7 +355,7 @@
mLocalMediaManager.mMediaDeviceCallback.onConnectedDeviceChanged(TEST_DEVICE_ID_2);
assertThat(mLocalMediaManager.getCurrentConnectedDevice()).isEqualTo(device2);
- verify(mCallback).onDeviceListUpdate(any());
+ verify(mCallback).onDeviceAttributesChanged();
}
@Test
@@ -373,7 +373,7 @@
mLocalMediaManager.registerCallback(mCallback);
mLocalMediaManager.mMediaDeviceCallback.onConnectedDeviceChanged(TEST_DEVICE_ID_1);
- verify(mCallback, never()).onDeviceListUpdate(any());
+ verify(mCallback, never()).onDeviceAttributesChanged();
}
@Test
@@ -382,6 +382,6 @@
mLocalMediaManager.mMediaDeviceCallback.onDeviceAttributesChanged();
- verify(mCallback).onDeviceListUpdate(any());
+ verify(mCallback).onDeviceAttributesChanged();
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java
index d98f50b..b0a647e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/DataUsageUtilsTest.java
@@ -31,6 +31,7 @@
import android.telephony.TelephonyManager;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -71,12 +72,13 @@
when(mTelephonyManager.getSubscriberId(SUB_ID)).thenReturn(SUBSCRIBER_ID);
when(mTelephonyManager.getSubscriberId(SUB_ID_2)).thenReturn(SUBSCRIBER_ID_2);
when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mTelephonyManager);
- when(mSubscriptionManager.isActiveSubId(anyInt())).thenReturn(true);
+ when(mSubscriptionManager.isActiveSubscriptionId(anyInt())).thenReturn(true);
}
@Test
+ @Ignore
public void getMobileTemplate_infoNull_returnMobileAll() {
- when(mSubscriptionManager.isActiveSubId(SUB_ID)).thenReturn(false);
+ when(mSubscriptionManager.isActiveSubscriptionId(SUB_ID)).thenReturn(false);
final NetworkTemplate networkTemplate = DataUsageUtils.getMobileTemplate(mContext, SUB_ID);
assertThat(networkTemplate.matchesSubscriberId(SUBSCRIBER_ID)).isTrue();
@@ -84,6 +86,7 @@
}
@Test
+ @Ignore
public void getMobileTemplate_groupUuidNull_returnMobileAll() {
when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(mInfo1);
when(mInfo1.getGroupUuid()).thenReturn(null);
@@ -96,6 +99,7 @@
}
@Test
+ @Ignore
public void getMobileTemplate_groupUuidExist_returnMobileMerged() {
when(mSubscriptionManager.getActiveSubscriptionInfo(SUB_ID)).thenReturn(mInfo1);
when(mInfo1.getGroupUuid()).thenReturn(mParcelUuid);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java
index 21aa526..2bd20a9 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/AccessPointPreferenceTest.java
@@ -72,7 +72,7 @@
assertThat(AccessPointPreference.buildContentDescription(
RuntimeEnvironment.application, pref, ap))
- .isEqualTo("ssid,connected,Wifi signal full.,Secure network");
+ .isEqualTo("ssid,connected,Wifi disconnected.,Secure network");
}
@Test
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/OWNER b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/OWNER
new file mode 100644
index 0000000..5c2a7b8
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/OWNER
@@ -0,0 +1,4 @@
+# People who can approve changes for submission
+arcwang@google.com
+govenliu@google.com
+qal@google.com
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java
index 752a549..0f1e0ff 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiEntryPreferenceTest.java
@@ -67,7 +67,7 @@
MockitoAnnotations.initMocks(this);
when(mMockWifiEntry.getTitle()).thenReturn(MOCK_TITLE);
- when(mMockWifiEntry.getSummary()).thenReturn(MOCK_SUMMARY);
+ when(mMockWifiEntry.getSummary(false /* concise */)).thenReturn(MOCK_SUMMARY);
when(mMockIconInjector.getIcon(0)).thenReturn(mMockDrawable0);
when(mMockIconInjector.getIcon(1)).thenReturn(mMockDrawable1);
@@ -112,7 +112,7 @@
final WifiEntryPreference pref =
new WifiEntryPreference(mContext, mMockWifiEntry, mMockIconInjector);
final String updatedSummary = "updated summary";
- when(mMockWifiEntry.getSummary()).thenReturn(updatedSummary);
+ when(mMockWifiEntry.getSummary(false /* concise */)).thenReturn(updatedSummary);
pref.refresh();
diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp
index f40d3a1..96a98dc 100644
--- a/packages/SettingsProvider/Android.bp
+++ b/packages/SettingsProvider/Android.bp
@@ -8,6 +8,7 @@
libs: [
"telephony-common",
"ims-common",
+ "unsupportedappusage",
],
static_libs: [
"junit",
@@ -40,6 +41,7 @@
libs: [
"android.test.base",
"android.test.mock",
+ "unsupportedappusage",
],
resource_dirs: ["res"],
aaptflags: [
diff --git a/packages/SettingsProvider/res/values/overlayable.xml b/packages/SettingsProvider/res/values/overlayable.xml
deleted file mode 100644
index dc41a77..0000000
--- a/packages/SettingsProvider/res/values/overlayable.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?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:android="http://schemas.android.com/apk/res/android">
- <overlayable name="SettingsToNotRestore">
- <policy type="product|system|vendor">
- <item type="array" name="restore_blocked_device_specific_settings" />
- <item type="array" name="restore_blocked_global_settings" />
- <item type="array" name="restore_blocked_secure_settings" />
- <item type="array" name="restore_blocked_system_settings" />
- </policy>
- </overlayable>
-</resources>
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 049b9f0..5636cc8 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -16,7 +16,7 @@
package android.provider.settings.backup;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.provider.Settings;
/** Information relating to the Secure settings which should be backed up */
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
index 89b19de..3f5b0da 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SystemSettings.java
@@ -16,7 +16,7 @@
package android.provider.settings.backup;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.provider.Settings;
/** Information about the system settings to back up */
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index 72923a3..dd94d2e 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -141,9 +141,6 @@
VALIDATORS.put(Global.DEVICE_PROVISIONING_MOBILE_DATA_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.REQUIRE_PASSWORD_TO_DECRYPT, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.DEVICE_DEMO_MODE, BOOLEAN_VALIDATOR);
- VALIDATORS.put(Global.WIFI_PNO_FREQUENCY_CULLING_ENABLED, BOOLEAN_VALIDATOR);
- VALIDATORS.put(Global.WIFI_PNO_RECENCY_SORTING_ENABLED, BOOLEAN_VALIDATOR);
- VALIDATORS.put(Global.WIFI_LINK_PROBING_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.AWARE_ALLOWED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.POWER_BUTTON_LONG_PRESS, new InclusiveIntegerRangeValidator(0, 5));
VALIDATORS.put(
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
index 94ab0f1..c5d4fa9 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SystemSettingsValidators.java
@@ -25,7 +25,7 @@
import static android.provider.settings.validators.SettingsValidators.VIBRATION_INTENSITY_VALIDATOR;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.hardware.display.ColorDisplayManager;
import android.os.BatteryManager;
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index 375a650..266bfe0 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -2554,7 +2554,7 @@
for (int phoneId = 0; phoneId < phoneCount; phoneId++) {
int mode = defaultNetworks.size() <= phoneId
|| defaultNetworks.get(phoneId) == null
- ? RILConstants.PREFERRED_NETWORK_MODE : defaultNetworks.get(phoneId);
+ ? TelephonyManager.DEFAULT_PREFERRED_NETWORK_MODE : defaultNetworks.get(phoneId);
if (phoneId > 0) val.append(',');
val.append(mode);
}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 0e3f81b..449a135 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -266,9 +266,6 @@
dumpSetting(s, p,
Settings.Global.BACKUP_AGENT_TIMEOUT_PARAMETERS,
GlobalSettingsProto.Backup.BACKUP_AGENT_TIMEOUT_PARAMETERS);
- dumpSetting(s, p,
- Settings.Global.BACKUP_MULTI_USER_ENABLED,
- GlobalSettingsProto.Backup.BACKUP_MULTI_USER_ENABLED);
p.end(backupToken);
final long batteryToken = p.start(GlobalSettingsProto.BATTERY);
@@ -1073,9 +1070,6 @@
Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE,
GlobalSettingsProto.Network.RECOMMENDATIONS_PACKAGE);
dumpSetting(s, p,
- Settings.Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS,
- GlobalSettingsProto.Network.RECOMMENDATION_REQUEST_TIMEOUT_MS);
- dumpSetting(s, p,
Settings.Global.NETWORK_WATCHLIST_ENABLED,
GlobalSettingsProto.Network.WATCHLIST_ENABLED);
dumpSetting(s, p,
@@ -1590,9 +1584,6 @@
Settings.Global.WIFI_WATCHDOG_POOR_NETWORK_TEST_ENABLED,
GlobalSettingsProto.Wifi.WATCHDOG_POOR_NETWORK_TEST_ENABLED);
dumpSetting(s, p,
- Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED,
- GlobalSettingsProto.Wifi.SUSPEND_OPTIMIZATIONS_ENABLED);
- dumpSetting(s, p,
Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED,
GlobalSettingsProto.Wifi.VERBOSE_LOGGING_ENABLED);
dumpSetting(s, p,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 4309c80..c913999 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -20,6 +20,7 @@
import static android.os.Process.ROOT_UID;
import static android.os.Process.SHELL_UID;
import static android.os.Process.SYSTEM_UID;
+import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON_OVERLAY;
import android.Manifest;
@@ -366,7 +367,9 @@
String value = getSettingValue(args);
String tag = getSettingTag(args);
final boolean makeDefault = getSettingMakeDefault(args);
- insertGlobalSetting(name, value, tag, makeDefault, requestingUserId, false);
+ final boolean overrideableByRestore = getSettingOverrideableByRestore(args);
+ insertGlobalSetting(name, value, tag, makeDefault, requestingUserId, false,
+ overrideableByRestore);
break;
}
@@ -374,21 +377,26 @@
String value = getSettingValue(args);
String tag = getSettingTag(args);
final boolean makeDefault = getSettingMakeDefault(args);
- insertSecureSetting(name, value, tag, makeDefault, requestingUserId, false);
+ final boolean overrideableByRestore = getSettingOverrideableByRestore(args);
+ insertSecureSetting(name, value, tag, makeDefault, requestingUserId, false,
+ overrideableByRestore);
break;
}
case Settings.CALL_METHOD_PUT_SYSTEM: {
String value = getSettingValue(args);
- insertSystemSetting(name, value, requestingUserId);
+ boolean overrideableByRestore = getSettingOverrideableByRestore(args);
+ insertSystemSetting(name, value, requestingUserId, overrideableByRestore);
break;
}
case Settings.CALL_METHOD_SET_ALL_CONFIG: {
String prefix = getSettingPrefix(args);
Map<String, String> flags = getSettingFlags(args);
- setAllConfigSettings(prefix, flags);
- break;
+ Bundle result = new Bundle();
+ result.putBoolean(Settings.KEY_CONFIG_SET_RETURN,
+ setAllConfigSettings(prefix, flags));
+ return result;
}
case Settings.CALL_METHOD_RESET_CONFIG: {
@@ -573,20 +581,23 @@
switch (table) {
case TABLE_GLOBAL: {
if (insertGlobalSetting(name, value, null, false,
- UserHandle.getCallingUserId(), false)) {
+ UserHandle.getCallingUserId(), false,
+ /* overrideableByRestore */ false)) {
return Uri.withAppendedPath(Settings.Global.CONTENT_URI, name);
}
} break;
case TABLE_SECURE: {
if (insertSecureSetting(name, value, null, false,
- UserHandle.getCallingUserId(), false)) {
+ UserHandle.getCallingUserId(), false,
+ /* overrideableByRestore */ false)) {
return Uri.withAppendedPath(Settings.Secure.CONTENT_URI, name);
}
} break;
case TABLE_SYSTEM: {
- if (insertSystemSetting(name, value, UserHandle.getCallingUserId())) {
+ if (insertSystemSetting(name, value, UserHandle.getCallingUserId(),
+ /* overridableByRestore */ false)) {
return Uri.withAppendedPath(Settings.System.CONTENT_URI, name);
}
} break;
@@ -1072,7 +1083,8 @@
case MUTATION_OPERATION_INSERT: {
return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_CONFIG,
UserHandle.USER_SYSTEM, name, value, null, makeDefault, true,
- resolveCallingPackage(), false, null);
+ resolveCallingPackage(), false, null,
+ /* overrideableByRestore */ false);
}
case MUTATION_OPERATION_DELETE: {
@@ -1176,14 +1188,15 @@
}
private boolean insertGlobalSetting(String name, String value, String tag,
- boolean makeDefault, int requestingUserId, boolean forceNotify) {
+ boolean makeDefault, int requestingUserId, boolean forceNotify,
+ boolean overrideableByRestore) {
if (DEBUG) {
Slog.v(LOG_TAG, "insertGlobalSetting(" + name + ", " + value + ", "
+ ", " + tag + ", " + makeDefault + ", " + requestingUserId
+ ", " + forceNotify + ")");
}
return mutateGlobalSetting(name, value, tag, makeDefault, requestingUserId,
- MUTATION_OPERATION_INSERT, forceNotify, 0);
+ MUTATION_OPERATION_INSERT, forceNotify, 0, overrideableByRestore);
}
private boolean deleteGlobalSetting(String name, int requestingUserId, boolean forceNotify) {
@@ -1218,6 +1231,15 @@
private boolean mutateGlobalSetting(String name, String value, String tag,
boolean makeDefault, int requestingUserId, int operation, boolean forceNotify,
int mode) {
+ // overrideableByRestore = false as by default settings values shouldn't be overrideable by
+ // restore.
+ return mutateGlobalSetting(name, value, tag, makeDefault, requestingUserId, operation,
+ forceNotify, mode, /* overrideableByRestore */ false);
+ }
+
+ private boolean mutateGlobalSetting(String name, String value, String tag,
+ boolean makeDefault, int requestingUserId, int operation, boolean forceNotify,
+ int mode, boolean overrideableByRestore) {
// Make sure the caller can change the settings - treated as secure.
enforceWritePermission(Manifest.permission.WRITE_SECURE_SETTINGS);
@@ -1236,7 +1258,8 @@
case MUTATION_OPERATION_INSERT: {
return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_GLOBAL,
UserHandle.USER_SYSTEM, name, value, tag, makeDefault,
- getCallingPackage(), forceNotify, CRITICAL_GLOBAL_SETTINGS);
+ getCallingPackage(), forceNotify,
+ CRITICAL_GLOBAL_SETTINGS, overrideableByRestore);
}
case MUTATION_OPERATION_DELETE: {
@@ -1472,7 +1495,7 @@
) {
@Override
public boolean update(String value, boolean setDefault, String packageName,
- String tag, boolean forceNonSystemPackage) {
+ String tag, boolean forceNonSystemPackage, boolean overrideableByRestore) {
Slog.wtf(LOG_TAG, "update shouldn't be called on this instance.");
return false;
}
@@ -1481,14 +1504,15 @@
}
private boolean insertSecureSetting(String name, String value, String tag,
- boolean makeDefault, int requestingUserId, boolean forceNotify) {
+ boolean makeDefault, int requestingUserId, boolean forceNotify,
+ boolean overrideableByRestore) {
if (DEBUG) {
Slog.v(LOG_TAG, "insertSecureSetting(" + name + ", " + value + ", "
+ ", " + tag + ", " + makeDefault + ", " + requestingUserId
+ ", " + forceNotify + ")");
}
return mutateSecureSetting(name, value, tag, makeDefault, requestingUserId,
- MUTATION_OPERATION_INSERT, forceNotify, 0);
+ MUTATION_OPERATION_INSERT, forceNotify, 0, overrideableByRestore);
}
private boolean deleteSecureSetting(String name, int requestingUserId, boolean forceNotify) {
@@ -1526,6 +1550,15 @@
private boolean mutateSecureSetting(String name, String value, String tag,
boolean makeDefault, int requestingUserId, int operation, boolean forceNotify,
int mode) {
+ // overrideableByRestore = false as by default settings values shouldn't be overrideable by
+ // restore.
+ return mutateSecureSetting(name, value, tag, makeDefault, requestingUserId, operation,
+ forceNotify, mode, /* overrideableByRestore */ false);
+ }
+
+ private boolean mutateSecureSetting(String name, String value, String tag,
+ boolean makeDefault, int requestingUserId, int operation, boolean forceNotify,
+ int mode, boolean overrideableByRestore) {
// Make sure the caller can change the settings.
enforceWritePermission(Manifest.permission.WRITE_SECURE_SETTINGS);
@@ -1558,7 +1591,8 @@
case MUTATION_OPERATION_INSERT: {
return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_SECURE,
owningUserId, name, value, tag, makeDefault,
- getCallingPackage(), forceNotify, CRITICAL_SECURE_SETTINGS);
+ getCallingPackage(), forceNotify, CRITICAL_SECURE_SETTINGS,
+ overrideableByRestore);
}
case MUTATION_OPERATION_DELETE: {
@@ -1634,13 +1668,15 @@
}
}
- private boolean insertSystemSetting(String name, String value, int requestingUserId) {
+ private boolean insertSystemSetting(String name, String value, int requestingUserId,
+ boolean overrideableByRestore) {
if (DEBUG) {
Slog.v(LOG_TAG, "insertSystemSetting(" + name + ", " + value + ", "
+ requestingUserId + ")");
}
- return mutateSystemSetting(name, value, requestingUserId, MUTATION_OPERATION_INSERT);
+ return mutateSystemSetting(name, value, requestingUserId, MUTATION_OPERATION_INSERT,
+ overrideableByRestore);
}
private boolean deleteSystemSetting(String name, int requestingUserId) {
@@ -1660,8 +1696,15 @@
return mutateSystemSetting(name, value, requestingUserId, MUTATION_OPERATION_UPDATE);
}
- private boolean mutateSystemSetting(String name, String value, int runAsUserId,
- int operation) {
+ private boolean mutateSystemSetting(String name, String value, int runAsUserId, int operation) {
+ // overrideableByRestore = false as by default settings values shouldn't be overrideable by
+ // restore.
+ return mutateSystemSetting(name, value, runAsUserId, operation,
+ /* overrideableByRestore */ false);
+ }
+
+ private boolean mutateSystemSetting(String name, String value, int runAsUserId, int operation,
+ boolean overrideableByRestore) {
if (!hasWriteSecureSettingsPermission()) {
// If the caller doesn't hold WRITE_SECURE_SETTINGS, we verify whether this
// operation is allowed for the calling package through appops.
@@ -1711,7 +1754,7 @@
validateSystemSettingValue(name, value);
return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_SYSTEM,
owningUserId, name, value, null, false, getCallingPackage(),
- false, null);
+ false, null, overrideableByRestore);
}
case MUTATION_OPERATION_DELETE: {
@@ -2048,7 +2091,8 @@
}
return mSettingsRegistry.insertSettingLocked(SETTINGS_TYPE_SECURE,
owningUserId, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, newProviders, tag,
- makeDefault, getCallingPackage(), forceNotify, CRITICAL_SECURE_SETTINGS);
+ makeDefault, getCallingPackage(), forceNotify, CRITICAL_SECURE_SETTINGS,
+ /* overrideableByRestore */ false);
}
private static void warnOrThrowForUndesiredSecureSettingsMutationForTargetSdk(
@@ -2142,6 +2186,10 @@
return (args != null) && args.getBoolean(Settings.CALL_METHOD_MAKE_DEFAULT_KEY);
}
+ private static boolean getSettingOverrideableByRestore(Bundle args) {
+ return (args != null) && args.getBoolean(Settings.CALL_METHOD_OVERRIDEABLE_BY_RESTORE_KEY);
+ }
+
private static int getResetModeEnforcingPermission(Bundle args) {
final int mode = (args != null) ? args.getInt(Settings.CALL_METHOD_RESET_MODE_KEY) : 0;
switch (mode) {
@@ -2639,21 +2687,21 @@
public boolean insertSettingLocked(int type, int userId, String name, String value,
String tag, boolean makeDefault, String packageName, boolean forceNotify,
- Set<String> criticalSettings) {
+ Set<String> criticalSettings, boolean overrideableByRestore) {
return insertSettingLocked(type, userId, name, value, tag, makeDefault, false,
- packageName, forceNotify, criticalSettings);
+ packageName, forceNotify, criticalSettings, overrideableByRestore);
}
public boolean insertSettingLocked(int type, int userId, String name, String value,
String tag, boolean makeDefault, boolean forceNonSystemPackage, String packageName,
- boolean forceNotify, Set<String> criticalSettings) {
+ boolean forceNotify, Set<String> criticalSettings, boolean overrideableByRestore) {
final int key = makeKey(type, userId);
boolean success = false;
SettingsState settingsState = peekSettingsStateLocked(key);
if (settingsState != null) {
success = settingsState.insertSettingLocked(name, value,
- tag, makeDefault, forceNonSystemPackage, packageName);
+ tag, makeDefault, forceNonSystemPackage, packageName, overrideableByRestore);
}
if (success && criticalSettings != null && criticalSettings.contains(name)) {
@@ -2666,20 +2714,28 @@
return success;
}
+ /**
+ * Set Settings using consumed keyValues, returns true if the keyValues can be set, false
+ * otherwise.
+ */
public boolean setSettingsLocked(int type, int userId, String prefix,
Map<String, String> keyValues, String packageName) {
final int key = makeKey(type, userId);
SettingsState settingsState = peekSettingsStateLocked(key);
if (settingsState != null) {
+ if (SETTINGS_TYPE_CONFIG == type && settingsState.isNewConfigBannedLocked(prefix,
+ keyValues)) {
+ return false;
+ }
List<String> changedSettings =
settingsState.setSettingsLocked(prefix, keyValues, packageName);
if (!changedSettings.isEmpty()) {
notifyForConfigSettingsChangeLocked(key, prefix, changedSettings);
}
}
-
- return settingsState != null;
+ // keyValues aren't banned and can be set
+ return true;
}
public boolean deleteSettingLocked(int type, int userId, String name, boolean forceNotify,
@@ -2739,7 +2795,8 @@
public void resetSettingsLocked(int type, int userId, String packageName, int mode,
String tag) {
- resetSettingsLocked(type, userId, packageName, mode, tag, null);
+ resetSettingsLocked(type, userId, packageName, mode, tag, /*prefix=*/
+ null);
}
public void resetSettingsLocked(int type, int userId, String packageName, int mode,
@@ -2750,6 +2807,7 @@
return;
}
+ banConfigurationIfNecessary(type, prefix, settingsState);
switch (mode) {
case Settings.RESET_MODE_PACKAGE_DEFAULTS: {
for (String name : settingsState.getSettingNamesLocked()) {
@@ -3173,6 +3231,34 @@
return getTypeFromKey(key) == SETTINGS_TYPE_SSAID;
}
+ private boolean shouldBan(int type) {
+ if (SETTINGS_TYPE_CONFIG != type) {
+ return false;
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int appId = UserHandle.getAppId(callingUid);
+
+ // Only non-shell resets should result in namespace banning
+ return appId != SHELL_UID;
+ }
+
+ private void banConfigurationIfNecessary(int type, @Nullable String prefix,
+ SettingsState settingsState) {
+ // Banning should be performed only for Settings.Config and for non-shell reset calls
+ if (!shouldBan(type)) {
+ return;
+ }
+ if (prefix != null) {
+ settingsState.banConfigurationLocked(prefix, getAllConfigFlags(prefix));
+ } else {
+ Set<String> configPrefixes = settingsState.getAllConfigPrefixesLocked();
+ for (String configPrefix : configPrefixes) {
+ settingsState.banConfigurationLocked(configPrefix,
+ getAllConfigFlags(configPrefix));
+ }
+ }
+ }
+
private File getSettingsFile(int key) {
if (isConfigSettingsKey(key)) {
final int userId = getUserIdFromKey(key);
@@ -3264,7 +3350,7 @@
}
private final class UpgradeController {
- private static final int SETTINGS_VERSION = 185;
+ private static final int SETTINGS_VERSION = 186;
private final int mUserId;
@@ -3350,6 +3436,10 @@
* for this user from the old to the new version. When you add a new
* upgrade step you *must* update SETTINGS_VERSION.
*
+ * All settings modifications should be made through
+ * {@link SettingsState#insertSettingOverrideableByRestoreLocked(String, String, String,
+ * boolean, String)} so that restore can override those values if needed.
+ *
* This is an example of moving a setting from secure to global.
*
* // v119: Example settings changes.
@@ -3395,7 +3485,8 @@
// v120: Add double tap to wake setting.
if (currentVersion == 119) {
SettingsState secureSettings = getSecureSettingsLocked(userId);
- secureSettings.insertSettingLocked(Settings.Secure.DOUBLE_TAP_TO_WAKE,
+ secureSettings.insertSettingOverrideableByRestoreLocked(
+ Settings.Secure.DOUBLE_TAP_TO_WAKE,
getContext().getResources().getBoolean(
R.bool.def_double_tap_to_wake) ? "1" : "0", null, true,
SettingsState.SYSTEM_PACKAGE_NAME);
@@ -3420,7 +3511,7 @@
Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT);
if (defaultComponent != null && !defaultComponent.isEmpty() &&
currentSetting.isNull()) {
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT,
defaultComponent, null, true, SettingsState.SYSTEM_PACKAGE_NAME);
}
@@ -3435,7 +3526,7 @@
Setting currentSetting = globalSettings.getSettingLocked(
Settings.Global.ADD_USERS_WHEN_LOCKED);
if (currentSetting.isNull()) {
- globalSettings.insertSettingLocked(
+ globalSettings.insertSettingOverrideableByRestoreLocked(
Settings.Global.ADD_USERS_WHEN_LOCKED,
getContext().getResources().getBoolean(
R.bool.def_add_users_from_lockscreen) ? "1" : "0",
@@ -3449,8 +3540,9 @@
final SettingsState globalSettings = getGlobalSettingsLocked();
String defaultDisabledProfiles = (getContext().getResources().getString(
R.string.def_bluetooth_disabled_profiles));
- globalSettings.insertSettingLocked(Settings.Global.BLUETOOTH_DISABLED_PROFILES,
- defaultDisabledProfiles, null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+ globalSettings.insertSettingOverrideableByRestoreLocked(
+ Settings.Global.BLUETOOTH_DISABLED_PROFILES, defaultDisabledProfiles,
+ null, true, SettingsState.SYSTEM_PACKAGE_NAME);
currentVersion = 124;
}
@@ -3461,7 +3553,7 @@
Setting currentSetting = secureSettings.getSettingLocked(
Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD);
if (currentSetting.isNull()) {
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD,
getContext().getResources().getBoolean(
R.bool.def_show_ime_with_hard_keyboard) ? "1" : "0",
@@ -3490,7 +3582,7 @@
b.append(c.flattenToString());
start = false;
}
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Settings.Secure.ENABLED_VR_LISTENERS, b.toString(),
null, true, SettingsState.SYSTEM_PACKAGE_NAME);
}
@@ -3510,7 +3602,7 @@
Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS);
if (!showNotifications.isNull()) {
final SettingsState secureSettings = getSecureSettingsLocked(userId);
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS,
showNotifications.getValue(), null, true,
SettingsState.SYSTEM_PACKAGE_NAME);
@@ -3520,7 +3612,7 @@
Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
if (!allowPrivate.isNull()) {
final SettingsState secureSettings = getSecureSettingsLocked(userId);
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS,
allowPrivate.getValue(), null, true,
SettingsState.SYSTEM_PACKAGE_NAME);
@@ -3547,7 +3639,7 @@
final String oldValue = systemSecureSettings.getSettingLocked(
Settings.Secure.LONG_PRESS_TIMEOUT).getValue();
if (TextUtils.equals("500", oldValue)) {
- systemSecureSettings.insertSettingLocked(
+ systemSecureSettings.insertSettingOverrideableByRestoreLocked(
Settings.Secure.LONG_PRESS_TIMEOUT,
String.valueOf(getContext().getResources().getInteger(
R.integer.def_long_press_timeout_millis)),
@@ -3563,10 +3655,12 @@
getSettingLocked(Settings.Secure.DOZE_ENABLED).getValue());
if (dozeExplicitlyDisabled) {
- secureSettings.insertSettingLocked(Settings.Secure.DOZE_PICK_UP_GESTURE,
- "0", null, true, SettingsState.SYSTEM_PACKAGE_NAME);
- secureSettings.insertSettingLocked(Settings.Secure.DOZE_DOUBLE_TAP_GESTURE,
- "0", null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+ secureSettings.insertSettingOverrideableByRestoreLocked(
+ Settings.Secure.DOZE_PICK_UP_GESTURE, "0", null, true,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ secureSettings.insertSettingOverrideableByRestoreLocked(
+ Settings.Secure.DOZE_DOUBLE_TAP_GESTURE, "0", null, true,
+ SettingsState.SYSTEM_PACKAGE_NAME);
}
currentVersion = 131;
}
@@ -3577,7 +3671,7 @@
final String oldValue = systemSecureSettings.getSettingLocked(
Settings.Secure.MULTI_PRESS_TIMEOUT).getValue();
if (TextUtils.equals(null, oldValue)) {
- systemSecureSettings.insertSettingLocked(
+ systemSecureSettings.insertSettingOverrideableByRestoreLocked(
Settings.Secure.MULTI_PRESS_TIMEOUT,
String.valueOf(getContext().getResources().getInteger(
R.integer.def_multi_press_timeout_millis)),
@@ -3592,7 +3686,7 @@
final SettingsState systemSecureSettings = getSecureSettingsLocked(userId);
String defaultSyncParentSounds = (getContext().getResources()
.getBoolean(R.bool.def_sync_parent_sounds) ? "1" : "0");
- systemSecureSettings.insertSettingLocked(
+ systemSecureSettings.insertSettingOverrideableByRestoreLocked(
Settings.Secure.SYNC_PARENT_SOUNDS, defaultSyncParentSounds,
null, true, SettingsState.SYSTEM_PACKAGE_NAME);
currentVersion = 133;
@@ -3605,9 +3699,9 @@
.isNull()) {
String defaultEndButtonBehavior = Integer.toString(getContext()
.getResources().getInteger(R.integer.def_end_button_behavior));
- systemSettings.insertSettingLocked(Settings.System.END_BUTTON_BEHAVIOR,
- defaultEndButtonBehavior, null, true,
- SettingsState.SYSTEM_PACKAGE_NAME);
+ systemSettings.insertSettingOverrideableByRestoreLocked(
+ Settings.System.END_BUTTON_BEHAVIOR, defaultEndButtonBehavior, null,
+ true, SettingsState.SYSTEM_PACKAGE_NAME);
}
currentVersion = 134;
}
@@ -3665,8 +3759,8 @@
if (ssaid.isNull() || ssaid.getValue() == null) {
// Android Id doesn't exist for this package so create it.
- ssaidSettings.insertSettingLocked(uid, legacySsaid, null, true,
- info.packageName);
+ ssaidSettings.insertSettingOverrideableByRestoreLocked(uid,
+ legacySsaid, null, true, info.packageName);
if (DEBUG) {
Slog.d(LOG_TAG, "Keep the legacy ssaid for uid=" + uid);
}
@@ -3686,13 +3780,14 @@
&& secureSetting.getSettingLocked(
Settings.Secure.INSTALL_NON_MARKET_APPS).getValue().equals("0")) {
- secureSetting.insertSettingLocked(Settings.Secure.INSTALL_NON_MARKET_APPS,
- "1", null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+ secureSetting.insertSettingOverrideableByRestoreLocked(
+ Settings.Secure.INSTALL_NON_MARKET_APPS, "1", null, true,
+ SettingsState.SYSTEM_PACKAGE_NAME);
// For managed profiles with profile owners, DevicePolicyManagerService
// may want to set the user restriction in this case
- secureSetting.insertSettingLocked(
- Settings.Secure.UNKNOWN_SOURCES_DEFAULT_REVERSED, "1", null, true,
- SettingsState.SYSTEM_PACKAGE_NAME);
+ secureSetting.insertSettingOverrideableByRestoreLocked(
+ Settings.Secure.UNKNOWN_SOURCES_DEFAULT_REVERSED, "1", null,
+ true, SettingsState.SYSTEM_PACKAGE_NAME);
}
currentVersion = 138;
}
@@ -3733,7 +3828,7 @@
Setting currentSetting = globalSettings.getSettingLocked(
Settings.Global.WIFI_WAKEUP_ENABLED);
if (currentSetting.isNull()) {
- globalSettings.insertSettingLocked(
+ globalSettings.insertSettingOverrideableByRestoreLocked(
Settings.Global.WIFI_WAKEUP_ENABLED,
getContext().getResources().getBoolean(
R.bool.def_wifi_wakeup_enabled) ? "1" : "0",
@@ -3755,8 +3850,9 @@
if (defaultValue != null) {
Slog.d(LOG_TAG, "Setting [" + defaultValue + "] as Autofill Service "
+ "for user " + userId);
- secureSettings.insertSettingLocked(Settings.Secure.AUTOFILL_SERVICE,
- defaultValue, null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+ secureSettings.insertSettingOverrideableByRestoreLocked(
+ Settings.Secure.AUTOFILL_SERVICE, defaultValue, null, true,
+ SettingsState.SYSTEM_PACKAGE_NAME);
}
}
@@ -3807,7 +3903,7 @@
final Setting currentSetting = globalSettings.getSettingLocked(
Global.DEFAULT_RESTRICT_BACKGROUND_DATA);
if (currentSetting.isNull()) {
- globalSettings.insertSettingLocked(
+ globalSettings.insertSettingOverrideableByRestoreLocked(
Global.DEFAULT_RESTRICT_BACKGROUND_DATA,
getContext().getResources().getBoolean(
R.bool.def_restrict_background_data) ? "1" : "0",
@@ -3826,7 +3922,7 @@
final String defaultValue = getContext().getResources().getString(
R.string.def_backup_manager_constants);
if (!TextUtils.isEmpty(defaultValue)) {
- systemSecureSettings.insertSettingLocked(
+ systemSecureSettings.insertSettingOverrideableByRestoreLocked(
Settings.Secure.BACKUP_MANAGER_CONSTANTS, defaultValue, null,
true, SettingsState.SYSTEM_PACKAGE_NAME);
}
@@ -3840,7 +3936,7 @@
final Setting currentSetting = globalSettings.getSettingLocked(
Settings.Global.MOBILE_DATA_ALWAYS_ON);
if (currentSetting.isNull()) {
- globalSettings.insertSettingLocked(
+ globalSettings.insertSettingOverrideableByRestoreLocked(
Settings.Global.MOBILE_DATA_ALWAYS_ON,
getContext().getResources().getBoolean(
R.bool.def_mobile_data_always_on) ? "1" : "0",
@@ -3876,7 +3972,7 @@
if (showNotificationBadges.isNull()) {
final boolean defaultValue = getContext().getResources().getBoolean(
com.android.internal.R.bool.config_notificationBadging);
- systemSecureSettings.insertSettingLocked(
+ systemSecureSettings.insertSettingOverrideableByRestoreLocked(
Secure.NOTIFICATION_BADGING,
defaultValue ? "1" : "0",
null, true, SettingsState.SYSTEM_PACKAGE_NAME);
@@ -3893,7 +3989,7 @@
final String defaultValue = getContext().getResources().getString(
R.string.def_backup_local_transport_parameters);
if (!TextUtils.isEmpty(defaultValue)) {
- systemSecureSettings.insertSettingLocked(
+ systemSecureSettings.insertSettingOverrideableByRestoreLocked(
Settings.Secure.BACKUP_LOCAL_TRANSPORT_PARAMETERS, defaultValue,
null, true, SettingsState.SYSTEM_PACKAGE_NAME);
}
@@ -3916,7 +4012,7 @@
if (currentSetting.isNull()) {
String defaultZenDuration = Integer.toString(getContext()
.getResources().getInteger(R.integer.def_zen_duration));
- globalSettings.insertSettingLocked(
+ globalSettings.insertSettingOverrideableByRestoreLocked(
Global.ZEN_DURATION, defaultZenDuration,
null, true, SettingsState.SYSTEM_PACKAGE_NAME);
}
@@ -3932,7 +4028,7 @@
final String defaultValue = getContext().getResources().getString(
R.string.def_backup_agent_timeout_parameters);
if (!TextUtils.isEmpty(defaultValue)) {
- globalSettings.insertSettingLocked(
+ globalSettings.insertSettingOverrideableByRestoreLocked(
Settings.Global.BACKUP_AGENT_TIMEOUT_PARAMETERS, defaultValue,
null, true,
SettingsState.SYSTEM_PACKAGE_NAME);
@@ -3961,7 +4057,7 @@
Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS);
// The default value is "1", check if user has turned it off.
if ("0".equals(showNotifications.getValue())) {
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, "0",
null /* tag */, false /* makeDefault */,
SettingsState.SYSTEM_PACKAGE_NAME);
@@ -3982,7 +4078,7 @@
String oldValue = globalSettings.getSettingLocked(
Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY).getValue();
if (TextUtils.equals(null, oldValue)) {
- globalSettings.insertSettingLocked(
+ globalSettings.insertSettingOverrideableByRestoreLocked(
Settings.Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY,
Integer.toString(getContext().getResources().getInteger(
R.integer.def_max_sound_trigger_detection_service_ops_per_day)),
@@ -3992,7 +4088,7 @@
oldValue = globalSettings.getSettingLocked(
Global.SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT).getValue();
if (TextUtils.equals(null, oldValue)) {
- globalSettings.insertSettingLocked(
+ globalSettings.insertSettingOverrideableByRestoreLocked(
Settings.Global.SOUND_TRIGGER_DETECTION_SERVICE_OP_TIMEOUT,
Integer.toString(getContext().getResources().getInteger(
R.integer.def_sound_trigger_detection_service_op_timeout)),
@@ -4007,7 +4103,7 @@
final Setting currentSetting = secureSettings.getSettingLocked(
Secure.VOLUME_HUSH_GESTURE);
if (currentSetting.isNull()) {
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Secure.VOLUME_HUSH_GESTURE,
Integer.toString(Secure.VOLUME_HUSH_VIBRATE),
null, true, SettingsState.SYSTEM_PACKAGE_NAME);
@@ -4028,7 +4124,7 @@
final Setting currentSetting = settings.getSettingLocked(
Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY);
if (currentSetting.isDefaultFromSystem()) {
- settings.insertSettingLocked(
+ settings.insertSettingOverrideableByRestoreLocked(
Settings.Global.MAX_SOUND_TRIGGER_DETECTION_SERVICE_OPS_PER_DAY,
Integer.toString(getContext().getResources().getInteger(
R.integer
@@ -4057,7 +4153,7 @@
Setting currentHushUsedSetting = secureSettings.getSettingLocked(
Secure.HUSH_GESTURE_USED);
if (currentHushUsedSetting.isNull()) {
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Settings.Secure.HUSH_GESTURE_USED, "0", null, true,
SettingsState.SYSTEM_PACKAGE_NAME);
}
@@ -4065,7 +4161,7 @@
Setting currentRingerToggleCountSetting = secureSettings.getSettingLocked(
Secure.MANUAL_RINGER_TOGGLE_COUNT);
if (currentRingerToggleCountSetting.isNull()) {
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Settings.Secure.MANUAL_RINGER_TOGGLE_COUNT, "0", null, true,
SettingsState.SYSTEM_PACKAGE_NAME);
}
@@ -4084,7 +4180,7 @@
final Setting currentSetting = systemSettings.getSettingLocked(
Settings.System.VIBRATE_WHEN_RINGING);
if (currentSetting.isNull()) {
- systemSettings.insertSettingLocked(
+ systemSettings.insertSettingOverrideableByRestoreLocked(
Settings.System.VIBRATE_WHEN_RINGING,
getContext().getResources().getBoolean(
R.bool.def_vibrate_when_ringing) ? "1" : "0",
@@ -4108,18 +4204,18 @@
// ZEN_DURATION
if (!globalZenDuration.isNull()) {
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Secure.ZEN_DURATION, globalZenDuration.getValue(), null, false,
SettingsState.SYSTEM_PACKAGE_NAME);
// set global zen duration setting to null since it's deprecated
- globalSettings.insertSettingLocked(
+ globalSettings.insertSettingOverrideableByRestoreLocked(
Global.ZEN_DURATION, null, null, true,
SettingsState.SYSTEM_PACKAGE_NAME);
} else if (secureZenDuration.isNull()) {
String defaultZenDuration = Integer.toString(getContext()
.getResources().getInteger(R.integer.def_zen_duration));
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Secure.ZEN_DURATION, defaultZenDuration, null, true,
SettingsState.SYSTEM_PACKAGE_NAME);
}
@@ -4128,7 +4224,7 @@
final Setting currentShowZenSettingSuggestion = secureSettings.getSettingLocked(
Secure.SHOW_ZEN_SETTINGS_SUGGESTION);
if (currentShowZenSettingSuggestion.isNull()) {
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Secure.SHOW_ZEN_SETTINGS_SUGGESTION, "1",
null, true, SettingsState.SYSTEM_PACKAGE_NAME);
}
@@ -4137,7 +4233,7 @@
final Setting currentUpdatedSetting = secureSettings.getSettingLocked(
Secure.ZEN_SETTINGS_UPDATED);
if (currentUpdatedSetting.isNull()) {
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Secure.ZEN_SETTINGS_UPDATED, "0",
null, true, SettingsState.SYSTEM_PACKAGE_NAME);
}
@@ -4146,7 +4242,7 @@
final Setting currentSettingSuggestionViewed = secureSettings.getSettingLocked(
Secure.ZEN_SETTINGS_SUGGESTION_VIEWED);
if (currentSettingSuggestionViewed.isNull()) {
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Secure.ZEN_SETTINGS_SUGGESTION_VIEWED, "0",
null, true, SettingsState.SYSTEM_PACKAGE_NAME);
}
@@ -4169,20 +4265,20 @@
if (!globalChargingSoundEnabled.isNull()) {
if (secureChargingSoundsEnabled.isNull()) {
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Secure.CHARGING_SOUNDS_ENABLED,
globalChargingSoundEnabled.getValue(), null, false,
SettingsState.SYSTEM_PACKAGE_NAME);
}
// set global charging_sounds_enabled setting to null since it's deprecated
- globalSettings.insertSettingLocked(
+ globalSettings.insertSettingOverrideableByRestoreLocked(
Global.CHARGING_SOUNDS_ENABLED, null, null, true,
SettingsState.SYSTEM_PACKAGE_NAME);
} else if (secureChargingSoundsEnabled.isNull()) {
String defChargingSoundsEnabled = getContext().getResources()
.getBoolean(R.bool.def_charging_sounds_enabled) ? "1" : "0";
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Secure.CHARGING_SOUNDS_ENABLED, defChargingSoundsEnabled, null,
true, SettingsState.SYSTEM_PACKAGE_NAME);
}
@@ -4194,7 +4290,7 @@
if (secureChargingVibrationEnabled.isNull()) {
String defChargingVibrationEnabled = getContext().getResources()
.getBoolean(R.bool.def_charging_vibration_enabled) ? "1" : "0";
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Secure.CHARGING_VIBRATION_ENABLED, defChargingVibrationEnabled,
null, true, SettingsState.SYSTEM_PACKAGE_NAME);
}
@@ -4214,7 +4310,7 @@
currentSetting.getValue());
if ((currentSettingIntegerValue
& (1 << AudioManager.STREAM_VOICE_CALL)) == 0) {
- systemSettings.insertSettingLocked(
+ systemSettings.insertSettingOverrideableByRestoreLocked(
Settings.System.MUTE_STREAMS_AFFECTED,
Integer.toString(
currentSettingIntegerValue
@@ -4255,7 +4351,7 @@
? Secure.LOCATION_MODE_ON
: Secure.LOCATION_MODE_OFF;
}
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Secure.LOCATION_MODE, Integer.toString(defLocationMode),
null, true, SettingsState.SYSTEM_PACKAGE_NAME);
}
@@ -4277,7 +4373,7 @@
Setting currentRampingRingerSetting = globalSettings.getSettingLocked(
Settings.Global.APPLY_RAMPING_RINGER);
if (currentRampingRingerSetting.isNull()) {
- globalSettings.insertSettingLocked(
+ globalSettings.insertSettingOverrideableByRestoreLocked(
Settings.Global.APPLY_RAMPING_RINGER,
getContext().getResources().getBoolean(
R.bool.def_apply_ramping_ringer) ? "1" : "0", null,
@@ -4303,7 +4399,7 @@
if (!notificationVibrationIntensity.isNull()
&& ringVibrationIntensity.isNull()) {
- systemSettings.insertSettingLocked(
+ systemSettings.insertSettingOverrideableByRestoreLocked(
Settings.System.RING_VIBRATION_INTENSITY,
notificationVibrationIntensity.getValue(),
null , true, SettingsState.SYSTEM_PACKAGE_NAME);
@@ -4347,7 +4443,7 @@
if (awareEnabled.isNull()) {
final boolean defAwareEnabled = getContext().getResources().getBoolean(
R.bool.def_aware_enabled);
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Secure.AWARE_ENABLED, defAwareEnabled ? "1" : "0",
null, true, SettingsState.SYSTEM_PACKAGE_NAME);
}
@@ -4367,7 +4463,7 @@
if (skipGesture.isNull()) {
final boolean defSkipGesture = getContext().getResources().getBoolean(
R.bool.def_skip_gesture);
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Secure.SKIP_GESTURE, defSkipGesture ? "1" : "0",
null, true, SettingsState.SYSTEM_PACKAGE_NAME);
}
@@ -4378,7 +4474,7 @@
if (silenceGesture.isNull()) {
final boolean defSilenceGesture = getContext().getResources().getBoolean(
R.bool.def_silence_gesture);
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Secure.SILENCE_GESTURE, defSilenceGesture ? "1" : "0",
null, true, SettingsState.SYSTEM_PACKAGE_NAME);
}
@@ -4406,7 +4502,7 @@
if (awareLockEnabled.isNull()) {
final boolean defAwareLockEnabled = getContext().getResources().getBoolean(
R.bool.def_aware_lock_enabled);
- secureSettings.insertSettingLocked(
+ secureSettings.insertSettingOverrideableByRestoreLocked(
Secure.AWARE_LOCK_ENABLED, defAwareLockEnabled ? "1" : "0",
null, true, SettingsState.SYSTEM_PACKAGE_NAME);
}
@@ -4426,7 +4522,7 @@
currentSetting.getValue());
if ((currentSettingIntegerValue
& (1 << AudioManager.STREAM_BLUETOOTH_SCO)) == 0) {
- systemSettings.insertSettingLocked(
+ systemSettings.insertSettingOverrideableByRestoreLocked(
Settings.System.MUTE_STREAMS_AFFECTED,
Integer.toString(
currentSettingIntegerValue
@@ -4472,13 +4568,13 @@
if (oldValueWireless == null
|| TextUtils.equals(oldValueWireless, defaultValueWired)) {
if (!TextUtils.isEmpty(defaultValueWireless)) {
- globalSettings.insertSettingLocked(
+ globalSettings.insertSettingOverrideableByRestoreLocked(
Global.WIRELESS_CHARGING_STARTED_SOUND, defaultValueWireless,
null /* tag */, true /* makeDefault */,
SettingsState.SYSTEM_PACKAGE_NAME);
} else if (!TextUtils.isEmpty(defaultValueWired)) {
// if the wireless sound is empty, use the wired charging sound
- globalSettings.insertSettingLocked(
+ globalSettings.insertSettingOverrideableByRestoreLocked(
Global.WIRELESS_CHARGING_STARTED_SOUND, defaultValueWired,
null /* tag */, true /* makeDefault */,
SettingsState.SYSTEM_PACKAGE_NAME);
@@ -4487,7 +4583,7 @@
// wired charging sound
if (oldValueWired == null && !TextUtils.isEmpty(defaultValueWired)) {
- globalSettings.insertSettingLocked(
+ globalSettings.insertSettingOverrideableByRestoreLocked(
Global.CHARGING_STARTED_SOUND, defaultValueWired,
null /* tag */, true /* makeDefault */,
SettingsState.SYSTEM_PACKAGE_NAME);
@@ -4499,14 +4595,40 @@
// Version 184: Reset the default for Global Settings: NOTIFICATION_BUBBLES
// This is originally set in version 182, however, the default value changed
// so this step is to ensure the value is updated to the correct default.
- getGlobalSettingsLocked().insertSettingLocked(Global.NOTIFICATION_BUBBLES,
- getContext().getResources().getBoolean(
+ getGlobalSettingsLocked().insertSettingOverrideableByRestoreLocked(
+ Global.NOTIFICATION_BUBBLES, getContext().getResources().getBoolean(
R.bool.def_notification_bubbles) ? "1" : "0", null /* tag */,
true /* makeDefault */, SettingsState.SYSTEM_PACKAGE_NAME);
currentVersion = 185;
}
+ if (currentVersion == 185) {
+ // Deprecate ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, and migrate it
+ // to ACCESSIBILITY_BUTTON_TARGET_COMPONENT.
+ final SettingsState secureSettings = getSecureSettingsLocked(userId);
+ final Setting magnifyNavbarEnabled = secureSettings.getSettingLocked(
+ Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
+ if ("1".equals(magnifyNavbarEnabled.getValue())) {
+ secureSettings.insertSettingLocked(
+ Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
+ ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER,
+ null /* tag */, false /* makeDefault */,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ } else {
+ // Clear a11y button targets list setting. A11yManagerService will end up
+ // adding all legacy enabled services that want the button to the list, so
+ // there's no need to keep tracking them.
+ secureSettings.insertSettingLocked(
+ Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
+ null, null /* tag */, false /* makeDefault */,
+ SettingsState.SYSTEM_PACKAGE_NAME);
+ }
+ secureSettings.deleteSettingLocked(
+ Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
+ currentVersion = 186;
+ }
+
// vXXX: Add new settings above this point.
if (currentVersion != newVersion) {
@@ -4550,7 +4672,7 @@
final boolean systemSet = SettingsState.isSystemPackage(getContext(),
setting.getPackageName(), callingUid, userId);
if (systemSet) {
- settings.insertSettingLocked(name, setting.getValue(),
+ settings.insertSettingOverrideableByRestoreLocked(name, setting.getValue(),
setting.getTag(), true, setting.getPackageName());
} else if (setting.getDefaultValue() != null && setting.isDefaultFromSystem()) {
// We had a bug where changes by non-system packages were marked
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 086b20f..db18213 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -65,9 +65,12 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Set;
/**
* This class contains the state for one type of settings. It is responsible
@@ -109,6 +112,13 @@
private static final String ATTR_ID = "id";
private static final String ATTR_NAME = "name";
+ private static final String TAG_NAMESPACE_HASHES = "namespaceHashes";
+ private static final String TAG_NAMESPACE_HASH = "namespaceHash";
+ private static final String ATTR_NAMESPACE = "namespace";
+ private static final String ATTR_BANNED_HASH = "bannedHash";
+
+ private static final String ATTR_PRESERVE_IN_RESTORE = "preserve_in_restore";
+
/**
* Non-binary value will be written in this attributes.
*/
@@ -159,6 +169,9 @@
private final ArrayMap<String, Setting> mSettings = new ArrayMap<>();
@GuardedBy("mLock")
+ private final ArrayMap<String, String> mNamespaceBannedHashes = new ArrayMap<>();
+
+ @GuardedBy("mLock")
private final ArrayMap<String, Integer> mPackageToMemoryUsage;
@GuardedBy("mLock")
@@ -377,15 +390,25 @@
// The settings provider must hold its lock when calling here.
@GuardedBy("mLock")
- public boolean insertSettingLocked(String name, String value, String tag,
+ public boolean insertSettingOverrideableByRestoreLocked(String name, String value, String tag,
boolean makeDefault, String packageName) {
- return insertSettingLocked(name, value, tag, makeDefault, false, packageName);
+ return insertSettingLocked(name, value, tag, makeDefault, false, packageName,
+ /* overrideableByRestore */ true);
}
// The settings provider must hold its lock when calling here.
@GuardedBy("mLock")
public boolean insertSettingLocked(String name, String value, String tag,
- boolean makeDefault, boolean forceNonSystemPackage, String packageName) {
+ boolean makeDefault, String packageName) {
+ return insertSettingLocked(name, value, tag, makeDefault, false, packageName,
+ /* overrideableByRestore */ false);
+ }
+
+ // The settings provider must hold its lock when calling here.
+ @GuardedBy("mLock")
+ public boolean insertSettingLocked(String name, String value, String tag,
+ boolean makeDefault, boolean forceNonSystemPackage, String packageName,
+ boolean overrideableByRestore) {
if (TextUtils.isEmpty(name)) {
return false;
}
@@ -396,7 +419,8 @@
Setting newState;
if (oldState != null) {
- if (!oldState.update(value, makeDefault, packageName, tag, forceNonSystemPackage)) {
+ if (!oldState.update(value, makeDefault, packageName, tag, forceNonSystemPackage,
+ overrideableByRestore)) {
return false;
}
newState = oldState;
@@ -418,6 +442,41 @@
return true;
}
+ @GuardedBy("mLock")
+ public boolean isNewConfigBannedLocked(String prefix, Map<String, String> keyValues) {
+ // Replaces old style "null" String values with actual null's. This is done to simulate
+ // what will happen to String "null" values when they are written to Settings. This needs to
+ // be done here make sure that config hash computed during is banned check matches the
+ // one computed during banning when values are already stored.
+ keyValues = removeNullValueOldStyle(keyValues);
+ String bannedHash = mNamespaceBannedHashes.get(prefix);
+ if (bannedHash == null) {
+ return false;
+ }
+ return bannedHash.equals(hashCode(keyValues));
+ }
+
+ @GuardedBy("mLock")
+ public void banConfigurationLocked(String prefix, Map<String, String> keyValues) {
+ if (prefix == null || keyValues.isEmpty()) {
+ return;
+ }
+ // The write is intentionally not scheduled here, banned hashes should and will be written
+ // when the related setting changes are written
+ mNamespaceBannedHashes.put(prefix, hashCode(keyValues));
+ }
+
+ @GuardedBy("mLock")
+ public Set<String> getAllConfigPrefixesLocked() {
+ Set<String> prefixSet = new HashSet<>();
+ final int settingsCount = mSettings.size();
+ for (int i = 0; i < settingsCount; i++) {
+ String name = mSettings.keyAt(i);
+ prefixSet.add(name.split("/")[0] + "/");
+ }
+ return prefixSet;
+ }
+
// The settings provider must hold its lock when calling here.
// Returns the list of keys which changed (added, updated, or deleted).
@GuardedBy("mLock")
@@ -449,7 +508,8 @@
changedKeys.add(key); // key was added
} else if (state.value != value) {
oldValue = state.value;
- state.update(value, false, packageName, null, true);
+ state.update(value, false, packageName, null, true,
+ /* overrideableByRestore */ false);
changedKeys.add(key); // key was updated
} else {
// this key/value already exists, no change and no logging necessary
@@ -710,10 +770,12 @@
boolean wroteState = false;
final int version;
final ArrayMap<String, Setting> settings;
+ final ArrayMap<String, String> namespaceBannedHashes;
synchronized (mLock) {
version = mVersion;
settings = new ArrayMap<>(mSettings);
+ namespaceBannedHashes = new ArrayMap<>(mNamespaceBannedHashes);
mDirty = false;
mWriteScheduled = false;
}
@@ -749,15 +811,27 @@
writeSingleSetting(mVersion, serializer, setting.getId(), setting.getName(),
setting.getValue(), setting.getDefaultValue(), setting.getPackageName(),
- setting.getTag(), setting.isDefaultFromSystem());
+ setting.getTag(), setting.isDefaultFromSystem(),
+ setting.isValuePreservedInRestore());
if (DEBUG_PERSISTENCE) {
Slog.i(LOG_TAG, "[PERSISTED]" + setting.getName() + "="
+ setting.getValue());
}
}
-
serializer.endTag(null, TAG_SETTINGS);
+
+ serializer.startTag(null, TAG_NAMESPACE_HASHES);
+ for (int i = 0; i < namespaceBannedHashes.size(); i++) {
+ String namespace = namespaceBannedHashes.keyAt(i);
+ String bannedHash = namespaceBannedHashes.get(namespace);
+ writeSingleNamespaceHash(serializer, namespace, bannedHash);
+ if (DEBUG_PERSISTENCE) {
+ Slog.i(LOG_TAG, "[PERSISTED] namespace=" + namespace
+ + ", bannedHash=" + bannedHash);
+ }
+ }
+ serializer.endTag(null, TAG_NAMESPACE_HASHES);
serializer.endDocument();
destination.finishWrite(out);
@@ -827,7 +901,8 @@
static void writeSingleSetting(int version, XmlSerializer serializer, String id,
String name, String value, String defaultValue, String packageName,
- String tag, boolean defaultSysSet) throws IOException {
+ String tag, boolean defaultSysSet, boolean isValuePreservedInRestore)
+ throws IOException {
if (id == null || isBinary(id) || name == null || isBinary(name)
|| packageName == null || isBinary(packageName)) {
// This shouldn't happen.
@@ -846,6 +921,9 @@
setValueAttribute(ATTR_TAG, ATTR_TAG_BASE64,
version, serializer, tag);
}
+ if (isValuePreservedInRestore) {
+ serializer.attribute(null, ATTR_PRESERVE_IN_RESTORE, Boolean.toString(true));
+ }
serializer.endTag(null, TAG_SETTING);
}
@@ -869,6 +947,21 @@
}
}
+ private static void writeSingleNamespaceHash(XmlSerializer serializer, String namespace,
+ String bannedHashCode) throws IOException {
+ if (namespace == null || bannedHashCode == null) {
+ return;
+ }
+ serializer.startTag(null, TAG_NAMESPACE_HASH);
+ serializer.attribute(null, ATTR_NAMESPACE, namespace);
+ serializer.attribute(null, ATTR_BANNED_HASH, bannedHashCode);
+ serializer.endTag(null, TAG_NAMESPACE_HASH);
+ }
+
+ private static String hashCode(Map<String, String> keyValues) {
+ return Integer.toString(keyValues.hashCode());
+ }
+
private String getValueAttribute(XmlPullParser parser, String attr, String base64Attr) {
if (mVersion >= SETTINGS_VERSION_NEW_ENCODING) {
final String value = parser.getAttributeValue(null, attr);
@@ -939,6 +1032,8 @@
String tagName = parser.getName();
if (tagName.equals(TAG_SETTINGS)) {
parseSettingsLocked(parser);
+ } else if (tagName.equals(TAG_NAMESPACE_HASHES)) {
+ parseNamespaceHash(parser);
}
}
}
@@ -965,6 +1060,10 @@
String packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
String defaultValue = getValueAttribute(parser, ATTR_DEFAULT_VALUE,
ATTR_DEFAULT_VALUE_BASE64);
+ String isPreservedInRestoreString = parser.getAttributeValue(null,
+ ATTR_PRESERVE_IN_RESTORE);
+ boolean isPreservedInRestore = isPreservedInRestoreString != null
+ && Boolean.parseBoolean(isPreservedInRestoreString);
String tag = null;
boolean fromSystem = false;
if (defaultValue != null) {
@@ -973,7 +1072,7 @@
tag = getValueAttribute(parser, ATTR_TAG, ATTR_TAG_BASE64);
}
mSettings.put(name, new Setting(name, value, defaultValue, packageName, tag,
- fromSystem, id));
+ fromSystem, id, isPreservedInRestore));
if (DEBUG_PERSISTENCE) {
Slog.i(LOG_TAG, "[RESTORED] " + name + "=" + value);
@@ -982,6 +1081,37 @@
}
}
+ @GuardedBy("mLock")
+ private void parseNamespaceHash(XmlPullParser parser)
+ throws IOException, XmlPullParserException {
+
+ final int outerDepth = parser.getDepth();
+ int type;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ if (parser.getName().equals(TAG_NAMESPACE_HASH)) {
+ String namespace = parser.getAttributeValue(null, ATTR_NAMESPACE);
+ String bannedHashCode = parser.getAttributeValue(null, ATTR_BANNED_HASH);
+ mNamespaceBannedHashes.put(namespace, bannedHashCode);
+ }
+ }
+ }
+
+ private static Map<String, String> removeNullValueOldStyle(Map<String, String> keyValues) {
+ Iterator<Map.Entry<String, String>> it = keyValues.entrySet().iterator();
+ while (it.hasNext()) {
+ Map.Entry<String, String> keyValueEntry = it.next();
+ if (NULL_VALUE_OLD_STYLE.equals(keyValueEntry.getValue())) {
+ keyValueEntry.setValue(null);
+ }
+ }
+ return keyValues;
+ }
+
private final class MyHandler extends Handler {
public static final int MSG_PERSIST_SETTINGS = 1;
@@ -1026,6 +1156,8 @@
private String tag;
// Whether the default is set by the system
private boolean defaultFromSystem;
+ // Whether the value of this setting will be preserved when restore happens.
+ private boolean isValuePreservedInRestore;
public Setting(Setting other) {
name = other.name;
@@ -1035,25 +1167,38 @@
id = other.id;
defaultFromSystem = other.defaultFromSystem;
tag = other.tag;
+ isValuePreservedInRestore = other.isValuePreservedInRestore;
}
public Setting(String name, String value, boolean makeDefault, String packageName,
String tag) {
this.name = name;
- update(value, makeDefault, packageName, tag, false);
+ // overrideableByRestore = true as the first initialization isn't considered a
+ // modification.
+ update(value, makeDefault, packageName, tag, false,
+ /* overrideableByRestore */ true);
}
public Setting(String name, String value, String defaultValue,
String packageName, String tag, boolean fromSystem, String id) {
+ this(name, value, defaultValue, packageName, tag, fromSystem, id,
+ /* isOverrideableByRestore */ false);
+ }
+
+ Setting(String name, String value, String defaultValue,
+ String packageName, String tag, boolean fromSystem, String id,
+ boolean isValuePreservedInRestore) {
mNextId = Math.max(mNextId, Long.parseLong(id) + 1);
if (NULL_VALUE.equals(value)) {
value = null;
}
- init(name, value, tag, defaultValue, packageName, fromSystem, id);
+ init(name, value, tag, defaultValue, packageName, fromSystem, id,
+ isValuePreservedInRestore);
}
private void init(String name, String value, String tag, String defaultValue,
- String packageName, boolean fromSystem, String id) {
+ String packageName, boolean fromSystem, String id,
+ boolean isValuePreservedInRestore) {
this.name = name;
this.value = value;
this.tag = tag;
@@ -1061,6 +1206,7 @@
this.packageName = packageName;
this.id = id;
this.defaultFromSystem = fromSystem;
+ this.isValuePreservedInRestore = isValuePreservedInRestore;
}
public String getName() {
@@ -1091,6 +1237,10 @@
return defaultFromSystem;
}
+ public boolean isValuePreservedInRestore() {
+ return isValuePreservedInRestore;
+ }
+
public String getId() {
return id;
}
@@ -1101,7 +1251,9 @@
/** @return whether the value changed */
public boolean reset() {
- return update(this.defaultValue, false, packageName, null, true);
+ // overrideableByRestore = true as resetting to default value isn't considered a
+ // modification.
+ return update(this.defaultValue, false, packageName, null, true, true);
}
public boolean isTransient() {
@@ -1113,7 +1265,7 @@
}
public boolean update(String value, boolean setDefault, String packageName, String tag,
- boolean forceNonSystemPackage) {
+ boolean forceNonSystemPackage, boolean overrideableByRestore) {
if (NULL_VALUE.equals(value)) {
value = null;
}
@@ -1146,17 +1298,22 @@
}
}
+ // isValuePreservedInRestore shouldn't change back to false if it has been set to true.
+ boolean isPreserved = this.isValuePreservedInRestore || !overrideableByRestore;
+
// Is something gonna change?
if (Objects.equals(value, this.value)
&& Objects.equals(defaultValue, this.defaultValue)
&& Objects.equals(packageName, this.packageName)
&& Objects.equals(tag, this.tag)
- && defaultFromSystem == this.defaultFromSystem) {
+ && defaultFromSystem == this.defaultFromSystem
+ && isPreserved == this.isValuePreservedInRestore) {
return false;
}
init(name, value, tag, defaultValue, packageName, defaultFromSystem,
- String.valueOf(mNextId++));
+ String.valueOf(mNextId++), isPreserved);
+
return true;
}
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 443811f..6ea2c74 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -369,7 +369,6 @@
Settings.Global.NETWORK_WATCHLIST_LAST_REPORT_TIME,
Settings.Global.NETWORK_PREFERENCE,
Settings.Global.NETWORK_RECOMMENDATIONS_PACKAGE,
- Settings.Global.NETWORK_RECOMMENDATION_REQUEST_TIMEOUT_MS,
Settings.Global.NETWORK_SCORER_APP,
Settings.Global.NETWORK_SCORING_PROVISIONED,
Settings.Global.NETWORK_SCORING_UI_ENABLED,
@@ -523,8 +522,6 @@
Settings.Global.WIFI_BADGING_THRESHOLDS,
Settings.Global.WIFI_BOUNCE_DELAY_OVERRIDE_MS,
Settings.Global.WIFI_COUNTRY_CODE,
- Settings.Global.WIFI_DATA_STALL_MIN_TX_BAD,
- Settings.Global.WIFI_DATA_STALL_MIN_TX_SUCCESS_WITHOUT_RX,
Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN,
Settings.Global.WIFI_DISPLAY_CERTIFICATION_ON,
Settings.Global.WIFI_DISPLAY_ON,
@@ -534,11 +531,6 @@
Settings.Global.WIFI_FRAMEWORK_SCAN_INTERVAL_MS,
Settings.Global.WIFI_FREQUENCY_BAND,
Settings.Global.WIFI_IDLE_MS,
- Settings.Global.WIFI_IS_UNUSABLE_EVENT_METRICS_ENABLED,
- Settings.Global.WIFI_LINK_SPEED_METRICS_ENABLED,
- Settings.Global.WIFI_PNO_FREQUENCY_CULLING_ENABLED,
- Settings.Global.WIFI_PNO_RECENCY_SORTING_ENABLED,
- Settings.Global.WIFI_LINK_PROBING_ENABLED,
Settings.Global.WIFI_MAX_DHCP_RETRY_COUNT,
Settings.Global.WIFI_MOBILE_DATA_TRANSITION_WAKELOCK_TIMEOUT_MS,
Settings.Global.WIFI_NETWORK_SHOW_RSSI,
@@ -547,7 +539,6 @@
Settings.Global.WIFI_ON,
Settings.Global.WIFI_P2P_DEVICE_NAME,
Settings.Global.WIFI_P2P_PENDING_FACTORY_RESET,
- Settings.Global.WIFI_RTT_BACKGROUND_EXEC_GAP_MS,
Settings.Global.WIFI_SAVED_STATE,
Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE,
Settings.Global.WIFI_SCAN_INTERVAL_WHEN_P2P_CONNECTED_MS,
@@ -555,7 +546,6 @@
Settings.Global.WIFI_SCORE_PARAMS,
Settings.Global.WIFI_SLEEP_POLICY,
Settings.Global.WIFI_SUPPLICANT_SCAN_INTERVAL_MS,
- Settings.Global.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED,
Settings.Global.WIFI_VERBOSE_LOGGING_ENABLED,
Settings.Global.WIFI_WATCHDOG_ON,
Settings.Global.WIMAX_NETWORKS_AVAILABLE_NOTIFICATION_ON,
@@ -571,7 +561,6 @@
Settings.Global.CHAINED_BATTERY_ATTRIBUTION_ENABLED,
Settings.Global.HIDDEN_API_BLACKLIST_EXEMPTIONS,
Settings.Global.BACKUP_AGENT_TIMEOUT_PARAMETERS,
- Settings.Global.BACKUP_MULTI_USER_ENABLED,
Settings.Global.ISOLATED_STORAGE_LOCAL,
Settings.Global.ISOLATED_STORAGE_REMOTE,
Settings.Global.APPOP_HISTORY_PARAMETERS,
@@ -735,7 +724,8 @@
Settings.Secure.DOZE_WAKE_DISPLAY_GESTURE,
Settings.Secure.FACE_UNLOCK_RE_ENROLL,
Settings.Secure.TAP_GESTURE,
- Settings.Secure.WINDOW_MAGNIFICATION);
+ Settings.Secure.WINDOW_MAGNIFICATION,
+ Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER);
@Test
public void systemSettingsBackedUpOrBlacklisted() {
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
index 3f68554..b855d87 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsStateTest.java
@@ -46,6 +46,18 @@
"\uD800ab\uDC00 " + // broken surrogate pairs
"日本語";
+ private static final String TEST_PACKAGE = "package";
+ private static final String SETTING_NAME = "test_setting";
+
+ private final Object mLock = new Object();
+
+ private File mSettingsFile;
+
+ @Override
+ protected void setUp() {
+ mSettingsFile = new File(getContext().getCacheDir(), "setting.xml");
+ mSettingsFile.delete();
+ }
public void testIsBinary() {
assertFalse(SettingsState.isBinary(" abc 日本語"));
@@ -99,10 +111,10 @@
checkWriteSingleSetting(serializer, CRAZY_STRING, null);
SettingsState.writeSingleSetting(
SettingsState.SETTINGS_VERSION_NEW_ENCODING,
- serializer, null, "k", "v", null, "package", null, false);
+ serializer, null, "k", "v", null, "package", null, false, false);
SettingsState.writeSingleSetting(
SettingsState.SETTINGS_VERSION_NEW_ENCODING,
- serializer, "1", "k", "v", null, null, null, false);
+ serializer, "1", "k", "v", null, null, null, false, false);
}
private void checkWriteSingleSetting(XmlSerializer serializer, String key, String value)
@@ -115,7 +127,7 @@
// Make sure the XML serializer won't crash.
SettingsState.writeSingleSetting(
SettingsState.SETTINGS_VERSION_NEW_ENCODING,
- serializer, "1", key, value, null, "package", null, false);
+ serializer, "1", key, value, null, "package", null, false, false);
}
/**
@@ -182,4 +194,57 @@
assertEquals("p2", s.getPackageName());
}
}
+
+ public void testInitializeSetting_preserveFlagNotSet() {
+ SettingsState settingsWriter = getSettingStateObject();
+ settingsWriter.insertSettingLocked(SETTING_NAME, "1", null, false, TEST_PACKAGE);
+ settingsWriter.persistSyncLocked();
+
+ SettingsState settingsReader = getSettingStateObject();
+ assertFalse(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore());
+ }
+
+ public void testModifySetting_preserveFlagSet() {
+ SettingsState settingsWriter = getSettingStateObject();
+ settingsWriter.insertSettingLocked(SETTING_NAME, "1", null, false, TEST_PACKAGE);
+ settingsWriter.insertSettingLocked(SETTING_NAME, "2", null, false, TEST_PACKAGE);
+ settingsWriter.persistSyncLocked();
+
+ SettingsState settingsReader = getSettingStateObject();
+ assertTrue(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore());
+ }
+
+ public void testModifySettingOverrideableByRestore_preserveFlagNotSet() {
+ SettingsState settingsWriter = getSettingStateObject();
+ settingsWriter.insertSettingLocked(SETTING_NAME, "1", null, false, TEST_PACKAGE);
+ settingsWriter.insertSettingLocked(SETTING_NAME, "2", null, false, false, TEST_PACKAGE,
+ /* overrideableByRestore */ true);
+ settingsWriter.persistSyncLocked();
+
+ SettingsState settingsReader = getSettingStateObject();
+ assertFalse(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore());
+ }
+
+ public void testModifySettingOverrideableByRestore_preserveFlagAlreadySet_flagValueUnchanged() {
+ SettingsState settingsWriter = getSettingStateObject();
+ // Init the setting.
+ settingsWriter.insertSettingLocked(SETTING_NAME, "1", null, false, TEST_PACKAGE);
+ // This modification will set isValuePreservedInRestore = true.
+ settingsWriter.insertSettingLocked(SETTING_NAME, "1", null, false, TEST_PACKAGE);
+ // This modification shouldn't change the value of isValuePreservedInRestore since it's
+ // already been set to true.
+ settingsWriter.insertSettingLocked(SETTING_NAME, "2", null, false, false, TEST_PACKAGE,
+ /* overrideableByRestore */ true);
+ settingsWriter.persistSyncLocked();
+
+ SettingsState settingsReader = getSettingStateObject();
+ assertTrue(settingsReader.getSettingLocked(SETTING_NAME).isValuePreservedInRestore());
+ }
+
+ private SettingsState getSettingStateObject() {
+ SettingsState settingsState = new SettingsState(getContext(), mLock, mSettingsFile, 1,
+ SettingsState.MAX_BYTES_PER_APP_PACKAGE_UNLIMITED, Looper.getMainLooper());
+ settingsState.setVersionLocked(SettingsState.SETTINGS_VERSION_NEW_ENCODING);
+ return settingsState;
+ }
}
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index aefdce4..1c63efc 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -194,6 +194,9 @@
<uses-permission android:name="android.permission.MANAGE_APPOPS" />
+ <!-- Permission required for storage tests - FuseDaemonHostTest -->
+ <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+
<!-- Permission needed to run network tests in CTS -->
<uses-permission android:name="android.permission.MANAGE_TEST_NETWORKS" />
<!-- Permission needed to test tcp keepalive offload. -->
@@ -230,6 +233,9 @@
<!-- Permission required for CTS test - CtsOsTestCases -->
<uses-permission android:name="android.permission.MANAGE_CRATES"/>
+ <!-- Allows setting brightness from the shell -->
+ <uses-permission android:name="android.permission.CONTROL_DISPLAY_BRIGHTNESS"/>
+
<application android:label="@string/app_label"
android:theme="@android:style/Theme.DeviceDefault.DayNight"
android:defaultToDeviceProtectedStorage="true"
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 2a1e74e..0bcadce 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -70,7 +70,7 @@
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.OVERRIDE_WIFI_CONFIG" />
<uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" />
- <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
+ <uses-permission android:name="android.permission.OBSERVE_NETWORK_POLICY" />
<uses-permission android:name="android.permission.NETWORK_SETTINGS" />
<uses-permission android:name="android.permission.TETHER_PRIVILEGED" />
<uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" />
@@ -211,6 +211,7 @@
<!-- accessibility -->
<uses-permission android:name="android.permission.MODIFY_ACCESSIBILITY_DATA" />
+ <uses-permission android:name="android.permission.MANAGE_ACCESSIBILITY" />
<!-- to control accessibility volume -->
<uses-permission android:name="android.permission.CHANGE_ACCESSIBILITY_VOLUME" />
@@ -307,7 +308,8 @@
</receiver>
<activity android:name=".screenrecord.ScreenRecordDialog"
- android:theme="@style/ScreenRecord" />
+ android:theme="@style/ScreenRecord"
+ android:excludeFromRecents="true" />
<service android:name=".screenrecord.RecordingService" />
<receiver android:name=".SysuiRestartReceiver"
@@ -636,6 +638,23 @@
</intent-filter>
</activity>
+ <activity android:name=".controls.management.ControlsProviderSelectorActivity"
+ android:label="Controls Providers"
+ android:theme="@style/Theme.SystemUI"
+ android:exported="true"
+ android:excludeFromRecents="true"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
+ android:visibleToInstantApps="true">
+ </activity>
+
+ <activity android:name=".controls.management.ControlsFavoritingActivity"
+ android:parentActivityName=".controls.management.ControlsProviderSelectorActivity"
+ android:theme="@style/Theme.SystemUI"
+ android:excludeFromRecents="true"
+ android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
+ android:visibleToInstantApps="true">
+ </activity>
+
<!-- Doze with notifications, run in main sysui process for every user -->
<service
android:name=".doze.DozeService"
diff --git a/packages/SystemUI/docs/broadcasts.md b/packages/SystemUI/docs/broadcasts.md
index 56a637f..28657f2 100644
--- a/packages/SystemUI/docs/broadcasts.md
+++ b/packages/SystemUI/docs/broadcasts.md
@@ -42,24 +42,29 @@
```kotlin
/**
- * Register a receiver for broadcast with the dispatcher
- *
- * @param receiver A receiver to dispatch the [Intent]
- * @param filter A filter to determine what broadcasts should be dispatched to this receiver.
- * It will only take into account actions and categories for filtering. It must
- * have at least one action.
- * @param handler A handler to dispatch [BroadcastReceiver.onReceive]. By default, it is the
- * main handler. Pass `null` to use the default.
- * @param user A user handle to determine which broadcast should be dispatched to this receiver.
- * By default, it is the current user.
- * @throws IllegalArgumentException if the filter has other constraints that are not actions or
- * categories or the filter has no actions.
- */
+ * Register a receiver for broadcast with the dispatcher
+ *
+ * @param receiver A receiver to dispatch the [Intent]
+ * @param filter A filter to determine what broadcasts should be dispatched to this receiver.
+ * It will only take into account actions and categories for filtering. It must
+ * have at least one action.
+ * @param executor An executor to dispatch [BroadcastReceiver.onReceive]. Pass null to use an
+ * executor in the main thread (default).
+ * @param user A user handle to determine which broadcast should be dispatched to this receiver.
+ * By default, it is the current user.
+ * @throws IllegalArgumentException if the filter has other constraints that are not actions or
+ * categories or the filter has no actions.
+ */
@JvmOverloads
-fun registerReceiver(BroadcastReceiver, IntentFilter, Handler? = mainHandler, UserHandle = context.user)
+fun registerReceiver(
+ BroadcastReceiver,
+ IntentFilter,
+ Executor? = context.mainExecutor,
+ UserHandle = context.user
+) {
```
-All subscriptions are done with the same overloaded method. As specified in the doc, in order to pass a `UserHandle` with the default `Handler`, pass `null` for the `Handler`.
+All subscriptions are done with the same overloaded method. As specified in the doc, in order to pass a `UserHandle` with the default `Executor`, pass `null` for the `Executor`.
In the same way as with `Context`, subscribing the same `BroadcastReceiver` for the same user using different filters will result on two subscriptions, not in replacing the filter.
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
index 8db0d02..02c4c5e 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/statusbar/NotificationSwipeActionHelper.java
@@ -46,6 +46,8 @@
*/
public void snooze(StatusBarNotification sbn, SnoozeOption snoozeOption);
+ public void snooze(StatusBarNotification sbn, int hours);
+
public float getMinDismissVelocity();
public boolean isDismissGesture(MotionEvent ev);
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index f7e9fed..4d184d5 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -240,6 +240,15 @@
<!-- Description of airplane mode -->
<string name="airplane_mode">Airplane mode</string>
+ <!-- An explanation text that the PIN needs to be entered to prepare for an operating system update. [CHAR LIMIT=80] -->
+ <string name="kg_prompt_reason_prepare_for_update_pin">PIN required to prepare for update</string>
+
+ <!-- An explanation text that the pattern needs to be entered to prepare for an operating system update. [CHAR LIMIT=80] -->
+ <string name="kg_prompt_reason_prepare_for_update_pattern">Pattern required to prepare for update</string>
+
+ <!-- An explanation text that the password needs to be entered to prepare for an operating system update. [CHAR LIMIT=80] -->
+ <string name="kg_prompt_reason_prepare_for_update_password">Password required to prepare for update</string>
+
<!-- An explanation text that the pattern needs to be solved since the device has just been restarted. [CHAR LIMIT=80] -->
<string name="kg_prompt_reason_restart_pattern">Pattern required after device restarts</string>
diff --git a/packages/SystemUI/res/drawable/ic_add_to_home.xml b/packages/SystemUI/res/drawable/ic_add_to_home.xml
new file mode 100644
index 0000000..2b40c62
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_add_to_home.xml
@@ -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
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M18,1.01L8,1c-1.1,0 -2,0.9 -2,2v3h2V5h10v14H8v-1H6v3c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-1.99 -2,-1.99zM10,15h2V8H5v2h3.59L3,15.59 4.41,17 10,11.41V15z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_demote_conversation.xml b/packages/SystemUI/res/drawable/ic_demote_conversation.xml
new file mode 100644
index 0000000..5a88160
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_demote_conversation.xml
@@ -0,0 +1,25 @@
+<!--
+ 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
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M20,2L4.83,2l2,2L20,4v12h-1.17l1.87,1.87c0.75,-0.29 1.3,-1.02 1.3,-1.87L22,4c0,-1.1 -0.9,-2 -2,-2zM6,12h2v2L6,14zM18,11L18,9h-6.17l2,2zM18,6h-8v1.17l0.83,0.83L18,8zM0.69,3.51l1.32,1.32L2,22l4,-4h9.17l5.31,5.31 1.41,-1.41L2.1,2.1 0.69,3.51zM6,16h-0.83l-0.59,0.59 -0.58,0.58L4,6.83l2,2L6,11h2.17l5,5L6,16z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_qs_screenrecord.xml b/packages/SystemUI/res/drawable/ic_qs_screenrecord.xml
new file mode 100644
index 0000000..687c9c4
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_screenrecord.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M18,10.48L18,6c0,-1.1 -0.9,-2 -2,-2L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2v-4.48l4,3.98v-11l-4,3.98zM16,9.69L16,18L4,18L4,6h12v3.69z"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_snooze.xml b/packages/SystemUI/res/drawable/ic_snooze.xml
index b0b03a9..f4c074d 100644
--- a/packages/SystemUI/res/drawable/ic_snooze.xml
+++ b/packages/SystemUI/res/drawable/ic_snooze.xml
@@ -1,12 +1,24 @@
+<!--
+ 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
+ -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
- android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"
- android:fillColor="#757575"/>
- <path
- android:pathData="M12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z"
- android:fillColor="#757575"/>
+ android:fillColor="@android:color/white"
+ android:pathData="M9,11h3.63L9,15.2L9,17h6v-2h-3.63L15,10.8L15,9L9,9v2zM16.056,3.346l1.282,-1.535 4.607,3.85 -1.28,1.54zM3.336,7.19l-1.28,-1.536L6.662,1.81l1.28,1.536zM12,6c3.86,0 7,3.14 7,7s-3.14,7 -7,7 -7,-3.14 -7,-7 3.14,-7 7,-7m0,-2c-4.97,0 -9,4.03 -9,9s4.03,9 9,9 9,-4.03 9,-9 -4.03,-9 -9,-9z"/>
</vector>
diff --git a/packages/SystemUI/res/drawable/ic_star.xml b/packages/SystemUI/res/drawable/ic_star.xml
new file mode 100644
index 0000000..4a731b3
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_star.xml
@@ -0,0 +1,25 @@
+<!--
+ 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
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_star_border.xml b/packages/SystemUI/res/drawable/ic_star_border.xml
new file mode 100644
index 0000000..9ede40b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_star_border.xml
@@ -0,0 +1,25 @@
+<!--
+ 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
+ -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout-land/global_actions_grid_item.xml b/packages/SystemUI/res/layout-land/global_actions_grid_item.xml
index bc12338..0f9deaa 100644
--- a/packages/SystemUI/res/layout-land/global_actions_grid_item.xml
+++ b/packages/SystemUI/res/layout-land/global_actions_grid_item.xml
@@ -57,15 +57,5 @@
android:textColor="@color/global_actions_text"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
-
- <TextView
- android:visibility="gone"
- android:id="@*android:id/status"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:textColor="@color/global_actions_text"
- android:textAppearance="?android:attr/textAppearanceSmall"
- />
</LinearLayout>
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/app_item.xml b/packages/SystemUI/res/layout/app_item.xml
new file mode 100644
index 0000000..83e7887
--- /dev/null
+++ b/packages/SystemUI/res/layout/app_item.xml
@@ -0,0 +1,70 @@
+<?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.
+-->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?android:attr/selectableItemBackground"
+ android:gravity="center_vertical"
+ android:minHeight="?android:attr/listPreferredItemHeightSmall"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd">
+
+ <LinearLayout
+ android:id="@+id/icon_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="start|center_vertical"
+ android:minWidth="56dp"
+ android:orientation="horizontal"
+ android:paddingEnd="8dp"
+ android:paddingTop="4dp"
+ android:paddingBottom="4dp">
+ <ImageView
+ android:id="@android:id/icon"
+ android:layout_width="@dimen/app_icon_size"
+ android:layout_height="@dimen/app_icon_size"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="vertical"
+ android:paddingTop="16dp"
+ android:paddingBottom="16dp">
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal"
+ android:singleLine="true"
+ android:textAppearance="?android:attr/textAppearanceListItem"/>
+
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@android:id/widget_frame"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical|end"
+ android:minWidth="64dp"
+ android:orientation="vertical"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/control_item.xml b/packages/SystemUI/res/layout/control_item.xml
new file mode 100644
index 0000000..85701aa
--- /dev/null
+++ b/packages/SystemUI/res/layout/control_item.xml
@@ -0,0 +1,72 @@
+<?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.
+-->
+<androidx.constraintlayout.widget.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="100dp"
+ android:padding="15dp"
+ android:clickable="true"
+ android:focusable="true">
+
+ <ImageView
+ android:id="@+id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent" />
+
+ <TextView
+ android:id="@+id/status"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="12sp"
+ android:textColor="?android:attr/textColorPrimary"
+ android:fontFamily="@*android:string/config_bodyFontFamily"
+ android:paddingLeft="3dp"
+ app:layout_constraintBottom_toBottomOf="@+id/icon"
+ app:layout_constraintStart_toEndOf="@+id/icon" />
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="18sp"
+ android:textColor="?android:attr/textColorPrimary"
+ android:fontFamily="@*android:string/config_headlineFontFamily"
+ app:layout_constraintBottom_toTopOf="@+id/subtitle"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@+id/icon" />
+
+ <TextView
+ android:id="@+id/subtitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="16sp"
+ android:textColor="?android:attr/textColorSecondary"
+ android:fontFamily="@*android:string/config_headlineFontFamily"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintStart_toStartOf="parent" />
+
+ <CheckBox
+ android:id="@+id/favorite"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"/>
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/packages/SystemUI/res/layout/global_actions_grid_item.xml b/packages/SystemUI/res/layout/global_actions_grid_item.xml
index 4404c874..31c7cbf 100644
--- a/packages/SystemUI/res/layout/global_actions_grid_item.xml
+++ b/packages/SystemUI/res/layout/global_actions_grid_item.xml
@@ -56,15 +56,5 @@
android:textColor="@color/global_actions_text"
android:textAppearance="?android:attr/textAppearanceSmall"
/>
-
- <TextView
- android:visibility="gone"
- android:id="@*android:id/status"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:textColor="@color/global_actions_text"
- android:textAppearance="?android:attr/textAppearanceSmall"
- />
</LinearLayout>
</LinearLayout>
diff --git a/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml b/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml
new file mode 100644
index 0000000..50aa212
--- /dev/null
+++ b/packages/SystemUI/res/layout/global_actions_grid_item_v2.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2008 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.
+-->
+
+<!-- RelativeLayouts have an issue enforcing minimum heights, so just
+ work around this for now with LinearLayouts. -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:paddingTop="@dimen/global_actions_grid_item_vertical_margin"
+ android:paddingBottom="@dimen/global_actions_grid_item_vertical_margin"
+ android:paddingLeft="@dimen/global_actions_grid_item_side_margin"
+ android:paddingRight="@dimen/global_actions_grid_item_side_margin"
+ android:layout_marginRight="3dp"
+ android:layout_marginLeft="3dp"
+ android:background="@drawable/rounded_bg_full">
+ <LinearLayout
+ android:layout_width="@dimen/global_actions_grid_item_width"
+ android:layout_height="@dimen/global_actions_grid_item_height"
+ android:gravity="top|center_horizontal"
+ android:orientation="vertical">
+ <ImageView
+ android:id="@*android:id/icon"
+ android:layout_width="@dimen/global_actions_grid_item_icon_width"
+ android:layout_height="@dimen/global_actions_grid_item_icon_height"
+ android:layout_marginTop="@dimen/global_actions_grid_item_icon_top_margin"
+ android:layout_marginBottom="@dimen/global_actions_grid_item_icon_bottom_margin"
+ android:layout_marginLeft="@dimen/global_actions_grid_item_icon_side_margin"
+ android:layout_marginRight="@dimen/global_actions_grid_item_icon_side_margin"
+ android:scaleType="centerInside"
+ android:tint="@color/global_actions_text" />
+
+ <TextView
+ android:id="@*android:id/message"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:ellipsize="marquee"
+ android:marqueeRepeatLimit="marquee_forever"
+ android:singleLine="true"
+ android:gravity="center"
+ android:textSize="12dp"
+ android:textColor="@color/global_actions_text"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+
+ <TextView
+ android:visibility="gone"
+ android:id="@*android:id/status"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:textColor="@color/global_actions_text"
+ android:textAppearance="?android:attr/textAppearanceSmall" />
+ </LinearLayout>
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/global_actions_grid_v2.xml b/packages/SystemUI/res/layout/global_actions_grid_v2.xml
new file mode 100644
index 0000000..4cfb47e
--- /dev/null
+++ b/packages/SystemUI/res/layout/global_actions_grid_v2.xml
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:id="@+id/global_actions_grid_root"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:layout_marginBottom="@dimen/global_actions_grid_container_negative_shadow_offset">
+
+ <com.android.systemui.globalactions.GlobalActionsFlatLayout
+ android:id="@id/global_actions_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:theme="@style/qs_theme"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintRight_toRightOf="parent"
+ android:gravity="top | center_horizontal"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:paddingBottom="@dimen/global_actions_grid_container_shadow_offset"
+ android:layout_marginTop="@dimen/global_actions_top_margin"
+ android:layout_marginBottom="@dimen/global_actions_grid_container_negative_shadow_offset">
+ <LinearLayout
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layoutDirection="ltr"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:layout_marginBottom="@dimen/global_actions_grid_container_bottom_margin">
+ <!-- For separated items-->
+ <LinearLayout
+ android:id="@+id/separated_button"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_marginLeft="@dimen/global_actions_grid_side_margin"
+ android:layout_marginRight="@dimen/global_actions_grid_side_margin"
+ android:paddingLeft="@dimen/global_actions_grid_horizontal_padding"
+ android:paddingRight="@dimen/global_actions_grid_horizontal_padding"
+ android:paddingTop="@dimen/global_actions_grid_vertical_padding"
+ android:paddingBottom="@dimen/global_actions_grid_vertical_padding"
+ android:orientation="vertical"
+ android:gravity="center"
+ android:translationZ="@dimen/global_actions_translate"
+ />
+ <!-- Grid of action items -->
+ <com.android.systemui.globalactions.ListGridLayout
+ android:id="@android:id/list"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:gravity="right"
+ android:layout_marginRight="@dimen/global_actions_grid_side_margin"
+ android:translationZ="@dimen/global_actions_translate"
+ android:paddingLeft="@dimen/global_actions_grid_horizontal_padding"
+ android:paddingRight="@dimen/global_actions_grid_horizontal_padding"
+ android:paddingTop="@dimen/global_actions_grid_vertical_padding"
+ android:paddingBottom="@dimen/global_actions_grid_vertical_padding"
+ >
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:layoutDirection="locale"
+ />
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:layoutDirection="locale"
+ />
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:visibility="gone"
+ android:layoutDirection="locale"
+ />
+ </com.android.systemui.globalactions.ListGridLayout>
+ </LinearLayout>
+ </com.android.systemui.globalactions.GlobalActionsFlatLayout>
+
+ <LinearLayout
+ android:id="@+id/global_actions_panel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintRight_toRightOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/global_actions_view">
+
+ <FrameLayout
+ android:translationY="@dimen/global_actions_plugin_offset"
+ android:id="@+id/global_actions_panel_container"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:translationY="@dimen/global_actions_plugin_offset"
+ android:id="@+id/global_actions_controls"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ app:layout_constraintLeft_toLeftOf="parent"
+ app:layout_constraintRight_toRightOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/global_actions_panel">
+ <TextView
+ android:text="Home"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:singleLine="true"
+ android:gravity="center"
+ android:textSize="25dp"
+ android:textColor="?android:attr/textColorPrimary"
+ android:fontFamily="@*android:string/config_headlineFontFamily" />
+ <LinearLayout
+ android:id="@+id/global_actions_controls_list"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" />
+ </LinearLayout>
+ </androidx.constraintlayout.widget.ConstraintLayout>
+</ScrollView>
diff --git a/packages/SystemUI/res/layout/global_screenshot.xml b/packages/SystemUI/res/layout/global_screenshot.xml
index 6ac9da4..995cb7d 100644
--- a/packages/SystemUI/res/layout/global_screenshot.xml
+++ b/packages/SystemUI/res/layout/global_screenshot.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2011 The Android Open Source Project
+<!-- 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.
diff --git a/packages/SystemUI/res/layout/global_screenshot_action_chip.xml b/packages/SystemUI/res/layout/global_screenshot_action_chip.xml
index 6b42400..366abaa 100644
--- a/packages/SystemUI/res/layout/global_screenshot_action_chip.xml
+++ b/packages/SystemUI/res/layout/global_screenshot_action_chip.xml
@@ -14,12 +14,27 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<TextView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/global_screenshot_action_chip"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginHorizontal="@dimen/screenshot_action_chip_margin_horizontal"
- android:paddingVertical="@dimen/screenshot_action_chip_padding_vertical"
- android:paddingHorizontal="@dimen/screenshot_action_chip_padding_horizontal"
- android:background="@drawable/action_chip_background"
- android:textColor="@color/global_screenshot_button_text"/>
+<com.android.systemui.screenshot.ScreenshotActionChip
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/global_screenshot_action_chip"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="@dimen/screenshot_action_chip_margin_horizontal"
+ android:layout_gravity="center"
+ android:paddingVertical="@dimen/screenshot_action_chip_padding_vertical"
+ android:background="@drawable/action_chip_background"
+ android:gravity="center">
+ <ImageView
+ android:id="@+id/screenshot_action_chip_icon"
+ android:layout_width="@dimen/screenshot_action_chip_icon_size"
+ android:layout_height="@dimen/screenshot_action_chip_icon_size"
+ android:layout_marginStart="@dimen/screenshot_action_chip_padding_start"
+ android:layout_marginEnd="@dimen/screenshot_action_chip_padding_middle"/>
+ <TextView
+ android:id="@+id/screenshot_action_chip_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@dimen/screenshot_action_chip_padding_end"
+ android:textSize="@dimen/screenshot_action_chip_text_size"
+ android:textColor="@color/global_screenshot_button_text"/>
+</com.android.systemui.screenshot.ScreenshotActionChip>
diff --git a/packages/SystemUI/res/layout/global_screenshot_legacy.xml b/packages/SystemUI/res/layout/global_screenshot_legacy.xml
new file mode 100644
index 0000000..791c7ea
--- /dev/null
+++ b/packages/SystemUI/res/layout/global_screenshot_legacy.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <ImageView android:id="@+id/global_screenshot_legacy_background"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:src="@android:color/black"
+ android:visibility="gone" />
+ <ImageView android:id="@+id/global_screenshot_legacy"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:background="@drawable/screenshot_panel"
+ android:visibility="gone"
+ android:adjustViewBounds="true" />
+ <ImageView android:id="@+id/global_screenshot_legacy_flash"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:src="@android:color/white"
+ android:visibility="gone" />
+ <com.android.systemui.screenshot.ScreenshotSelectorView
+ android:id="@+id/global_screenshot_legacy_selector"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone"
+ android:pointerIcon="crosshair"/>
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/media_carousel.xml b/packages/SystemUI/res/layout/media_carousel.xml
new file mode 100644
index 0000000..e91f840f
--- /dev/null
+++ b/packages/SystemUI/res/layout/media_carousel.xml
@@ -0,0 +1,35 @@
+<?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
+ -->
+
+<!-- Carousel for media controls -->
+<HorizontalScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/qs_media_height"
+ android:padding="@dimen/qs_media_padding"
+ android:scrollbars="none"
+ android:visibility="gone"
+ >
+ <LinearLayout
+ android:id="@+id/media_carousel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ >
+ <!-- QSMediaPlayers will be added here dynamically -->
+ </LinearLayout>
+</HorizontalScrollView>
diff --git a/packages/SystemUI/res/layout/notification_conversation_info.xml b/packages/SystemUI/res/layout/notification_conversation_info.xml
new file mode 100644
index 0000000..8749b1a
--- /dev/null
+++ b/packages/SystemUI/res/layout/notification_conversation_info.xml
@@ -0,0 +1,254 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+-->
+
+<com.android.systemui.statusbar.notification.row.NotificationConversationInfo
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/notification_guts"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clickable="true"
+ android:clipChildren="false"
+ android:clipToPadding="true"
+ android:orientation="vertical"
+ android:background="@color/notification_material_background_color"
+ android:paddingStart="@*android:dimen/notification_content_margin_start">
+
+ <!-- Package Info -->
+ <RelativeLayout
+ android:id="@+id/header"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/notification_guts_conversation_header_height"
+ android:gravity="center_vertical"
+ android:clipChildren="false"
+ android:clipToPadding="false">
+ <ImageView
+ android:id="@+id/conversation_icon"
+ android:layout_width="@dimen/notification_guts_conversation_icon_size"
+ android:layout_height="@dimen/notification_guts_conversation_icon_size"
+ android:layout_centerVertical="true"
+ android:layout_alignParentStart="true"
+ android:layout_marginEnd="6dp" />
+ <LinearLayout
+ android:id="@+id/names"
+ android:orientation="vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:minHeight="@dimen/notification_guts_conversation_icon_size"
+ android:layout_centerVertical="true"
+ android:gravity="center_vertical"
+ android:layout_toEndOf="@id/conversation_icon">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="start"
+ android:orientation="horizontal">
+ <TextView
+ android:id="@+id/parent_channel_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ style="@style/TextAppearance.NotificationImportanceChannel"/>
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ style="@style/TextAppearance.NotificationImportanceHeader"
+ android:layout_marginStart="2dp"
+ android:layout_marginEnd="2dp"
+ android:text="@*android:string/notification_header_divider_symbol" />
+ <TextView
+ android:id="@+id/name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ style="@style/TextAppearance.NotificationImportanceChannel"/>
+ </LinearLayout>
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="start"
+ android:orientation="horizontal">
+ <TextView
+ android:id="@+id/pkg_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ style="@style/TextAppearance.NotificationImportanceChannelGroup"
+ android:ellipsize="end"
+ android:maxLines="1"/>
+ <TextView
+ android:id="@+id/group_divider"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ style="@style/TextAppearance.NotificationImportanceHeader"
+ android:layout_marginStart="2dp"
+ android:layout_marginEnd="2dp"
+ android:text="@*android:string/notification_header_divider_symbol" />
+ <TextView
+ android:id="@+id/group_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ style="@style/TextAppearance.NotificationImportanceChannel"/>
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/pkg_divider"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ style="@style/TextAppearance.NotificationImportanceHeader"
+ android:layout_marginStart="2dp"
+ android:layout_marginEnd="2dp"
+ android:layout_toEndOf="@id/name"
+ android:text="@*android:string/notification_header_divider_symbol" />
+ <TextView
+ android:id="@+id/delegate_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerVertical="true"
+ style="@style/TextAppearance.NotificationImportanceHeader"
+ android:layout_marginStart="2dp"
+ android:layout_marginEnd="2dp"
+ android:ellipsize="end"
+ android:text="@string/notification_delegate_header"
+ android:layout_toEndOf="@id/pkg_divider"
+ android:maxLines="1" />
+
+ <!-- end aligned fields -->
+ <ImageButton
+ android:id="@+id/demote"
+ android:layout_width="@dimen/notification_importance_toggle_size"
+ android:layout_height="@dimen/notification_importance_toggle_size"
+ android:layout_centerVertical="true"
+ android:background="@drawable/ripple_drawable"
+ android:contentDescription="@string/demote"
+ android:src="@drawable/ic_demote_conversation"
+ android:layout_toStartOf="@id/app_settings"
+ android:tint="@color/notification_guts_link_icon_tint"/>
+ <!-- Optional link to app. Only appears if the channel is not disabled and the app
+asked for it -->
+ <ImageButton
+ android:id="@+id/app_settings"
+ android:layout_width="@dimen/notification_importance_toggle_size"
+ android:layout_height="@dimen/notification_importance_toggle_size"
+ android:layout_centerVertical="true"
+ android:visibility="gone"
+ android:background="@drawable/ripple_drawable"
+ android:contentDescription="@string/notification_app_settings"
+ android:src="@drawable/ic_info"
+ android:layout_toStartOf="@id/info"
+ android:tint="@color/notification_guts_link_icon_tint"/>
+ <ImageButton
+ android:id="@+id/info"
+ android:layout_width="@dimen/notification_importance_toggle_size"
+ android:layout_height="@dimen/notification_importance_toggle_size"
+ android:layout_centerVertical="true"
+ android:background="@drawable/ripple_drawable"
+ android:contentDescription="@string/notification_more_settings"
+ android:src="@drawable/ic_settings"
+ android:layout_alignParentEnd="true"
+ android:tint="@color/notification_guts_link_icon_tint"/>
+ </RelativeLayout>
+
+ <LinearLayout
+ android:id="@+id/actions"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginEnd="@*android:dimen/notification_content_margin_end"
+ android:clipChildren="false"
+ android:clipToPadding="false"
+ android:orientation="vertical">
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="0.5dp"
+ android:background="@color/GM2_grey_300" />
+ <Button
+ android:id="@+id/bubble"
+ android:layout_height="@dimen/notification_guts_conversation_action_height"
+ android:layout_width="match_parent"
+ style="?android:attr/borderlessButtonStyle"
+ android:text="@string/notification_conversation_favorite"
+ android:gravity="left|center_vertical"
+ android:drawableStart="@drawable/ic_create_bubble"
+ android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
+ android:drawableTint="@color/notification_guts_link_icon_tint"/>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="0.5dp"
+ android:background="@color/GM2_grey_300" />
+ <Button
+ android:id="@+id/home"
+ android:layout_height="@dimen/notification_guts_conversation_action_height"
+ android:layout_width="match_parent"
+ style="?android:attr/borderlessButtonStyle"
+ android:text="@string/notification_conversation_home_screen"
+ android:gravity="left|center_vertical"
+ android:drawableStart="@drawable/ic_add_to_home"
+ android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
+ android:drawableTint="@color/notification_guts_link_icon_tint"/>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="0.5dp"
+ android:background="@color/GM2_grey_300" />
+ <Button
+ android:id="@+id/fave"
+ android:layout_height="@dimen/notification_guts_conversation_action_height"
+ android:layout_width="match_parent"
+ style="?android:attr/borderlessButtonStyle"
+ android:gravity="left|center_vertical"
+ android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
+ android:drawableTint="@color/notification_guts_link_icon_tint"/>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="0.5dp"
+ android:background="@color/GM2_grey_300" />
+ <Button
+ android:id="@+id/snooze"
+ android:layout_height="@dimen/notification_guts_conversation_action_height"
+ android:layout_width="match_parent"
+ style="?android:attr/borderlessButtonStyle"
+ android:text="@string/notification_menu_snooze_action"
+ android:gravity="left|center_vertical"
+ android:drawableStart="@drawable/ic_snooze"
+ android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
+ android:drawableTint="@color/notification_guts_link_icon_tint"/>
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="0.5dp"
+ android:background="@color/GM2_grey_300" />
+ <Button
+ android:id="@+id/mute"
+ android:layout_height="@dimen/notification_guts_conversation_action_height"
+ android:layout_width="match_parent"
+ style="?android:attr/borderlessButtonStyle"
+ android:text="@string/notification_conversation_mute"
+ android:gravity="left|center_vertical"
+ android:drawableStart="@drawable/ic_notifications_silence"
+ android:drawablePadding="@dimen/notification_guts_conversation_action_text_padding_start"
+ android:drawableTint="@color/notification_guts_link_icon_tint"/>
+
+ </LinearLayout>
+
+</com.android.systemui.statusbar.notification.row.NotificationConversationInfo>
diff --git a/packages/SystemUI/res/layout/people_strip.xml b/packages/SystemUI/res/layout/people_strip.xml
index f0ac08b..982aa8e 100644
--- a/packages/SystemUI/res/layout/people_strip.xml
+++ b/packages/SystemUI/res/layout/people_strip.xml
@@ -18,7 +18,10 @@
<com.android.systemui.statusbar.notification.stack.PeopleHubView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
- android:layout_height="105dp">
+ android:layout_height="@dimen/notification_section_header_height"
+ android:focusable="true"
+ android:clickable="true"
+>
<com.android.systemui.statusbar.notification.row.NotificationBackgroundView
android:id="@+id/backgroundNormal"
@@ -34,199 +37,56 @@
android:id="@+id/people_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:paddingTop="12dp"
- android:paddingBottom="12dp"
android:gravity="center"
android:orientation="horizontal">
- <View
- android:layout_width="8dp"
- android:layout_height="match_parent"
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
android:layout_weight="1"
+ android:layout_marginStart="@dimen/notification_section_header_padding_left"
+ android:gravity="start"
+ android:textAlignment="gravity"
+ android:text="@string/notification_section_header_conversations"
+ android:textSize="12sp"
+ android:textColor="@color/notification_section_header_label_color"
+ android:fontFamily="@*android:string/config_headlineFontFamilyMedium"
/>
- <LinearLayout
- android:layout_width="70dp"
- android:layout_height="match_parent"
- android:gravity="center_horizontal"
- android:orientation="vertical"
- android:visibility="invisible">
-
- <ImageView
- android:id="@+id/person_icon"
- android:layout_width="36dp"
- android:layout_height="36dp"
- android:scaleType="fitCenter"
- />
-
- <TextView
- android:id="@+id/person_name"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingTop="8dp"
- android:ellipsize="end"
- android:maxLines="2"
- android:textAlignment="center"
- />
-
- </LinearLayout>
-
- <View
- android:layout_width="8dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
+ <ImageView
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:padding="8dp"
+ android:scaleType="fitCenter"
/>
- <View
- android:layout_width="8dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
+ <ImageView
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:padding="8dp"
+ android:scaleType="fitCenter"
/>
- <LinearLayout
- android:layout_width="70dp"
- android:layout_height="match_parent"
- android:gravity="center_horizontal"
- android:orientation="vertical"
- android:visibility="invisible">
-
- <ImageView
- android:id="@+id/person_icon"
- android:layout_width="36dp"
- android:layout_height="36dp"
- android:scaleType="fitCenter"
- />
-
- <TextView
- android:id="@+id/person_name"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingTop="8dp"
- android:ellipsize="end"
- android:maxLines="2"
- android:textAlignment="center"
- />
-
- </LinearLayout>
-
- <View
- android:layout_width="8dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
+ <ImageView
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:padding="8dp"
+ android:scaleType="fitCenter"
/>
- <View
- android:layout_width="8dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
+ <ImageView
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:padding="8dp"
+ android:scaleType="fitCenter"
/>
- <LinearLayout
- android:layout_width="70dp"
- android:layout_height="match_parent"
- android:gravity="center_horizontal"
- android:orientation="vertical"
- android:visibility="invisible">
-
- <ImageView
- android:id="@+id/person_icon"
- android:layout_width="36dp"
- android:layout_height="36dp"
- android:scaleType="fitCenter"
- />
-
- <TextView
- android:id="@+id/person_name"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingTop="8dp"
- android:ellipsize="end"
- android:maxLines="2"
- android:textAlignment="center"
- />
-
- </LinearLayout>
-
- <View
- android:layout_width="8dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- />
-
- <View
- android:layout_width="8dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- />
-
- <LinearLayout
- android:layout_width="70dp"
- android:layout_height="match_parent"
- android:gravity="center_horizontal"
- android:orientation="vertical"
- android:visibility="invisible">
-
- <ImageView
- android:id="@+id/person_icon"
- android:layout_width="36dp"
- android:layout_height="36dp"
- android:scaleType="fitCenter"
- />
-
- <TextView
- android:id="@+id/person_name"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingTop="8dp"
- android:ellipsize="end"
- android:maxLines="2"
- android:textAlignment="center"
- />
-
- </LinearLayout>
-
- <View
- android:layout_width="8dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- />
-
- <View
- android:layout_width="8dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- />
-
- <LinearLayout
- android:layout_width="70dp"
- android:layout_height="match_parent"
- android:gravity="center_horizontal"
- android:orientation="vertical"
- android:visibility="invisible">
-
- <ImageView
- android:id="@+id/person_icon"
- android:layout_width="36dp"
- android:layout_height="36dp"
- android:scaleType="fitCenter"
- />
-
- <TextView
- android:id="@+id/person_name"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:paddingTop="8dp"
- android:ellipsize="end"
- android:maxLines="2"
- android:textAlignment="center"
- />
-
- </LinearLayout>
-
- <View
- android:layout_width="8dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
+ <ImageView
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:layout_marginEnd="8dp"
+ android:padding="8dp"
+ android:scaleType="fitCenter"
/>
</LinearLayout>
@@ -236,4 +96,4 @@
android:layout_width="match_parent"
android:layout_height="match_parent" />
-</com.android.systemui.statusbar.notification.stack.PeopleHubView>
\ No newline at end of file
+</com.android.systemui.statusbar.notification.stack.PeopleHubView>
diff --git a/packages/SystemUI/res/layout/screen_record_dialog.xml b/packages/SystemUI/res/layout/screen_record_dialog.xml
deleted file mode 100644
index 3d63b7d..0000000
--- a/packages/SystemUI/res/layout/screen_record_dialog.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:clipChildren="false"
- android:clipToPadding="false"
- android:gravity="top"
- android:orientation="vertical"
- android:padding="@dimen/global_actions_padding"
- android:background="@drawable/rounded_bg_full">
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical">
- <CheckBox
- android:id="@+id/checkbox_mic"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:text="@string/screenrecord_mic_label"/>
- <CheckBox
- android:id="@+id/checkbox_taps"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:text="@string/screenrecord_taps_label"/>
- <Button
- android:id="@+id/record_button"
- android:layout_width="match_parent"
- android:layout_height="50dp"
- android:text="@string/screenrecord_start_label"
- />
- </LinearLayout>
-
-</LinearLayout>
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 4869be1..479f255 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -17,9 +17,14 @@
*/
-->
-<merge
+
+<com.android.systemui.statusbar.phone.NotificationPanelView
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:systemui="http://schemas.android.com/apk/res-auto">
+ xmlns:systemui="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/notification_panel"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@android:color/transparent">
<FrameLayout
android:id="@+id/big_clock_container"
android:layout_width="match_parent"
@@ -97,4 +102,4 @@
android:background="@drawable/qs_navbar_scrim" />
<include layout="@layout/status_bar_expanded_plugin_frame"/>
-</merge>
+</com.android.systemui.statusbar.phone.NotificationPanelView>
diff --git a/packages/SystemUI/res/layout/super_status_bar.xml b/packages/SystemUI/res/layout/super_status_bar.xml
index 57834da..9716a00 100644
--- a/packages/SystemUI/res/layout/super_status_bar.xml
+++ b/packages/SystemUI/res/layout/super_status_bar.xml
@@ -64,7 +64,7 @@
sysui:ignoreRightInset="true"
/>
- <ViewStub android:id="@+id/status_bar_expanded"
+ <include layout="@layout/status_bar_expanded"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible" />
diff --git a/packages/SystemUI/res/values/attrs_car.xml b/packages/SystemUI/res/values/attrs_car.xml
deleted file mode 100644
index 49b87f3..0000000
--- a/packages/SystemUI/res/values/attrs_car.xml
+++ /dev/null
@@ -1,103 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-
-<resources>
- <attr name="icon" format="reference"/>
- <attr name="selectedIcon" format="reference"/>
- <attr name="intent" format="string"/>
- <attr name="longIntent" format="string"/>
- <attr name="selectedAlpha" format="float" />
- <attr name="unselectedAlpha" format="float" />
-
- <!-- Allow for custom attribs to be added to a facet button -->
- <declare-styleable name="CarFacetButton">
- <!-- icon to be rendered (drawable) -->
- <attr name="icon"/>
- <!-- icon to be rendered when in selected state -->
- <attr name="selectedIcon"/>
- <!-- intent to start when button is click -->
- <attr name="intent"/>
- <!-- intent to start when a long press has happened -->
- <attr name="longIntent"/>
- <!-- categories that will be added as extras to the fired intents -->
- <attr name="categories" format="string"/>
- <!-- package names that will be added as extras to the fired intents -->
- <attr name="packages" format="string" />
- <!-- componentName names that will be used for detecting selected state -->
- <attr name="componentNames" format="string" />
- <!-- Alpha value to used when in selected state. Defaults 1f -->
- <attr name="selectedAlpha" />
- <!-- Alpha value to used when in un-selected state. Defaults 0.7f -->
- <attr name="unselectedAlpha" />
- <!-- Render a "more" icon. Defaults true -->
- <attr name="useMoreIcon" format="boolean" />
-
- </declare-styleable>
-
-
- <!-- Allow for custom attribs to be added to a nav button -->
- <declare-styleable name="CarNavigationButton">
- <!-- intent to start when button is click -->
- <attr name="intent" />
- <!-- intent to start when a long press has happened -->
- <attr name="longIntent" />
- <!-- start the intent as a broad cast instead of an activity if true-->
- <attr name="broadcast" format="boolean"/>
- <!-- Alpha value to used when in selected state. Defaults 1f -->
- <attr name="selectedAlpha" />
- <!-- Alpha value to used when in un-selected state. Defaults 0.7f -->
- <attr name="unselectedAlpha" />
- <!-- icon to be rendered when in selected state -->
- <attr name="selectedIcon" />
- <!-- icon to be rendered (drawable) -->
- <attr name="icon"/>
- </declare-styleable>
-
- <!-- Custom attributes to configure hvac values -->
- <declare-styleable name="TemperatureView">
- <attr name="hvacAreaId" format="integer"/>
- <attr name="hvacPropertyId" format="integer"/>
- <attr name="hvacTempFormat" format="string"/>
- </declare-styleable>
-
- <declare-styleable name="carVolumeItems"/>
- <declare-styleable name="carVolumeItems_item">
- <!-- Align with AudioAttributes.USAGE_* -->
- <attr name="usage">
- <enum name="unknown" value="0"/>
- <enum name="media" value="1"/>
- <enum name="voice_communication" value="2"/>
- <enum name="voice_communication_signalling" value="3"/>
- <enum name="alarm" value="4"/>
- <enum name="notification" value="5"/>
- <enum name="notification_ringtone" value="6"/>
- <enum name="notification_communication_request" value="7"/>
- <enum name="notification_communication_instant" value="8"/>
- <enum name="notification_communication_delayed" value="9"/>
- <enum name="notification_event" value="10"/>
- <enum name="assistance_accessibility" value="11"/>
- <enum name="assistance_navigation_guidance" value="12"/>
- <enum name="assistance_sonification" value="13"/>
- <enum name="game" value="14"/>
- <!-- hidden, do not use -->
- <!-- enum name="virtual_source" value="15"/ -->
- <enum name="assistant" value="16"/>
- </attr>
-
- <!-- Icon resource ids to render on UI -->
- <attr name="icon" />
- </declare-styleable>
-</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 640f31b..8963157 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -109,7 +109,7 @@
<!-- The default tiles to display in QuickSettings -->
<string name="quick_settings_tiles_default" translatable="false">
- wifi,bt,dnd,flashlight,rotation,battery,cell,airplane,cast
+ wifi,bt,dnd,flashlight,rotation,battery,cell,airplane,cast,screenrecord
</string>
<!-- The minimum number of tiles to display in QuickSettings -->
@@ -117,7 +117,7 @@
<!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
<string name="quick_settings_tiles_stock" translatable="false">
- wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,controls
+ wifi,cell,battery,dnd,flashlight,rotation,bt,airplane,location,hotspot,inversion,saver,dark,work,cast,night,controls,screenrecord
</string>
<!-- The tiles to display in QuickSettings -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index da0323a..53df025 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -213,6 +213,11 @@
<!-- The horizontal space around the buttons in the inline settings -->
<dimen name="notification_guts_button_horizontal_spacing">8dp</dimen>
+ <dimen name="notification_guts_conversation_header_height">84dp</dimen>
+ <dimen name="notification_guts_conversation_icon_size">52dp</dimen>
+ <dimen name="notification_guts_conversation_action_height">56dp</dimen>
+ <dimen name="notification_guts_conversation_action_text_padding_start">32dp</dimen>
+
<!-- The height of the header in inline settings -->
<dimen name="notification_guts_header_height">24dp</dimen>
@@ -288,14 +293,20 @@
<!-- Dimensions related to screenshots -->
<!-- The padding on the global screenshot background image -->
+ <dimen name="global_screenshot_legacy_bg_padding">20dp</dimen>
<dimen name="global_screenshot_bg_padding">20dp</dimen>
<dimen name="screenshot_action_container_corner_radius">10dp</dimen>
- <dimen name="screenshot_action_container_padding">20dp</dimen>
+ <dimen name="screenshot_action_container_padding">10dp</dimen>
<!-- Radius of the chip background on global screenshot actions -->
<dimen name="screenshot_button_corner_radius">20dp</dimen>
- <dimen name="screenshot_action_chip_margin_horizontal">10dp</dimen>
+ <dimen name="screenshot_action_chip_margin_horizontal">4dp</dimen>
<dimen name="screenshot_action_chip_padding_vertical">10dp</dimen>
- <dimen name="screenshot_action_chip_padding_horizontal">15dp</dimen>
+ <dimen name="screenshot_action_chip_icon_size">20dp</dimen>
+ <dimen name="screenshot_action_chip_padding_start">4dp</dimen>
+ <!-- Padding between icon and text -->
+ <dimen name="screenshot_action_chip_padding_middle">8dp</dimen>
+ <dimen name="screenshot_action_chip_padding_end">12dp</dimen>
+ <dimen name="screenshot_action_chip_text_size">14sp</dimen>
<!-- The width of the view containing navigation buttons -->
@@ -950,6 +961,9 @@
<dimen name="cell_overlay_padding">18dp</dimen>
<!-- Global actions power menu -->
+ <dimen name="global_actions_top_margin">12dp</dimen>
+ <dimen name="global_actions_plugin_offset">-145dp</dimen>
+
<dimen name="global_actions_panel_width">120dp</dimen>
<dimen name="global_actions_padding">12dp</dimen>
<dimen name="global_actions_translate">9dp</dimen>
@@ -1151,4 +1165,5 @@
<dimen name="magnifier_up_down_controls_width">45dp</dimen>
<dimen name="magnifier_up_down_controls_height">40dp</dimen>
+ <dimen name="app_icon_size">32dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 52c0a26..9129938 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -928,6 +928,13 @@
<!-- QuickSettings: NFC (on) [CHAR LIMIT=NONE] -->
<string name="quick_settings_nfc_on">NFC is enabled</string>
+ <!-- QuickSettings: Screen record tile [CHAR LIMIT=NONE] -->
+ <string name="quick_settings_screen_record_label">Screen Record</string>
+ <!-- QuickSettings: Text to prompt the user to begin a new recording [CHAR LIMIT=20] -->
+ <string name="quick_settings_screen_record_start">Start</string>
+ <!-- QuickSettings: Text to prompt the user to stop an ongoing recording [CHAR LIMIT=20] -->
+ <string name="quick_settings_screen_record_stop">Stop</string>
+
<!-- Recents: Text that shows above the navigation bar after launching a few apps. [CHAR LIMIT=NONE] -->
<string name="recents_swipe_up_onboarding">Swipe up to switch apps</string>
<!-- Recents: Text that shows above the navigation bar after launching several apps. [CHAR LIMIT=NONE] -->
@@ -1163,6 +1170,9 @@
<!-- Section title for notifications that do not vibrate or make noise. [CHAR LIMIT=40] -->
<string name="notification_section_header_gentle">Silent notifications</string>
+ <!-- Section title for conversational notifications. [CHAR LIMIT=40] -->
+ <string name="notification_section_header_conversations">Conversations</string>
+
<!-- Content description for accessibility: Tapping this button will dismiss all gentle notifications [CHAR LIMIT=NONE] -->
<string name="accessibility_notification_section_header_gentle_clear_all">Clear all silent notifications</string>
@@ -1788,6 +1798,29 @@
<string name="notification_done">Done</string>
<!-- Notification: inline controls: undo block button -->
<string name="inline_undo">Undo</string>
+ <!-- Notification: Conversation: control panel, label for button that demotes notification from conversation to normal notification -->
+ <string name="demote">Mark this notification as not a conversation</string>
+
+ <!-- [CHAR LIMIT=100] Mark this conversation as a favorite -->
+ <string name="notification_conversation_favorite">Favorite</string>
+
+ <!-- [CHAR LIMIT=100] Unmark this conversation as a favorite -->
+ <string name="notification_conversation_unfavorite">Unfavorite</string>
+
+ <!-- [CHAR LIMIT=100] Mute this conversation -->
+ <string name="notification_conversation_mute">Mute</string>
+
+ <!-- [CHAR LIMIT=100] Umute this conversation -->
+ <string name="notification_conversation_unmute">Unmute</string>
+
+ <!-- [CHAR LIMIT=100] Show notification as bubble -->
+ <string name="notification_conversation_bubble">Show as bubble</string>
+
+ <!-- [CHAR LIMIT=100] Turn off bubbles for notification -->
+ <string name="notification_conversation_unbubble">Turn off bubbles</string>
+
+ <!-- [CHAR LIMIT=100] Add this conversation to home screen -->
+ <string name="notification_conversation_home_screen">Add to home screen</string>
<!-- Notification: Menu row: Content description for menu items. [CHAR LIMIT=NONE] -->
<string name="notification_menu_accessibility"><xliff:g id="app_name" example="YouTube">%1$s</xliff:g> <xliff:g id="menu_description" example="notification controls">%2$s</xliff:g></string>
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index df0dc46..e475ef1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -387,8 +387,8 @@
* Refresh the time of the clock, due to either time tick broadcast or doze time tick alarm.
*/
public void refresh() {
- mClockView.refresh();
- mClockViewBold.refresh();
+ mClockView.refreshTime();
+ mClockViewBold.refreshTime();
if (mClockPlugin != null) {
mClockPlugin.onTimeTick();
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
index 2c8f238..ed1cd81 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostView.java
@@ -168,8 +168,9 @@
*
* @param reason a flag indicating which string should be shown, see
* {@link KeyguardSecurityView#PROMPT_REASON_NONE},
- * {@link KeyguardSecurityView#PROMPT_REASON_RESTART} and
- * {@link KeyguardSecurityView#PROMPT_REASON_TIMEOUT}.
+ * {@link KeyguardSecurityView#PROMPT_REASON_RESTART},
+ * {@link KeyguardSecurityView#PROMPT_REASON_TIMEOUT}, and
+ * {@link KeyguardSecurityView#PROMPT_REASON_PREPARE_FOR_UPDATE}.
*/
public void showPromptReason(int reason) {
mSecurityContainer.showPromptReason(reason);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
index f8f3dc8..718bcf1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordView.java
@@ -137,6 +137,8 @@
return R.string.kg_prompt_reason_device_admin;
case PROMPT_REASON_USER_REQUEST:
return R.string.kg_prompt_reason_user_request;
+ case PROMPT_REASON_PREPARE_FOR_UPDATE:
+ return R.string.kg_prompt_reason_prepare_for_update_password;
case PROMPT_REASON_NONE:
return 0;
default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
index 9eb168a..48c6bd1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternView.java
@@ -438,6 +438,10 @@
case PROMPT_REASON_USER_REQUEST:
mSecurityMessageDisplay.setMessage(R.string.kg_prompt_reason_user_request);
break;
+ case PROMPT_REASON_PREPARE_FOR_UPDATE:
+ mSecurityMessageDisplay.setMessage(
+ R.string.kg_prompt_reason_prepare_for_update_pattern);
+ break;
case PROMPT_REASON_NONE:
break;
default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index c67decc..6d865ab 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -116,6 +116,8 @@
return R.string.kg_prompt_reason_device_admin;
case PROMPT_REASON_USER_REQUEST:
return R.string.kg_prompt_reason_user_request;
+ case PROMPT_REASON_PREPARE_FOR_UPDATE:
+ return R.string.kg_prompt_reason_prepare_for_update_pin;
case PROMPT_REASON_NONE:
return 0;
default:
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
index e108194..09d4d5f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityView.java
@@ -51,6 +51,11 @@
*/
int PROMPT_REASON_AFTER_LOCKOUT = 5;
+ /***
+ * Strong auth is require to prepare for an unattended update.
+ */
+ int PROMPT_REASON_PREPARE_FOR_UPDATE = 6;
+
/**
* Interface back to keyguard to tell it when security
* @param callback
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 694c623..65fc215 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -31,8 +31,8 @@
import static android.os.BatteryManager.EXTRA_PLUGGED;
import static android.os.BatteryManager.EXTRA_STATUS;
import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE;
+import static android.telephony.TelephonyManager.MODEM_COUNT_DUAL_MODEM;
-import static com.android.internal.telephony.PhoneConstants.MAX_PHONE_COUNT_DUAL_SIM;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
@@ -92,7 +92,6 @@
import android.util.SparseBooleanArray;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.widget.LockPatternUtils;
import com.android.settingslib.WirelessUtils;
@@ -446,7 +445,7 @@
*/
public List<SubscriptionInfo> getFilteredSubscriptionInfo(boolean forceReload) {
List<SubscriptionInfo> subscriptions = getSubscriptionInfo(false);
- if (subscriptions.size() == MAX_PHONE_COUNT_DUAL_SIM) {
+ if (subscriptions.size() == MODEM_COUNT_DUAL_MODEM) {
SubscriptionInfo info1 = subscriptions.get(0);
SubscriptionInfo info2 = subscriptions.get(1);
if (info1.getGroupUuid() != null && info1.getGroupUuid().equals(info2.getGroupUuid())) {
@@ -1072,9 +1071,9 @@
mHandler.sendMessage(mHandler.obtainMessage(MSG_PHONE_STATE_CHANGED, state));
} else if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(action)) {
mHandler.sendEmptyMessage(MSG_AIRPLANE_MODE_CHANGED);
- } else if (TelephonyIntents.ACTION_SERVICE_STATE_CHANGED.equals(action)) {
+ } else if (Intent.ACTION_SERVICE_STATE.equals(action)) {
ServiceState serviceState = ServiceState.newFromBundle(intent.getExtras());
- int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+ int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
if (DEBUG) {
Log.v(TAG, "action " + action + " serviceState=" + serviceState + " subId="
@@ -1236,8 +1235,8 @@
throw new IllegalArgumentException("only handles intent ACTION_SIM_STATE_CHANGED");
}
String stateExtra = intent.getStringExtra(Intent.EXTRA_SIM_STATE);
- int slotId = intent.getIntExtra(PhoneConstants.PHONE_KEY, 0);
- int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+ int slotId = intent.getIntExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 0);
+ int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
if (Intent.SIM_STATE_ABSENT.equals(stateExtra)) {
final String absentReason = intent
@@ -1633,12 +1632,12 @@
filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
- filter.addAction(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
- filter.addAction(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
+ filter.addAction(Intent.ACTION_SERVICE_STATE);
+ filter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
filter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
- broadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, mHandler);
+ broadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter, mHandler);
final IntentFilter allUserFilter = new IntentFilter();
allUserFilter.addAction(Intent.ACTION_USER_INFO_CHANGED);
@@ -1649,8 +1648,8 @@
allUserFilter.addAction(ACTION_USER_UNLOCKED);
allUserFilter.addAction(ACTION_USER_STOPPED);
allUserFilter.addAction(ACTION_USER_REMOVED);
- broadcastDispatcher.registerReceiver(mBroadcastAllReceiver, allUserFilter, mHandler,
- UserHandle.ALL);
+ broadcastDispatcher.registerReceiverWithHandler(mBroadcastAllReceiver, allUserFilter,
+ mHandler, UserHandle.ALL);
mSubscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionListener);
try {
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java
index eba2400..99e122e 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/AnalogClockController.java
@@ -188,7 +188,7 @@
public void onTimeTick() {
mAnalogClock.onTimeChanged();
mBigClockView.onTimeChanged();
- mLockClock.refresh();
+ mLockClock.refreshTime();
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java b/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
index 3a2fbe5..fac923c01 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/BubbleClockController.java
@@ -195,7 +195,7 @@
public void onTimeTick() {
mAnalogClock.onTimeChanged();
mView.onTimeChanged();
- mLockClock.refresh();
+ mLockClock.refreshTime();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/Dependency.java b/packages/SystemUI/src/com/android/systemui/Dependency.java
index eecc54c..d149591 100644
--- a/packages/SystemUI/src/com/android/systemui/Dependency.java
+++ b/packages/SystemUI/src/com/android/systemui/Dependency.java
@@ -56,6 +56,7 @@
import com.android.systemui.power.PowerUI;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.recents.Recents;
+import com.android.systemui.screenrecord.RecordingController;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.shared.system.ActivityManagerWrapper;
import com.android.systemui.shared.system.DevicePolicyManagerWrapper;
@@ -121,6 +122,7 @@
import com.android.systemui.util.leak.LeakDetector;
import com.android.systemui.util.leak.LeakReporter;
import com.android.systemui.util.sensors.AsyncSensorManager;
+import com.android.systemui.wm.DisplayImeController;
import com.android.systemui.wm.DisplayWindowController;
import com.android.systemui.wm.SystemWindows;
@@ -321,6 +323,8 @@
@Inject Lazy<StatusBar> mStatusBar;
@Inject Lazy<DisplayWindowController> mDisplayWindowController;
@Inject Lazy<SystemWindows> mSystemWindows;
+ @Inject Lazy<DisplayImeController> mDisplayImeController;
+ @Inject Lazy<RecordingController> mRecordingController;
@Inject
public Dependency() {
@@ -509,6 +513,7 @@
mProviders.put(StatusBar.class, mStatusBar::get);
mProviders.put(DisplayWindowController.class, mDisplayWindowController::get);
mProviders.put(SystemWindows.class, mSystemWindows::get);
+ mProviders.put(DisplayImeController.class, mDisplayImeController::get);
// TODO(b/118592525): to support multi-display , we start to add something which is
// per-display, while others may be global. I think it's time to add
@@ -516,6 +521,8 @@
// Dependency problem.
mProviders.put(AutoHideController.class, mAutoHideController::get);
+ mProviders.put(RecordingController.class, mRecordingController::get);
+
sDependency = this;
}
diff --git a/packages/SystemUI/src/com/android/systemui/DumpController.kt b/packages/SystemUI/src/com/android/systemui/DumpController.kt
index 8c7075b..f14c4cd 100644
--- a/packages/SystemUI/src/com/android/systemui/DumpController.kt
+++ b/packages/SystemUI/src/com/android/systemui/DumpController.kt
@@ -30,6 +30,14 @@
/**
* Controller that allows any [Dumpable] to subscribe and be dumped along with other SystemUI
* dependencies.
+ *
+ * To dump a specific dumpable on-demand:
+ *
+ * ```
+ * $ adb shell dumpsys activity service com.android.systemui/.SystemUIService dependency DumpController <tag1>,<tag2>,<tag3>
+ * ```
+ *
+ * Where tag1, tag2, etc. are the tags of the dumpables you want to dump.
*/
@Singleton
class DumpController @Inject constructor() : Dumpable {
diff --git a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
index 8105faa..eab9706 100644
--- a/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
+++ b/packages/SystemUI/src/com/android/systemui/ForegroundServiceNotificationListener.java
@@ -27,9 +27,9 @@
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.NotifCollection;
-import com.android.systemui.statusbar.notification.collection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import javax.inject.Inject;
import javax.inject.Singleton;
@@ -49,7 +49,7 @@
public ForegroundServiceNotificationListener(Context context,
ForegroundServiceController foregroundServiceController,
NotificationEntryManager notificationEntryManager,
- NotifCollection notifCollection) {
+ NotifPipeline notifPipeline) {
mContext = context;
mForegroundServiceController = foregroundServiceController;
@@ -77,7 +77,7 @@
});
mEntryManager.addNotificationLifetimeExtender(new ForegroundServiceLifetimeExtender());
- notifCollection.addCollectionListener(new NotifCollectionListener() {
+ notifPipeline.addCollectionListener(new NotifCollectionListener() {
@Override
public void onEntryAdded(NotificationEntry entry) {
addNotification(entry, entry.getImportance());
diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
index 0e736dc..c533755 100644
--- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
+++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java
@@ -266,7 +266,7 @@
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_USER_SWITCHED);
- mBroadcastDispatcher.registerReceiver(mIntentReceiver, filter, mHandler);
+ mBroadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter, mHandler);
mOverlay.addOnLayoutChangeListener(new OnLayoutChangeListener() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
index b083123..23d6458 100644
--- a/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/appops/AppOpsControllerImpl.java
@@ -18,7 +18,6 @@
import android.app.AppOpsManager;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
@@ -64,7 +63,6 @@
private H mBGHandler;
private final List<AppOpsController.Callback> mCallbacks = new ArrayList<>();
private final ArrayMap<Integer, Set<Callback>> mCallbacksByCode = new ArrayMap<>();
- private final PermissionFlagsCache mFlagsCache;
private boolean mListening;
@GuardedBy("mActiveItems")
@@ -81,17 +79,10 @@
};
@Inject
- public AppOpsControllerImpl(Context context, @Background Looper bgLooper,
- DumpController dumpController) {
- this(context, bgLooper, new PermissionFlagsCache(context), dumpController);
- }
-
- @VisibleForTesting
- protected AppOpsControllerImpl(Context context, Looper bgLooper, PermissionFlagsCache cache,
- DumpController dumpController) {
+ public AppOpsControllerImpl(Context context,
+ @Background Looper bgLooper, DumpController dumpController) {
mContext = context;
mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
- mFlagsCache = cache;
mBGHandler = new H(bgLooper);
final int numOps = OPS.length;
for (int i = 0; i < numOps; i++) {
@@ -209,7 +200,14 @@
mNotedItems.remove(item);
if (DEBUG) Log.w(TAG, "Removed item: " + item.toString());
}
- notifySuscribers(code, uid, packageName, false);
+ boolean active;
+ // Check if the item is also active
+ synchronized (mActiveItems) {
+ active = getAppOpItemLocked(mActiveItems, code, uid, packageName) != null;
+ }
+ if (!active) {
+ notifySuscribers(code, uid, packageName, false);
+ }
}
private boolean addNoted(int code, int uid, String packageName) {
@@ -224,64 +222,13 @@
createdNew = true;
}
}
+ // We should keep this so we make sure it cannot time out.
+ mBGHandler.removeCallbacksAndMessages(item);
mBGHandler.scheduleRemoval(item, NOTED_OP_TIME_DELAY_MS);
return createdNew;
}
/**
- * Does the app-op code refer to a user sensitive permission for the specified user id
- * and package. Only user sensitive permission should be shown to the user by default.
- *
- * @param appOpCode The code of the app-op.
- * @param uid The uid of the user.
- * @param packageName The name of the package.
- *
- * @return {@code true} iff the app-op item is user sensitive
- */
- private boolean isUserSensitive(int appOpCode, int uid, String packageName) {
- String permission = AppOpsManager.opToPermission(appOpCode);
- if (permission == null) {
- return false;
- }
- int permFlags = mFlagsCache.getPermissionFlags(permission,
- packageName, UserHandle.getUserHandleForUid(uid));
- return (permFlags & PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED) != 0;
- }
-
- /**
- * Does the app-op item refer to an operation that should be shown to the user.
- * Only specficic ops (like SYSTEM_ALERT_WINDOW) or ops that refer to user sensitive
- * permission should be shown to the user by default.
- *
- * @param item The item
- *
- * @return {@code true} iff the app-op item should be shown to the user
- */
- private boolean isUserVisible(AppOpItem item) {
- return isUserVisible(item.getCode(), item.getUid(), item.getPackageName());
- }
-
-
- /**
- * Does the app-op, uid and package name, refer to an operation that should be shown to the
- * user. Only specficic ops (like {@link AppOpsManager.OP_SYSTEM_ALERT_WINDOW}) or
- * ops that refer to user sensitive permission should be shown to the user by default.
- *
- * @param item The item
- *
- * @return {@code true} iff the app-op for should be shown to the user
- */
- private boolean isUserVisible(int appOpCode, int uid, String packageName) {
- // currently OP_SYSTEM_ALERT_WINDOW does not correspond to a platform permission
- // which may be user senstive, so for now always show it to the user.
- if (appOpCode == AppOpsManager.OP_SYSTEM_ALERT_WINDOW) {
- return true;
- }
-
- return isUserSensitive(appOpCode, uid, packageName);
- }
-
- /**
* Returns a copy of the list containing all the active AppOps that the controller tracks.
*
* @return List of active AppOps information
@@ -304,8 +251,8 @@
final int numActiveItems = mActiveItems.size();
for (int i = 0; i < numActiveItems; i++) {
AppOpItem item = mActiveItems.get(i);
- if ((userId == UserHandle.USER_ALL || UserHandle.getUserId(item.getUid()) == userId)
- && isUserVisible(item)) {
+ if ((userId == UserHandle.USER_ALL
+ || UserHandle.getUserId(item.getUid()) == userId)) {
list.add(item);
}
}
@@ -314,8 +261,8 @@
final int numNotedItems = mNotedItems.size();
for (int i = 0; i < numNotedItems; i++) {
AppOpItem item = mNotedItems.get(i);
- if ((userId == UserHandle.USER_ALL || UserHandle.getUserId(item.getUid()) == userId)
- && isUserVisible(item)) {
+ if ((userId == UserHandle.USER_ALL
+ || UserHandle.getUserId(item.getUid()) == userId)) {
list.add(item);
}
}
@@ -325,7 +272,21 @@
@Override
public void onOpActiveChanged(int code, int uid, String packageName, boolean active) {
- if (updateActives(code, uid, packageName, active)) {
+ if (DEBUG) {
+ Log.w(TAG, String.format("onActiveChanged(%d,%d,%s,%s", code, uid, packageName,
+ Boolean.toString(active)));
+ }
+ boolean activeChanged = updateActives(code, uid, packageName, active);
+ if (!activeChanged) return; // early return
+ // Check if the item is also noted, in that case, there's no update.
+ boolean alsoNoted;
+ synchronized (mNotedItems) {
+ alsoNoted = getAppOpItemLocked(mNotedItems, code, uid, packageName) != null;
+ }
+ // If active is true, we only send the update if the op is not actively noted (already true)
+ // If active is false, we only send the update if the op is not actively noted (prevent
+ // early removal)
+ if (!alsoNoted) {
mBGHandler.post(() -> notifySuscribers(code, uid, packageName, active));
}
}
@@ -333,17 +294,23 @@
@Override
public void onOpNoted(int code, int uid, String packageName, int result) {
if (DEBUG) {
- Log.w(TAG, "Op: " + code + " with result " + AppOpsManager.MODE_NAMES[result]);
+ Log.w(TAG, "Noted op: " + code + " with result "
+ + AppOpsManager.MODE_NAMES[result] + " for package " + packageName);
}
if (result != AppOpsManager.MODE_ALLOWED) return;
- if (addNoted(code, uid, packageName)) {
+ boolean notedAdded = addNoted(code, uid, packageName);
+ if (!notedAdded) return; // early return
+ boolean alsoActive;
+ synchronized (mActiveItems) {
+ alsoActive = getAppOpItemLocked(mActiveItems, code, uid, packageName) != null;
+ }
+ if (!alsoActive) {
mBGHandler.post(() -> notifySuscribers(code, uid, packageName, true));
}
}
private void notifySuscribers(int code, int uid, String packageName, boolean active) {
- if (mCallbacksByCode.containsKey(code)
- && isUserVisible(code, uid, packageName)) {
+ if (mCallbacksByCode.containsKey(code)) {
if (DEBUG) Log.d(TAG, "Notifying of change in package " + packageName);
for (Callback cb: mCallbacksByCode.get(code)) {
cb.onActiveStateChanged(code, uid, packageName, active);
@@ -368,7 +335,7 @@
}
- protected final class H extends Handler {
+ protected class H extends Handler {
H(Looper looper) {
super(looper);
}
diff --git a/packages/SystemUI/src/com/android/systemui/appops/PermissionFlagsCache.kt b/packages/SystemUI/src/com/android/systemui/appops/PermissionFlagsCache.kt
deleted file mode 100644
index f02c7af..0000000
--- a/packages/SystemUI/src/com/android/systemui/appops/PermissionFlagsCache.kt
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * 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 com.android.systemui.appops
-
-import android.content.Context
-import android.content.pm.PackageManager
-import android.os.UserHandle
-import android.util.ArrayMap
-import com.android.internal.annotations.VisibleForTesting
-
-private data class PermissionFlag(val flag: Int, val timestamp: Long)
-
-private data class PermissionFlagKey(
- val permission: String,
- val packageName: String,
- val user: UserHandle
-)
-
-internal const val CACHE_EXPIRATION = 10000L
-
-/**
- * Cache for PackageManager's PermissionFlags.
- *
- * Flags older than [CACHE_EXPIRATION] will be retrieved again.
- */
-internal open class PermissionFlagsCache(context: Context) {
- private val packageManager = context.packageManager
- private val permissionFlagsCache = ArrayMap<PermissionFlagKey, PermissionFlag>()
-
- /**
- * Retrieve permission flags from cache or PackageManager. There parameters will be passed
- * directly to [PackageManager].
- *
- * Calls to this method should be done from a background thread.
- */
- fun getPermissionFlags(permission: String, packageName: String, user: UserHandle): Int {
- val key = PermissionFlagKey(permission, packageName, user)
- val now = getCurrentTime()
- val value = permissionFlagsCache.getOrPut(key) {
- PermissionFlag(getFlags(key), now)
- }
- if (now - value.timestamp > CACHE_EXPIRATION) {
- val newValue = PermissionFlag(getFlags(key), now)
- permissionFlagsCache.put(key, newValue)
- return newValue.flag
- } else {
- return value.flag
- }
- }
-
- private fun getFlags(key: PermissionFlagKey) =
- packageManager.getPermissionFlags(key.permission, key.packageName, key.user)
-
- @VisibleForTesting
- protected open fun getCurrentTime() = System.currentTimeMillis()
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
index 8cb0cc5..cedf7c3 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
@@ -20,6 +20,7 @@
import android.content.Context
import android.content.IntentFilter
import android.os.Handler
+import android.os.HandlerExecutor
import android.os.Looper
import android.os.Message
import android.os.UserHandle
@@ -32,13 +33,14 @@
import com.android.systemui.dagger.qualifiers.Main
import java.io.FileDescriptor
import java.io.PrintWriter
+import java.util.concurrent.Executor
import javax.inject.Inject
import javax.inject.Singleton
data class ReceiverData(
val receiver: BroadcastReceiver,
val filter: IntentFilter,
- val handler: Handler,
+ val executor: Executor,
val user: UserHandle
)
@@ -76,8 +78,33 @@
* @param filter A filter to determine what broadcasts should be dispatched to this receiver.
* It will only take into account actions and categories for filtering. It must
* have at least one action.
- * @param handler A handler to dispatch [BroadcastReceiver.onReceive]. By default, it is the
- * main handler. Pass `null` to use the default.
+ * @param handler A handler to dispatch [BroadcastReceiver.onReceive].
+ * @param user A user handle to determine which broadcast should be dispatched to this receiver.
+ * By default, it is the current user.
+ * @throws IllegalArgumentException if the filter has other constraints that are not actions or
+ * categories or the filter has no actions.
+ */
+ @Deprecated(message = "Replacing Handler for Executor in SystemUI",
+ replaceWith = ReplaceWith("registerReceiver(receiver, filter, executor, user)"))
+ @JvmOverloads
+ fun registerReceiverWithHandler(
+ receiver: BroadcastReceiver,
+ filter: IntentFilter,
+ handler: Handler,
+ user: UserHandle = context.user
+ ) {
+ registerReceiver(receiver, filter, HandlerExecutor(handler), user)
+ }
+
+ /**
+ * Register a receiver for broadcast with the dispatcher
+ *
+ * @param receiver A receiver to dispatch the [Intent]
+ * @param filter A filter to determine what broadcasts should be dispatched to this receiver.
+ * It will only take into account actions and categories for filtering. It must
+ * have at least one action.
+ * @param executor An executor to dispatch [BroadcastReceiver.onReceive]. Pass null to use an
+ * executor in the main thread (default).
* @param user A user handle to determine which broadcast should be dispatched to this receiver.
* By default, it is the current user.
* @throws IllegalArgumentException if the filter has other constraints that are not actions or
@@ -85,15 +112,15 @@
*/
@JvmOverloads
fun registerReceiver(
- receiver: BroadcastReceiver,
- filter: IntentFilter,
- handler: Handler? = mainHandler,
- user: UserHandle = context.user
+ receiver: BroadcastReceiver,
+ filter: IntentFilter,
+ executor: Executor? = context.mainExecutor,
+ user: UserHandle = context.user
) {
checkFilter(filter)
this.handler
.obtainMessage(MSG_ADD_RECEIVER,
- ReceiverData(receiver, filter, handler ?: mainHandler, user))
+ ReceiverData(receiver, filter, executor ?: context.mainExecutor, user))
.sendToTarget()
}
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
index b2942bb..0c631aa 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
@@ -193,7 +193,7 @@
it.filter.hasAction(intent.action) &&
it.filter.matchCategories(intent.categories) == null }
?.forEach {
- it.handler.post {
+ it.executor.execute {
if (DEBUG) Log.w(TAG,
"[$index] Dispatching ${intent.action} to ${it.receiver}")
it.receiver.pendingResult = pendingResult
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index 7cd29ea..d835ee1 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -344,11 +344,14 @@
mSavedBubbleKeysPerUser = new SparseSetArray<>();
mCurrentUserId = mNotifUserManager.getCurrentUserId();
mNotifUserManager.addUserChangedListener(
- newUserId -> {
- saveBubbles(mCurrentUserId);
- mBubbleData.dismissAll(DISMISS_USER_CHANGED);
- restoreBubbles(newUserId);
- mCurrentUserId = newUserId;
+ new NotificationLockscreenUserManager.UserChangedListener() {
+ @Override
+ public void onUserChanged(int newUserId) {
+ BubbleController.this.saveBubbles(mCurrentUserId);
+ mBubbleData.dismissAll(DISMISS_USER_CHANGED);
+ BubbleController.this.restoreBubbles(newUserId);
+ mCurrentUserId = newUserId;
+ }
});
mUserCreatedBubbles = new HashSet<>();
@@ -743,9 +746,16 @@
return !isAutogroupSummary;
} else {
// If it's not a user dismiss it's a cancel.
+ for (int i = 0; i < bubbleChildren.size(); i++) {
+ // First check if any of these are user-created (i.e. experimental bubbles)
+ if (mUserCreatedBubbles.contains(bubbleChildren.get(i).getKey())) {
+ // Experimental bubble! Intercept the removal.
+ return true;
+ }
+ }
+ // Not an experimental bubble, safe to remove.
mBubbleData.removeSuppressedSummary(groupKey);
-
- // Remove any associated bubble children.
+ // Remove any associated bubble children with the summary.
for (int i = 0; i < bubbleChildren.size(); i++) {
Bubble bubbleChild = bubbleChildren.get(i);
mBubbleData.notificationEntryRemoved(bubbleChild.getEntry(),
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
index 97224f1..ccbbb24 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java
@@ -111,7 +111,10 @@
}
private final Context mContext;
+ /** Bubbles that are actively in the stack. */
private final List<Bubble> mBubbles;
+ /** Bubbles that are being loaded but haven't been added to the stack just yet. */
+ private final List<Bubble> mPendingBubbles;
private Bubble mSelectedBubble;
private boolean mExpanded;
private final int mMaxBubbles;
@@ -143,6 +146,7 @@
public BubbleData(Context context) {
mContext = context;
mBubbles = new ArrayList<>();
+ mPendingBubbles = new ArrayList<>();
mStateChange = new Update(mBubbles);
mMaxBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_rendered);
}
@@ -188,7 +192,15 @@
Bubble getOrCreateBubble(NotificationEntry entry) {
Bubble bubble = getBubbleWithKey(entry.getKey());
if (bubble == null) {
+ // Check for it in pending
+ for (int i = 0; i < mPendingBubbles.size(); i++) {
+ Bubble b = mPendingBubbles.get(i);
+ if (b.getKey().equals(entry.getKey())) {
+ return b;
+ }
+ }
bubble = new Bubble(entry);
+ mPendingBubbles.add(bubble);
} else {
bubble.setEntry(entry);
}
@@ -204,7 +216,7 @@
if (DEBUG_BUBBLE_DATA) {
Log.d(TAG, "notificationEntryUpdated: " + bubble);
}
-
+ mPendingBubbles.remove(bubble); // No longer pending once we're here
Bubble prevBubble = getBubbleWithKey(bubble.getKey());
suppressFlyout |= !shouldShowFlyout(bubble.getEntry());
@@ -377,6 +389,12 @@
}
private void doRemove(String key, @DismissReason int reason) {
+ // If it was pending remove it
+ for (int i = 0; i < mPendingBubbles.size(); i++) {
+ if (mPendingBubbles.get(i).getKey().equals(key)) {
+ mPendingBubbles.remove(mPendingBubbles.get(i));
+ }
+ }
int indexToRemove = indexForKey(key);
if (indexToRemove == -1) {
return;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
index 018b631..d99607f 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java
@@ -10,6 +10,8 @@
import com.android.systemui.R;
+import javax.inject.Inject;
+
/**
* Activity for showing aged out bubbles.
* Must be public to be accessible to androidx...AppComponentFactory
@@ -17,6 +19,12 @@
public class BubbleOverflowActivity extends Activity {
private RecyclerView mRecyclerView;
private int mMaxBubbles;
+ private BubbleController mBubbleController;
+
+ @Inject
+ public BubbleOverflowActivity(BubbleController controller) {
+ mBubbleController = controller;
+ }
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -64,6 +72,6 @@
}
public void onDestroy() {
- super.onStop();
+ super.onDestroy();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 8987683..15c1c55 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -73,7 +73,6 @@
import com.android.systemui.bubbles.animation.ExpandedAnimationController;
import com.android.systemui.bubbles.animation.PhysicsAnimationLayout;
import com.android.systemui.bubbles.animation.StackAnimationController;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -716,22 +715,6 @@
return mExpandedBubble;
}
- /**
- * Sets the bubble that should be expanded and expands if needed.
- *
- * @param key the {@link NotificationEntry#key} associated with the bubble to expand.
- * @deprecated replaced by setSelectedBubble(Bubble) + setExpanded(true)
- */
- @Deprecated
- void setExpandedBubble(String key) {
- Bubble bubbleToExpand = mBubbleData.getBubbleWithKey(key);
- if (bubbleToExpand != null) {
- setSelectedBubble(bubbleToExpand);
- bubbleToExpand.setShowInShade(false);
- setExpanded(true);
- }
- }
-
// via BubbleData.Listener
void addBubble(Bubble bubble) {
if (DEBUG_BUBBLE_STACK_VIEW) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
index 59d68bc..6528f37 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/ExpandedAnimationController.java
@@ -97,13 +97,11 @@
private boolean mSpringingBubbleToTouch = false;
private int mExpandedViewPadding;
- private float mLauncherGridDiff;
public ExpandedAnimationController(Point displaySize, int expandedViewPadding,
int orientation) {
updateOrientation(orientation, displaySize);
mExpandedViewPadding = expandedViewPadding;
- mLauncherGridDiff = 30f;
}
/**
@@ -569,15 +567,7 @@
* @return Space between bubbles in row above expanded view.
*/
private float getSpaceBetweenBubbles() {
- /**
- * Ordered left to right:
- * Screen edge
- * [mExpandedViewPadding]
- * Expanded view edge
- * [launcherGridDiff] --- arbitrary value until launcher exports widths
- * Launcher's app icon grid edge that we must match
- */
- final float rowMargins = (mExpandedViewPadding + mLauncherGridDiff) * 2;
+ final float rowMargins = mExpandedViewPadding * 2;
final float maxRowWidth = getWidthForDisplayingBubbles() - rowMargins;
final float totalBubbleWidth = mBubblesMaxRendered * mBubbleSizePx;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
index 563a0a7..31656a0 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/animation/PhysicsAnimationLayout.java
@@ -961,6 +961,13 @@
if (view != null) {
final SpringAnimation animation =
(SpringAnimation) view.getTag(getTagIdForProperty(property));
+
+ // If the animation is null, the view was probably removed from the layout before
+ // the animation started.
+ if (animation == null) {
+ return;
+ }
+
if (afterCallbacks != null) {
animation.addEndListener(new OneTimeEndListener() {
@Override
diff --git a/media/java/android/media/RouteSessionInfo.aidl b/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt
similarity index 70%
copy from media/java/android/media/RouteSessionInfo.aidl
copy to packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt
index fb5d836..e6cdf50 100644
--- a/media/java/android/media/RouteSessionInfo.aidl
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 The Android Open Source Project
+ * 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.
@@ -14,6 +14,8 @@
* limitations under the License.
*/
-package android.media;
+package com.android.systemui.controls
-parcelable RouteSessionInfo;
+import android.service.controls.Control
+
+data class ControlStatus(val control: Control, val favorite: Boolean, val removed: Boolean = false)
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
new file mode 100644
index 0000000..265ddd8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt
@@ -0,0 +1,31 @@
+/*
+ * 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 com.android.systemui.controls
+
+import android.content.Context
+import android.content.pm.ServiceInfo
+import com.android.settingslib.applications.DefaultAppInfo
+
+class ControlsServiceInfo(
+ context: Context,
+ serviceInfo: ServiceInfo
+) : DefaultAppInfo(
+ context,
+ context.packageManager,
+ context.userId,
+ serviceInfo.componentName
+)
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt
new file mode 100644
index 0000000..b6cca3f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.controller
+
+import android.content.ComponentName
+import android.service.controls.DeviceTypes
+import android.util.Log
+
+/**
+ * Stores basic information about a [Control] to persist and keep track of favorites.
+ */
+data class ControlInfo(
+ val component: ComponentName,
+ val controlId: String,
+ val controlTitle: CharSequence,
+ @DeviceTypes.DeviceType val deviceType: Int
+) {
+
+ companion object {
+ private const val TAG = "ControlInfo"
+ private const val SEPARATOR = ":"
+ fun createFromString(string: String): ControlInfo? {
+ val parts = string.split(SEPARATOR)
+ val component = ComponentName.unflattenFromString(parts[0])
+ if (parts.size != 4 || component == null) {
+ Log.e(TAG, "Cannot parse ControlInfo from $string")
+ return null
+ }
+ val type = try {
+ parts[3].toInt()
+ } catch (e: Exception) {
+ Log.e(TAG, "Cannot parse deviceType from ${parts[3]}")
+ return null
+ }
+ return ControlInfo(
+ component,
+ parts[1],
+ parts[2],
+ if (DeviceTypes.validDeviceType(type)) type else DeviceTypes.TYPE_UNKNOWN)
+ }
+ }
+ override fun toString(): String {
+ return component.flattenToString() +
+ "$SEPARATOR$controlId$SEPARATOR$controlTitle$SEPARATOR$deviceType"
+ }
+
+ class Builder {
+ lateinit var componentName: ComponentName
+ lateinit var controlId: String
+ lateinit var controlTitle: CharSequence
+ var deviceType: Int = DeviceTypes.TYPE_UNKNOWN
+
+ fun build() = ControlInfo(componentName, controlId, controlTitle, deviceType)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt
new file mode 100644
index 0000000..6b7fc4b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt
@@ -0,0 +1,29 @@
+/*
+ * 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 com.android.systemui.controls.controller
+
+import android.content.ComponentName
+import android.service.controls.Control
+import android.service.controls.actions.ControlAction
+
+interface ControlsBindingController {
+ fun bindAndLoad(component: ComponentName, callback: (List<Control>) -> Unit)
+ fun bindServices(components: List<ComponentName>)
+ fun subscribe(controls: List<ControlInfo>)
+ fun action(controlInfo: ControlInfo, action: ControlAction)
+ fun unsubscribe()
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
new file mode 100644
index 0000000..80e48b9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
@@ -0,0 +1,204 @@
+/*
+ * 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 com.android.systemui.controls.controller
+
+import android.content.ComponentName
+import android.content.Context
+import android.os.IBinder
+import android.service.controls.Control
+import android.service.controls.IControlsProviderCallback
+import android.service.controls.actions.ControlAction
+import android.util.ArrayMap
+import android.util.Log
+import com.android.internal.annotations.GuardedBy
+import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.concurrency.DelayableExecutor
+import dagger.Lazy
+import java.util.concurrent.atomic.AtomicBoolean
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+@VisibleForTesting
+open class ControlsBindingControllerImpl @Inject constructor(
+ private val context: Context,
+ @Background private val backgroundExecutor: DelayableExecutor,
+ private val lazyController: Lazy<ControlsController>
+) : ControlsBindingController {
+
+ companion object {
+ private const val TAG = "ControlsBindingControllerImpl"
+ }
+
+ private val refreshing = AtomicBoolean(false)
+
+ @GuardedBy("componentMap")
+ private val tokenMap: MutableMap<IBinder, ControlsProviderLifecycleManager> =
+ ArrayMap<IBinder, ControlsProviderLifecycleManager>()
+ @GuardedBy("componentMap")
+ private val componentMap: MutableMap<ComponentName, ControlsProviderLifecycleManager> =
+ ArrayMap<ComponentName, ControlsProviderLifecycleManager>()
+
+ private val serviceCallback = object : IControlsProviderCallback.Stub() {
+ override fun onLoad(token: IBinder, controls: MutableList<Control>) {
+ backgroundExecutor.execute(OnLoadRunnable(token, controls))
+ }
+
+ override fun onRefreshState(token: IBinder, controlStates: List<Control>) {
+ if (!refreshing.get()) {
+ Log.d(TAG, "Refresh outside of window for token:$token")
+ } else {
+ backgroundExecutor.execute(OnRefreshStateRunnable(token, controlStates))
+ }
+ }
+
+ override fun onControlActionResponse(
+ token: IBinder,
+ controlId: String,
+ @ControlAction.ResponseResult response: Int
+ ) {
+ backgroundExecutor.execute(OnActionResponseRunnable(token, controlId, response))
+ }
+ }
+
+ @VisibleForTesting
+ internal open fun createProviderManager(component: ComponentName):
+ ControlsProviderLifecycleManager {
+ return ControlsProviderLifecycleManager(
+ context,
+ backgroundExecutor,
+ serviceCallback,
+ component
+ )
+ }
+
+ private fun retrieveLifecycleManager(component: ComponentName):
+ ControlsProviderLifecycleManager {
+ synchronized(componentMap) {
+ val provider = componentMap.getOrPut(component) {
+ createProviderManager(component)
+ }
+ tokenMap.putIfAbsent(provider.token, provider)
+ return provider
+ }
+ }
+
+ override fun bindAndLoad(component: ComponentName, callback: (List<Control>) -> Unit) {
+ val provider = retrieveLifecycleManager(component)
+ provider.maybeBindAndLoad(callback)
+ }
+
+ override fun subscribe(controls: List<ControlInfo>) {
+ val controlsByComponentName = controls.groupBy { it.component }
+ if (refreshing.compareAndSet(false, true)) {
+ controlsByComponentName.forEach {
+ val provider = retrieveLifecycleManager(it.key)
+ backgroundExecutor.execute {
+ provider.maybeBindAndSubscribe(it.value.map { it.controlId })
+ }
+ }
+ }
+ // Unbind unneeded providers
+ val providersWithFavorites = controlsByComponentName.keys
+ synchronized(componentMap) {
+ componentMap.forEach {
+ if (it.key !in providersWithFavorites) {
+ backgroundExecutor.execute { it.value.unbindService() }
+ }
+ }
+ }
+ }
+
+ override fun unsubscribe() {
+ if (refreshing.compareAndSet(true, false)) {
+ val providers = synchronized(componentMap) {
+ componentMap.values.toList()
+ }
+ providers.forEach {
+ backgroundExecutor.execute { it.unsubscribe() }
+ }
+ }
+ }
+
+ override fun action(controlInfo: ControlInfo, action: ControlAction) {
+ val provider = retrieveLifecycleManager(controlInfo.component)
+ provider.maybeBindAndSendAction(controlInfo.controlId, action)
+ }
+
+ override fun bindServices(components: List<ComponentName>) {
+ components.forEach {
+ val provider = retrieveLifecycleManager(it)
+ backgroundExecutor.execute { provider.bindPermanently() }
+ }
+ }
+
+ private abstract inner class CallbackRunnable(val token: IBinder) : Runnable {
+ protected val provider: ControlsProviderLifecycleManager? =
+ synchronized(componentMap) {
+ tokenMap.get(token)
+ }
+ }
+
+ private inner class OnLoadRunnable(
+ token: IBinder,
+ val list: List<Control>
+ ) : CallbackRunnable(token) {
+ override fun run() {
+ if (provider == null) {
+ Log.e(TAG, "No provider found for token:$token")
+ return
+ }
+ synchronized(componentMap) {
+ if (token !in tokenMap.keys) {
+ Log.e(TAG, "Provider for token:$token does not exist anymore")
+ return
+ }
+ }
+ provider.lastLoadCallback?.invoke(list) ?: run {
+ Log.w(TAG, "Null callback")
+ }
+ provider.maybeUnbindAndRemoveCallback()
+ }
+ }
+
+ private inner class OnRefreshStateRunnable(
+ token: IBinder,
+ val list: List<Control>
+ ) : CallbackRunnable(token) {
+ override fun run() {
+ if (!refreshing.get()) {
+ Log.d(TAG, "onRefresh outside of window from:${provider?.componentName}")
+ }
+ provider?.let {
+ lazyController.get().refreshStatus(it.componentName, list)
+ }
+ }
+ }
+
+ private inner class OnActionResponseRunnable(
+ token: IBinder,
+ val controlId: String,
+ @ControlAction.ResponseResult val response: Int
+ ) : CallbackRunnable(token) {
+ override fun run() {
+ provider?.let {
+ lazyController.get().onActionResponse(it.componentName, controlId, response)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
new file mode 100644
index 0000000..4d95822
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.systemui.controls.controller
+
+import android.content.ComponentName
+import android.service.controls.Control
+import android.service.controls.actions.ControlAction
+import com.android.systemui.controls.ControlStatus
+
+interface ControlsController {
+ val available: Boolean
+
+ fun getFavoriteControls(): List<ControlInfo>
+ fun loadForComponent(componentName: ComponentName, callback: (List<ControlStatus>) -> Unit)
+ fun subscribeToFavorites()
+ fun changeFavoriteStatus(controlInfo: ControlInfo, state: Boolean)
+ fun unsubscribe()
+ fun action(controlInfo: ControlInfo, action: ControlAction)
+ fun refreshStatus(componentName: ComponentName, controls: List<Control>)
+ fun onActionResponse(
+ componentName: ComponentName,
+ controlId: String,
+ @ControlAction.ResponseResult response: Int
+ )
+ fun clearFavorites()
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
new file mode 100644
index 0000000..7e328e4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -0,0 +1,273 @@
+/*
+ * 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 com.android.systemui.controls.controller
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.os.Environment
+import android.provider.Settings
+import android.service.controls.Control
+import android.service.controls.actions.ControlAction
+import android.util.ArrayMap
+import android.util.Log
+import com.android.internal.annotations.GuardedBy
+import com.android.systemui.DumpController
+import com.android.systemui.Dumpable
+import com.android.systemui.controls.ControlStatus
+import com.android.systemui.controls.management.ControlsFavoritingActivity
+import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.concurrency.DelayableExecutor
+import java.io.FileDescriptor
+import java.io.PrintWriter
+import java.util.Optional
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class ControlsControllerImpl @Inject constructor (
+ private val context: Context,
+ @Background private val executor: DelayableExecutor,
+ private val uiController: ControlsUiController,
+ private val bindingController: ControlsBindingController,
+ private val optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
+ dumpController: DumpController
+) : Dumpable, ControlsController {
+
+ companion object {
+ private const val TAG = "ControlsControllerImpl"
+ const val CONTROLS_AVAILABLE = "systemui.controls_available"
+ }
+
+ override val available = Settings.Secure.getInt(
+ context.contentResolver, CONTROLS_AVAILABLE, 0) != 0
+ val persistenceWrapper = optionalWrapper.orElseGet {
+ ControlsFavoritePersistenceWrapper(
+ Environment.buildPath(
+ context.filesDir,
+ ControlsFavoritePersistenceWrapper.FILE_NAME),
+ executor
+ )
+ }
+
+ // Map of map: ComponentName -> (String -> ControlInfo)
+ @GuardedBy("currentFavorites")
+ private val currentFavorites = ArrayMap<ComponentName, MutableMap<String, ControlInfo>>()
+
+ init {
+ if (available) {
+ dumpController.registerDumpable(this)
+ loadFavorites()
+ }
+ }
+
+ private fun loadFavorites() {
+ val infos = persistenceWrapper.readFavorites()
+ synchronized(currentFavorites) {
+ infos.forEach {
+ currentFavorites.getOrPut(it.component, { ArrayMap<String, ControlInfo>() })
+ .put(it.controlId, it)
+ }
+ }
+ }
+
+ override fun loadForComponent(
+ componentName: ComponentName,
+ callback: (List<ControlStatus>) -> Unit
+ ) {
+ if (!available) {
+ Log.d(TAG, "Controls not available")
+ return
+ }
+ bindingController.bindAndLoad(componentName) {
+ synchronized(currentFavorites) {
+ val favoritesForComponentKeys: Set<String> =
+ currentFavorites.get(componentName)?.keys ?: emptySet()
+ val changed = updateFavoritesLocked(componentName, it)
+ if (changed) {
+ persistenceWrapper.storeFavorites(favoritesAsListLocked())
+ }
+ val removed = findRemovedLocked(favoritesForComponentKeys, it)
+ callback(removed.map { currentFavorites.getValue(componentName).getValue(it) }
+ .map(::createRemovedStatus) +
+ it.map { ControlStatus(it, it.controlId in favoritesForComponentKeys) })
+ }
+ }
+ }
+
+ private fun createRemovedStatus(controlInfo: ControlInfo): ControlStatus {
+ val intent = Intent(context, ControlsFavoritingActivity::class.java).apply {
+ putExtra(ControlsFavoritingActivity.EXTRA_COMPONENT, controlInfo.component)
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP
+ }
+ val pendingIntent = PendingIntent.getActivity(context,
+ controlInfo.component.hashCode(),
+ intent,
+ 0)
+ val control = Control.StatelessBuilder(controlInfo.controlId, pendingIntent)
+ .setTitle(controlInfo.controlTitle)
+ .setDeviceType(controlInfo.deviceType)
+ .build()
+ return ControlStatus(control, true, true)
+ }
+
+ @GuardedBy("currentFavorites")
+ private fun findRemovedLocked(favoriteKeys: Set<String>, list: List<Control>): Set<String> {
+ val controlsKeys = list.map { it.controlId }
+ return favoriteKeys.minus(controlsKeys)
+ }
+
+ @GuardedBy("currentFavorites")
+ private fun updateFavoritesLocked(componentName: ComponentName, list: List<Control>): Boolean {
+ val favorites = currentFavorites.get(componentName) ?: mutableMapOf()
+ val favoriteKeys = favorites.keys
+ if (favoriteKeys.isEmpty()) return false // early return
+ var changed = false
+ list.forEach {
+ if (it.controlId in favoriteKeys) {
+ val value = favorites.getValue(it.controlId)
+ if (value.controlTitle != it.title || value.deviceType != it.deviceType) {
+ favorites[it.controlId] = value.copy(controlTitle = it.title,
+ deviceType = it.deviceType)
+ changed = true
+ }
+ }
+ }
+ return changed
+ }
+
+ @GuardedBy("currentFavorites")
+ private fun favoritesAsListLocked(): List<ControlInfo> {
+ return currentFavorites.flatMap { it.value.values }
+ }
+
+ override fun subscribeToFavorites() {
+ if (!available) {
+ Log.d(TAG, "Controls not available")
+ return
+ }
+ // Make a copy of the favorites list
+ val favorites = synchronized(currentFavorites) {
+ currentFavorites.flatMap { it.value.values.toList() }
+ }
+ bindingController.subscribe(favorites)
+ }
+
+ override fun unsubscribe() {
+ if (!available) {
+ Log.d(TAG, "Controls not available")
+ return
+ }
+ bindingController.unsubscribe()
+ }
+
+ override fun changeFavoriteStatus(controlInfo: ControlInfo, state: Boolean) {
+ if (!available) {
+ Log.d(TAG, "Controls not available")
+ return
+ }
+ var changed = false
+ val listOfControls = synchronized(currentFavorites) {
+ if (state) {
+ if (controlInfo.component !in currentFavorites) {
+ currentFavorites.put(controlInfo.component, ArrayMap<String, ControlInfo>())
+ changed = true
+ }
+ val controlsForComponent = currentFavorites.getValue(controlInfo.component)
+ if (controlInfo.controlId !in controlsForComponent) {
+ controlsForComponent.put(controlInfo.controlId, controlInfo)
+ changed = true
+ } else {
+ if (controlsForComponent.getValue(controlInfo.controlId) != controlInfo) {
+ controlsForComponent.put(controlInfo.controlId, controlInfo)
+ changed = true
+ }
+ }
+ } else {
+ changed = currentFavorites.get(controlInfo.component)
+ ?.remove(controlInfo.controlId) != null
+ }
+ favoritesAsListLocked()
+ }
+ if (changed) {
+ persistenceWrapper.storeFavorites(listOfControls)
+ }
+ }
+
+ override fun refreshStatus(componentName: ComponentName, controls: List<Control>) {
+ if (!available) {
+ Log.d(TAG, "Controls not available")
+ return
+ }
+ executor.execute {
+ synchronized(currentFavorites) {
+ val changed = updateFavoritesLocked(componentName, controls)
+ if (changed) {
+ persistenceWrapper.storeFavorites(favoritesAsListLocked())
+ }
+ }
+ }
+ uiController.onRefreshState(componentName, controls)
+ }
+
+ override fun onActionResponse(componentName: ComponentName, controlId: String, response: Int) {
+ if (!available) {
+ Log.d(TAG, "Controls not available")
+ return
+ }
+ uiController.onActionResponse(componentName, controlId, response)
+ }
+
+ override fun getFavoriteControls(): List<ControlInfo> {
+ if (!available) {
+ Log.d(TAG, "Controls not available")
+ return emptyList()
+ }
+ synchronized(currentFavorites) {
+ return favoritesAsListLocked()
+ }
+ }
+
+ override fun action(controlInfo: ControlInfo, action: ControlAction) {
+ bindingController.action(controlInfo, action)
+ }
+
+ override fun clearFavorites() {
+ val changed = synchronized(currentFavorites) {
+ currentFavorites.isNotEmpty().also {
+ currentFavorites.clear()
+ }
+ }
+ if (changed) {
+ persistenceWrapper.storeFavorites(emptyList())
+ }
+ }
+
+ override fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<out String>) {
+ pw.println("ControlsController state:")
+ pw.println(" Favorites:")
+ synchronized(currentFavorites) {
+ currentFavorites.forEach {
+ it.value.forEach {
+ pw.println(" ${it.value}")
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt
new file mode 100644
index 0000000..6f2d71f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt
@@ -0,0 +1,134 @@
+/*
+ * 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.systemui.controls.controller
+
+import android.app.ActivityManager
+import android.content.ComponentName
+import android.util.AtomicFile
+import android.util.Log
+import android.util.Slog
+import android.util.Xml
+import com.android.systemui.util.concurrency.DelayableExecutor
+import libcore.io.IoUtils
+import org.xmlpull.v1.XmlPullParser
+import org.xmlpull.v1.XmlPullParserException
+import java.io.File
+import java.io.FileInputStream
+import java.io.FileNotFoundException
+import java.io.IOException
+
+class ControlsFavoritePersistenceWrapper(
+ val file: File,
+ val executor: DelayableExecutor
+) {
+
+ companion object {
+ private const val TAG = "ControlsFavoritePersistenceWrapper"
+ const val FILE_NAME = "controls_favorites.xml"
+ private const val TAG_CONTROLS = "controls"
+ private const val TAG_CONTROL = "control"
+ private const val TAG_COMPONENT = "component"
+ private const val TAG_ID = "id"
+ private const val TAG_TITLE = "title"
+ private const val TAG_TYPE = "type"
+ }
+
+ val currentUser: Int
+ get() = ActivityManager.getCurrentUser()
+
+ fun storeFavorites(list: List<ControlInfo>) {
+ executor.execute {
+ val atomicFile = AtomicFile(file)
+ val writer = try {
+ atomicFile.startWrite()
+ } catch (e: IOException) {
+ Log.e(TAG, "Failed to start write file", e)
+ return@execute
+ }
+ try {
+ Xml.newSerializer().apply {
+ setOutput(writer, "utf-8")
+ setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true)
+ startDocument(null, true)
+ startTag(null, TAG_CONTROLS)
+ list.forEach {
+ startTag(null, TAG_CONTROL)
+ attribute(null, TAG_COMPONENT, it.component.flattenToString())
+ attribute(null, TAG_ID, it.controlId)
+ attribute(null, TAG_TITLE, it.controlTitle.toString())
+ attribute(null, TAG_TYPE, it.deviceType.toString())
+ endTag(null, TAG_CONTROL)
+ }
+ endTag(null, TAG_CONTROLS)
+ endDocument()
+ atomicFile.finishWrite(writer)
+ }
+ } catch (t: Throwable) {
+ Log.e(TAG, "Failed to write file, reverting to previous version")
+ atomicFile.failWrite(writer)
+ } finally {
+ IoUtils.closeQuietly(writer)
+ }
+ }
+ }
+
+ fun readFavorites(): List<ControlInfo> {
+ if (!file.exists()) {
+ Log.d(TAG, "No favorites, returning empty list")
+ return emptyList()
+ }
+ val reader = try {
+ FileInputStream(file)
+ } catch (fnfe: FileNotFoundException) {
+ Slog.i(TAG, "No file found")
+ return emptyList()
+ }
+ try {
+ val parser = Xml.newPullParser()
+ parser.setInput(reader, null)
+ return parseXml(parser)
+ } catch (e: XmlPullParserException) {
+ throw IllegalStateException("Failed parsing favorites file: $file", e)
+ } catch (e: IOException) {
+ throw IllegalStateException("Failed parsing favorites file: $file", e)
+ } finally {
+ IoUtils.closeQuietly(reader)
+ }
+ }
+
+ private fun parseXml(parser: XmlPullParser): List<ControlInfo> {
+ var type: Int = 0
+ val infos = mutableListOf<ControlInfo>()
+ while (parser.next().also { type = it } != XmlPullParser.END_DOCUMENT) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue
+ }
+ val tagName = parser.name
+ if (tagName == TAG_CONTROL) {
+ val component = ComponentName.unflattenFromString(
+ parser.getAttributeValue(null, TAG_COMPONENT))
+ val id = parser.getAttributeValue(null, TAG_ID)
+ val title = parser.getAttributeValue(null, TAG_TITLE)
+ val type = parser.getAttributeValue(null, TAG_TYPE)?.toInt()
+ if (component != null && id != null && title != null && type != null) {
+ infos.add(ControlInfo(component, id, title, type))
+ }
+ }
+ }
+ return infos
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
new file mode 100644
index 0000000..79057ad
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt
@@ -0,0 +1,283 @@
+/*
+ * 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.systemui.controls.controller
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.ServiceConnection
+import android.os.Binder
+import android.os.Bundle
+import android.os.IBinder
+import android.os.RemoteException
+import android.service.controls.Control
+import android.service.controls.ControlsProviderService.CALLBACK_BINDER
+import android.service.controls.ControlsProviderService.CALLBACK_BUNDLE
+import android.service.controls.ControlsProviderService.CALLBACK_TOKEN
+import android.service.controls.IControlsProvider
+import android.service.controls.IControlsProviderCallback
+import android.service.controls.actions.ControlAction
+import android.util.ArraySet
+import android.util.Log
+import com.android.internal.annotations.GuardedBy
+import com.android.systemui.util.concurrency.DelayableExecutor
+import java.util.concurrent.TimeUnit
+
+typealias LoadCallback = (List<Control>) -> Unit
+class ControlsProviderLifecycleManager(
+ private val context: Context,
+ private val executor: DelayableExecutor,
+ private val serviceCallback: IControlsProviderCallback.Stub,
+ val componentName: ComponentName
+) : IBinder.DeathRecipient {
+
+ var lastLoadCallback: LoadCallback? = null
+ private set
+ val token: IBinder = Binder()
+ private var unbindImmediate = false
+ private var requiresBound = false
+ private var isBound = false
+ @GuardedBy("queuedMessages")
+ private val queuedMessages: MutableSet<Message> = ArraySet()
+ private var wrapper: ControlsProviderServiceWrapper? = null
+ private var bindTryCount = 0
+ private val TAG = javaClass.simpleName
+ private var onLoadCanceller: Runnable? = null
+
+ companion object {
+ private const val MSG_LOAD = 0
+ private const val MSG_SUBSCRIBE = 1
+ private const val MSG_UNSUBSCRIBE = 2
+ private const val MSG_ON_ACTION = 3
+ private const val MSG_UNBIND = 4
+ private const val BIND_RETRY_DELAY = 1000L // ms
+ private const val LOAD_TIMEOUT = 5000L // ms
+ private const val MAX_BIND_RETRIES = 5
+ private const val DEBUG = true
+ private val BIND_FLAGS = Context.BIND_AUTO_CREATE or Context.BIND_FOREGROUND_SERVICE or
+ Context.BIND_WAIVE_PRIORITY
+ }
+
+ private val intent = Intent().apply {
+ component = componentName
+ putExtra(CALLBACK_BUNDLE, Bundle().apply {
+ putBinder(CALLBACK_BINDER, serviceCallback)
+ putBinder(CALLBACK_TOKEN, token)
+ })
+ }
+
+ private fun bindService(bind: Boolean) {
+ requiresBound = bind
+ if (bind) {
+ if (bindTryCount == MAX_BIND_RETRIES) {
+ return
+ }
+ if (DEBUG) {
+ Log.d(TAG, "Binding service $intent")
+ }
+ bindTryCount++
+ try {
+ isBound = context.bindService(intent, serviceConnection, BIND_FLAGS)
+ } catch (e: SecurityException) {
+ Log.e(TAG, "Failed to bind to service", e)
+ isBound = false
+ }
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "Unbinding service $intent")
+ }
+ bindTryCount = 0
+ wrapper = null
+ if (isBound) {
+ context.unbindService(serviceConnection)
+ isBound = false
+ }
+ }
+ }
+
+ fun bindPermanently() {
+ unbindImmediate = false
+ unqueueMessage(Message.Unbind)
+ bindService(true)
+ }
+
+ private val serviceConnection = object : ServiceConnection {
+ override fun onServiceConnected(name: ComponentName, service: IBinder) {
+ if (DEBUG) Log.d(TAG, "onServiceConnected $name")
+ bindTryCount = 0
+ wrapper = ControlsProviderServiceWrapper(IControlsProvider.Stub.asInterface(service))
+ try {
+ service.linkToDeath(this@ControlsProviderLifecycleManager, 0)
+ } catch (_: RemoteException) {}
+ handlePendingMessages()
+ }
+
+ override fun onServiceDisconnected(name: ComponentName?) {
+ if (DEBUG) Log.d(TAG, "onServiceDisconnected $name")
+ isBound = false
+ bindService(false)
+ }
+ }
+
+ private fun handlePendingMessages() {
+ val queue = synchronized(queuedMessages) {
+ ArraySet(queuedMessages).also {
+ queuedMessages.clear()
+ }
+ }
+ if (Message.Unbind in queue) {
+ bindService(false)
+ return
+ }
+ if (Message.Load in queue) {
+ load()
+ }
+ queue.filter { it is Message.Subscribe }.flatMap { (it as Message.Subscribe).list }.run {
+ subscribe(this)
+ }
+ queue.filter { it is Message.Action }.forEach {
+ val msg = it as Message.Action
+ onAction(msg.id, msg.action)
+ }
+ }
+
+ override fun binderDied() {
+ if (wrapper == null) return
+ wrapper = null
+ if (requiresBound) {
+ if (DEBUG) {
+ Log.d(TAG, "binderDied")
+ }
+ // Try rebinding some time later
+ }
+ }
+
+ private fun queueMessage(message: Message) {
+ synchronized(queuedMessages) {
+ queuedMessages.add(message)
+ }
+ }
+
+ private fun unqueueMessage(message: Message) {
+ synchronized(queuedMessages) {
+ queuedMessages.removeIf { it.type == message.type }
+ }
+ }
+
+ private fun load() {
+ if (DEBUG) {
+ Log.d(TAG, "load $componentName")
+ }
+ if (!(wrapper?.load() ?: false)) {
+ queueMessage(Message.Load)
+ binderDied()
+ }
+ }
+
+ fun maybeBindAndLoad(callback: LoadCallback) {
+ unqueueMessage(Message.Unbind)
+ lastLoadCallback = callback
+ onLoadCanceller = executor.executeDelayed({
+ // Didn't receive a response in time, log and send back empty list
+ Log.d(TAG, "Timeout waiting onLoad for $componentName")
+ serviceCallback.onLoad(token, emptyList())
+ }, LOAD_TIMEOUT, TimeUnit.MILLISECONDS)
+ if (isBound) {
+ load()
+ } else {
+ queueMessage(Message.Load)
+ unbindImmediate = true
+ bindService(true)
+ }
+ }
+
+ fun maybeBindAndSubscribe(controlIds: List<String>) {
+ if (isBound) {
+ subscribe(controlIds)
+ } else {
+ queueMessage(Message.Subscribe(controlIds))
+ bindService(true)
+ }
+ }
+
+ private fun subscribe(controlIds: List<String>) {
+ if (DEBUG) {
+ Log.d(TAG, "subscribe $componentName - $controlIds")
+ }
+ if (!(wrapper?.subscribe(controlIds) ?: false)) {
+ queueMessage(Message.Subscribe(controlIds))
+ binderDied()
+ }
+ }
+
+ fun maybeBindAndSendAction(controlId: String, action: ControlAction) {
+ if (isBound) {
+ onAction(controlId, action)
+ } else {
+ queueMessage(Message.Action(controlId, action))
+ bindService(true)
+ }
+ }
+
+ private fun onAction(controlId: String, action: ControlAction) {
+ if (DEBUG) {
+ Log.d(TAG, "onAction $componentName - $controlId")
+ }
+ if (!(wrapper?.onAction(controlId, action) ?: false)) {
+ queueMessage(Message.Action(controlId, action))
+ binderDied()
+ }
+ }
+
+ fun unsubscribe() {
+ if (DEBUG) {
+ Log.d(TAG, "unsubscribe $componentName")
+ }
+ unqueueMessage(Message.Subscribe(emptyList())) // Removes all subscribe messages
+ wrapper?.unsubscribe()
+ }
+
+ fun maybeUnbindAndRemoveCallback() {
+ lastLoadCallback = null
+ onLoadCanceller?.run()
+ onLoadCanceller = null
+ if (unbindImmediate) {
+ bindService(false)
+ }
+ }
+
+ fun unbindService() {
+ unbindImmediate = true
+ maybeUnbindAndRemoveCallback()
+ }
+
+ sealed class Message {
+ abstract val type: Int
+ object Load : Message() {
+ override val type = MSG_LOAD
+ }
+ object Unbind : Message() {
+ override val type = MSG_UNBIND
+ }
+ class Subscribe(val list: List<String>) : Message() {
+ override val type = MSG_SUBSCRIBE
+ }
+ class Action(val id: String, val action: ControlAction) : Message() {
+ override val type = MSG_ON_ACTION
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapper.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapper.kt
new file mode 100644
index 0000000..882a10d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapper.kt
@@ -0,0 +1,61 @@
+/*
+ * 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 com.android.systemui.controls.controller
+
+import android.service.controls.actions.ControlAction
+import android.service.controls.IControlsProvider
+import android.util.Log
+
+class ControlsProviderServiceWrapper(val service: IControlsProvider) {
+ companion object {
+ private const val TAG = "ControlsProviderServiceWrapper"
+ }
+
+ private fun callThroughService(block: () -> Unit): Boolean {
+ try {
+ block()
+ return true
+ } catch (ex: Exception) {
+ Log.d(TAG, "Caught exception from ControlsProviderService", ex)
+ return false
+ }
+ }
+
+ fun load(): Boolean {
+ return callThroughService {
+ service.load()
+ }
+ }
+
+ fun subscribe(controlIds: List<String>): Boolean {
+ return callThroughService {
+ service.subscribe(controlIds)
+ }
+ }
+
+ fun unsubscribe(): Boolean {
+ return callThroughService {
+ service.unsubscribe()
+ }
+ }
+
+ fun onAction(controlId: String, action: ControlAction): Boolean {
+ return callThroughService {
+ service.onAction(controlId, action)
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
new file mode 100644
index 0000000..859311e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt
@@ -0,0 +1,72 @@
+/*
+ * 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 com.android.systemui.controls.dagger
+
+import android.app.Activity
+import com.android.systemui.controls.controller.ControlsBindingController
+import com.android.systemui.controls.controller.ControlsBindingControllerImpl
+import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.controls.controller.ControlsControllerImpl
+import com.android.systemui.controls.controller.ControlsFavoritePersistenceWrapper
+import com.android.systemui.controls.management.ControlsFavoritingActivity
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.controls.management.ControlsListingControllerImpl
+import com.android.systemui.controls.management.ControlsProviderSelectorActivity
+import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.controls.ui.ControlsUiControllerImpl
+import dagger.Binds
+import dagger.BindsOptionalOf
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+abstract class ControlsModule {
+
+ @Binds
+ abstract fun provideControlsListingController(
+ controller: ControlsListingControllerImpl
+ ): ControlsListingController
+
+ @Binds
+ abstract fun provideControlsController(controller: ControlsControllerImpl): ControlsController
+
+ @Binds
+ abstract fun provideControlsBindingController(
+ controller: ControlsBindingControllerImpl
+ ): ControlsBindingController
+
+ @Binds
+ abstract fun provideUiController(controller: ControlsUiControllerImpl): ControlsUiController
+
+ @BindsOptionalOf
+ abstract fun optionalPersistenceWrapper(): ControlsFavoritePersistenceWrapper
+
+ @Binds
+ @IntoMap
+ @ClassKey(ControlsProviderSelectorActivity::class)
+ abstract fun provideControlsProviderActivity(
+ activity: ControlsProviderSelectorActivity
+ ): Activity
+
+ @Binds
+ @IntoMap
+ @ClassKey(ControlsFavoritingActivity::class)
+ abstract fun provideControlsFavoritingActivity(
+ activity: ControlsFavoritingActivity
+ ): Activity
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
new file mode 100644
index 0000000..d62bb4d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
@@ -0,0 +1,95 @@
+/*
+ * 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 com.android.systemui.controls.management
+
+import android.content.ComponentName
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ImageView
+import android.widget.TextView
+import androidx.lifecycle.Lifecycle
+import androidx.recyclerview.widget.RecyclerView
+import com.android.settingslib.widget.CandidateInfo
+import com.android.systemui.R
+import java.util.concurrent.Executor
+
+/**
+ * Adapter for binding [CandidateInfo] related to [ControlsProviderService].
+ *
+ * This class handles subscribing and keeping track of the list of valid applications for
+ * displaying.
+ *
+ * @param uiExecutor an executor on the view thread of the containing [RecyclerView]
+ * @param lifecycle the lifecycle of the containing [LifecycleOwner] to control listening status
+ * @param controlsListingController the controller to keep track of valid applications
+ * @param layoutInflater an inflater for the views in the containing [RecyclerView]
+ * @param onAppSelected a callback to indicate that an app has been selected in the list.
+ */
+class AppAdapter(
+ uiExecutor: Executor,
+ lifecycle: Lifecycle,
+ controlsListingController: ControlsListingController,
+ private val layoutInflater: LayoutInflater,
+ private val onAppSelected: (ComponentName?) -> Unit = {}
+) : RecyclerView.Adapter<AppAdapter.Holder>() {
+
+ private var listOfServices = emptyList<CandidateInfo>()
+
+ private val callback = object : ControlsListingController.ControlsListingCallback {
+ override fun onServicesUpdated(list: List<CandidateInfo>) {
+ uiExecutor.execute {
+ listOfServices = list
+ notifyDataSetChanged()
+ }
+ }
+ }
+
+ init {
+ controlsListingController.observe(lifecycle, callback)
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, i: Int): Holder {
+ return Holder(layoutInflater.inflate(R.layout.app_item, parent, false))
+ }
+
+ override fun getItemCount() = listOfServices.size
+
+ override fun onBindViewHolder(holder: Holder, index: Int) {
+ holder.bindData(listOfServices[index])
+ holder.itemView.setOnClickListener {
+ onAppSelected(ComponentName.unflattenFromString(listOfServices[index].key))
+ }
+ }
+
+ /**
+ * Holder for binding views in the [RecyclerView]-
+ */
+ class Holder(view: View) : RecyclerView.ViewHolder(view) {
+ private val icon: ImageView = itemView.requireViewById(com.android.internal.R.id.icon)
+ private val title: TextView = itemView.requireViewById(com.android.internal.R.id.title)
+
+ /**
+ * Bind data to the view
+ * @param data Information about the [ControlsProviderService] to bind to the data
+ */
+ fun bindData(data: CandidateInfo) {
+ icon.setImageDrawable(data.loadIcon())
+ title.text = data.loadLabel()
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
new file mode 100644
index 0000000..e6d3c26
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.management
+
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.CheckBox
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.android.systemui.R
+import com.android.systemui.controls.ControlStatus
+import com.android.systemui.controls.controller.ControlInfo
+
+/**
+ * Adapter for binding [Control] information to views.
+ *
+ * @param layoutInflater an inflater for the views in the containing [RecyclerView]
+ * @param favoriteCallback a callback to be called when the favorite status of a [Control] is
+ * changed. The callback will take a [ControlInfo.Builder] that's
+ * pre-populated with the [Control] information and the new favorite
+ * status.
+ */
+class ControlAdapter(
+ private val layoutInflater: LayoutInflater,
+ private val favoriteCallback: (ControlInfo.Builder, Boolean) -> Unit
+) : RecyclerView.Adapter<ControlAdapter.Holder>() {
+
+ var listOfControls = emptyList<ControlStatus>()
+
+ override fun onCreateViewHolder(parent: ViewGroup, i: Int): Holder {
+ return Holder(layoutInflater.inflate(R.layout.control_item, parent, false))
+ }
+
+ override fun getItemCount() = listOfControls.size
+
+ override fun onBindViewHolder(holder: Holder, index: Int) {
+ holder.bindData(listOfControls[index], favoriteCallback)
+ }
+
+ /**
+ * Holder for binding views in the [RecyclerView]-
+ */
+ class Holder(view: View) : RecyclerView.ViewHolder(view) {
+ private val title: TextView = itemView.requireViewById(R.id.title)
+ private val subtitle: TextView = itemView.requireViewById(R.id.subtitle)
+ private val favorite: CheckBox = itemView.requireViewById(R.id.favorite)
+
+ /**
+ * Bind data to the view
+ * @param data information about the [Control]
+ * @param callback a callback to be called when the favorite status of the [Control] is
+ * changed. The callback will take a [ControlInfo.Builder] that's
+ * pre-populated with the [Control] information and the new favorite status.
+ */
+ fun bindData(data: ControlStatus, callback: (ControlInfo.Builder, Boolean) -> Unit) {
+ title.text = data.control.title
+ subtitle.text = data.control.subtitle
+ favorite.isChecked = data.favorite
+ favorite.setOnClickListener {
+ val infoBuilder = ControlInfo.Builder().apply {
+ controlId = data.control.controlId
+ controlTitle = data.control.title
+ deviceType = data.control.deviceType
+ }
+ callback(infoBuilder, favorite.isChecked)
+ }
+ }
+ }
+
+ fun setItems(list: List<ControlStatus>) {
+ listOfControls = list
+ notifyDataSetChanged()
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
new file mode 100644
index 0000000..01c4fef
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -0,0 +1,79 @@
+/*
+ * 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.systemui.controls.management
+
+import android.app.Activity
+import android.content.ComponentName
+import android.os.Bundle
+import android.view.LayoutInflater
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.android.systemui.controls.controller.ControlInfo
+import com.android.systemui.controls.controller.ControlsControllerImpl
+import com.android.systemui.dagger.qualifiers.Main
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+class ControlsFavoritingActivity @Inject constructor(
+ @Main private val executor: Executor,
+ private val controller: ControlsControllerImpl
+) : Activity() {
+
+ companion object {
+ private const val TAG = "ControlsFavoritingActivity"
+ const val EXTRA_APP = "extra_app_label"
+ const val EXTRA_COMPONENT = "extra_component"
+ }
+
+ private lateinit var recyclerView: RecyclerView
+ private lateinit var adapter: ControlAdapter
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ val app = intent.getCharSequenceExtra(EXTRA_APP)
+ val component = intent.getParcelableExtra<ComponentName>(EXTRA_COMPONENT)
+
+ // If we have no component name, there's not much we can do.
+ val callback = component?.let {
+ { infoBuilder: ControlInfo.Builder, status: Boolean ->
+ infoBuilder.componentName = it
+ controller.changeFavoriteStatus(infoBuilder.build(), status)
+ }
+ } ?: { _, _ -> Unit }
+
+ recyclerView = RecyclerView(applicationContext)
+ adapter = ControlAdapter(LayoutInflater.from(applicationContext), callback)
+ recyclerView.adapter = adapter
+ recyclerView.layoutManager = LinearLayoutManager(applicationContext)
+
+ if (app != null) {
+ setTitle("Controls for $app")
+ } else {
+ setTitle("Controls")
+ }
+ setContentView(recyclerView)
+
+ component?.let {
+ controller.loadForComponent(it) {
+ executor.execute {
+ adapter.setItems(it)
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt
new file mode 100644
index 0000000..09e0ce9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt
@@ -0,0 +1,32 @@
+/*
+ * 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 com.android.systemui.controls.management
+
+import android.content.ComponentName
+import com.android.settingslib.widget.CandidateInfo
+import com.android.systemui.statusbar.policy.CallbackController
+
+interface ControlsListingController :
+ CallbackController<ControlsListingController.ControlsListingCallback> {
+
+ fun getCurrentServices(): List<CandidateInfo>
+ fun getAppLabel(name: ComponentName): CharSequence? = ""
+
+ interface ControlsListingCallback {
+ fun onServicesUpdated(list: List<CandidateInfo>)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
new file mode 100644
index 0000000..9372162
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
@@ -0,0 +1,135 @@
+/*
+ * 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 com.android.systemui.controls.management
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.ServiceInfo
+import android.service.controls.ControlsProviderService
+import android.util.Log
+import com.android.internal.annotations.VisibleForTesting
+import com.android.settingslib.applications.DefaultAppInfo
+import com.android.settingslib.applications.ServiceListing
+import com.android.settingslib.widget.CandidateInfo
+import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.dagger.qualifiers.Background
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/**
+ * Provides a listing of components to be used as ControlsServiceProvider.
+ *
+ * This controller keeps track of components that satisfy:
+ *
+ * * Has an intent-filter responding to [ControlsProviderService.CONTROLS_ACTION]
+ * * Has the bind permission `android.permission.BIND_CONTROLS`
+ */
+@Singleton
+class ControlsListingControllerImpl @VisibleForTesting constructor(
+ private val context: Context,
+ @Background private val backgroundExecutor: Executor,
+ private val serviceListing: ServiceListing
+) : ControlsListingController {
+
+ @Inject
+ constructor(context: Context, executor: Executor): this(
+ context,
+ executor,
+ ServiceListing.Builder(context)
+ .setIntentAction(ControlsProviderService.CONTROLS_ACTION)
+ .setPermission("android.permission.BIND_CONTROLS")
+ .setNoun("Controls Provider")
+ .setSetting("controls_providers")
+ .setTag("controls_providers")
+ .build()
+ )
+
+ companion object {
+ private const val TAG = "ControlsListingControllerImpl"
+ }
+
+ private var availableServices = emptyList<ServiceInfo>()
+
+ init {
+ serviceListing.addCallback {
+ Log.d(TAG, "ServiceConfig reloaded")
+ availableServices = it.toList()
+
+ backgroundExecutor.execute {
+ callbacks.forEach {
+ it.onServicesUpdated(getCurrentServices())
+ }
+ }
+ }
+ }
+
+ // All operations in background thread
+ private val callbacks = mutableSetOf<ControlsListingController.ControlsListingCallback>()
+
+ /**
+ * Adds a callback to this controller.
+ *
+ * The callback will be notified after it is added as well as any time that the valid
+ * components change.
+ *
+ * @param listener a callback to be notified
+ */
+ override fun addCallback(listener: ControlsListingController.ControlsListingCallback) {
+ backgroundExecutor.execute {
+ callbacks.add(listener)
+ if (callbacks.size == 1) {
+ serviceListing.setListening(true)
+ serviceListing.reload()
+ } else {
+ listener.onServicesUpdated(getCurrentServices())
+ }
+ }
+ }
+
+ /**
+ * Removes a callback from this controller.
+ *
+ * @param listener the callback to be removed.
+ */
+ override fun removeCallback(listener: ControlsListingController.ControlsListingCallback) {
+ backgroundExecutor.execute {
+ callbacks.remove(listener)
+ if (callbacks.size == 0) {
+ serviceListing.setListening(false)
+ }
+ }
+ }
+
+ /**
+ * @return a list of components that satisfy the requirements to be a
+ * [ControlsProviderService]
+ */
+ override fun getCurrentServices(): List<CandidateInfo> =
+ availableServices.map { ControlsServiceInfo(context, it) }
+
+ /**
+ * Get the localized label for the component.
+ *
+ * @param name the name of the component
+ * @return a label as returned by [CandidateInfo.loadLabel] or `null`.
+ */
+ override fun getAppLabel(name: ComponentName): CharSequence? {
+ return getCurrentServices().firstOrNull { (it as? DefaultAppInfo)?.componentName == name }
+ ?.loadLabel()
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
new file mode 100644
index 0000000..69af516
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
@@ -0,0 +1,69 @@
+/*
+ * 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 com.android.systemui.controls.management
+
+import android.content.ComponentName
+import android.content.Intent
+import android.os.Bundle
+import android.view.LayoutInflater
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.util.LifecycleActivity
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * Activity to select an application to favorite the [Control] provided by them.
+ */
+class ControlsProviderSelectorActivity @Inject constructor(
+ @Main private val executor: Executor,
+ private val listingController: ControlsListingController
+) : LifecycleActivity() {
+
+ companion object {
+ private const val TAG = "ControlsProviderSelectorActivity"
+ }
+
+ private lateinit var recyclerView: RecyclerView
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ recyclerView = RecyclerView(applicationContext)
+ recyclerView.adapter = AppAdapter(executor, lifecycle, listingController,
+ LayoutInflater.from(this), ::launchFavoritingActivity)
+ recyclerView.layoutManager = LinearLayoutManager(applicationContext)
+
+ setContentView(recyclerView)
+ }
+
+ /**
+ * Launch the [ControlsFavoritingActivity] for the specified component.
+ * @param component a component name for a [ControlsProviderService]
+ */
+ fun launchFavoritingActivity(component: ComponentName?) {
+ component?.let {
+ val intent = Intent(applicationContext, ControlsFavoritingActivity::class.java).apply {
+ putExtra(ControlsFavoritingActivity.EXTRA_APP, listingController.getAppLabel(it))
+ putExtra(ControlsFavoritingActivity.EXTRA_COMPONENT, it)
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP
+ }
+ startActivity(intent)
+ }
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
new file mode 100644
index 0000000..0270c2b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.ui
+
+import android.content.ComponentName
+import android.service.controls.Control
+import android.service.controls.actions.ControlAction
+
+interface ControlsUiController {
+ fun onRefreshState(componentName: ComponentName, controls: List<Control>)
+ fun onActionResponse(
+ componentName: ComponentName,
+ controlId: String,
+ @ControlAction.ResponseResult response: Int
+ )
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
new file mode 100644
index 0000000..0ace126
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.systemui.controls.ui
+
+import android.content.ComponentName
+import android.service.controls.Control
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class ControlsUiControllerImpl @Inject constructor() : ControlsUiController {
+
+ override fun onRefreshState(componentName: ComponentName, controls: List<Control>) {
+ TODO("not implemented")
+ }
+
+ override fun onActionResponse(componentName: ComponentName, controlId: String, response: Int) {
+ TODO("not implemented")
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index df79310..91f032d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -19,7 +19,9 @@
import android.app.Activity;
import com.android.systemui.ForegroundServicesDialog;
+import com.android.systemui.bubbles.BubbleOverflowActivity;
import com.android.systemui.keyguard.WorkLockActivity;
+import com.android.systemui.screenrecord.ScreenRecordDialog;
import com.android.systemui.settings.BrightnessDialog;
import com.android.systemui.tuner.TunerActivity;
@@ -29,7 +31,7 @@
import dagger.multibindings.IntoMap;
/**
- * Services and Activities that are injectable should go here.
+ * Activities that are injectable should go here.
*/
@Module
public abstract class DefaultActivityBinder {
@@ -56,4 +58,16 @@
@IntoMap
@ClassKey(BrightnessDialog.class)
public abstract Activity bindBrightnessDialog(BrightnessDialog activity);
+
+ /** Inject into ScreenRecordDialog */
+ @Binds
+ @IntoMap
+ @ClassKey(ScreenRecordDialog.class)
+ public abstract Activity bindScreenRecordDialog(ScreenRecordDialog activity);
+
+ /** Inject into BubbleOverflowActivity. */
+ @Binds
+ @IntoMap
+ @ClassKey(BubbleOverflowActivity.class)
+ public abstract Activity bindBubbleOverflowActivity(BubbleOverflowActivity activity);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
index f790d99..f006acf 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultServiceBinder.java
@@ -22,6 +22,7 @@
import com.android.systemui.SystemUIService;
import com.android.systemui.doze.DozeService;
import com.android.systemui.keyguard.KeyguardService;
+import com.android.systemui.screenrecord.RecordingService;
import com.android.systemui.screenshot.TakeScreenshotService;
import dagger.Binds;
@@ -63,4 +64,10 @@
@IntoMap
@ClassKey(TakeScreenshotService.class)
public abstract Service bindTakeScreenshotService(TakeScreenshotService service);
+
+ /** Inject into RecordingService */
+ @Binds
+ @IntoMap
+ @ClassKey(RecordingService.class)
+ public abstract Service bindRecordingService(RecordingService service);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
index 6744d74..2877ed0 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyBinder.java
@@ -20,10 +20,14 @@
import com.android.systemui.appops.AppOpsController;
import com.android.systemui.appops.AppOpsControllerImpl;
import com.android.systemui.classifier.FalsingManagerProxy;
+import com.android.systemui.controls.dagger.ControlsModule;
import com.android.systemui.doze.DozeHost;
+import com.android.systemui.globalactions.GlobalActionsComponent;
+import com.android.systemui.globalactions.GlobalActionsImpl;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.GlobalActions;
import com.android.systemui.plugins.VolumeDialogController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.PowerNotificationWarnings;
@@ -82,7 +86,7 @@
/**
* Maps interfaces to implementations for use with Dagger.
*/
-@Module
+@Module(includes = {ControlsModule.class})
public abstract class DependencyBinder {
/**
@@ -99,6 +103,17 @@
/**
*/
@Binds
+ public abstract GlobalActions provideGlobalActions(GlobalActionsImpl controllerImpl);
+
+ /**
+ */
+ @Binds
+ public abstract GlobalActions.GlobalActionsManager provideGlobalActionsManager(
+ GlobalActionsComponent controllerImpl);
+
+ /**
+ */
+ @Binds
public abstract LocationController provideLocationController(
LocationControllerImpl controllerImpl);
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
index 8d10552..53a23b8 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DependencyProvider.java
@@ -20,7 +20,6 @@
import android.app.INotificationManager;
import android.content.Context;
-import android.content.pm.IPackageManager;
import android.hardware.display.AmbientDisplayConfiguration;
import android.hardware.display.NightDisplayListener;
import android.os.Handler;
@@ -115,13 +114,6 @@
/** */
@Singleton
@Provides
- public IPackageManager provideIPackageManager() {
- return IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
- }
-
- /** */
- @Singleton
- @Provides
public LayoutInflater providerLayoutInflater(Context context) {
return LayoutInflater.from(context);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
index 0b73ab6..3aa14a3 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemServicesModule.java
@@ -26,16 +26,24 @@
import android.app.NotificationManager;
import android.app.WallpaperManager;
import android.app.admin.DevicePolicyManager;
+import android.app.trust.TrustManager;
+import android.content.ContentResolver;
import android.content.Context;
+import android.content.pm.IPackageManager;
import android.content.res.Resources;
import android.hardware.SensorPrivacyManager;
+import android.media.AudioManager;
+import android.net.ConnectivityManager;
import android.os.Handler;
import android.os.PowerManager;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.Vibrator;
import android.service.dreams.DreamService;
import android.service.dreams.IDreamManager;
+import android.telecom.TelecomManager;
+import android.telephony.TelephonyManager;
import android.view.IWindowManager;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
@@ -45,6 +53,7 @@
import com.android.internal.util.LatencyTracker;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.shared.system.PackageManagerWrapper;
@@ -64,6 +73,12 @@
return context.getSystemService(AccessibilityManager.class);
}
+ @Provides
+ @Singleton
+ static ActivityManager provideActivityManager(Context context) {
+ return context.getSystemService(ActivityManager.class);
+ }
+
@Singleton
@Provides
static AlarmManager provideAlarmManager(Context context) {
@@ -72,6 +87,30 @@
@Provides
@Singleton
+ static AudioManager provideAudioManager(Context context) {
+ return context.getSystemService(AudioManager.class);
+ }
+
+ @Provides
+ @Singleton
+ static ConnectivityManager provideConnectivityManagager(Context context) {
+ return context.getSystemService(ConnectivityManager.class);
+ }
+
+ @Provides
+ @Singleton
+ static ContentResolver provideContentResolver(Context context) {
+ return context.getContentResolver();
+ }
+
+ @Provides
+ @DisplayId
+ static int provideDisplayId(Context context) {
+ return context.getDisplayId();
+ }
+
+ @Provides
+ @Singleton
static DevicePolicyManager provideDevicePolicyManager(Context context) {
return context.getSystemService(DevicePolicyManager.class);
}
@@ -109,6 +148,13 @@
return WindowManagerGlobal.getWindowManagerService();
}
+ /** */
+ @Singleton
+ @Provides
+ public IPackageManager provideIPackageManager() {
+ return IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
+ }
+
@Singleton
@Provides
static KeyguardManager provideKeyguardManager(Context context) {
@@ -163,6 +209,31 @@
@Provides
@Singleton
+ static TelecomManager provideTelecomManager(Context context) {
+ return context.getSystemService(TelecomManager.class);
+ }
+
+ @Provides
+ @Singleton
+ static TelephonyManager provideTelephonyManager(Context context) {
+ return context.getSystemService(TelephonyManager.class);
+ }
+
+ @Provides
+ @Singleton
+ static TrustManager provideTrustManager(Context context) {
+ return context.getSystemService(TrustManager.class);
+ }
+
+ @Provides
+ @Singleton
+ @Nullable
+ static Vibrator provideVibrator(Context context) {
+ return context.getSystemService(Vibrator.class);
+ }
+
+ @Provides
+ @Singleton
static UserManager provideUserManager(Context context) {
return context.getSystemService(UserManager.class);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 442313d..a6fa414 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -30,12 +30,12 @@
import com.android.systemui.recents.Recents;
import com.android.systemui.stackdivider.Divider;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
import com.android.systemui.statusbar.notification.people.PeopleHubModule;
import com.android.systemui.statusbar.phone.KeyguardLiftController;
import com.android.systemui.statusbar.phone.StatusBar;
-import com.android.systemui.statusbar.phone.StatusBarComponent;
+import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.util.concurrency.ConcurrencyModule;
import com.android.systemui.util.sensors.AsyncSensorManager;
@@ -82,6 +82,11 @@
keyguardUpdateMonitor, dumpController);
}
+ /** */
+ @Binds
+ public abstract NotificationRowBinder bindNotificationRowBinder(
+ NotificationRowBinderImpl notificationRowBinder);
+
@Singleton
@Provides
static SysUiState provideSysUiState() {
@@ -106,9 +111,4 @@
@Singleton
@Binds
abstract SystemClock bindSystemClock(SystemClockImpl systemClock);
-
- @Singleton
- @Binds
- abstract NotifListBuilder bindNotifListBuilder(NotifListBuilderImpl impl);
-
}
diff --git a/media/java/android/media/RouteSessionInfo.aidl b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/DisplayId.java
similarity index 61%
copy from media/java/android/media/RouteSessionInfo.aidl
copy to packages/SystemUI/src/com/android/systemui/dagger/qualifiers/DisplayId.java
index fb5d836..155a6d2 100644
--- a/media/java/android/media/RouteSessionInfo.aidl
+++ b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/DisplayId.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 The Android Open Source Project
+ * 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.
@@ -14,6 +14,17 @@
* limitations under the License.
*/
-package android.media;
+package com.android.systemui.dagger.qualifiers;
-parcelable RouteSessionInfo;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface DisplayId {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index c9faf69..4e88726 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -91,7 +91,7 @@
if (mDebuggable) {
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_AOD_BRIGHTNESS);
- mBroadcastDispatcher.registerReceiver(this, filter, handler, UserHandle.ALL);
+ mBroadcastDispatcher.registerReceiverWithHandler(this, filter, handler, UserHandle.ALL);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
index 19b6f82..e949007 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsComponent.java
@@ -19,7 +19,6 @@
import android.os.ServiceManager;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.Dependency;
import com.android.systemui.SystemUI;
import com.android.systemui.plugins.GlobalActions;
import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
@@ -29,6 +28,7 @@
import com.android.systemui.statusbar.policy.ExtensionController.Extension;
import javax.inject.Inject;
+import javax.inject.Provider;
import javax.inject.Singleton;
/**
@@ -38,23 +38,29 @@
public class GlobalActionsComponent extends SystemUI implements Callbacks, GlobalActionsManager {
private final CommandQueue mCommandQueue;
+ private final ExtensionController mExtensionController;
+ private final Provider<GlobalActions> mGlobalActionsProvider;
private GlobalActions mPlugin;
private Extension<GlobalActions> mExtension;
private IStatusBarService mBarService;
@Inject
- public GlobalActionsComponent(Context context, CommandQueue commandQueue) {
+ public GlobalActionsComponent(Context context, CommandQueue commandQueue,
+ ExtensionController extensionController,
+ Provider<GlobalActions> globalActionsProvider) {
super(context);
mCommandQueue = commandQueue;
+ mExtensionController = extensionController;
+ mGlobalActionsProvider = globalActionsProvider;
}
@Override
public void start() {
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
- mExtension = Dependency.get(ExtensionController.class).newExtension(GlobalActions.class)
+ mExtension = mExtensionController.newExtension(GlobalActions.class)
.withPlugin(GlobalActions.class)
- .withDefault(() -> new GlobalActionsImpl(mContext, mCommandQueue))
+ .withDefault(mGlobalActionsProvider::get)
.withCallback(this::onExtensionCallback)
.build();
mPlugin = mExtension.get();
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 3ccad64..c138462 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -21,8 +21,10 @@
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
+import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.Dialog;
+import android.app.IActivityManager;
import android.app.KeyguardManager;
import android.app.PendingIntent;
import android.app.StatusBarManager;
@@ -30,11 +32,13 @@
import android.app.admin.DevicePolicyManager;
import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.UserInfo;
+import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
@@ -44,20 +48,17 @@
import android.os.IBinder;
import android.os.Message;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.Vibrator;
import android.provider.Settings;
-import android.service.dreams.DreamService;
import android.service.dreams.IDreamManager;
import android.sysprop.TelephonyProperties;
import android.telecom.TelecomManager;
import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
-import android.text.TextUtils;
import android.util.ArraySet;
import android.util.FeatureFlagUtils;
import android.util.Log;
@@ -92,6 +93,7 @@
import com.android.systemui.MultiListLayout.MultiListAdapter;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
import com.android.systemui.plugins.GlobalActionsPanelPlugin;
@@ -106,6 +108,8 @@
import java.util.ArrayList;
import java.util.List;
+import javax.inject.Inject;
+
/**
* Helper to show the global actions dialog. Each item is an {@link Action} that
* may show depending on whether the keyguard is showing, and whether the device
@@ -146,6 +150,13 @@
private final LockPatternUtils mLockPatternUtils;
private final KeyguardManager mKeyguardManager;
private final BroadcastDispatcher mBroadcastDispatcher;
+ private final ContentResolver mContentResolver;
+ private final Resources mResources;
+ private final UserManager mUserManager;
+ private final TrustManager mTrustManager;
+ private final IActivityManager mIActivityManager;
+ private final TelecomManager mTelecomManager;
+ private final MetricsLogger mMetricsLogger;
private ArrayList<Action> mItems;
private ActionsDialog mDialog;
@@ -161,8 +172,6 @@
private boolean mIsWaitingForEcmExit = false;
private boolean mHasTelephony;
private boolean mHasVibrator;
- private boolean mHasLogoutButton;
- private boolean mHasLockdownButton;
private final boolean mShowSilentToggle;
private final EmergencyAffordanceManager mEmergencyAffordanceManager;
private final ScreenshotHelper mScreenshotHelper;
@@ -173,17 +182,32 @@
/**
* @param context everything needs a context :(
*/
- public GlobalActionsDialog(Context context, GlobalActionsManager windowManagerFuncs) {
+ @Inject
+ public GlobalActionsDialog(Context context, GlobalActionsManager windowManagerFuncs,
+ AudioManager audioManager, IDreamManager iDreamManager,
+ DevicePolicyManager devicePolicyManager, LockPatternUtils lockPatternUtils,
+ KeyguardManager keyguardManager, BroadcastDispatcher broadcastDispatcher,
+ ConnectivityManager connectivityManager, TelephonyManager telephonyManager,
+ ContentResolver contentResolver, @Nullable Vibrator vibrator, @Main Resources resources,
+ ConfigurationController configurationController, ActivityStarter activityStarter,
+ KeyguardStateController keyguardStateController, UserManager userManager,
+ TrustManager trustManager, IActivityManager iActivityManager,
+ TelecomManager telecomManager, MetricsLogger metricsLogger) {
mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme);
mWindowManagerFuncs = windowManagerFuncs;
- mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
- mDreamManager = IDreamManager.Stub.asInterface(
- ServiceManager.getService(DreamService.DREAM_SERVICE));
- mDevicePolicyManager = (DevicePolicyManager) mContext.getSystemService(
- Context.DEVICE_POLICY_SERVICE);
- mLockPatternUtils = new LockPatternUtils(mContext);
- mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
- mBroadcastDispatcher = Dependency.get(BroadcastDispatcher.class);
+ mAudioManager = audioManager;
+ mDreamManager = iDreamManager;
+ mDevicePolicyManager = devicePolicyManager;
+ mLockPatternUtils = lockPatternUtils;
+ mKeyguardManager = keyguardManager;
+ mBroadcastDispatcher = broadcastDispatcher;
+ mContentResolver = contentResolver;
+ mResources = resources;
+ mUserManager = userManager;
+ mTrustManager = trustManager;
+ mIActivityManager = iActivityManager;
+ mTelecomManager = telecomManager;
+ mMetricsLogger = metricsLogger;
// receive broadcasts
IntentFilter filter = new IntentFilter();
@@ -192,32 +216,25 @@
filter.addAction(TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter);
- ConnectivityManager cm = (ConnectivityManager)
- context.getSystemService(Context.CONNECTIVITY_SERVICE);
- mHasTelephony = cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
+ mHasTelephony = connectivityManager.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
// get notified of phone state changes
- TelephonyManager telephonyManager =
- (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SERVICE_STATE);
- mContext.getContentResolver().registerContentObserver(
+ contentResolver.registerContentObserver(
Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true,
mAirplaneModeObserver);
- Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
mHasVibrator = vibrator != null && vibrator.hasVibrator();
- mShowSilentToggle = SHOW_SILENT_TOGGLE && !mContext.getResources().getBoolean(
+ mShowSilentToggle = SHOW_SILENT_TOGGLE && !resources.getBoolean(
R.bool.config_useFixedVolume);
mEmergencyAffordanceManager = new EmergencyAffordanceManager(context);
mScreenshotHelper = new ScreenshotHelper(context);
mScreenRecordHelper = new ScreenRecordHelper(context);
- Dependency.get(ConfigurationController.class).addCallback(this);
+ configurationController.addCallback(this);
- mActivityStarter = Dependency.get(ActivityStarter.class);
- KeyguardStateController keyguardStateController =
- Dependency.get(KeyguardStateController.class);
+ mActivityStarter = activityStarter;
keyguardStateController.addCallback(new KeyguardStateController.Callback() {
@Override
public void onUnlockedChanged() {
@@ -343,12 +360,9 @@
onAirplaneModeChanged();
mItems = new ArrayList<Action>();
- String[] defaultActions = mContext.getResources().getStringArray(
- R.array.config_globalActionsList);
+ String[] defaultActions = mResources.getStringArray(R.array.config_globalActionsList);
ArraySet<String> addedKeys = new ArraySet<String>();
- mHasLogoutButton = false;
- mHasLockdownButton = false;
for (int i = 0; i < defaultActions.length; i++) {
String actionKey = defaultActions[i];
if (addedKeys.contains(actionKey)) {
@@ -360,7 +374,7 @@
} else if (GLOBAL_ACTION_KEY_AIRPLANE.equals(actionKey)) {
mItems.add(mAirplaneModeOn);
} else if (GLOBAL_ACTION_KEY_BUGREPORT.equals(actionKey)) {
- if (Settings.Global.getInt(mContext.getContentResolver(),
+ if (Settings.Global.getInt(mContentResolver,
Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0 && isCurrentUserOwner()) {
mItems.add(new BugReportAction());
}
@@ -375,11 +389,10 @@
} else if (GLOBAL_ACTION_KEY_SETTINGS.equals(actionKey)) {
mItems.add(getSettingsAction());
} else if (GLOBAL_ACTION_KEY_LOCKDOWN.equals(actionKey)) {
- if (Settings.Secure.getIntForUser(mContext.getContentResolver(),
+ if (Settings.Secure.getIntForUser(mContentResolver,
Settings.Secure.LOCKDOWN_IN_POWER_MENU, 0, getCurrentUser().id) != 0
&& shouldDisplayLockdown()) {
mItems.add(getLockdownAction());
- mHasLockdownButton = true;
}
} else if (GLOBAL_ACTION_KEY_VOICEASSIST.equals(actionKey)) {
mItems.add(getVoiceAssistAction());
@@ -393,7 +406,6 @@
if (mDevicePolicyManager.isLogoutEnabled()
&& getCurrentUser().id != UserHandle.USER_SYSTEM) {
mItems.add(new LogoutAction());
- mHasLogoutButton = true;
}
} else if (GLOBAL_ACTION_KEY_EMERGENCY.equals(actionKey)) {
if (!mEmergencyAffordanceManager.needsEmergencyAffordance()) {
@@ -431,7 +443,8 @@
mKeyguardManager.isDeviceLocked())
: null;
- ActionsDialog dialog = new ActionsDialog(mContext, mAdapter, panelViewController);
+ ActionsDialog dialog = new ActionsDialog(
+ mContext, mAdapter, panelViewController, isControlsEnabled(mContext));
dialog.setCanceledOnTouchOutside(false); // Handled by the custom class.
dialog.setKeyguardShowing(mKeyguardShowing);
@@ -474,8 +487,7 @@
@Override
public boolean onLongPress() {
- UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
+ if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
mWindowManagerFuncs.reboot(true);
return true;
}
@@ -506,7 +518,7 @@
@Override
public boolean shouldBeSeparated() {
- return shouldUseSeparatedView();
+ return !isControlsEnabled(mContext);
}
@Override
@@ -560,9 +572,8 @@
@Override
public void onPress() {
- MetricsLogger.action(mContext, MetricsEvent.ACTION_EMERGENCY_DIALER_FROM_POWER_MENU);
- Intent intent = mContext.getSystemService(TelecomManager.class)
- .createLaunchEmergencyDialerIntent(null /* number */);
+ mMetricsLogger.action(MetricsEvent.ACTION_EMERGENCY_DIALER_FROM_POWER_MENU);
+ Intent intent = mTelecomManager.createLaunchEmergencyDialerIntent(null /* number */);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
@@ -579,8 +590,7 @@
@Override
public boolean onLongPress() {
- UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- if (!um.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
+ if (!mUserManager.hasUserRestriction(UserManager.DISALLOW_SAFE_BOOT)) {
mWindowManagerFuncs.reboot(true);
return true;
}
@@ -618,8 +628,7 @@
@Override
public void run() {
mScreenshotHelper.takeScreenshot(1, true, true, mHandler, null);
- MetricsLogger.action(mContext,
- MetricsEvent.ACTION_SCREENSHOT_POWER_MENU);
+ mMetricsLogger.action(MetricsEvent.ACTION_SCREENSHOT_POWER_MENU);
}
}, 500);
}
@@ -666,11 +675,11 @@
public void run() {
try {
// Take an "interactive" bugreport.
- MetricsLogger.action(mContext,
+ mMetricsLogger.action(
MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_INTERACTIVE);
- if (!ActivityManager.getService().launchBugReportHandlerApp()) {
+ if (!mIActivityManager.launchBugReportHandlerApp()) {
Log.w(TAG, "Bugreport handler could not be launched");
- ActivityManager.getService().requestInteractiveBugReport();
+ mIActivityManager.requestInteractiveBugReport();
}
} catch (RemoteException e) {
}
@@ -687,8 +696,8 @@
}
try {
// Take a "full" bugreport.
- MetricsLogger.action(mContext, MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL);
- ActivityManager.getService().requestFullBugReport();
+ mMetricsLogger.action(MetricsEvent.ACTION_BUGREPORT_FROM_POWER_MENU_FULL);
+ mIActivityManager.requestFullBugReport();
} catch (RemoteException e) {
}
return false;
@@ -726,8 +735,8 @@
mHandler.postDelayed(() -> {
try {
int currentUserId = getCurrentUser().id;
- ActivityManager.getService().switchUser(UserHandle.USER_SYSTEM);
- ActivityManager.getService().stopUser(currentUserId, true /*force*/, null);
+ mIActivityManager.switchUser(UserHandle.USER_SYSTEM);
+ mIActivityManager.stopUser(currentUserId, true /*force*/, null);
} catch (RemoteException re) {
Log.e(TAG, "Couldn't logout user " + re);
}
@@ -834,20 +843,18 @@
}
private void lockProfiles() {
- final UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- final TrustManager tm = (TrustManager) mContext.getSystemService(Context.TRUST_SERVICE);
final int currentUserId = getCurrentUser().id;
- final int[] profileIds = um.getEnabledProfileIds(currentUserId);
+ final int[] profileIds = mUserManager.getEnabledProfileIds(currentUserId);
for (final int id : profileIds) {
if (id != currentUserId) {
- tm.setDeviceLockedForUser(id, true);
+ mTrustManager.setDeviceLockedForUser(id, true);
}
}
}
private UserInfo getCurrentUser() {
try {
- return ActivityManager.getService().getCurrentUser();
+ return mIActivityManager.getCurrentUser();
} catch (RemoteException re) {
return null;
}
@@ -859,9 +866,8 @@
}
private void addUsersToMenu(ArrayList<Action> items) {
- UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- if (um.isUserSwitcherEnabled()) {
- List<UserInfo> users = um.getUsers();
+ if (mUserManager.isUserSwitcherEnabled()) {
+ List<UserInfo> users = mUserManager.getUsers();
UserInfo currentUser = getCurrentUser();
for (final UserInfo user : users) {
if (user.supportsSwitchToByUser()) {
@@ -875,7 +881,7 @@
+ (isCurrentUser ? " \u2714" : "")) {
public void onPress() {
try {
- ActivityManager.getService().switchUser(user.id);
+ mIActivityManager.switchUser(user.id);
} catch (RemoteException re) {
Log.e(TAG, "Couldn't switch user " + re);
}
@@ -932,7 +938,7 @@
/** {@inheritDoc} */
public void onShow(DialogInterface dialog) {
- MetricsLogger.visible(mContext, MetricsEvent.POWER_MENU);
+ mMetricsLogger.visible(MetricsEvent.POWER_MENU);
}
/**
@@ -1148,6 +1154,9 @@
}
protected int getActionLayoutId(Context context) {
+ if (isControlsEnabled(context)) {
+ return com.android.systemui.R.layout.global_actions_grid_item_v2;
+ }
return com.android.systemui.R.layout.global_actions_grid_item;
}
@@ -1160,13 +1169,6 @@
TextView messageView = (TextView) v.findViewById(R.id.message);
messageView.setSelected(true); // necessary for marquee to work
- TextView statusView = (TextView) v.findViewById(R.id.status);
- final String status = getStatus();
- if (!TextUtils.isEmpty(status)) {
- statusView.setText(status);
- } else {
- statusView.setVisibility(View.GONE);
- }
if (mIcon != null) {
icon.setImageDrawable(mIcon);
icon.setScaleType(ScaleType.CENTER_CROP);
@@ -1251,32 +1253,26 @@
LayoutInflater inflater) {
willCreate();
- View v = inflater.inflate(R
- .layout.global_actions_item, parent, false);
+ View v = inflater.inflate(com.android.systemui.R
+ .layout.global_actions_grid_item, parent, false);
ImageView icon = (ImageView) v.findViewById(R.id.icon);
TextView messageView = (TextView) v.findViewById(R.id.message);
- TextView statusView = (TextView) v.findViewById(R.id.status);
final boolean enabled = isEnabled();
+ boolean on = ((mState == State.On) || (mState == State.TurningOn));
if (messageView != null) {
- messageView.setText(mMessageResId);
+ messageView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId);
messageView.setEnabled(enabled);
messageView.setSelected(true); // necessary for marquee to work
}
- boolean on = ((mState == State.On) || (mState == State.TurningOn));
if (icon != null) {
icon.setImageDrawable(context.getDrawable(
(on ? mEnabledIconResId : mDisabledIconResid)));
icon.setEnabled(enabled);
}
- if (statusView != null) {
- statusView.setText(on ? mEnabledStatusMessageResId : mDisabledStatusMessageResId);
- statusView.setVisibility(View.VISIBLE);
- statusView.setEnabled(enabled);
- }
v.setEnabled(enabled);
return v;
@@ -1422,8 +1418,8 @@
} else if (TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) {
// Airplane mode can be changed after ECM exits if airplane toggle button
// is pressed during ECM mode
- if (!(intent.getBooleanExtra("PHONE_IN_ECM_STATE", false)) &&
- mIsWaitingForEcmExit) {
+ if (!(intent.getBooleanExtra(TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, false))
+ && mIsWaitingForEcmExit) {
mIsWaitingForEcmExit = false;
changeAirplaneModeSystemSetting(true);
}
@@ -1492,7 +1488,7 @@
if (mHasTelephony) return;
boolean airplaneModeOn = Settings.Global.getInt(
- mContext.getContentResolver(),
+ mContentResolver,
Settings.Global.AIRPLANE_MODE_ON,
0) == 1;
mAirplaneState = airplaneModeOn ? ToggleAction.State.On : ToggleAction.State.Off;
@@ -1504,7 +1500,7 @@
*/
private void changeAirplaneModeSystemSetting(boolean on) {
Settings.Global.putInt(
- mContext.getContentResolver(),
+ mContentResolver,
Settings.Global.AIRPLANE_MODE_ON,
on ? 1 : 0);
Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
@@ -1533,15 +1529,18 @@
private ResetOrientationData mResetOrientationData;
private boolean mHadTopUi;
private final StatusBarWindowController mStatusBarWindowController;
+ private boolean mControlsEnabled;
ActionsDialog(Context context, MyAdapter adapter,
- GlobalActionsPanelPlugin.PanelViewController plugin) {
+ GlobalActionsPanelPlugin.PanelViewController plugin,
+ boolean controlsEnabled) {
super(context, com.android.systemui.R.style.Theme_SystemUI_Dialog_GlobalActions);
mContext = context;
mAdapter = adapter;
mColorExtractor = Dependency.get(SysuiColorExtractor.class);
mStatusBarService = Dependency.get(IStatusBarService.class);
mStatusBarWindowController = Dependency.get(StatusBarWindowController.class);
+ mControlsEnabled = controlsEnabled;
// Window initialization
Window window = getWindow();
@@ -1658,6 +1657,10 @@
}
private int getGlobalActionsLayoutId(Context context) {
+ if (mControlsEnabled) {
+ return com.android.systemui.R.layout.global_actions_grid_v2;
+ }
+
int rotation = RotationUtils.getRotation(context);
boolean useGridLayout = isForceGridEnabled(context)
|| (shouldUsePanel() && rotation == RotationUtils.ROTATION_NONE);
@@ -1861,4 +1864,9 @@
private static boolean shouldUseSeparatedView() {
return true;
}
+
+ private static boolean isControlsEnabled(Context context) {
+ return Settings.Secure.getInt(
+ context.getContentResolver(), "systemui.controls_available", 0) == 1;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java
new file mode 100644
index 0000000..6749f1d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsFlatLayout.java
@@ -0,0 +1,186 @@
+/*
+ * 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 com.android.systemui.globalactions;
+
+import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE;
+import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE;
+import static com.android.systemui.util.leak.RotationUtils.ROTATION_SEASCAPE;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Single row implementation of the button layout created by the global actions dialog.
+ */
+public class GlobalActionsFlatLayout extends GlobalActionsLayout {
+ public GlobalActionsFlatLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ mBackgroundsSet = true;
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ // backgrounds set only once, the first time onMeasure is called after inflation
+ // if (getListView() != null && !mBackgroundsSet) {
+ // setBackgrounds();
+ // mBackgroundsSet = true;
+ // }
+ }
+
+ @VisibleForTesting
+ protected void setupListView() {
+ ListGridLayout listView = getListView();
+ listView.setExpectedCount(Math.min(2, mAdapter.countListItems()));
+ listView.setReverseSublists(shouldReverseSublists());
+ listView.setReverseItems(shouldReverseListItems());
+ listView.setSwapRowsAndColumns(shouldSwapRowsAndColumns());
+ }
+
+ @Override
+ public void onUpdateList() {
+ setupListView();
+ super.onUpdateList();
+ updateSeparatedItemSize();
+ }
+
+ /**
+ * If the separated view contains only one item, expand the bounds of that item to take up the
+ * entire view, so that the whole thing is touch-able.
+ */
+ @VisibleForTesting
+ protected void updateSeparatedItemSize() {
+ ViewGroup separated = getSeparatedView();
+ if (separated.getChildCount() == 0) {
+ return;
+ }
+ View firstChild = separated.getChildAt(0);
+ ViewGroup.LayoutParams childParams = firstChild.getLayoutParams();
+
+ if (separated.getChildCount() == 1) {
+ childParams.width = ViewGroup.LayoutParams.MATCH_PARENT;
+ childParams.height = ViewGroup.LayoutParams.MATCH_PARENT;
+ } else {
+ childParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
+ childParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+ }
+ }
+
+ @Override
+ protected ListGridLayout getListView() {
+ return (ListGridLayout) super.getListView();
+ }
+
+ @Override
+ protected void removeAllListViews() {
+ ListGridLayout list = getListView();
+ if (list != null) {
+ list.removeAllItems();
+ }
+ }
+
+ @Override
+ protected void addToListView(View v, boolean reverse) {
+ ListGridLayout list = getListView();
+ if (list != null) {
+ list.addItem(v);
+ }
+ }
+
+ @Override
+ public void removeAllItems() {
+ ViewGroup separatedList = getSeparatedView();
+ ListGridLayout list = getListView();
+ if (separatedList != null) {
+ separatedList.removeAllViews();
+ }
+ if (list != null) {
+ list.removeAllItems();
+ }
+ }
+
+ /**
+ * Determines whether the ListGridLayout should fill sublists in the reverse order.
+ * Used to account for sublist ordering changing between landscape and seascape views.
+ */
+ @VisibleForTesting
+ protected boolean shouldReverseSublists() {
+ if (getCurrentRotation() == ROTATION_SEASCAPE) {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Determines whether the ListGridLayout should fill rows first instead of columns.
+ * Used to account for vertical/horizontal changes due to landscape or seascape rotations.
+ */
+ @VisibleForTesting
+ protected boolean shouldSwapRowsAndColumns() {
+ if (getCurrentRotation() == ROTATION_NONE) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ protected boolean shouldReverseListItems() {
+ int rotation = getCurrentRotation();
+ boolean reverse = false; // should we add items to parents in the reverse order?
+ if (rotation == ROTATION_NONE
+ || rotation == ROTATION_SEASCAPE) {
+ reverse = !reverse; // if we're in portrait or seascape, reverse items
+ }
+ if (getCurrentLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
+ reverse = !reverse; // if we're in an RTL language, reverse items (again)
+ }
+ return reverse;
+ }
+
+ @VisibleForTesting
+ protected float getAnimationDistance() {
+ int rows = getListView().getRowCount();
+ float gridItemSize = getContext().getResources().getDimension(
+ com.android.systemui.R.dimen.global_actions_grid_item_height);
+ return rows * gridItemSize / 2;
+ }
+
+ @Override
+ public float getAnimationOffsetX() {
+ switch (getCurrentRotation()) {
+ case ROTATION_LANDSCAPE:
+ return getAnimationDistance();
+ case ROTATION_SEASCAPE:
+ return -getAnimationDistance();
+ default: // Portrait
+ return 0;
+ }
+ }
+
+ @Override
+ public float getAnimationOffsetY() {
+ if (getCurrentRotation() == ROTATION_NONE) {
+ return getAnimationDistance();
+ }
+ return 0;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
index d385123..c911bf2 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsImpl.java
@@ -45,24 +45,32 @@
import com.android.systemui.statusbar.policy.ExtensionController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import javax.inject.Inject;
+
+import dagger.Lazy;
+
public class GlobalActionsImpl implements GlobalActions, CommandQueue.Callbacks,
PluginListener<GlobalActionsPanelPlugin> {
private static final float SHUTDOWN_SCRIM_ALPHA = 0.95f;
private final Context mContext;
+ private final Lazy<GlobalActionsDialog> mGlobalActionsDialogLazy;
private final KeyguardStateController mKeyguardStateController;
private final DeviceProvisionedController mDeviceProvisionedController;
private final ExtensionController.Extension<GlobalActionsPanelPlugin> mPanelExtension;
private GlobalActionsPanelPlugin mPlugin;
private final CommandQueue mCommandQueue;
- private GlobalActionsDialog mGlobalActions;
+ private GlobalActionsDialog mGlobalActionsDialog;
private boolean mDisabled;
private final PluginManager mPluginManager;
private final String mPluginPackageName;
- public GlobalActionsImpl(Context context, CommandQueue commandQueue) {
+ @Inject
+ public GlobalActionsImpl(Context context, CommandQueue commandQueue,
+ Lazy<GlobalActionsDialog> globalActionsDialogLazy) {
mContext = context;
+ mGlobalActionsDialogLazy = globalActionsDialogLazy;
mKeyguardStateController = Dependency.get(KeyguardStateController.class);
mDeviceProvisionedController = Dependency.get(DeviceProvisionedController.class);
mPluginManager = Dependency.get(PluginManager.class);
@@ -83,19 +91,17 @@
mCommandQueue.removeCallback(this);
mPluginManager.removePluginListener(this);
if (mPlugin != null) mPlugin.onDestroy();
- if (mGlobalActions != null) {
- mGlobalActions.destroy();
- mGlobalActions = null;
+ if (mGlobalActionsDialog != null) {
+ mGlobalActionsDialog.destroy();
+ mGlobalActionsDialog = null;
}
}
@Override
public void showGlobalActions(GlobalActionsManager manager) {
if (mDisabled) return;
- if (mGlobalActions == null) {
- mGlobalActions = new GlobalActionsDialog(mContext, manager);
- }
- mGlobalActions.showDialog(mKeyguardStateController.isShowing(),
+ mGlobalActionsDialog = mGlobalActionsDialogLazy.get();
+ mGlobalActionsDialog.showDialog(mKeyguardStateController.isShowing(),
mDeviceProvisionedController.isDeviceProvisioned(),
mPlugin != null ? mPlugin : mPanelExtension.get());
Dependency.get(KeyguardUpdateMonitor.class).requestFaceAuth();
@@ -189,8 +195,8 @@
final boolean disabled = (state2 & DISABLE2_GLOBAL_ACTIONS) != 0;
if (displayId != mContext.getDisplayId() || disabled == mDisabled) return;
mDisabled = disabled;
- if (disabled && mGlobalActions != null) {
- mGlobalActions.dismissDialog();
+ if (disabled && mGlobalActionsDialog != null) {
+ mGlobalActionsDialog.dismissDialog();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java b/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java
index 4b28540..657a308 100644
--- a/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/glwallpaper/EglHelper.java
@@ -206,7 +206,7 @@
Log.d(TAG, "createEglSurface start");
}
- if (hasEglDisplay()) {
+ if (hasEglDisplay() && surfaceHolder.getSurface().isValid()) {
int[] attrs = null;
int wcgCapability = getWcgCapability();
if (wcg && checkExtensionCapability(KHR_GL_COLOR_SPACE) && wcgCapability > 0) {
@@ -214,7 +214,8 @@
}
mEglSurface = eglCreateWindowSurface(mEglDisplay, mEglConfig, surfaceHolder, attrs, 0);
} else {
- Log.w(TAG, "mEglDisplay is null");
+ Log.w(TAG, "Create EglSurface failed: hasEglDisplay=" + hasEglDisplay()
+ + ", has valid surface=" + surfaceHolder.getSurface().isValid());
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 9fcf022..e13c3e0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -22,6 +22,7 @@
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
import static com.android.systemui.DejankUtils.whitelistIpcs;
import android.app.ActivityManager;
@@ -86,7 +87,7 @@
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.statusbar.phone.BiometricUnlockController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.NotificationPanelView;
+import com.android.systemui.statusbar.phone.NotificationPanelViewController;
import com.android.systemui.statusbar.phone.StatusBar;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.StatusBarWindowController;
@@ -670,6 +671,8 @@
return KeyguardSecurityView.PROMPT_REASON_USER_REQUEST;
} else if (any && (strongAuth & STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) != 0) {
return KeyguardSecurityView.PROMPT_REASON_AFTER_LOCKOUT;
+ } else if (any && (strongAuth & STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE) != 0) {
+ return KeyguardSecurityView.PROMPT_REASON_PREPARE_FOR_UPDATE;
}
return KeyguardSecurityView.PROMPT_REASON_NONE;
}
@@ -2102,7 +2105,7 @@
}
public StatusBarKeyguardViewManager registerStatusBar(StatusBar statusBar,
- ViewGroup container, NotificationPanelView panelView,
+ ViewGroup container, NotificationPanelViewController panelView,
BiometricUnlockController biometricUnlockController, ViewGroup lockIconContainer,
View notificationContainer, KeyguardBypassController bypassController) {
mStatusBarKeyguardViewManagerLazy.get().registerStatusBar(statusBar, container, panelView,
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
index 59ac329..48750fa 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerUI.java
@@ -233,7 +233,7 @@
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_USER_SWITCHED);
- mBroadcastDispatcher.registerReceiver(this, filter, mHandler);
+ mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mHandler);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
index f710f7f..f66a1ec 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/DoubleLineTileLayout.kt
@@ -68,7 +68,7 @@
override fun updateResources(): Boolean {
with(mContext.resources) {
smallTileSize = getDimensionPixelSize(R.dimen.qs_quick_tile_size)
- cellMarginHorizontal = getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal)
+ cellMarginHorizontal = getDimensionPixelSize(R.dimen.qs_tile_margin_horizontal) / 2
cellMarginVertical = getDimensionPixelSize(R.dimen.new_qs_vertical_margin)
}
requestLayout()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
index f7e4c79..1077834e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
@@ -124,7 +124,7 @@
}
}
});
- btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_replay));
+ btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_play));
btn.setImageTintList(ColorStateList.valueOf(mForegroundColor));
btn.setVisibility(View.VISIBLE);
@@ -199,8 +199,7 @@
List<ResolveInfo> info = pm.queryBroadcastReceiversAsUser(it, 0, mContext.getUser());
if (info != null) {
for (ResolveInfo inf : info) {
- if (inf.activityInfo.packageName.equals(notif.contentIntent.getCreatorPackage())) {
- Log.d(TAG, "Found receiver for package: " + inf);
+ if (inf.activityInfo.packageName.equals(mController.getPackageName())) {
mRecvComponent = inf.getComponentInfo().getComponentName();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 51e352b..35b8312 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -184,21 +184,10 @@
// Add media carousel
if (useQsMediaPlayer(context)) {
- HorizontalScrollView mediaScrollView = new HorizontalScrollView(mContext);
- mediaScrollView.setHorizontalScrollBarEnabled(false);
- int playerHeight = (int) getResources().getDimension(R.dimen.qs_media_height);
- int padding = (int) getResources().getDimension(R.dimen.qs_media_padding);
- LayoutParams lpView = new LayoutParams(LayoutParams.MATCH_PARENT, playerHeight);
- lpView.setMarginStart(padding);
- lpView.setMarginEnd(padding);
- addView(mediaScrollView, lpView);
-
- LayoutParams lpCarousel = new LayoutParams(LayoutParams.MATCH_PARENT,
- LayoutParams.WRAP_CONTENT);
- mMediaCarousel = new LinearLayout(mContext);
- mMediaCarousel.setOrientation(LinearLayout.HORIZONTAL);
- mediaScrollView.addView(mMediaCarousel, lpCarousel);
- mediaScrollView.setVisibility(View.GONE);
+ HorizontalScrollView mediaScrollView = (HorizontalScrollView) LayoutInflater.from(
+ mContext).inflate(R.layout.media_carousel, this, false);
+ mMediaCarousel = mediaScrollView.findViewById(R.id.media_carousel);
+ addView(mediaScrollView);
} else {
mMediaCarousel = null;
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
index d40e250..cec1cb2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
@@ -106,7 +106,7 @@
}
}
});
- btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_replay));
+ btn.setImageDrawable(mContext.getResources().getDrawable(R.drawable.lb_ic_play));
btn.setImageTintList(ColorStateList.valueOf(mForegroundColor));
btn.setVisibility(View.VISIBLE);
}
@@ -136,14 +136,25 @@
* @param actionsContainer a LinearLayout containing the media action buttons
* @param actionsToShow indices of which actions to display in the mini player
* (max 3: Notification.MediaStyle.MAX_MEDIA_BUTTONS_IN_COMPACT)
+ * @param contentIntent Intent to send when user taps on the view
*/
public void setMediaSession(MediaSession.Token token, Icon icon, int iconColor, int bgColor,
- View actionsContainer, int[] actionsToShow) {
- Log.d(TAG, "Setting media session: " + token);
+ View actionsContainer, int[] actionsToShow, PendingIntent contentIntent) {
mToken = token;
mForegroundColor = iconColor;
mBackgroundColor = bgColor;
- mController = new MediaController(mContext, token);
+
+ String oldPackage = "";
+ if (mController != null) {
+ oldPackage = mController.getPackageName();
+ }
+ MediaController controller = new MediaController(mContext, token);
+ boolean samePlayer = mToken.equals(token) && oldPackage.equals(controller.getPackageName());
+ if (mController != null && !samePlayer && !isPlaying(controller)) {
+ // Only update if this is a different session and currently playing
+ return;
+ }
+ mController = controller;
MediaMetadata mMediaMetadata = mController.getMetadata();
// Try to find a receiver for the media button that matches this app
@@ -153,7 +164,6 @@
if (info != null) {
for (ResolveInfo inf : info) {
if (inf.activityInfo.packageName.equals(mController.getPackageName())) {
- Log.d(TAG, "Found receiver for package: " + inf);
mRecvComponent = inf.getComponentInfo().getComponentName();
}
}
@@ -165,6 +175,16 @@
return;
}
+ // Action
+ mMediaNotifView.setOnClickListener(v -> {
+ try {
+ contentIntent.send();
+ mContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
+ } catch (PendingIntent.CanceledException e) {
+ Log.e(TAG, "Pending intent was canceled: " + e.getMessage());
+ }
+ });
+
// Album art
addAlbumArtBackground(mMediaMetadata, mBackgroundColor);
@@ -237,12 +257,12 @@
* Check whether the media controlled by this player is currently playing
* @return whether it is playing, or false if no controller information
*/
- public boolean isPlaying() {
- if (mController == null) {
+ public boolean isPlaying(MediaController controller) {
+ if (controller == null) {
return false;
}
- PlaybackState state = mController.getPlaybackState();
+ PlaybackState state = controller.getPlaybackState();
if (state == null) {
return false;
}
@@ -261,12 +281,11 @@
private void addAlbumArtBackground(MediaMetadata metadata, int bgColor) {
Bitmap albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
float radius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius);
- if (albumArt != null) {
- Rect bounds = new Rect();
- mMediaNotifView.getBoundsOnScreen(bounds);
- int width = bounds.width();
- int height = bounds.height();
-
+ Rect bounds = new Rect();
+ mMediaNotifView.getBoundsOnScreen(bounds);
+ int width = bounds.width();
+ int height = bounds.height();
+ if (albumArt != null && width > 0 && height > 0) {
Bitmap original = albumArt.copy(Bitmap.Config.ARGB_8888, true);
Bitmap scaled = scaleBitmap(original, width, height);
Canvas canvas = new Canvas(scaled);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
index db52e7d..b05d4fd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanel.java
@@ -85,20 +85,19 @@
mHorizontalLinearLayout.setClipChildren(false);
mHorizontalLinearLayout.setClipToPadding(false);
- LayoutParams lp = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1);
-
mTileLayout = new DoubleLineTileLayout(context);
mMediaTileLayout = mTileLayout;
mRegularTileLayout = new HeaderTileLayout(context);
+ LayoutParams lp = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1);
lp.setMarginEnd(10);
lp.setMarginStart(0);
mHorizontalLinearLayout.addView((View) mTileLayout, lp);
mMediaPlayer = new QuickQSMediaPlayer(mContext, mHorizontalLinearLayout);
-
- lp.setMarginEnd(0);
- lp.setMarginStart(10);
- mHorizontalLinearLayout.addView(mMediaPlayer.getView(), lp);
+ LayoutParams lp2 = new LayoutParams(0, LayoutParams.MATCH_PARENT, 1);
+ lp2.setMarginEnd(0);
+ lp2.setMarginStart(25);
+ mHorizontalLinearLayout.addView(mMediaPlayer.getView(), lp2);
sDefaultMaxTiles = getResources().getInteger(R.integer.quick_qs_panel_max_columns);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index bbff117..ad79cad 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -322,7 +322,7 @@
filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED);
try {
mUserReceiverRegistered.set(true);
- mBroadcastDispatcher.registerReceiver(this, filter, mHandler, mUser);
+ mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mHandler, mUser);
} catch (Exception ex) {
mUserReceiverRegistered.set(false);
Log.e(TAG, "Could not register unlock receiver", ex);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index f06c849..2b53727 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -42,6 +42,7 @@
import com.android.systemui.qs.tiles.NfcTile;
import com.android.systemui.qs.tiles.NightDisplayTile;
import com.android.systemui.qs.tiles.RotationLockTile;
+import com.android.systemui.qs.tiles.ScreenRecordTile;
import com.android.systemui.qs.tiles.UiModeNightTile;
import com.android.systemui.qs.tiles.UserTile;
import com.android.systemui.qs.tiles.WifiTile;
@@ -77,6 +78,7 @@
private final Provider<NfcTile> mNfcTileProvider;
private final Provider<GarbageMonitor.MemoryTile> mMemoryTileProvider;
private final Provider<UiModeNightTile> mUiModeNightTileProvider;
+ private final Provider<ScreenRecordTile> mScreenRecordTileProvider;
private QSTileHost mHost;
@@ -100,7 +102,8 @@
Provider<NightDisplayTile> nightDisplayTileProvider,
Provider<NfcTile> nfcTileProvider,
Provider<GarbageMonitor.MemoryTile> memoryTileProvider,
- Provider<UiModeNightTile> uiModeNightTileProvider) {
+ Provider<UiModeNightTile> uiModeNightTileProvider,
+ Provider<ScreenRecordTile> screenRecordTileProvider) {
mWifiTileProvider = wifiTileProvider;
mBluetoothTileProvider = bluetoothTileProvider;
mControlsTileProvider = controlsTileProvider;
@@ -121,6 +124,7 @@
mNfcTileProvider = nfcTileProvider;
mMemoryTileProvider = memoryTileProvider;
mUiModeNightTileProvider = uiModeNightTileProvider;
+ mScreenRecordTileProvider = screenRecordTileProvider;
}
public void setHost(QSTileHost host) {
@@ -179,6 +183,8 @@
return mNfcTileProvider.get();
case "dark":
return mUiModeNightTileProvider.get();
+ case "screenrecord":
+ return mScreenRecordTileProvider.get();
}
// Custom tiles
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
new file mode 100644
index 0000000..596c3b9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles;
+
+import android.content.Intent;
+import android.service.quicksettings.Tile;
+import android.util.Log;
+
+import com.android.systemui.R;
+import com.android.systemui.plugins.qs.QSTile;
+import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.screenrecord.RecordingController;
+
+import javax.inject.Inject;
+
+/**
+ * Quick settings tile for screen recording
+ */
+public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> {
+ private static final String TAG = "ScreenRecordTile";
+ private RecordingController mController;
+ private long mMillisUntilFinished = 0;
+
+ @Inject
+ public ScreenRecordTile(QSHost host, RecordingController controller) {
+ super(host);
+ mController = controller;
+ }
+
+ @Override
+ public BooleanState newTileState() {
+ return new BooleanState();
+ }
+
+ @Override
+ protected void handleClick() {
+ if (mController.isStarting()) {
+ cancelCountdown();
+ } else if (mController.isRecording()) {
+ stopRecording();
+ } else {
+ startCountdown();
+ }
+ refreshState();
+ }
+
+ /**
+ * Refresh tile state
+ * @param millisUntilFinished Time until countdown completes, or 0 if not counting down
+ */
+ public void refreshState(long millisUntilFinished) {
+ mMillisUntilFinished = millisUntilFinished;
+ refreshState();
+ }
+
+ @Override
+ protected void handleUpdateState(BooleanState state, Object arg) {
+ boolean isStarting = mController.isStarting();
+ boolean isRecording = mController.isRecording();
+
+ state.label = mContext.getString(R.string.quick_settings_screen_record_label);
+ state.value = isRecording || isStarting;
+ state.state = (isRecording || isStarting) ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
+ state.handlesLongClick = false;
+
+ if (isRecording) {
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_screenrecord);
+ state.secondaryLabel = mContext.getString(R.string.quick_settings_screen_record_stop);
+ } else if (isStarting) {
+ // round, since the timer isn't exact
+ int countdown = (int) Math.floorDiv(mMillisUntilFinished + 500, 1000);
+ // TODO update icon
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_screenrecord);
+ state.secondaryLabel = String.format("%d...", countdown);
+ } else {
+ // TODO update icon
+ state.icon = ResourceIcon.get(R.drawable.ic_qs_screenrecord);
+ state.secondaryLabel = mContext.getString(R.string.quick_settings_screen_record_start);
+ }
+ }
+
+ @Override
+ public int getMetricsCategory() {
+ return 0;
+ }
+
+ @Override
+ public Intent getLongClickIntent() {
+ return null;
+ }
+
+ @Override
+ protected void handleSetListening(boolean listening) {
+ }
+
+ @Override
+ public CharSequence getTileLabel() {
+ return mContext.getString(R.string.quick_settings_screen_record_label);
+ }
+
+ private void startCountdown() {
+ Log.d(TAG, "Starting countdown");
+ // Close QS, otherwise the permission dialog appears beneath it
+ getHost().collapsePanels();
+ mController.launchRecordPrompt(this);
+ }
+
+ private void cancelCountdown() {
+ Log.d(TAG, "Cancelling countdown");
+ mController.cancelCountdown();
+ }
+
+ private void stopRecording() {
+ Log.d(TAG, "Stopping recording from tile");
+ mController.stopRecording();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
new file mode 100644
index 0000000..188501e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -0,0 +1,166 @@
+/*
+ * 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.systemui.screenrecord;
+
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.CountDownTimer;
+import android.util.Log;
+
+import com.android.systemui.qs.tiles.ScreenRecordTile;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Helper class to initiate a screen recording
+ */
+@Singleton
+public class RecordingController {
+ private static final String TAG = "RecordingController";
+ private static final String SYSUI_PACKAGE = "com.android.systemui";
+ private static final String SYSUI_SCREENRECORD_LAUNCHER =
+ "com.android.systemui.screenrecord.ScreenRecordDialog";
+
+ private final Context mContext;
+ private boolean mIsStarting;
+ private boolean mIsRecording;
+ private ScreenRecordTile mTileToUpdate;
+ private PendingIntent mStopIntent;
+ private CountDownTimer mCountDownTimer = null;
+
+ /**
+ * Create a new RecordingController
+ * @param context Context for the controller
+ */
+ @Inject
+ public RecordingController(Context context) {
+ mContext = context;
+ }
+
+ /**
+ * Show dialog of screen recording options to user.
+ */
+ public void launchRecordPrompt(ScreenRecordTile tileToUpdate) {
+ final ComponentName launcherComponent = new ComponentName(SYSUI_PACKAGE,
+ SYSUI_SCREENRECORD_LAUNCHER);
+ final Intent intent = new Intent();
+ intent.setComponent(launcherComponent);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.putExtra("com.android.systemui.screenrecord.EXTRA_SETTINGS_ONLY", true);
+ mContext.startActivity(intent);
+
+ mTileToUpdate = tileToUpdate;
+ }
+
+ /**
+ * Start counting down in preparation to start a recording
+ * @param ms Time in ms to count down
+ * @param startIntent Intent to start a recording
+ * @param stopIntent Intent to stop a recording
+ */
+ public void startCountdown(long ms, PendingIntent startIntent, PendingIntent stopIntent) {
+ mIsStarting = true;
+ mStopIntent = stopIntent;
+
+ mCountDownTimer = new CountDownTimer(ms, 1000) {
+ @Override
+ public void onTick(long millisUntilFinished) {
+ refreshTile(millisUntilFinished);
+ }
+
+ @Override
+ public void onFinish() {
+ mIsStarting = false;
+ mIsRecording = true;
+ refreshTile();
+ try {
+ startIntent.send();
+ } catch (PendingIntent.CanceledException e) {
+ Log.e(TAG, "Pending intent was cancelled: " + e.getMessage());
+ }
+ }
+ };
+
+ mCountDownTimer.start();
+ }
+
+ private void refreshTile() {
+ refreshTile(0);
+ }
+
+ private void refreshTile(long millisUntilFinished) {
+ if (mTileToUpdate != null) {
+ mTileToUpdate.refreshState(millisUntilFinished);
+ } else {
+ Log.e(TAG, "No tile to refresh");
+ }
+ }
+
+ /**
+ * Cancel a countdown in progress. This will not stop the recording if it already started.
+ */
+ public void cancelCountdown() {
+ if (mCountDownTimer != null) {
+ mCountDownTimer.cancel();
+ } else {
+ Log.e(TAG, "Timer was null");
+ }
+ mIsStarting = false;
+ refreshTile();
+ }
+
+ /**
+ * Check if the recording is currently counting down to begin
+ * @return
+ */
+ public boolean isStarting() {
+ return mIsStarting;
+ }
+
+ /**
+ * Check if the recording is ongoing
+ * @return
+ */
+ public boolean isRecording() {
+ return mIsRecording;
+ }
+
+ /**
+ * Stop the recording
+ */
+ public void stopRecording() {
+ updateState(false);
+ try {
+ mStopIntent.send();
+ } catch (PendingIntent.CanceledException e) {
+ Log.e(TAG, "Error stopping: " + e.getMessage());
+ }
+ refreshTile();
+ }
+
+ /**
+ * Update the current status
+ * @param isRecording
+ */
+ public void updateState(boolean isRecording) {
+ mIsRecording = isRecording;
+ refreshTile();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index 77c3ad9..1b32168 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -53,10 +53,14 @@
import java.text.SimpleDateFormat;
import java.util.Date;
+import javax.inject.Inject;
+
/**
* A service which records the device screen and optionally microphone input.
*/
public class RecordingService extends Service {
+ public static final int REQUEST_CODE = 2;
+
private static final int NOTIFICATION_ID = 1;
private static final String TAG = "RecordingService";
private static final String CHANNEL_ID = "screen_record";
@@ -65,7 +69,6 @@
private static final String EXTRA_PATH = "extra_path";
private static final String EXTRA_USE_AUDIO = "extra_useAudio";
private static final String EXTRA_SHOW_TAPS = "extra_showTaps";
- private static final int REQUEST_CODE = 2;
private static final String ACTION_START = "com.android.systemui.screenrecord.START";
private static final String ACTION_STOP = "com.android.systemui.screenrecord.STOP";
@@ -81,6 +84,7 @@
private static final int AUDIO_BIT_RATE = 16;
private static final int AUDIO_SAMPLE_RATE = 44100;
+ private final RecordingController mController;
private MediaProjectionManager mMediaProjectionManager;
private MediaProjection mMediaProjection;
private Surface mInputSurface;
@@ -92,6 +96,11 @@
private boolean mShowTaps;
private File mTempFile;
+ @Inject
+ public RecordingService(RecordingController controller) {
+ mController = controller;
+ }
+
/**
* Get an intent to start the recording service.
*
@@ -272,6 +281,7 @@
null);
mMediaRecorder.start();
+ mController.updateState(true);
} catch (IOException e) {
Log.e(TAG, "Error starting screen recording: " + e.getMessage());
e.printStackTrace();
@@ -285,7 +295,7 @@
NotificationChannel channel = new NotificationChannel(
CHANNEL_ID,
getString(R.string.screenrecord_name),
- NotificationManager.IMPORTANCE_HIGH);
+ NotificationManager.IMPORTANCE_LOW);
channel.setDescription(getString(R.string.screenrecord_channel_description));
channel.enableVibration(true);
NotificationManager notificationManager =
@@ -399,6 +409,7 @@
mInputSurface.release();
mVirtualDisplay.release();
stopSelf();
+ mController.updateState(false);
}
private void saveRecording(NotificationManager notificationManager) {
@@ -439,7 +450,12 @@
Settings.System.SHOW_TOUCHES, value);
}
- private static Intent getStopIntent(Context context) {
+ /**
+ * Get an intent to stop the recording service.
+ * @param context Context from the requesting activity
+ * @return
+ */
+ public static Intent getStopIntent(Context context) {
return new Intent(context, RecordingService.class).setAction(ACTION_STOP);
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
index 27e9fba..8324986 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenRecordDialog.java
@@ -18,55 +18,41 @@
import android.Manifest;
import android.app.Activity;
+import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.media.projection.MediaProjectionManager;
import android.os.Bundle;
-import android.util.Log;
-import android.widget.Button;
-import android.widget.CheckBox;
import android.widget.Toast;
import com.android.systemui.R;
+import javax.inject.Inject;
+
/**
* Activity to select screen recording options
*/
public class ScreenRecordDialog extends Activity {
- private static final String TAG = "ScreenRecord";
private static final int REQUEST_CODE_VIDEO_ONLY = 200;
private static final int REQUEST_CODE_VIDEO_TAPS = 201;
private static final int REQUEST_CODE_PERMISSIONS = 299;
private static final int REQUEST_CODE_VIDEO_AUDIO = 300;
private static final int REQUEST_CODE_VIDEO_AUDIO_TAPS = 301;
private static final int REQUEST_CODE_PERMISSIONS_AUDIO = 399;
- private boolean mUseAudio;
- private boolean mShowTaps;
+ private static final long DELAY_MS = 3000;
+
+ private final RecordingController mController;
+
+ @Inject
+ public ScreenRecordDialog(RecordingController controller) {
+ mController = controller;
+ }
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setContentView(R.layout.screen_record_dialog);
-
- final CheckBox micCheckBox = findViewById(R.id.checkbox_mic);
- final CheckBox tapsCheckBox = findViewById(R.id.checkbox_taps);
-
- final Button recordButton = findViewById(R.id.record_button);
- recordButton.setOnClickListener(v -> {
- mUseAudio = micCheckBox.isChecked();
- mShowTaps = tapsCheckBox.isChecked();
- Log.d(TAG, "Record button clicked: audio " + mUseAudio + ", taps " + mShowTaps);
-
- if (mUseAudio && checkSelfPermission(Manifest.permission.RECORD_AUDIO)
- != PackageManager.PERMISSION_GRANTED) {
- Log.d(TAG, "Requesting permission for audio");
- requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO},
- REQUEST_CODE_PERMISSIONS_AUDIO);
- } else {
- requestScreenCapture();
- }
- });
+ requestScreenCapture();
}
private void requestScreenCapture() {
@@ -74,18 +60,23 @@
Context.MEDIA_PROJECTION_SERVICE);
Intent permissionIntent = mediaProjectionManager.createScreenCaptureIntent();
- if (mUseAudio) {
+ // TODO get saved settings
+ boolean useAudio = false;
+ boolean showTaps = false;
+ if (useAudio) {
startActivityForResult(permissionIntent,
- mShowTaps ? REQUEST_CODE_VIDEO_AUDIO_TAPS : REQUEST_CODE_VIDEO_AUDIO);
+ showTaps ? REQUEST_CODE_VIDEO_AUDIO_TAPS : REQUEST_CODE_VIDEO_AUDIO);
} else {
startActivityForResult(permissionIntent,
- mShowTaps ? REQUEST_CODE_VIDEO_TAPS : REQUEST_CODE_VIDEO_ONLY);
+ showTaps ? REQUEST_CODE_VIDEO_TAPS : REQUEST_CODE_VIDEO_ONLY);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- mShowTaps = (requestCode == REQUEST_CODE_VIDEO_TAPS
+ boolean showTaps = (requestCode == REQUEST_CODE_VIDEO_TAPS
+ || requestCode == REQUEST_CODE_VIDEO_AUDIO_TAPS);
+ boolean useAudio = (requestCode == REQUEST_CODE_VIDEO_AUDIO
|| requestCode == REQUEST_CODE_VIDEO_AUDIO_TAPS);
switch (requestCode) {
case REQUEST_CODE_VIDEO_TAPS:
@@ -93,11 +84,17 @@
case REQUEST_CODE_VIDEO_ONLY:
case REQUEST_CODE_VIDEO_AUDIO:
if (resultCode == RESULT_OK) {
- mUseAudio = (requestCode == REQUEST_CODE_VIDEO_AUDIO
- || requestCode == REQUEST_CODE_VIDEO_AUDIO_TAPS);
- startForegroundService(
- RecordingService.getStartIntent(this, resultCode, data, mUseAudio,
- mShowTaps));
+ PendingIntent startIntent = PendingIntent.getForegroundService(
+ this, RecordingService.REQUEST_CODE, RecordingService.getStartIntent(
+ ScreenRecordDialog.this, resultCode, data, useAudio,
+ showTaps),
+ PendingIntent.FLAG_UPDATE_CURRENT
+ );
+ PendingIntent stopIntent = PendingIntent.getService(
+ this, RecordingService.REQUEST_CODE,
+ RecordingService.getStopIntent(this),
+ PendingIntent.FLAG_UPDATE_CURRENT);
+ mController.startCountdown(DELAY_MS, startIntent, stopIntent);
} else {
Toast.makeText(this,
getResources().getString(R.string.screenrecord_permission_error),
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 02c4beb..27c9555 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -20,7 +20,7 @@
import static android.view.View.VISIBLE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_CORNER_FLOW;
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_SCROLLING_ENABLED;
import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_SCREENSHOT;
import android.animation.Animator;
@@ -64,7 +64,6 @@
import android.view.animation.Interpolator;
import android.widget.ImageView;
import android.widget.LinearLayout;
-import android.widget.TextView;
import android.widget.Toast;
import com.android.systemui.R;
@@ -246,20 +245,13 @@
mSaveInBgTask.cancel(false);
}
- if (!DeviceConfig.getBoolean(
- NAMESPACE_SYSTEMUI, SCREENSHOT_CORNER_FLOW, false)) {
- mNotificationsController.reset();
- mNotificationsController.setImage(mScreenBitmap);
- mNotificationsController.showSavingScreenshotNotification();
- }
mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data).execute();
}
/**
* Takes a screenshot of the current display and shows an animation.
*/
- private void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible,
- boolean navBarVisible, Rect crop) {
+ private void takeScreenshot(Consumer<Uri> finisher, Rect crop) {
int rot = mDisplay.getRotation();
int width = crop.width();
int height = crop.height();
@@ -278,21 +270,20 @@
mScreenBitmap.prepareToDraw();
// Start the post-screenshot animation
- startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
- statusBarVisible, navBarVisible);
+ startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels);
}
- void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible, boolean navBarVisible) {
+ void takeScreenshot(Consumer<Uri> finisher) {
mDisplay.getRealMetrics(mDisplayMetrics);
- takeScreenshot(finisher, statusBarVisible, navBarVisible,
+ takeScreenshot(
+ finisher,
new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
}
/**
* Displays a screenshot selector
*/
- void takeScreenshotPartial(final Consumer<Uri> finisher, final boolean statusBarVisible,
- final boolean navBarVisible) {
+ void takeScreenshotPartial(final Consumer<Uri> finisher) {
mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
mScreenshotSelectorView.setOnTouchListener(new View.OnTouchListener() {
@Override
@@ -312,8 +303,7 @@
if (rect != null) {
if (rect.width() != 0 && rect.height() != 0) {
// Need mScreenshotLayout to handle it after the view disappears
- mScreenshotLayout.post(() -> takeScreenshot(
- finisher, statusBarVisible, navBarVisible, rect));
+ mScreenshotLayout.post(() -> takeScreenshot(finisher, rect));
}
}
@@ -364,8 +354,7 @@
/**
* Starts the animation after taking the screenshot
*/
- private void startAnimation(final Consumer<Uri> finisher, int w, int h,
- boolean statusBarVisible, boolean navBarVisible) {
+ private void startAnimation(final Consumer<Uri> finisher, int w, int h) {
// If power save is on, show a toast so there is some visual indication that a screenshot
// has been taken.
PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
@@ -385,50 +374,30 @@
mScreenshotAnimation.removeAllListeners();
}
- boolean useCornerFlow =
- DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, SCREENSHOT_CORNER_FLOW, false);
mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation();
- ValueAnimator screenshotFadeOutAnim = useCornerFlow
- ? createScreenshotToCornerAnimation(w, h)
- : createScreenshotDropOutAnimation(w, h, statusBarVisible, navBarVisible);
+ ValueAnimator screenshotFadeOutAnim = createScreenshotToCornerAnimation(w, h);
mScreenshotAnimation = new AnimatorSet();
mScreenshotAnimation.playSequentially(screenshotDropInAnim, screenshotFadeOutAnim);
mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
// Save the screenshot once we have a bit of time now
- if (!useCornerFlow) {
- saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
- @Override
- void onActionsReady(Uri uri, List<Notification.Action> actions) {
- if (uri == null) {
- mNotificationsController.notifyScreenshotError(
- R.string.screenshot_failed_to_capture_text);
- } else {
- mNotificationsController
- .showScreenshotActionsNotification(uri, actions);
- }
+ saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
+ @Override
+ void onActionsReady(Uri uri, List<Notification.Action> actions) {
+ if (uri == null) {
+ mNotificationsController.notifyScreenshotError(
+ R.string.screenshot_failed_to_capture_text);
+ } else {
+ mScreenshotHandler.post(() ->
+ createScreenshotActionsShadeAnimation(actions).start());
}
- });
- clearScreenshot();
- } else {
- saveScreenshotInWorkerThread(finisher, new ActionsReadyListener() {
- @Override
- void onActionsReady(Uri uri, List<Notification.Action> actions) {
- if (uri == null) {
- mNotificationsController.notifyScreenshotError(
- R.string.screenshot_failed_to_capture_text);
- } else {
- mScreenshotHandler.post(() ->
- createScreenshotActionsShadeAnimation(actions).start());
- }
- }
- });
- mScreenshotHandler.sendMessageDelayed(
- mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT),
- SCREENSHOT_CORNER_TIMEOUT_MILLIS);
- }
+ }
+ });
+ mScreenshotHandler.sendMessageDelayed(
+ mScreenshotHandler.obtainMessage(MESSAGE_CORNER_TIMEOUT),
+ SCREENSHOT_CORNER_TIMEOUT_MILLIS);
}
});
mScreenshotHandler.post(() -> {
@@ -492,7 +461,7 @@
}
@Override
- public void onAnimationEnd(android.animation.Animator animation) {
+ public void onAnimationEnd(Animator animation) {
mScreenshotFlash.setVisibility(View.GONE);
}
});
@@ -513,81 +482,6 @@
return anim;
}
- private ValueAnimator createScreenshotDropOutAnimation(int w, int h, boolean statusBarVisible,
- boolean navBarVisible) {
- ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
- anim.setStartDelay(SCREENSHOT_DROP_OUT_DELAY);
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mBackgroundView.setVisibility(View.GONE);
- mScreenshotView.setVisibility(View.GONE);
- mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null);
- }
- });
-
- if (!statusBarVisible || !navBarVisible) {
- // There is no status bar/nav bar, so just fade the screenshot away in place
- anim.setDuration(SCREENSHOT_FAST_DROP_OUT_DURATION);
- anim.addUpdateListener(new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float t = (Float) animation.getAnimatedValue();
- float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
- - t * (SCREENSHOT_DROP_IN_MIN_SCALE
- - SCREENSHOT_FAST_DROP_OUT_MIN_SCALE);
- mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
- mScreenshotView.setAlpha(1f - t);
- mScreenshotView.setScaleX(scaleT);
- mScreenshotView.setScaleY(scaleT);
- }
- });
- } else {
- // In the case where there is a status bar, animate to the origin of the bar (top-left)
- final float scaleDurationPct = (float) SCREENSHOT_DROP_OUT_SCALE_DURATION
- / SCREENSHOT_DROP_OUT_DURATION;
- final Interpolator scaleInterpolator = new Interpolator() {
- @Override
- public float getInterpolation(float x) {
- if (x < scaleDurationPct) {
- // Decelerate, and scale the input accordingly
- return (float) (1f - Math.pow(1f - (x / scaleDurationPct), 2f));
- }
- return 1f;
- }
- };
-
- // Determine the bounds of how to scale
- float halfScreenWidth = (w - 2f * mBgPadding) / 2f;
- float halfScreenHeight = (h - 2f * mBgPadding) / 2f;
- final float offsetPct = SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET;
- final PointF finalPos = new PointF(
- -halfScreenWidth
- + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenWidth,
- -halfScreenHeight
- + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenHeight);
-
- // Animate the screenshot to the status bar
- anim.setDuration(SCREENSHOT_DROP_OUT_DURATION);
- anim.addUpdateListener(new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float t = (Float) animation.getAnimatedValue();
- float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
- - scaleInterpolator.getInterpolation(t)
- * (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_DROP_OUT_MIN_SCALE);
- mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
- mScreenshotView.setAlpha(1f - scaleInterpolator.getInterpolation(t));
- mScreenshotView.setScaleX(scaleT);
- mScreenshotView.setScaleY(scaleT);
- mScreenshotView.setTranslationX(t * finalPos.x);
- mScreenshotView.setTranslationY(t * finalPos.y);
- }
- });
- }
- return anim;
- }
-
private ValueAnimator createScreenshotToCornerAnimation(int w, int h) {
ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
anim.setStartDelay(SCREENSHOT_DROP_OUT_DELAY);
@@ -634,9 +528,10 @@
mActionsView.removeAllViews();
for (Notification.Action action : actions) {
- TextView actionChip = (TextView) inflater.inflate(
+ ScreenshotActionChip actionChip = (ScreenshotActionChip) inflater.inflate(
R.layout.global_screenshot_action_chip, mActionsView, false);
actionChip.setText(action.title);
+ actionChip.setIcon(action.getIcon(), true);
actionChip.setOnClickListener(v -> {
try {
action.actionIntent.send();
@@ -648,13 +543,16 @@
});
mActionsView.addView(actionChip);
}
- TextView scrollChip = (TextView) inflater.inflate(
- R.layout.global_screenshot_action_chip, mActionsView, false);
- Toast scrollNotImplemented = Toast.makeText(
- mContext, "Not implemented", Toast.LENGTH_SHORT);
- scrollChip.setText("Scroll"); // TODO (mkephart): add resource and translate
- scrollChip.setOnClickListener(v -> scrollNotImplemented.show());
- mActionsView.addView(scrollChip);
+
+ if (DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, SCREENSHOT_SCROLLING_ENABLED, false)) {
+ ScreenshotActionChip scrollChip = (ScreenshotActionChip) inflater.inflate(
+ R.layout.global_screenshot_action_chip, mActionsView, false);
+ Toast scrollNotImplemented = Toast.makeText(
+ mContext, "Not implemented", Toast.LENGTH_SHORT);
+ scrollChip.setText("Extend"); // TODO (mkephart): add resource and translate
+ scrollChip.setOnClickListener(v -> scrollNotImplemented.show());
+ mActionsView.addView(scrollChip);
+ }
ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
mActionsView.setY(mDisplayMetrics.heightPixels);
@@ -776,8 +674,7 @@
String actionType = intent.getStringExtra(EXTRA_ACTION_TYPE);
Slog.d(TAG, "Executing smart action [" + actionType + "]:" + actionIntent);
ActivityOptions opts = ActivityOptions.makeBasic();
- context.startActivityAsUser(actionIntent, opts.toBundle(),
- UserHandle.CURRENT);
+ context.startActivityAsUser(actionIntent, opts.toBundle(), UserHandle.CURRENT);
ScreenshotSmartActions.notifyScreenshotAction(
context, intent.getStringExtra(EXTRA_ID), actionType, true);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java
new file mode 100644
index 0000000..11aa80b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshotLegacy.java
@@ -0,0 +1,508 @@
+/*
+ * Copyright (C) 2011 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.systemui.screenshot;
+
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.animation.ValueAnimator.AnimatorUpdateListener;
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.media.MediaActionSound;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.PowerManager;
+import android.util.DisplayMetrics;
+import android.view.Display;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.view.animation.Interpolator;
+import android.widget.ImageView;
+import android.widget.Toast;
+
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.Main;
+
+import java.util.List;
+import java.util.function.Consumer;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Class for handling device screen shots
+ *
+ * @deprecated will be removed when corner flow is complete and tested
+ */
+@Singleton
+@Deprecated
+public class GlobalScreenshotLegacy {
+
+ // These strings are used for communicating the action invoked to
+ // ScreenshotNotificationSmartActionsProvider.
+ static final String EXTRA_ACTION_TYPE = "android:screenshot_action_type";
+ static final String EXTRA_ID = "android:screenshot_id";
+ static final String ACTION_TYPE_DELETE = "Delete";
+ static final String ACTION_TYPE_SHARE = "Share";
+ static final String ACTION_TYPE_EDIT = "Edit";
+ static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled";
+ static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent";
+
+ static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id";
+ static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification";
+ static final String EXTRA_DISALLOW_ENTER_PIP = "android:screenshot_disallow_enter_pip";
+
+ private static final String TAG = "GlobalScreenshot";
+
+ private static final int SCREENSHOT_FLASH_TO_PEAK_DURATION = 130;
+ private static final int SCREENSHOT_DROP_IN_DURATION = 430;
+ private static final int SCREENSHOT_DROP_OUT_DELAY = 500;
+ private static final int SCREENSHOT_DROP_OUT_DURATION = 430;
+ private static final int SCREENSHOT_DROP_OUT_SCALE_DURATION = 370;
+ private static final int SCREENSHOT_FAST_DROP_OUT_DURATION = 320;
+ private static final float BACKGROUND_ALPHA = 0.5f;
+ private static final float SCREENSHOT_SCALE = 1f;
+ private static final float SCREENSHOT_DROP_IN_MIN_SCALE = SCREENSHOT_SCALE * 0.725f;
+ private static final float SCREENSHOT_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.45f;
+ private static final float SCREENSHOT_FAST_DROP_OUT_MIN_SCALE = SCREENSHOT_SCALE * 0.6f;
+ private static final float SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET = 0f;
+
+ private final ScreenshotNotificationsController mNotificationsController;
+
+ private Context mContext;
+ private WindowManager mWindowManager;
+ private WindowManager.LayoutParams mWindowLayoutParams;
+ private Display mDisplay;
+ private DisplayMetrics mDisplayMetrics;
+
+ private Bitmap mScreenBitmap;
+ private View mScreenshotLayout;
+ private ScreenshotSelectorView mScreenshotSelectorView;
+ private ImageView mBackgroundView;
+ private ImageView mScreenshotView;
+ private ImageView mScreenshotFlash;
+
+ private AnimatorSet mScreenshotAnimation;
+
+ private float mBgPadding;
+ private float mBgPaddingScale;
+
+ private AsyncTask<Void, Void, Void> mSaveInBgTask;
+
+ private MediaActionSound mCameraSound;
+
+ /**
+ * @param context everything needs a context :(
+ */
+ @Inject
+ public GlobalScreenshotLegacy(
+ Context context, @Main Resources resources, LayoutInflater layoutInflater,
+ ScreenshotNotificationsController screenshotNotificationsController) {
+ mContext = context;
+ mNotificationsController = screenshotNotificationsController;
+
+ // Inflate the screenshot layout
+ mScreenshotLayout = layoutInflater.inflate(R.layout.global_screenshot_legacy, null);
+ mBackgroundView = mScreenshotLayout.findViewById(R.id.global_screenshot_legacy_background);
+ mScreenshotView = mScreenshotLayout.findViewById(R.id.global_screenshot_legacy);
+
+ mScreenshotFlash = mScreenshotLayout.findViewById(R.id.global_screenshot_legacy_flash);
+ mScreenshotSelectorView = mScreenshotLayout.findViewById(
+ R.id.global_screenshot_legacy_selector);
+ mScreenshotLayout.setFocusable(true);
+ mScreenshotSelectorView.setFocusable(true);
+ mScreenshotSelectorView.setFocusableInTouchMode(true);
+ mScreenshotLayout.setOnTouchListener((v, event) -> {
+ // Intercept and ignore all touch events
+ return true;
+ });
+
+ // Setup the window that we are going to use
+ mWindowLayoutParams = new WindowManager.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, 0, 0,
+ WindowManager.LayoutParams.TYPE_SCREENSHOT,
+ WindowManager.LayoutParams.FLAG_FULLSCREEN
+ | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+ | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED,
+ PixelFormat.TRANSLUCENT);
+ mWindowLayoutParams.setTitle("ScreenshotAnimation");
+ mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ mWindowLayoutParams.setFitWindowInsetsTypes(0 /* types */);
+ mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+ mDisplay = mWindowManager.getDefaultDisplay();
+ mDisplayMetrics = new DisplayMetrics();
+ mDisplay.getRealMetrics(mDisplayMetrics);
+
+ // Scale has to account for both sides of the bg
+ mBgPadding = (float) resources.getDimensionPixelSize(
+ R.dimen.global_screenshot_legacy_bg_padding);
+ mBgPaddingScale = mBgPadding / mDisplayMetrics.widthPixels;
+
+
+ // Setup the Camera shutter sound
+ mCameraSound = new MediaActionSound();
+ mCameraSound.load(MediaActionSound.SHUTTER_CLICK);
+ }
+
+ /**
+ * Creates a new worker thread and saves the screenshot to the media store.
+ */
+ private void saveScreenshotInWorkerThread(
+ Consumer<Uri> finisher,
+ @Nullable GlobalScreenshot.ActionsReadyListener actionsReadyListener) {
+ GlobalScreenshot.SaveImageInBackgroundData data =
+ new GlobalScreenshot.SaveImageInBackgroundData();
+ data.image = mScreenBitmap;
+ data.finisher = finisher;
+ data.mActionsReadyListener = actionsReadyListener;
+ if (mSaveInBgTask != null) {
+ mSaveInBgTask.cancel(false);
+ }
+
+ mNotificationsController.reset();
+ mNotificationsController.setImage(mScreenBitmap);
+ mNotificationsController.showSavingScreenshotNotification();
+
+ mSaveInBgTask = new SaveImageInBackgroundTask(mContext, data).execute();
+ }
+
+ /**
+ * Takes a screenshot of the current display and shows an animation.
+ */
+ private void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible,
+ boolean navBarVisible, Rect crop) {
+ int rot = mDisplay.getRotation();
+ int width = crop.width();
+ int height = crop.height();
+
+ // Take the screenshot
+ mScreenBitmap = SurfaceControl.screenshot(crop, width, height, rot);
+ if (mScreenBitmap == null) {
+ mNotificationsController.notifyScreenshotError(
+ R.string.screenshot_failed_to_capture_text);
+ finisher.accept(null);
+ return;
+ }
+
+ // Optimizations
+ mScreenBitmap.setHasAlpha(false);
+ mScreenBitmap.prepareToDraw();
+
+ // Start the post-screenshot animation
+ startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
+ statusBarVisible, navBarVisible);
+ }
+
+ void takeScreenshot(Consumer<Uri> finisher, boolean statusBarVisible, boolean navBarVisible) {
+ mDisplay.getRealMetrics(mDisplayMetrics);
+ takeScreenshot(finisher, statusBarVisible, navBarVisible,
+ new Rect(0, 0, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels));
+ }
+
+ /**
+ * Displays a screenshot selector
+ */
+ void takeScreenshotPartial(final Consumer<Uri> finisher, final boolean statusBarVisible,
+ final boolean navBarVisible) {
+ mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
+ mScreenshotSelectorView.setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ ScreenshotSelectorView view = (ScreenshotSelectorView) v;
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ view.startSelection((int) event.getX(), (int) event.getY());
+ return true;
+ case MotionEvent.ACTION_MOVE:
+ view.updateSelection((int) event.getX(), (int) event.getY());
+ return true;
+ case MotionEvent.ACTION_UP:
+ view.setVisibility(View.GONE);
+ mWindowManager.removeView(mScreenshotLayout);
+ final Rect rect = view.getSelectionRect();
+ if (rect != null) {
+ if (rect.width() != 0 && rect.height() != 0) {
+ // Need mScreenshotLayout to handle it after the view disappears
+ mScreenshotLayout.post(() -> takeScreenshot(
+ finisher, statusBarVisible, navBarVisible, rect));
+ }
+ }
+
+ view.stopSelection();
+ return true;
+ }
+
+ return false;
+ }
+ });
+ mScreenshotLayout.post(new Runnable() {
+ @Override
+ public void run() {
+ mScreenshotSelectorView.setVisibility(View.VISIBLE);
+ mScreenshotSelectorView.requestFocus();
+ }
+ });
+ }
+
+ /**
+ * Cancels screenshot request
+ */
+ void stopScreenshot() {
+ // If the selector layer still presents on screen, we remove it and resets its state.
+ if (mScreenshotSelectorView.getSelectionRect() != null) {
+ mWindowManager.removeView(mScreenshotLayout);
+ mScreenshotSelectorView.stopSelection();
+ }
+ }
+
+ /**
+ * Clears current screenshot
+ */
+ private void clearScreenshot() {
+ if (mScreenshotLayout.isAttachedToWindow()) {
+ mWindowManager.removeView(mScreenshotLayout);
+ }
+
+ // Clear any references to the bitmap
+ mScreenBitmap = null;
+ mScreenshotView.setImageBitmap(null);
+ mBackgroundView.setVisibility(View.GONE);
+ mScreenshotView.setVisibility(View.GONE);
+ mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null);
+ }
+
+ /**
+ * Starts the animation after taking the screenshot
+ */
+ private void startAnimation(final Consumer<Uri> finisher, int w, int h,
+ boolean statusBarVisible, boolean navBarVisible) {
+ // If power save is on, show a toast so there is some visual indication that a screenshot
+ // has been taken.
+ PowerManager powerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ if (powerManager.isPowerSaveMode()) {
+ Toast.makeText(mContext, R.string.screenshot_saved_title, Toast.LENGTH_SHORT).show();
+ }
+
+ // Add the view for the animation
+ mScreenshotView.setImageBitmap(mScreenBitmap);
+ mScreenshotLayout.requestFocus();
+
+ // Setup the animation with the screenshot just taken
+ if (mScreenshotAnimation != null) {
+ if (mScreenshotAnimation.isStarted()) {
+ mScreenshotAnimation.end();
+ }
+ mScreenshotAnimation.removeAllListeners();
+ }
+
+ mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
+ ValueAnimator screenshotDropInAnim = createScreenshotDropInAnimation();
+ ValueAnimator screenshotFadeOutAnim = createScreenshotDropOutAnimation(w, h,
+ statusBarVisible, navBarVisible);
+ mScreenshotAnimation = new AnimatorSet();
+ mScreenshotAnimation.playSequentially(screenshotDropInAnim, screenshotFadeOutAnim);
+ mScreenshotAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ // Save the screenshot once we have a bit of time now
+ saveScreenshotInWorkerThread(finisher, new GlobalScreenshot.ActionsReadyListener() {
+ @Override
+ void onActionsReady(Uri uri, List<Notification.Action> actions) {
+ if (uri == null) {
+ mNotificationsController.notifyScreenshotError(
+ R.string.screenshot_failed_to_capture_text);
+ } else {
+ mNotificationsController.showScreenshotActionsNotification(
+ uri, actions);
+ }
+ }
+ });
+ clearScreenshot();
+ }
+ });
+ mScreenshotLayout.post(() -> {
+ // Play the shutter sound to notify that we've taken a screenshot
+ mCameraSound.play(MediaActionSound.SHUTTER_CLICK);
+
+ mScreenshotView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ mScreenshotView.buildLayer();
+ mScreenshotAnimation.start();
+ });
+ }
+
+ private ValueAnimator createScreenshotDropInAnimation() {
+ final float flashPeakDurationPct = ((float) (SCREENSHOT_FLASH_TO_PEAK_DURATION)
+ / SCREENSHOT_DROP_IN_DURATION);
+ final float flashDurationPct = 2f * flashPeakDurationPct;
+ final Interpolator flashAlphaInterpolator = new Interpolator() {
+ @Override
+ public float getInterpolation(float x) {
+ // Flash the flash view in and out quickly
+ if (x <= flashDurationPct) {
+ return (float) Math.sin(Math.PI * (x / flashDurationPct));
+ }
+ return 0;
+ }
+ };
+ final Interpolator scaleInterpolator = new Interpolator() {
+ @Override
+ public float getInterpolation(float x) {
+ // We start scaling when the flash is at it's peak
+ if (x < flashPeakDurationPct) {
+ return 0;
+ }
+ return (x - flashDurationPct) / (1f - flashDurationPct);
+ }
+ };
+
+ Resources r = mContext.getResources();
+ if ((r.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)
+ == Configuration.UI_MODE_NIGHT_YES) {
+ mScreenshotView.getBackground().setTint(Color.BLACK);
+ } else {
+ mScreenshotView.getBackground().setTintList(null);
+ }
+
+ ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
+ anim.setDuration(SCREENSHOT_DROP_IN_DURATION);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mBackgroundView.setAlpha(0f);
+ mBackgroundView.setVisibility(View.VISIBLE);
+ mScreenshotView.setAlpha(0f);
+ mScreenshotView.setTranslationX(0f);
+ mScreenshotView.setTranslationY(0f);
+ mScreenshotView.setScaleX(SCREENSHOT_SCALE + mBgPaddingScale);
+ mScreenshotView.setScaleY(SCREENSHOT_SCALE + mBgPaddingScale);
+ mScreenshotView.setVisibility(View.VISIBLE);
+ mScreenshotFlash.setAlpha(0f);
+ mScreenshotFlash.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onAnimationEnd(android.animation.Animator animation) {
+ mScreenshotFlash.setVisibility(View.GONE);
+ }
+ });
+ anim.addUpdateListener(new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float t = (Float) animation.getAnimatedValue();
+ float scaleT = (SCREENSHOT_SCALE + mBgPaddingScale)
+ - scaleInterpolator.getInterpolation(t)
+ * (SCREENSHOT_SCALE - SCREENSHOT_DROP_IN_MIN_SCALE);
+ mBackgroundView.setAlpha(scaleInterpolator.getInterpolation(t) * BACKGROUND_ALPHA);
+ mScreenshotView.setAlpha(t);
+ mScreenshotView.setScaleX(scaleT);
+ mScreenshotView.setScaleY(scaleT);
+ mScreenshotFlash.setAlpha(flashAlphaInterpolator.getInterpolation(t));
+ }
+ });
+ return anim;
+ }
+
+ private ValueAnimator createScreenshotDropOutAnimation(int w, int h, boolean statusBarVisible,
+ boolean navBarVisible) {
+ ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
+ anim.setStartDelay(SCREENSHOT_DROP_OUT_DELAY);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mBackgroundView.setVisibility(View.GONE);
+ mScreenshotView.setVisibility(View.GONE);
+ mScreenshotView.setLayerType(View.LAYER_TYPE_NONE, null);
+ }
+ });
+
+ if (!statusBarVisible || !navBarVisible) {
+ // There is no status bar/nav bar, so just fade the screenshot away in place
+ anim.setDuration(SCREENSHOT_FAST_DROP_OUT_DURATION);
+ anim.addUpdateListener(new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float t = (Float) animation.getAnimatedValue();
+ float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
+ - t * (SCREENSHOT_DROP_IN_MIN_SCALE
+ - SCREENSHOT_FAST_DROP_OUT_MIN_SCALE);
+ mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
+ mScreenshotView.setAlpha(1f - t);
+ mScreenshotView.setScaleX(scaleT);
+ mScreenshotView.setScaleY(scaleT);
+ }
+ });
+ } else {
+ // In the case where there is a status bar, animate to the origin of the bar (top-left)
+ final float scaleDurationPct = (float) SCREENSHOT_DROP_OUT_SCALE_DURATION
+ / SCREENSHOT_DROP_OUT_DURATION;
+ final Interpolator scaleInterpolator = new Interpolator() {
+ @Override
+ public float getInterpolation(float x) {
+ if (x < scaleDurationPct) {
+ // Decelerate, and scale the input accordingly
+ return (float) (1f - Math.pow(1f - (x / scaleDurationPct), 2f));
+ }
+ return 1f;
+ }
+ };
+
+ // Determine the bounds of how to scale
+ float halfScreenWidth = (w - 2f * mBgPadding) / 2f;
+ float halfScreenHeight = (h - 2f * mBgPadding) / 2f;
+ final float offsetPct = SCREENSHOT_DROP_OUT_MIN_SCALE_OFFSET;
+ final PointF finalPos = new PointF(
+ -halfScreenWidth
+ + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenWidth,
+ -halfScreenHeight
+ + (SCREENSHOT_DROP_OUT_MIN_SCALE + offsetPct) * halfScreenHeight);
+
+ // Animate the screenshot to the status bar
+ anim.setDuration(SCREENSHOT_DROP_OUT_DURATION);
+ anim.addUpdateListener(new AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ float t = (Float) animation.getAnimatedValue();
+ float scaleT = (SCREENSHOT_DROP_IN_MIN_SCALE + mBgPaddingScale)
+ - scaleInterpolator.getInterpolation(t)
+ * (SCREENSHOT_DROP_IN_MIN_SCALE - SCREENSHOT_DROP_OUT_MIN_SCALE);
+ mBackgroundView.setAlpha((1f - t) * BACKGROUND_ALPHA);
+ mScreenshotView.setAlpha(1f - scaleInterpolator.getInterpolation(t));
+ mScreenshotView.setScaleX(scaleT);
+ mScreenshotView.setScaleY(scaleT);
+ mScreenshotView.setTranslationX(t * finalPos.x);
+ mScreenshotView.setTranslationY(t * finalPos.y);
+ }
+ });
+ }
+ return anim;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
new file mode 100644
index 0000000..6edacd1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionChip.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+
+import android.annotation.ColorInt;
+import android.content.Context;
+import android.graphics.drawable.Icon;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+
+/**
+ * View for a chip with an icon and text.
+ */
+public class ScreenshotActionChip extends LinearLayout {
+
+ private ImageView mIcon;
+ private TextView mText;
+ private @ColorInt int mIconColor;
+
+ public ScreenshotActionChip(Context context) {
+ this(context, null);
+ }
+
+ public ScreenshotActionChip(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public ScreenshotActionChip(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public ScreenshotActionChip(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ mIconColor = context.getColor(R.color.global_screenshot_button_text);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ mIcon = findViewById(R.id.screenshot_action_chip_icon);
+ mText = findViewById(R.id.screenshot_action_chip_text);
+ }
+
+ void setIcon(Icon icon, boolean tint) {
+ if (tint) {
+ icon.setTint(mIconColor);
+ }
+ mIcon.setImageIcon(icon);
+ }
+
+ void setText(CharSequence text) {
+ mText.setText(text);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index 29b96a9..4f045d5 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -16,15 +16,21 @@
package com.android.systemui.screenshot;
+import static android.provider.DeviceConfig.NAMESPACE_SYSTEMUI;
+
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_CORNER_FLOW;
+
import android.app.Service;
import android.content.Intent;
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.os.UserManager;
+import android.provider.DeviceConfig;
import android.util.Log;
import android.view.WindowManager;
@@ -36,9 +42,10 @@
private static final String TAG = "TakeScreenshotService";
private final GlobalScreenshot mScreenshot;
+ private final GlobalScreenshotLegacy mScreenshotLegacy;
private final UserManager mUserManager;
- private Handler mHandler = new Handler() {
+ private Handler mHandler = new Handler(Looper.myLooper()) {
@Override
public void handleMessage(Message msg) {
final Messenger callback = msg.replyTo;
@@ -59,12 +66,24 @@
return;
}
+ // TODO (mkephart): clean up once notifications flow is fully deprecated
+ boolean useCornerFlow = DeviceConfig.getBoolean(
+ NAMESPACE_SYSTEMUI, SCREENSHOT_CORNER_FLOW, false);
switch (msg.what) {
case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
- mScreenshot.takeScreenshot(finisher, msg.arg1 > 0, msg.arg2 > 0);
+ if (useCornerFlow) {
+ mScreenshot.takeScreenshot(finisher);
+ } else {
+ mScreenshotLegacy.takeScreenshot(finisher, msg.arg1 > 0, msg.arg2 > 0);
+ }
break;
case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION:
- mScreenshot.takeScreenshotPartial(finisher, msg.arg1 > 0, msg.arg2 > 0);
+ if (useCornerFlow) {
+ mScreenshot.takeScreenshotPartial(finisher);
+ } else {
+ mScreenshotLegacy.takeScreenshotPartial(
+ finisher, msg.arg1 > 0, msg.arg2 > 0);
+ }
break;
default:
Log.d(TAG, "Invalid screenshot option: " + msg.what);
@@ -73,8 +92,10 @@
};
@Inject
- public TakeScreenshotService(GlobalScreenshot globalScreenshot, UserManager userManager) {
+ public TakeScreenshotService(GlobalScreenshot globalScreenshot,
+ GlobalScreenshotLegacy globalScreenshotLegacy, UserManager userManager) {
mScreenshot = globalScreenshot;
+ mScreenshotLegacy = globalScreenshotLegacy;
mUserManager = userManager;
}
@@ -86,6 +107,7 @@
@Override
public boolean onUnbind(Intent intent) {
if (mScreenshot != null) mScreenshot.stopScreenshot();
+ if (mScreenshotLegacy != null) mScreenshotLegacy.stopScreenshot();
return true;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
index 077d260..9599d77 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserTracker.java
@@ -98,7 +98,8 @@
if (!mReceiverRegistered) {
mCurrentUserId = ActivityManager.getCurrentUser();
IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
- mBroadcastDispatcher.registerReceiver(this, filter, null, UserHandle.ALL);
+ mBroadcastDispatcher.registerReceiver(this, filter, null,
+ UserHandle.ALL);
mReceiverRegistered = true;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
index 2005d79..ac05c53 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FeatureFlags.java
@@ -40,7 +40,7 @@
* You will probably need to restart systemui for the changes to be picked up:
*
* {@code
- * $ adb shell am crash com.android.systemui
+ * $ adb shell am restart com.android.systemui
* }
*/
@Singleton
@@ -59,6 +59,11 @@
return getDeviceConfigFlag("notification.newpipeline.enabled", false);
}
+ public boolean isNewNotifPipelineRenderingEnabled() {
+ return isNewNotifPipelineEnabled()
+ && getDeviceConfigFlag("notification.newpipeline.rendering", false);
+ }
+
private void onPropertiesChanged(@NonNull DeviceConfig.Properties properties) {
synchronized (mCachedDeviceConfigFlags) {
for (String key : properties.getKeyset()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java b/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java
index 525b5b7..12749fd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/FlingAnimationUtils.java
@@ -368,13 +368,14 @@
public static class Builder {
private final DisplayMetrics mDisplayMetrics;
float mMaxLengthSeconds;
- float mSpeedUpFactor = 0.0f;
- float mX2 = -1.0f;
- float mY2 = 1.0f;
+ float mSpeedUpFactor;
+ float mX2;
+ float mY2;
@Inject
public Builder(DisplayMetrics displayMetrics) {
mDisplayMetrics = displayMetrics;
+ reset();
}
public Builder setMaxLengthSeconds(float maxLengthSeconds) {
@@ -397,6 +398,15 @@
return this;
}
+ public Builder reset() {
+ mMaxLengthSeconds = 0;
+ mSpeedUpFactor = 0.0f;
+ mX2 = -1.0f;
+ mY2 = 1.0f;
+
+ return this;
+ }
+
public FlingAnimationUtils build() {
return new FlingAnimationUtils(mDisplayMetrics, mMaxLengthSeconds, mSpeedUpFactor,
mX2, mY2);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
index ff4ce94..ebf7c2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManager.java
@@ -49,6 +49,12 @@
/** Adds a listener to be notified when the current user changes. */
void addUserChangedListener(UserChangedListener listener);
+ /**
+ * Removes a listener previously registered with
+ * {@link #addUserChangedListener(UserChangedListener)}
+ */
+ void removeUserChangedListener(UserChangedListener listener);
+
SparseArray<UserInfo> getCurrentProfiles();
void setLockscreenPublicMode(boolean isProfilePublic, int userId);
@@ -79,6 +85,7 @@
/** Notified when the current user changes. */
interface UserChangedListener {
- void onUserChanged(int userId);
+ default void onUserChanged(int userId) {}
+ default void onCurrentProfilesChanged(SparseArray<UserInfo> currentProfiles) {}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 2e369b3..976531d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -117,51 +117,63 @@
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
- if (Intent.ACTION_USER_SWITCHED.equals(action)) {
- mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
- updateCurrentProfilesCache();
- Log.v(TAG, "userId " + mCurrentUserId + " is in the house");
+ switch (action) {
+ case Intent.ACTION_USER_SWITCHED:
+ mCurrentUserId = intent.getIntExtra(
+ Intent.EXTRA_USER_HANDLE, UserHandle.USER_ALL);
+ updateCurrentProfilesCache();
- updateLockscreenNotificationSetting();
- updatePublicMode();
- // The filtering needs to happen before the update call below in order to make sure
- // the presenter has the updated notifications from the new user
- getEntryManager().reapplyFilterAndSort("user switched");
- mPresenter.onUserSwitched(mCurrentUserId);
+ Log.v(TAG, "userId " + mCurrentUserId + " is in the house");
- for (UserChangedListener listener : mListeners) {
- listener.onUserChanged(mCurrentUserId);
- }
- } else if (Intent.ACTION_USER_ADDED.equals(action)) {
- updateCurrentProfilesCache();
- } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) {
- // Start the overview connection to the launcher service
- Dependency.get(OverviewProxyService.class).startConnectionToCurrentUser();
- } else if (NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION.equals(action)) {
- final IntentSender intentSender = intent.getParcelableExtra(Intent.EXTRA_INTENT);
- final String notificationKey = intent.getStringExtra(Intent.EXTRA_INDEX);
- if (intentSender != null) {
- try {
- mContext.startIntentSender(intentSender, null, 0, 0, 0);
- } catch (IntentSender.SendIntentException e) {
- /* ignore */
+ updateLockscreenNotificationSetting();
+ updatePublicMode();
+ // The filtering needs to happen before the update call below in order to
+ // make sure
+ // the presenter has the updated notifications from the new user
+ getEntryManager().reapplyFilterAndSort("user switched");
+ mPresenter.onUserSwitched(mCurrentUserId);
+
+ for (UserChangedListener listener : mListeners) {
+ listener.onUserChanged(mCurrentUserId);
}
- }
- if (notificationKey != null) {
- NotificationEntry entry =
- getEntryManager().getActiveNotificationUnfiltered(notificationKey);
- final int count = getEntryManager().getActiveNotificationsCount();
- final int rank = entry != null ? entry.getRanking().getRank() : 0;
- NotificationVisibility.NotificationLocation location =
- NotificationLogger.getNotificationLocation(entry);
- final NotificationVisibility nv = NotificationVisibility.obtain(notificationKey,
- rank, count, true, location);
- try {
- mBarService.onNotificationClick(notificationKey, nv);
- } catch (RemoteException exception) {
- /* ignore */
+ break;
+ case Intent.ACTION_USER_ADDED:
+ case Intent.ACTION_MANAGED_PROFILE_AVAILABLE:
+ case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE:
+ updateCurrentProfilesCache();
+ break;
+ case Intent.ACTION_USER_UNLOCKED:
+ // Start the overview connection to the launcher service
+ Dependency.get(OverviewProxyService.class).startConnectionToCurrentUser();
+ break;
+ case NOTIFICATION_UNLOCKED_BY_WORK_CHALLENGE_ACTION:
+ final IntentSender intentSender = intent.getParcelableExtra(
+ Intent.EXTRA_INTENT);
+ final String notificationKey = intent.getStringExtra(Intent.EXTRA_INDEX);
+ if (intentSender != null) {
+ try {
+ mContext.startIntentSender(intentSender, null, 0, 0, 0);
+ } catch (IntentSender.SendIntentException e) {
+ /* ignore */
+ }
}
- }
+ if (notificationKey != null) {
+ NotificationEntry entry =
+ getEntryManager().getActiveNotificationUnfiltered(notificationKey);
+ final int count = getEntryManager().getActiveNotificationsCount();
+ final int rank = entry != null ? entry.getRanking().getRank() : 0;
+ NotificationVisibility.NotificationLocation location =
+ NotificationLogger.getNotificationLocation(entry);
+ final NotificationVisibility nv = NotificationVisibility.obtain(
+ notificationKey,
+ rank, count, true, location);
+ try {
+ mBarService.onNotificationClick(notificationKey, nv);
+ } catch (RemoteException exception) {
+ /* ignore */
+ }
+ }
+ break;
}
}
};
@@ -266,6 +278,8 @@
filter.addAction(Intent.ACTION_USER_SWITCHED);
filter.addAction(Intent.ACTION_USER_ADDED);
filter.addAction(Intent.ACTION_USER_UNLOCKED);
+ filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
+ filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
mBroadcastDispatcher.registerReceiver(mBaseBroadcastReceiver, filter);
IntentFilter internalFilter = new IntentFilter();
@@ -489,6 +503,11 @@
}
}
}
+ mMainHandler.post(() -> {
+ for (UserChangedListener listener : mListeners) {
+ listener.onCurrentProfilesChanged(mCurrentProfiles);
+ }
+ });
}
public boolean isAnyProfilePublicMode() {
@@ -555,6 +574,11 @@
mListeners.add(listener);
}
+ @Override
+ public void removeUserChangedListener(UserChangedListener listener) {
+ mListeners.remove(listener);
+ }
+
// public void updatePublicMode() {
// //TODO: I think there may be a race condition where mKeyguardViewManager.isShowing() returns
// // false when it should be true. Therefore, if we are not on the SHADE, don't even bother
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index 43d0399..667e721 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -584,7 +584,15 @@
public void bindRow(ExpandableNotificationRow row) {
row.setRemoteInputController(mRemoteInputController);
- row.setRemoteViewClickHandler(mOnClickHandler);
+ }
+
+ /**
+ * Return on-click handler for notification remote views
+ *
+ * @return on-click handler
+ */
+ public RemoteViews.OnClickHandler getRemoteViewsOnClickHandler() {
+ return mOnClickHandler;
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java
index d1f6ebf..ec8dbea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarDependenciesModule.java
@@ -18,6 +18,8 @@
import android.content.Context;
+import com.android.systemui.statusbar.notification.row.NotificationRowModule;
+
import javax.inject.Singleton;
import dagger.Module;
@@ -26,7 +28,7 @@
/**
* Dagger Module providing common dependencies of StatusBar.
*/
-@Module
+@Module(includes = {NotificationRowModule.class})
public class StatusBarDependenciesModule {
/**
* Provides our instance of CommandQueue which is considered optional.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index dc84b57..9c626f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -307,6 +307,7 @@
case Icon.TYPE_RESOURCE:
return a.getResPackage().equals(b.getResPackage()) && a.getResId() == b.getResId();
case Icon.TYPE_URI:
+ case Icon.TYPE_URI_ADAPTIVE_BITMAP:
return a.getUriString().equals(b.getUriString());
default:
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
index 9b31234..6660569 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
@@ -37,7 +37,7 @@
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
-import com.android.systemui.statusbar.phone.NotificationPanelView;
+import com.android.systemui.statusbar.phone.NotificationPanelViewController;
import com.android.systemui.statusbar.phone.StatusBarWindowViewController;
/**
@@ -53,7 +53,7 @@
CollapsedStatusBarFragment.FADE_IN_DURATION - CollapsedStatusBarFragment.FADE_IN_DELAY
- 16;
private static final long LAUNCH_TIMEOUT = 500;
- private final NotificationPanelView mNotificationPanel;
+ private final NotificationPanelViewController mNotificationPanel;
private final NotificationListContainer mNotificationContainer;
private final float mWindowCornerRadius;
private final StatusBarWindowViewController mStatusBarWindowViewController;
@@ -69,7 +69,7 @@
public ActivityLaunchAnimator(
StatusBarWindowViewController statusBarWindowViewController,
Callback callback,
- NotificationPanelView notificationPanel,
+ NotificationPanelViewController notificationPanel,
NotificationListContainer container) {
mNotificationPanel = notificationPanel;
mNotificationContainer = container;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 33d97a1..4a22831 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -32,8 +32,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.NotificationLifetimeExtender;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
@@ -43,7 +43,7 @@
import com.android.systemui.statusbar.NotificationUiAdjustment;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
-import com.android.systemui.statusbar.notification.collection.NotificationRowBinder;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
import com.android.systemui.statusbar.notification.logging.NotifEvent;
import com.android.systemui.statusbar.notification.logging.NotifLog;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
@@ -67,6 +67,8 @@
import javax.inject.Inject;
import javax.inject.Singleton;
+import dagger.Lazy;
+
/**
* NotificationEntryManager is responsible for the adding, removing, and updating of
* {@link NotificationEntry}s. It also handles tasks such as their inflation and their interaction
@@ -125,12 +127,14 @@
new ArrayMap<>();
// Lazily retrieved dependencies
- private NotificationRemoteInputManager mRemoteInputManager;
- private NotificationRowBinder mNotificationRowBinder;
+ private final Lazy<NotificationRowBinder> mNotificationRowBinderLazy;
+ private final Lazy<NotificationRemoteInputManager> mRemoteInputManagerLazy;
+ private final LeakDetector mLeakDetector;
private final KeyguardEnvironment mKeyguardEnvironment;
private final NotificationGroupManager mGroupManager;
private final NotificationRankingManager mRankingManager;
+ private final FeatureFlags mFeatureFlags;
private NotificationPresenter mPresenter;
private RankingMap mLatestRankingMap;
@@ -170,11 +174,19 @@
NotifLog notifLog,
NotificationGroupManager groupManager,
NotificationRankingManager rankingManager,
- KeyguardEnvironment keyguardEnvironment) {
+ KeyguardEnvironment keyguardEnvironment,
+ FeatureFlags featureFlags,
+ Lazy<NotificationRowBinder> notificationRowBinderLazy,
+ Lazy<NotificationRemoteInputManager> notificationRemoteInputManagerLazy,
+ LeakDetector leakDetector) {
mNotifLog = notifLog;
mGroupManager = groupManager;
mRankingManager = rankingManager;
mKeyguardEnvironment = keyguardEnvironment;
+ mFeatureFlags = featureFlags;
+ mNotificationRowBinderLazy = notificationRowBinderLazy;
+ mRemoteInputManagerLazy = notificationRemoteInputManagerLazy;
+ mLeakDetector = leakDetector;
}
/** Once called, the NEM will start processing notification events from system server. */
@@ -200,20 +212,6 @@
mRemoveInterceptor = interceptor;
}
- /**
- * Our dependencies can have cyclic references, so some need to be lazy
- */
- private NotificationRemoteInputManager getRemoteInputManager() {
- if (mRemoteInputManager == null) {
- mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class);
- }
- return mRemoteInputManager;
- }
-
- public void setRowBinder(NotificationRowBinder notificationRowBinder) {
- mNotificationRowBinder = notificationRowBinder;
- }
-
public void setUpWithPresenter(NotificationPresenter presenter,
NotificationListContainer listContainer,
HeadsUpManager headsUpManager) {
@@ -290,6 +288,10 @@
* WARNING: this will call back into us. Don't hold any locks.
*/
@Override
+ public void handleInflationException(NotificationEntry n, Exception e) {
+ handleInflationException(n.getSbn(), e);
+ }
+
public void handleInflationException(StatusBarNotification n, Exception e) {
removeNotificationInternal(
n.getKey(), null, null, true /* forceRemove */, false /* removedByUser */,
@@ -460,7 +462,7 @@
handleGroupSummaryRemoved(key);
removeVisibleNotification(key);
updateNotifications("removeNotificationInternal");
- Dependency.get(LeakDetector.class).trackGarbage(entry);
+ mLeakDetector.trackGarbage(entry);
removedByUser |= entryDismissed;
mNotifLog.log(NotifEvent.NOTIF_REMOVED, entry.getSbn(),
@@ -499,8 +501,8 @@
boolean isForeground = (entry.getSbn().getNotification().flags
& Notification.FLAG_FOREGROUND_SERVICE) != 0;
boolean keepForReply =
- getRemoteInputManager().shouldKeepForRemoteInputHistory(childEntry)
- || getRemoteInputManager().shouldKeepForSmartReplyHistory(childEntry);
+ mRemoteInputManagerLazy.get().shouldKeepForRemoteInputHistory(childEntry)
+ || mRemoteInputManagerLazy.get().shouldKeepForSmartReplyHistory(childEntry);
if (isForeground || keepForReply) {
// the child is a foreground service notification which we can't remove or it's
// a child we're keeping around for reply!
@@ -528,10 +530,14 @@
NotificationEntry entry = new NotificationEntry(notification, ranking);
- Dependency.get(LeakDetector.class).trackInstance(entry);
+ mLeakDetector.trackInstance(entry);
+
// Construct the expanded view.
- requireBinder().inflateViews(entry, () -> performRemoveNotification(notification,
- REASON_CANCEL));
+ if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+ mNotificationRowBinderLazy.get()
+ .inflateViews(entry, () -> performRemoveNotification(notification,
+ REASON_CANCEL));
+ }
abortExistingInflation(key, "addNotification");
mPendingNotifications.put(key, entry);
@@ -574,13 +580,17 @@
listener.onPreEntryUpdated(entry);
}
- requireBinder().inflateViews(entry, () -> performRemoveNotification(notification,
- REASON_CANCEL));
+ if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+ mNotificationRowBinderLazy.get()
+ .inflateViews(entry, () -> performRemoveNotification(notification,
+ REASON_CANCEL));
+ }
+
updateNotifications("updateNotificationInternal");
if (DEBUG) {
// Is this for you?
- boolean isForCurrentUser = Dependency.get(KeyguardEnvironment.class)
+ boolean isForCurrentUser = mKeyguardEnvironment
.isNotificationForCurrentProfiles(notification);
Log.d(TAG, "notification is " + (isForCurrentUser ? "" : "not ") + "for you");
}
@@ -630,11 +640,12 @@
// By comparing the old and new UI adjustments, reinflate the view accordingly.
for (NotificationEntry entry : entries) {
- requireBinder().onNotificationRankingUpdated(
- entry,
- oldImportances.get(entry.getKey()),
- oldAdjustments.get(entry.getKey()),
- NotificationUiAdjustment.extractFromNotificationEntry(entry));
+ mNotificationRowBinderLazy.get()
+ .onNotificationRankingUpdated(
+ entry,
+ oldImportances.get(entry.getKey()),
+ oldAdjustments.get(entry.getKey()),
+ NotificationUiAdjustment.extractFromNotificationEntry(entry));
}
updateNotifications("updateNotificationRanking");
@@ -714,14 +725,6 @@
}
}
- private NotificationRowBinder requireBinder() {
- if (mNotificationRowBinder == null) {
- throw new RuntimeException("You must initialize NotificationEntryManager by calling"
- + "setRowBinder() before using.");
- }
- return mNotificationRowBinder;
- }
-
/*
* -----
* Annexed from NotificationData below:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java
index ec1efa5..b960b42 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/GroupEntry.java
@@ -24,7 +24,6 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
-import java.util.Objects;
/**
* Represents a set of grouped notifications. The final notification list is usually a mix of
@@ -58,22 +57,15 @@
@VisibleForTesting
public void setSummary(@Nullable NotificationEntry summary) {
- if (!Objects.equals(mSummary, summary)) {
- mSummary = summary;
- onGroupingUpdated();
- }
+ mSummary = summary;
}
void clearChildren() {
- if (mChildren.size() != 0) {
- mChildren.clear();
- onGroupingUpdated();
- }
+ mChildren.clear();
}
void addChild(NotificationEntry child) {
mChildren.add(child);
- onGroupingUpdated();
}
void sortChildren(Comparator<? super NotificationEntry> c) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
index e1268f6..eaa9d78 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
@@ -16,44 +16,135 @@
package com.android.systemui.statusbar.notification.collection;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
-
+import java.util.Arrays;
import java.util.List;
-
/**
- * Utility class for dumping the results of a {@link NotifListBuilder} to a debug string.
+ * Utility class for dumping the results of a {@link ShadeListBuilder} to a debug string.
*/
public class ListDumper {
- /** See class description */
- public static String dumpList(List<ListEntry> entries) {
+ /**
+ * Creates a debug string for a list of grouped notifications that will be printed
+ * in the order given in a tiered/tree structure.
+ * @param includeRecordKeeping whether to print out the Pluggables that caused the notification
+ * entry to be in its current state (ie: filter, lifeExtender)
+ */
+ public static String dumpTree(
+ List<ListEntry> entries,
+ boolean includeRecordKeeping,
+ String indent) {
StringBuilder sb = new StringBuilder();
- for (int i = 0; i < entries.size(); i++) {
- ListEntry entry = entries.get(i);
- dumpEntry(entry, Integer.toString(i), "", sb);
+ final String childEntryIndent = indent + INDENT;
+ for (int topEntryIndex = 0; topEntryIndex < entries.size(); topEntryIndex++) {
+ ListEntry entry = entries.get(topEntryIndex);
+ dumpEntry(entry,
+ Integer.toString(topEntryIndex),
+ indent,
+ sb,
+ true,
+ includeRecordKeeping);
if (entry instanceof GroupEntry) {
GroupEntry ge = (GroupEntry) entry;
- for (int j = 0; j < ge.getChildren().size(); j++) {
- dumpEntry(
- ge.getChildren().get(j),
- Integer.toString(j),
- INDENT,
- sb);
+ List<NotificationEntry> children = ge.getChildren();
+ for (int childIndex = 0; childIndex < children.size(); childIndex++) {
+ dumpEntry(children.get(childIndex),
+ Integer.toString(topEntryIndex) + "." + Integer.toString(childIndex),
+ childEntryIndent,
+ sb,
+ true,
+ includeRecordKeeping);
}
}
}
return sb.toString();
}
+ /**
+ * Creates a debug string for a flat list of notifications
+ * @param includeRecordKeeping whether to print out the Pluggables that caused the notification
+ * entry to be in its current state (ie: filter, lifeExtender)
+ */
+ public static String dumpList(
+ List<NotificationEntry> entries,
+ boolean includeRecordKeeping,
+ String indent) {
+ StringBuilder sb = new StringBuilder();
+ for (int j = 0; j < entries.size(); j++) {
+ dumpEntry(
+ entries.get(j),
+ Integer.toString(j),
+ indent,
+ sb,
+ false,
+ includeRecordKeeping);
+ }
+ return sb.toString();
+ }
+
private static void dumpEntry(
- ListEntry entry, String index, String indent, StringBuilder sb) {
+ ListEntry entry,
+ String index,
+ String indent,
+ StringBuilder sb,
+ boolean includeParent,
+ boolean includeRecordKeeping) {
sb.append(indent)
.append("[").append(index).append("] ")
- .append(entry.getKey())
- .append(" (parent=")
- .append(entry.getParent() != null ? entry.getParent().getKey() : null)
- .append(")\n");
+ .append(entry.getKey());
+
+ if (includeParent) {
+ sb.append(" (parent=")
+ .append(entry.getParent() != null ? entry.getParent().getKey() : null)
+ .append(")");
+ }
+
+ if (entry.mNotifSection != null) {
+ sb.append(" sectionIndex=")
+ .append(entry.getSection())
+ .append(" sectionName=")
+ .append(entry.mNotifSection.getName());
+ }
+
+ if (includeRecordKeeping) {
+ NotificationEntry notifEntry = entry.getRepresentativeEntry();
+ StringBuilder rksb = new StringBuilder();
+
+ if (!notifEntry.mLifetimeExtenders.isEmpty()) {
+ String[] lifetimeExtenderNames = new String[notifEntry.mLifetimeExtenders.size()];
+ for (int i = 0; i < lifetimeExtenderNames.length; i++) {
+ lifetimeExtenderNames[i] = notifEntry.mLifetimeExtenders.get(i).getName();
+ }
+ rksb.append("lifetimeExtenders=")
+ .append(Arrays.toString(lifetimeExtenderNames))
+ .append(" ");
+ }
+
+ if (notifEntry.mExcludingFilter != null) {
+ rksb.append("filter=")
+ .append(notifEntry.mExcludingFilter)
+ .append(" ");
+ }
+
+ if (notifEntry.mNotifPromoter != null) {
+ rksb.append("promoter=")
+ .append(notifEntry.mNotifPromoter)
+ .append(" ");
+ }
+
+ if (notifEntry.hasInflationError()) {
+ rksb.append("hasInflationError ");
+ }
+
+ String rkString = rksb.toString();
+ if (!rkString.isEmpty()) {
+ sb.append("\n\t")
+ .append(indent)
+ .append(rkString);
+ }
+ }
+
+ sb.append("\n");
}
private static final String INDENT = " ";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
index 601b3e0..56ad0e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListEntry.java
@@ -18,14 +18,7 @@
import android.annotation.Nullable;
-import com.android.systemui.Dependency;
-import com.android.systemui.statusbar.notification.collection.provider.DerivedMember;
-import com.android.systemui.statusbar.notification.collection.provider.IsHighPriorityProvider;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.Objects;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
/**
* Abstract superclass for top-level entries, i.e. things that can appear in the final notification
@@ -33,23 +26,16 @@
*/
public abstract class ListEntry {
private final String mKey;
- private final IsHighPriorityProvider mIsHighPriorityProvider = new IsHighPriorityProvider();
- private final List<DerivedMember> mDerivedMemberList = Arrays.asList(mIsHighPriorityProvider);
@Nullable private GroupEntry mParent;
@Nullable private GroupEntry mPreviousParent;
- private int mSection;
- int mFirstAddedIteration = -1;
+ @Nullable NotifSection mNotifSection;
- // TODO: (b/145659174) remove groupManager when moving to NewNotifPipeline. Logic
- // replaced in GroupEntry and NotifListBuilderImpl
- private final NotificationGroupManager mGroupManager;
+ private int mSection = -1;
+ int mFirstAddedIteration = -1;
ListEntry(String key) {
mKey = key;
-
- // TODO: (b/145659174) remove
- mGroupManager = Dependency.get(NotificationGroupManager.class);
}
public String getKey() {
@@ -68,11 +54,7 @@
}
void setParent(@Nullable GroupEntry parent) {
- if (!Objects.equals(mParent, parent)) {
- invalidateParent();
- mParent = parent;
- onGroupingUpdated();
- }
+ mParent = parent;
}
@Nullable public GroupEntry getPreviousParent() {
@@ -91,58 +73,4 @@
void setSection(int section) {
mSection = section;
}
-
- /**
- * Resets the cached values of DerivedMembers.
- */
- void invalidateDerivedMembers() {
- for (int i = 0; i < mDerivedMemberList.size(); i++) {
- mDerivedMemberList.get(i).invalidate();
- }
- }
-
- /**
- * Whether this notification is shown to the user as a high priority notification: visible on
- * the lock screen/status bar and in the top section in the shade.
- */
- public boolean isHighPriority() {
- return mIsHighPriorityProvider.get(this);
- }
-
- private void invalidateParent() {
- // invalidate our parent (GroupEntry) since DerivedMembers may be dependent on children
- if (getParent() != null) {
- getParent().invalidateDerivedMembers();
- }
-
- // TODO: (b/145659174) remove
- final NotificationEntry notifEntry = getRepresentativeEntry();
- if (notifEntry != null && mGroupManager.isGroupChild(notifEntry.getSbn())) {
- NotificationEntry summary = mGroupManager.getLogicalGroupSummary(notifEntry.getSbn());
- if (summary != null) {
- summary.invalidateDerivedMembers();
- }
- }
- }
-
- void onGroupingUpdated() {
- for (int i = 0; i < mDerivedMemberList.size(); i++) {
- mDerivedMemberList.get(i).onGroupingUpdated();
- }
- invalidateParent();
- }
-
- void onSbnUpdated() {
- for (int i = 0; i < mDerivedMemberList.size(); i++) {
- mDerivedMemberList.get(i).onSbnUpdated();
- }
- invalidateParent();
- }
-
- void onRankingUpdated() {
- for (int i = 0; i < mDerivedMemberList.size(); i++) {
- mDerivedMemberList.get(i).onRankingUpdated();
- }
- invalidateParent();
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index 873cdbc..c488c6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -47,11 +47,19 @@
import android.util.Log;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.statusbar.notification.collection.notifcollection.CoalescedEvent;
-import com.android.systemui.statusbar.notification.collection.notifcollection.GroupCoalescer;
-import com.android.systemui.statusbar.notification.collection.notifcollection.GroupCoalescer.BatchableNotificationHandler;
+import com.android.systemui.DumpController;
+import com.android.systemui.Dumpable;
+import com.android.systemui.statusbar.notification.collection.coalescer.CoalescedEvent;
+import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer;
+import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer.BatchableNotificationHandler;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
+import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
import com.android.systemui.util.Assert;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -88,7 +96,7 @@
*/
@MainThread
@Singleton
-public class NotifCollection {
+public class NotifCollection implements Dumpable {
private final IStatusBarService mStatusBarService;
private final Map<String, NotificationEntry> mNotificationSet = new ArrayMap<>();
@@ -103,9 +111,10 @@
private boolean mAmDispatchingToOtherCode;
@Inject
- public NotifCollection(IStatusBarService statusBarService) {
+ public NotifCollection(IStatusBarService statusBarService, DumpController dumpController) {
Assert.isMainThread();
mStatusBarService = statusBarService;
+ dumpController.registerDumpable(TAG, this);
}
/** Initializes the NotifCollection and registers it to receive notification events. */
@@ -123,36 +132,25 @@
* Sets the class responsible for converting the collection into the list of currently-visible
* notifications.
*/
- public void setBuildListener(CollectionReadyForBuildListener buildListener) {
+ void setBuildListener(CollectionReadyForBuildListener buildListener) {
Assert.isMainThread();
mBuildListener = buildListener;
}
- /**
- * Returns the list of "active" notifications, i.e. the notifications that are currently posted
- * to the phone. In general, this tracks closely to the list maintained by NotificationManager,
- * but it can diverge slightly due to lifetime extenders.
- *
- * The returned list is read-only, unsorted, unfiltered, and ungrouped.
- */
- public Collection<NotificationEntry> getNotifs() {
+ /** @see NotifPipeline#getActiveNotifs() */
+ Collection<NotificationEntry> getActiveNotifs() {
Assert.isMainThread();
return mReadOnlyNotificationSet;
}
- /**
- * Registers a listener to be informed when notifications are added, removed or updated.
- */
- public void addCollectionListener(NotifCollectionListener listener) {
+ /** @see NotifPipeline#addCollectionListener(NotifCollectionListener) */
+ void addCollectionListener(NotifCollectionListener listener) {
Assert.isMainThread();
mNotifCollectionListeners.add(listener);
}
- /**
- * Registers a lifetime extender. Lifetime extenders can cause notifications that have been
- * dismissed or retracted to be temporarily retained in the collection.
- */
- public void addNotificationLifetimeExtender(NotifLifetimeExtender extender) {
+ /** @see NotifPipeline#addNotificationLifetimeExtender(NotifLifetimeExtender) */
+ void addNotificationLifetimeExtender(NotifLifetimeExtender extender) {
Assert.isMainThread();
checkForReentrantCall();
if (mLifetimeExtenders.contains(extender)) {
@@ -165,7 +163,7 @@
/**
* Dismiss a notification on behalf of the user.
*/
- public void dismissNotification(
+ void dismissNotification(
NotificationEntry entry,
@CancellationReason int reason,
@NonNull DismissedByUserStats stats) {
@@ -299,6 +297,14 @@
if (!isLifetimeExtended(entry)) {
Ranking ranking = requireRanking(rankingMap, entry.getKey());
entry.setRanking(ranking);
+
+ // TODO: (b/145659174) update the sbn's overrideGroupKey in
+ // NotificationEntry.setRanking instead of here once we fully migrate to the
+ // NewNotifPipeline
+ final String newOverrideGroupKey = ranking.getOverrideGroupKey();
+ if (!Objects.equals(entry.getSbn().getOverrideGroupKey(), newOverrideGroupKey)) {
+ entry.getSbn().setOverrideGroupKey(newOverrideGroupKey);
+ }
}
}
}
@@ -438,7 +444,22 @@
REASON_TIMEOUT,
})
@Retention(RetentionPolicy.SOURCE)
- @interface CancellationReason {}
+ public @interface CancellationReason {}
public static final int REASON_UNKNOWN = 0;
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ final List<NotificationEntry> entries = new ArrayList<>(getActiveNotifs());
+
+ pw.println("\t" + TAG + " unsorted/unfiltered notifications:");
+ if (entries.size() == 0) {
+ pw.println("\t\t None");
+ }
+ pw.println(
+ ListDumper.dumpList(
+ entries,
+ true,
+ "\t\t"));
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
new file mode 100644
index 0000000..e7b772f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
@@ -0,0 +1,161 @@
+/*
+ * 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.systemui.statusbar.notification.collection;
+
+import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
+
+import android.os.RemoteException;
+import android.service.notification.NotificationStats;
+import android.service.notification.StatusBarNotification;
+
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.statusbar.notification.InflationException;
+import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
+import com.android.systemui.statusbar.notification.logging.NotificationLogger;
+import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Handles notification inflating, rebinding, and inflation aborting.
+ *
+ * Currently a wrapper for NotificationRowBinderImpl.
+ */
+@Singleton
+public class NotifInflaterImpl implements NotifInflater {
+
+ private final IStatusBarService mStatusBarService;
+ private final NotifCollection mNotifCollection;
+
+ private NotificationRowBinderImpl mNotificationRowBinder;
+ private InflationCallback mExternalInflationCallback;
+
+ @Inject
+ public NotifInflaterImpl(
+ IStatusBarService statusBarService,
+ NotifCollection notifCollection) {
+ mStatusBarService = statusBarService;
+ mNotifCollection = notifCollection;
+ }
+
+ /**
+ * Attaches the row binder for inflation.
+ */
+ public void setRowBinder(NotificationRowBinderImpl rowBinder) {
+ mNotificationRowBinder = rowBinder;
+ mNotificationRowBinder.setInflationCallback(mInflationCallback);
+ }
+
+ @Override
+ public void setInflationCallback(InflationCallback callback) {
+ mExternalInflationCallback = callback;
+ }
+
+ @Override
+ public void rebindViews(NotificationEntry entry) {
+ inflateViews(entry);
+ }
+
+ /**
+ * Called to inflate the views of an entry. Views are not considered inflated until all of its
+ * views are bound.
+ */
+ @Override
+ public void inflateViews(NotificationEntry entry) {
+ try {
+ entry.setHasInflationError(false);
+ requireBinder().inflateViews(entry, getDismissCallback(entry));
+ } catch (InflationException e) {
+ // logged in mInflationCallback.handleInflationException
+ }
+ }
+
+ @Override
+ public void abortInflation(NotificationEntry entry) {
+ entry.abortTask();
+ }
+
+ private Runnable getDismissCallback(NotificationEntry entry) {
+ return new Runnable() {
+ @Override
+ public void run() {
+ int dismissalSurface = NotificationStats.DISMISSAL_SHADE;
+ /**
+ * TODO: determine dismissal surface (ie: shade / headsup / aod)
+ * see {@link NotificationLogger#logNotificationClear}
+ */
+ mNotifCollection.dismissNotification(
+ entry,
+ 0,
+ new DismissedByUserStats(
+ dismissalSurface,
+ DISMISS_SENTIMENT_NEUTRAL,
+ NotificationVisibility.obtain(entry.getKey(),
+ entry.getRanking().getRank(),
+ mNotifCollection.getActiveNotifs().size(),
+ true,
+ NotificationLogger.getNotificationLocation(entry))
+ ));
+ }
+ };
+ }
+
+ private NotificationRowBinderImpl requireBinder() {
+ if (mNotificationRowBinder == null) {
+ throw new RuntimeException("NotificationRowBinder must be attached before using "
+ + "NotifInflaterImpl.");
+ }
+ return mNotificationRowBinder;
+ }
+
+ private final NotificationContentInflater.InflationCallback mInflationCallback =
+ new NotificationContentInflater.InflationCallback() {
+ @Override
+ public void handleInflationException(
+ NotificationEntry entry,
+ Exception e) {
+ entry.setHasInflationError(true);
+ try {
+ final StatusBarNotification sbn = entry.getSbn();
+ // report notification inflation errors back up
+ // to notification delegates
+ mStatusBarService.onNotificationError(
+ sbn.getPackageName(),
+ sbn.getTag(),
+ sbn.getId(),
+ sbn.getUid(),
+ sbn.getInitialPid(),
+ e.getMessage(),
+ sbn.getUserId());
+ } catch (RemoteException ex) {
+ }
+ }
+
+ @Override
+ public void onAsyncInflationFinished(
+ NotificationEntry entry,
+ int inflatedFlags) {
+ if (mExternalInflationCallback != null) {
+ mExternalInflationCallback.onInflationFinished(entry);
+ }
+ }
+ };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
new file mode 100644
index 0000000..0377f57
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
@@ -0,0 +1,190 @@
+/*
+ * 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.systemui.statusbar.notification.collection;
+
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener;
+import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
+
+import java.util.Collection;
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * The system that constructs the "shade list", the filtered, grouped, and sorted list of
+ * notifications that are currently being displayed to the user in the notification shade.
+ *
+ * The pipeline proceeds through a series of stages in order to produce the final list (see below).
+ * Each stage exposes hooks and listeners to allow other code to participate.
+ *
+ * This list differs from the canonical one we receive from system server in a few ways:
+ * - Filtered: Some notifications are filtered out. For example, we filter out notifications whose
+ * views haven't been inflated yet. We also filter out some notifications if we're on the lock
+ * screen and notifications for other users. So participate, see
+ * {@link #addPreGroupFilter} and similar methods.
+ * - Grouped: Notifications that are part of the same group are clustered together into a single
+ * GroupEntry. These groups are then transformed in order to remove children or completely split
+ * them apart. To participate, see {@link #addPromoter}.
+ * - Sorted: All top-level notifications are sorted. To participate, see
+ * {@link #setSections} and {@link #setComparators}
+ *
+ * The exact order of all hooks is as follows:
+ * 0. Collection listeners are fired ({@link #addCollectionListener}).
+ * 1. Pre-group filters are fired on each notification ({@link #addPreGroupFilter}).
+ * 2. Initial grouping is performed (NotificationEntries will have their parents set
+ * appropriately).
+ * 3. OnBeforeTransformGroupListeners are fired ({@link #addOnBeforeTransformGroupsListener})
+ * 4. NotifPromoters are called on each notification with a parent ({@link #addPromoter})
+ * 5. OnBeforeSortListeners are fired ({@link #addOnBeforeSortListener})
+ * 6. Top-level entries are assigned sections by NotifSections ({@link #setSections})
+ * 7. Top-level entries within the same section are sorted by NotifComparators
+ * ({@link #setComparators})
+ * 8. Pre-render filters are fired on each notification ({@link #addPreRenderFilter})
+ * 9. OnBeforeRenderListListeners are fired ({@link #addOnBeforeRenderListListener})
+ * 9. The list is handed off to the view layer to be rendered
+ */
+@Singleton
+public class NotifPipeline {
+ private final NotifCollection mNotifCollection;
+ private final ShadeListBuilder mShadeListBuilder;
+
+ @Inject
+ public NotifPipeline(
+ NotifCollection notifCollection,
+ ShadeListBuilder shadeListBuilder) {
+ mNotifCollection = notifCollection;
+ mShadeListBuilder = shadeListBuilder;
+ }
+
+ /**
+ * Returns the list of "active" notifications, i.e. the notifications that are currently posted
+ * to the phone. In general, this tracks closely to the list maintained by NotificationManager,
+ * but it can diverge slightly due to lifetime extenders.
+ *
+ * The returned collection is read-only, unsorted, unfiltered, and ungrouped.
+ */
+ public Collection<NotificationEntry> getActiveNotifs() {
+ return mNotifCollection.getActiveNotifs();
+ }
+
+ /**
+ * Registers a listener to be informed when notifications are added, removed or updated.
+ */
+ public void addCollectionListener(NotifCollectionListener listener) {
+ mNotifCollection.addCollectionListener(listener);
+ }
+
+ /**
+ * Registers a lifetime extender. Lifetime extenders can cause notifications that have been
+ * dismissed or retracted to be temporarily retained in the collection.
+ */
+ public void addNotificationLifetimeExtender(NotifLifetimeExtender extender) {
+ mNotifCollection.addNotificationLifetimeExtender(extender);
+ }
+
+ /**
+ * Registers a filter with the pipeline before grouping, promoting and sorting occurs. Filters
+ * are called on each notification in the order that they were registered. If any filter
+ * returns true, the notification is removed from the pipeline (and no other filters are
+ * called on that notif).
+ */
+ public void addPreGroupFilter(NotifFilter filter) {
+ mShadeListBuilder.addPreGroupFilter(filter);
+ }
+
+ /**
+ * Called after notifications have been filtered and after the initial grouping has been
+ * performed but before NotifPromoters have had a chance to promote children out of groups.
+ */
+ public void addOnBeforeTransformGroupsListener(OnBeforeTransformGroupsListener listener) {
+ mShadeListBuilder.addOnBeforeTransformGroupsListener(listener);
+ }
+
+ /**
+ * Registers a promoter with the pipeline. Promoters are able to promote child notifications to
+ * top-level, i.e. move a notification that would be a child of a group and make it appear
+ * ungrouped. Promoters are called on each child notification in the order that they are
+ * registered. If any promoter returns true, the notification is removed from the group (and no
+ * other promoters are called on it).
+ */
+ public void addPromoter(NotifPromoter promoter) {
+ mShadeListBuilder.addPromoter(promoter);
+ }
+
+ /**
+ * Called after notifs have been filtered and groups have been determined but before sections
+ * have been determined or the notifs have been sorted.
+ */
+ public void addOnBeforeSortListener(OnBeforeSortListener listener) {
+ mShadeListBuilder.addOnBeforeSortListener(listener);
+ }
+
+ /**
+ * Sections that are used to sort top-level entries. If two entries have the same section,
+ * NotifComparators are consulted. Sections from this list are called in order for each
+ * notification passed through the pipeline. The first NotifSection to return true for
+ * {@link NotifSection#isInSection(ListEntry)} sets the entry as part of its Section.
+ */
+ public void setSections(List<NotifSection> sections) {
+ mShadeListBuilder.setSections(sections);
+ }
+
+ /**
+ * Comparators that are used to sort top-level entries that share the same section. The
+ * comparators are executed in order until one of them returns a non-zero result. If all return
+ * zero, the pipeline falls back to sorting by rank (and, failing that, Notification.when).
+ */
+ public void setComparators(List<NotifComparator> comparators) {
+ mShadeListBuilder.setComparators(comparators);
+ }
+
+ /**
+ * Registers a filter with the pipeline to filter right before rendering the list (after
+ * pre-group filtering, grouping, promoting and sorting occurs). Filters are
+ * called on each notification in the order that they were registered. If any filter returns
+ * true, the notification is removed from the pipeline (and no other filters are called on that
+ * notif).
+ */
+ public void addPreRenderFilter(NotifFilter filter) {
+ mShadeListBuilder.addPreRenderFilter(filter);
+ }
+
+ /**
+ * Called at the end of the pipeline after the notif list has been finalized but before it has
+ * been handed off to the view layer.
+ */
+ public void addOnBeforeRenderListListener(OnBeforeRenderListListener listener) {
+ mShadeListBuilder.addOnBeforeRenderListListener(listener);
+ }
+
+ /**
+ * Returns a read-only view in to the current shade list, i.e. the list of notifications that
+ * are currently present in the shade. If this method is called during pipeline execution it
+ * will return the current state of the list, which will likely be only partially-generated.
+ */
+ public List<ListEntry> getShadeList() {
+ return mShadeListBuilder.getShadeList();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index 4f4fb24..2fcfb8c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -59,6 +59,7 @@
import com.android.systemui.statusbar.notification.InflationException;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGuts;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
@@ -102,6 +103,9 @@
/** If this was a group child that was promoted to the top level, then who did the promoting. */
@Nullable NotifPromoter mNotifPromoter;
+ /** If this notification had an issue with inflating. Only used with the NewNotifPipeline **/
+ private boolean mHasInflationError;
+
/*
* Old members
@@ -201,11 +205,8 @@
+ " doesn't match existing key " + mKey);
}
- if (!Objects.equals(mSbn, sbn)) {
- mSbn = sbn;
- mBubbleMetadata = mSbn.getNotification().getBubbleMetadata();
- onSbnUpdated();
- }
+ mSbn = sbn;
+ mBubbleMetadata = mSbn.getNotification().getBubbleMetadata();
}
/**
@@ -230,10 +231,7 @@
+ " doesn't match existing key " + mKey);
}
- if (!Objects.equals(mRanking, ranking)) {
- mRanking = ranking;
- onRankingUpdated();
- }
+ mRanking = ranking;
}
/*
@@ -576,6 +574,18 @@
remoteInputTextWhenReset = null;
}
+ void setHasInflationError(boolean hasError) {
+ mHasInflationError = hasError;
+ }
+
+ /**
+ * Whether this notification had an error when attempting to inflate. This is only used in
+ * the NewNotifPipeline
+ */
+ public boolean hasInflationError() {
+ return mHasInflationError;
+ }
+
public void setHasSentReply() {
hasSentReply = true;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
index 7010943..f7fe064 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManager.kt
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.collection
+import android.app.NotificationChannel
import android.app.NotificationManager.IMPORTANCE_HIGH
import android.app.NotificationManager.IMPORTANCE_MIN
import android.service.notification.NotificationListenerService.Ranking
@@ -24,6 +25,7 @@
import com.android.systemui.statusbar.NotificationMediaManager
import com.android.systemui.statusbar.notification.NotificationFilter
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
import com.android.systemui.statusbar.notification.logging.NotifEvent
import com.android.systemui.statusbar.notification.logging.NotifLog
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
@@ -54,7 +56,8 @@
private val notifFilter: NotificationFilter,
private val notifLog: NotifLog,
sectionsFeatureManager: NotificationSectionsFeatureManager,
- private val peopleNotificationIdentifier: PeopleNotificationIdentifier
+ private val peopleNotificationIdentifier: PeopleNotificationIdentifier,
+ private val highPriorityProvider: HighPriorityProvider
) {
var rankingMap: RankingMap? = null
@@ -81,6 +84,9 @@
val aHeadsUp = a.isRowHeadsUp
val bHeadsUp = b.isRowHeadsUp
+ val aIsHighPriority = a.isHighPriority()
+ val bIsHighPriority = b.isHighPriority()
+
when {
usePeopleFiltering && aIsPeople != bIsPeople -> if (aIsPeople) -1 else 1
aHeadsUp != bHeadsUp -> if (aHeadsUp) -1 else 1
@@ -90,8 +96,8 @@
aMedia != bMedia -> if (aMedia) -1 else 1
// Upsort PRIORITY_MAX system notifications
aSystemMax != bSystemMax -> if (aSystemMax) -1 else 1
- a.isHighPriority != b.isHighPriority ->
- -1 * a.isHighPriority.compareTo(b.isHighPriority)
+ aIsHighPriority != bIsHighPriority ->
+ -1 * aIsHighPriority.compareTo(bIsHighPriority)
aRank != bRank -> aRank - bRank
else -> nb.notification.`when`.compareTo(na.notification.`when`)
}
@@ -154,7 +160,7 @@
) {
if (usePeopleFiltering && entry.isPeopleNotification()) {
entry.bucket = BUCKET_PEOPLE
- } else if (isHeadsUp || isMedia || isSystemMax || entry.isHighPriority) {
+ } else if (isHeadsUp || isMedia || isSystemMax || entry.isHighPriority()) {
entry.bucket = BUCKET_ALERTING
} else {
entry.bucket = BUCKET_SILENT
@@ -171,26 +177,27 @@
}
entry.ranking = newRanking
- val oldSbn = entry.sbn.cloneLight()
val newOverrideGroupKey = newRanking.overrideGroupKey
- if (!Objects.equals(oldSbn.overrideGroupKey, newOverrideGroupKey)) {
+ if (!Objects.equals(entry.sbn.overrideGroupKey, newOverrideGroupKey)) {
+ val oldGroupKey = entry.sbn.groupKey
+ val oldIsGroup = entry.sbn.isGroup
+ val oldIsGroupSummary = entry.sbn.notification.isGroupSummary
entry.sbn.overrideGroupKey = newOverrideGroupKey
- // TODO: notify group manager here?
- groupManager.onEntryUpdated(entry, oldSbn)
+ groupManager.onEntryUpdated(entry, oldGroupKey, oldIsGroup,
+ oldIsGroupSummary)
}
-
- // TODO: (b/145659174) remove after moving to new NotifPipeline
- // (should be able to remove all groupManager code post-migration)
- entry.invalidateDerivedMembers()
}
}
}
}
private fun NotificationEntry.isPeopleNotification() =
- sbn.isPeopleNotification()
- private fun StatusBarNotification.isPeopleNotification() =
- peopleNotificationIdentifier.isPeopleNotification(this)
+ sbn.isPeopleNotification(channel)
+ private fun StatusBarNotification.isPeopleNotification(channel: NotificationChannel) =
+ peopleNotificationIdentifier.isPeopleNotification(this, channel)
+
+ private fun NotificationEntry.isHighPriority() =
+ highPriorityProvider.isHighPriority(this)
}
// Convenience functions
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
similarity index 87%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImpl.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index 19d90f0..97f8ec5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -17,7 +17,6 @@
package com.android.systemui.statusbar.notification.collection;
import static com.android.systemui.statusbar.notification.collection.GroupEntry.ROOT_ENTRY;
-import static com.android.systemui.statusbar.notification.collection.ListDumper.dumpList;
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_BUILD_STARTED;
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_FINALIZING;
import static com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.STATE_GROUPING;
@@ -31,8 +30,10 @@
import android.annotation.MainThread;
import android.annotation.Nullable;
import android.util.ArrayMap;
+import android.util.Pair;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
+import com.android.systemui.DumpController;
+import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener;
@@ -40,12 +41,15 @@
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.SectionsProvider;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
import com.android.systemui.statusbar.notification.logging.NotifEvent;
import com.android.systemui.statusbar.notification.logging.NotifLog;
import com.android.systemui.util.Assert;
import com.android.systemui.util.time.SystemClock;
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -57,11 +61,13 @@
import javax.inject.Singleton;
/**
- * The implementation of {@link NotifListBuilder}.
+ * The second half of {@link NotifPipeline}. Sits downstream of the NotifCollection and transforms
+ * its "notification set" into the "shade list", the filtered, grouped, and sorted list of
+ * notifications that are currently present in the notification shade.
*/
@MainThread
@Singleton
-public class NotifListBuilderImpl implements NotifListBuilder {
+public class ShadeListBuilder implements Dumpable {
private final SystemClock mSystemClock;
private final NotifLog mNotifLog;
@@ -77,7 +83,7 @@
private final List<NotifPromoter> mNotifPromoters = new ArrayList<>();
private final List<NotifFilter> mNotifPreRenderFilters = new ArrayList<>();
private final List<NotifComparator> mNotifComparators = new ArrayList<>();
- private SectionsProvider mSectionsProvider = new DefaultSectionsProvider();
+ private final List<NotifSection> mNotifSections = new ArrayList<>();
private final List<OnBeforeTransformGroupsListener> mOnBeforeTransformGroupsListeners =
new ArrayList<>();
@@ -90,10 +96,14 @@
private final List<ListEntry> mReadOnlyNotifList = Collections.unmodifiableList(mNotifList);
@Inject
- public NotifListBuilderImpl(SystemClock systemClock, NotifLog notifLog) {
+ public ShadeListBuilder(
+ SystemClock systemClock,
+ NotifLog notifLog,
+ DumpController dumpController) {
Assert.isMainThread();
mSystemClock = systemClock;
mNotifLog = notifLog;
+ dumpController.registerDumpable(TAG, this);
}
/**
@@ -116,32 +126,28 @@
mOnRenderListListener = onRenderListListener;
}
- @Override
- public void addOnBeforeTransformGroupsListener(OnBeforeTransformGroupsListener listener) {
+ void addOnBeforeTransformGroupsListener(OnBeforeTransformGroupsListener listener) {
Assert.isMainThread();
mPipelineState.requireState(STATE_IDLE);
mOnBeforeTransformGroupsListeners.add(listener);
}
- @Override
- public void addOnBeforeSortListener(OnBeforeSortListener listener) {
+ void addOnBeforeSortListener(OnBeforeSortListener listener) {
Assert.isMainThread();
mPipelineState.requireState(STATE_IDLE);
mOnBeforeSortListeners.add(listener);
}
- @Override
- public void addOnBeforeRenderListListener(OnBeforeRenderListListener listener) {
+ void addOnBeforeRenderListListener(OnBeforeRenderListListener listener) {
Assert.isMainThread();
mPipelineState.requireState(STATE_IDLE);
mOnBeforeRenderListListeners.add(listener);
}
- @Override
- public void addPreGroupFilter(NotifFilter filter) {
+ void addPreGroupFilter(NotifFilter filter) {
Assert.isMainThread();
mPipelineState.requireState(STATE_IDLE);
@@ -149,8 +155,7 @@
filter.setInvalidationListener(this::onPreGroupFilterInvalidated);
}
- @Override
- public void addPreRenderFilter(NotifFilter filter) {
+ void addPreRenderFilter(NotifFilter filter) {
Assert.isMainThread();
mPipelineState.requireState(STATE_IDLE);
@@ -158,8 +163,7 @@
filter.setInvalidationListener(this::onPreRenderFilterInvalidated);
}
- @Override
- public void addPromoter(NotifPromoter promoter) {
+ void addPromoter(NotifPromoter promoter) {
Assert.isMainThread();
mPipelineState.requireState(STATE_IDLE);
@@ -167,17 +171,18 @@
promoter.setInvalidationListener(this::onPromoterInvalidated);
}
- @Override
- public void setSectionsProvider(SectionsProvider provider) {
+ void setSections(List<NotifSection> sections) {
Assert.isMainThread();
mPipelineState.requireState(STATE_IDLE);
- mSectionsProvider = provider;
- provider.setInvalidationListener(this::onSectionsProviderInvalidated);
+ mNotifSections.clear();
+ for (NotifSection section : sections) {
+ mNotifSections.add(section);
+ section.setInvalidationListener(this::onNotifSectionInvalidated);
+ }
}
- @Override
- public void setComparators(List<NotifComparator> comparators) {
+ void setComparators(List<NotifComparator> comparators) {
Assert.isMainThread();
mPipelineState.requireState(STATE_IDLE);
@@ -188,8 +193,7 @@
}
}
- @Override
- public List<ListEntry> getActiveNotifs() {
+ List<ListEntry> getShadeList() {
Assert.isMainThread();
return mReadOnlyNotifList;
}
@@ -230,12 +234,12 @@
rebuildListIfBefore(STATE_TRANSFORMING);
}
- private void onSectionsProviderInvalidated(SectionsProvider provider) {
+ private void onNotifSectionInvalidated(NotifSection section) {
Assert.isMainThread();
- mNotifLog.log(NotifEvent.SECTIONS_PROVIDER_INVALIDATED, String.format(
- "Sections provider \"%s\" invalidated; pipeline state is %d",
- provider.getName(),
+ mNotifLog.log(NotifEvent.SECTION_INVALIDATED, String.format(
+ "Section \"%s\" invalidated; pipeline state is %d",
+ section.getName(),
mPipelineState.getState()));
rebuildListIfBefore(STATE_SORTING);
@@ -275,7 +279,7 @@
}
/**
- * The core algorithm of the pipeline. See the top comment in {@link NotifListBuilder} for
+ * The core algorithm of the pipeline. See the top comment in {@link NotifPipeline} for
* details on our contracts with other code.
*
* Once the build starts we are very careful to protect against reentrant code. Anything that
@@ -318,7 +322,7 @@
sortList();
// Step 6: Filter out entries after pre-group filtering, grouping, promoting and sorting
- // Now filters can see grouping information to determine whether to filter or not
+ // Now filters can see grouping information to determine whether to filter or not.
mPipelineState.incrementTo(STATE_PRE_RENDER_FILTERING);
filterNotifs(mNotifList, mNewNotifList, mNotifPreRenderFilters);
applyNewNotifList();
@@ -331,7 +335,7 @@
// Step 6: Dispatch the new list, first to any listeners and then to the view layer
mNotifLog.log(NotifEvent.DISPATCH_FINAL_LIST, "List finalized, is:\n"
- + dumpList(mNotifList));
+ + ListDumper.dumpTree(mNotifList, false, "\t\t"));
dispatchOnBeforeRenderList(mReadOnlyNotifList);
if (mOnRenderListListener != null) {
mOnRenderListListener.onRenderList(mReadOnlyNotifList);
@@ -580,6 +584,8 @@
* filtered out during any of the filtering steps.
*/
private void annulAddition(ListEntry entry) {
+ entry.setSection(-1);
+ entry.mNotifSection = null;
entry.setParent(null);
if (entry.mFirstAddedIteration == mIterationCount) {
entry.mFirstAddedIteration = -1;
@@ -589,11 +595,12 @@
private void sortList() {
// Assign sections to top-level elements and sort their children
for (ListEntry entry : mNotifList) {
- entry.setSection(mSectionsProvider.getSection(entry));
+ Pair<NotifSection, Integer> sectionWithIndex = applySections(entry);
if (entry instanceof GroupEntry) {
GroupEntry parent = (GroupEntry) entry;
for (NotificationEntry child : parent.getChildren()) {
- child.setSection(0);
+ child.mNotifSection = sectionWithIndex.first;
+ child.setSection(sectionWithIndex.second);
}
parent.sortChildren(sChildComparator);
}
@@ -754,6 +761,45 @@
return null;
}
+ private Pair<NotifSection, Integer> applySections(ListEntry entry) {
+ final Pair<NotifSection, Integer> sectionWithIndex = findSection(entry);
+ final NotifSection section = sectionWithIndex.first;
+ final Integer sectionIndex = sectionWithIndex.second;
+
+ if (section != entry.mNotifSection) {
+ if (entry.mNotifSection == null) {
+ mNotifLog.log(NotifEvent.SECTION_CHANGED, String.format(
+ "%s: sectioned by '%s' [index=%d].",
+ entry.getKey(),
+ section.getName(),
+ sectionIndex));
+ } else {
+ mNotifLog.log(NotifEvent.SECTION_CHANGED, String.format(
+ "%s: section changed: '%s' [index=%d] -> '%s [index=%d]'.",
+ entry.getKey(),
+ entry.mNotifSection,
+ entry.getSection(),
+ section,
+ sectionIndex));
+ }
+
+ entry.mNotifSection = section;
+ entry.setSection(sectionIndex);
+ }
+
+ return sectionWithIndex;
+ }
+
+ private Pair<NotifSection, Integer> findSection(ListEntry entry) {
+ for (int i = 0; i < mNotifSections.size(); i++) {
+ NotifSection sectioner = mNotifSections.get(i);
+ if (sectioner.isInSection(entry)) {
+ return new Pair<>(sectioner, i);
+ }
+ }
+ return new Pair<>(sDefaultSection, mNotifSections.size());
+ }
+
private void rebuildListIfBefore(@PipelineState.StateName int state) {
mPipelineState.requireIsBefore(state);
if (mPipelineState.is(STATE_IDLE)) {
@@ -779,6 +825,19 @@
}
}
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("\t" + TAG + " shade notifications:");
+ if (getShadeList().size() == 0) {
+ pw.println("\t\t None");
+ }
+
+ pw.println(ListDumper.dumpTree(
+ getShadeList(),
+ true,
+ "\t\t"));
+ }
+
/** See {@link #setOnRenderListListener(OnRenderListListener)} */
public interface OnRenderListListener {
/**
@@ -790,16 +849,13 @@
void onRenderList(List<ListEntry> entries);
}
- private static class DefaultSectionsProvider extends SectionsProvider {
- DefaultSectionsProvider() {
- super("DefaultSectionsProvider");
- }
-
- @Override
- public int getSection(ListEntry entry) {
- return 0;
- }
- }
+ private static final NotifSection sDefaultSection =
+ new NotifSection("DefaultSection") {
+ @Override
+ public boolean isInSection(ListEntry entry) {
+ return true;
+ }
+ };
private static final String TAG = "NotifListBuilderImpl";
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CoalescedEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/CoalescedEvent.kt
similarity index 87%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CoalescedEvent.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/CoalescedEvent.kt
index b6218b4..143de8a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CoalescedEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/CoalescedEvent.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.collection.notifcollection
+package com.android.systemui.statusbar.notification.collection.coalescer
import android.service.notification.NotificationListenerService.Ranking
import android.service.notification.StatusBarNotification
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/EventBatch.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/EventBatch.java
similarity index 85%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/EventBatch.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/EventBatch.java
index ac51178..2eec68b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/EventBatch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/EventBatch.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.collection.notifcollection;
+package com.android.systemui.statusbar.notification.collection.coalescer;
+
+import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
@@ -35,6 +37,8 @@
*/
final List<CoalescedEvent> mMembers = new ArrayList<>();
+ @Nullable Runnable mCancelShortTimeout;
+
EventBatch(long createdTimestamp, String groupKey) {
mCreatedTimestamp = createdTimestamp;
this.mGroupKey = groupKey;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java
similarity index 76%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescer.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java
index 069c15f..f589038 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescer.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.collection.notifcollection;
+package com.android.systemui.statusbar.notification.collection.coalescer;
+import static com.android.systemui.statusbar.notification.logging.NotifEvent.BATCH_MAX_TIMEOUT;
import static com.android.systemui.statusbar.notification.logging.NotifEvent.COALESCED_EVENT;
import static com.android.systemui.statusbar.notification.logging.NotifEvent.EARLY_BATCH_EMIT;
import static com.android.systemui.statusbar.notification.logging.NotifEvent.EMIT_EVENT_BATCH;
@@ -71,7 +72,8 @@
private final DelayableExecutor mMainExecutor;
private final SystemClock mClock;
private final NotifLog mLog;
- private final long mGroupLingerDuration;
+ private final long mMinGroupLingerDuration;
+ private final long mMaxGroupLingerDuration;
private BatchableNotificationHandler mHandler;
@@ -82,22 +84,28 @@
public GroupCoalescer(
@Main DelayableExecutor mainExecutor,
SystemClock clock, NotifLog log) {
- this(mainExecutor, clock, log, GROUP_LINGER_DURATION);
+ this(mainExecutor, clock, log, MIN_GROUP_LINGER_DURATION, MAX_GROUP_LINGER_DURATION);
}
/**
- * @param groupLingerDuration How long, in ms, that notifications that are members of a group
- * are delayed within the GroupCoalescer before being posted
+ * @param minGroupLingerDuration How long, in ms, to wait for another notification from the same
+ * group to arrive before emitting all pending events for that
+ * group. Each subsequent arrival of a group member resets the
+ * timer for that group.
+ * @param maxGroupLingerDuration The maximum time, in ms, that a group can linger in the
+ * coalescer before it's force-emitted.
*/
GroupCoalescer(
@Main DelayableExecutor mainExecutor,
SystemClock clock,
NotifLog log,
- long groupLingerDuration) {
+ long minGroupLingerDuration,
+ long maxGroupLingerDuration) {
mMainExecutor = mainExecutor;
mClock = clock;
mLog = log;
- mGroupLingerDuration = groupLingerDuration;
+ mMinGroupLingerDuration = minGroupLingerDuration;
+ mMaxGroupLingerDuration = maxGroupLingerDuration;
}
/**
@@ -115,7 +123,7 @@
private final NotificationHandler mListener = new NotificationHandler() {
@Override
public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) {
- maybeEmitBatch(sbn.getKey());
+ maybeEmitBatch(sbn);
applyRanking(rankingMap);
final boolean shouldCoalesce = handleNotificationPosted(sbn, rankingMap);
@@ -130,7 +138,7 @@
@Override
public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap) {
- maybeEmitBatch(sbn.getKey());
+ maybeEmitBatch(sbn);
applyRanking(rankingMap);
mHandler.onNotificationRemoved(sbn, rankingMap);
}
@@ -140,7 +148,7 @@
StatusBarNotification sbn,
RankingMap rankingMap,
int reason) {
- maybeEmitBatch(sbn.getKey());
+ maybeEmitBatch(sbn);
applyRanking(rankingMap);
mHandler.onNotificationRemoved(sbn, rankingMap, reason);
}
@@ -152,13 +160,20 @@
}
};
- private void maybeEmitBatch(String memberKey) {
- CoalescedEvent event = mCoalescedEvents.get(memberKey);
+ private void maybeEmitBatch(StatusBarNotification sbn) {
+ final CoalescedEvent event = mCoalescedEvents.get(sbn.getKey());
+ final EventBatch batch = mBatches.get(sbn.getGroupKey());
if (event != null) {
mLog.log(EARLY_BATCH_EMIT,
String.format("Modification of %s triggered early emit of batched group %s",
- memberKey, requireNonNull(event.getBatch()).mGroupKey));
+ sbn.getKey(), requireNonNull(event.getBatch()).mGroupKey));
emitBatch(requireNonNull(event.getBatch()));
+ } else if (batch != null
+ && mClock.uptimeMillis() - batch.mCreatedTimestamp >= mMaxGroupLingerDuration) {
+ mLog.log(BATCH_MAX_TIMEOUT,
+ String.format("Modification of %s triggered timeout emit of batched group %s",
+ sbn.getKey(), batch.mGroupKey));
+ emitBatch(batch);
}
}
@@ -175,7 +190,8 @@
}
if (sbn.isGroup()) {
- EventBatch batch = startBatchingGroup(sbn.getGroupKey());
+ final EventBatch batch = getOrBuildBatch(sbn.getGroupKey());
+
CoalescedEvent event =
new CoalescedEvent(
sbn.getKey(),
@@ -183,10 +199,10 @@
sbn,
requireRanking(rankingMap, sbn.getKey()),
batch);
+ mCoalescedEvents.put(event.getKey(), event);
batch.mMembers.add(event);
-
- mCoalescedEvents.put(event.getKey(), event);
+ resetShortTimeout(batch);
return true;
} else {
@@ -194,27 +210,39 @@
}
}
- private EventBatch startBatchingGroup(final String groupKey) {
+ private EventBatch getOrBuildBatch(final String groupKey) {
EventBatch batch = mBatches.get(groupKey);
if (batch == null) {
- final EventBatch newBatch = new EventBatch(mClock.uptimeMillis(), groupKey);
- mBatches.put(groupKey, newBatch);
- mMainExecutor.executeDelayed(() -> emitBatch(newBatch), mGroupLingerDuration);
-
- batch = newBatch;
+ batch = new EventBatch(mClock.uptimeMillis(), groupKey);
+ mBatches.put(groupKey, batch);
}
return batch;
}
+ private void resetShortTimeout(EventBatch batch) {
+ if (batch.mCancelShortTimeout != null) {
+ batch.mCancelShortTimeout.run();
+ }
+ batch.mCancelShortTimeout =
+ mMainExecutor.executeDelayed(
+ () -> {
+ batch.mCancelShortTimeout = null;
+ emitBatch(batch);
+ },
+ mMinGroupLingerDuration);
+ }
+
private void emitBatch(EventBatch batch) {
if (batch != mBatches.get(batch.mGroupKey)) {
- // If we emit a batch early, we don't want to emit it a second time when its timeout
- // expires.
- return;
+ throw new IllegalStateException("Cannot emit out-of-date batch " + batch.mGroupKey);
}
if (batch.mMembers.isEmpty()) {
throw new IllegalStateException("Batch " + batch.mGroupKey + " cannot be empty");
}
+ if (batch.mCancelShortTimeout != null) {
+ batch.mCancelShortTimeout.run();
+ batch.mCancelShortTimeout = null;
+ }
mBatches.remove(batch.mGroupKey);
@@ -259,7 +287,7 @@
pw.println("Coalesced notifications:");
for (EventBatch batch : mBatches.values()) {
pw.println(" Batch " + batch.mGroupKey + ":");
- pw.println(" Created" + (now - batch.mCreatedTimestamp) + "ms ago");
+ pw.println(" Created " + (now - batch.mCreatedTimestamp) + "ms ago");
for (CoalescedEvent event : batch.mMembers) {
pw.println(" " + event.getKey());
eventCount++;
@@ -299,5 +327,6 @@
void onNotificationBatchPosted(List<CoalescedEvent> events);
}
- private static final int GROUP_LINGER_DURATION = 40;
+ private static final int MIN_GROUP_LINGER_DURATION = 50;
+ private static final int MAX_GROUP_LINGER_DURATION = 500;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/Coordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/Coordinator.java
index 898918e..d8b2e40 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/Coordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/Coordinator.java
@@ -16,27 +16,21 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
-import com.android.systemui.statusbar.notification.collection.NotifCollection;
-import com.android.systemui.statusbar.notification.collection.NotifCollectionListener;
-import com.android.systemui.statusbar.notification.collection.NotifLifetimeExtender;
-import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
/**
- * Interface for registering callbacks to the {@link NewNotifPipeline}.
- *
- * This includes registering:
- * {@link Pluggable}s to the {@link NotifListBuilder}
- * {@link NotifCollectionListener}s and {@link NotifLifetimeExtender}s to {@link NotifCollection}
+ * Interface for registering callbacks to the {@link NotifPipeline}.
*/
public interface Coordinator {
-
/**
* Called after the NewNotifPipeline is initialized.
- * Coordinators should register their {@link Pluggable}s to the notifListBuilder
- * and their {@link NotifCollectionListener}s and {@link NotifLifetimeExtender}s
- * to the notifCollection in this method.
+ * Coordinators should register their listeners and {@link Pluggable}s to the pipeline.
*/
- void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder);
+ void attach(NotifPipeline pipeline);
+
+ default NotifSection getSection() {
+ return null;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
index 5e7dd98..625d1b9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
@@ -23,9 +23,8 @@
import android.os.RemoteException;
import android.service.notification.StatusBarNotification;
-import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -52,10 +51,10 @@
}
@Override
- public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) {
+ public void attach(NotifPipeline pipeline) {
mDeviceProvisionedController.addCallback(mDeviceProvisionedListener);
- notifListBuilder.addPreGroupFilter(mNotifFilter);
+ pipeline.addPreGroupFilter(mNotifFilter);
}
private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java
index 62342b1..da119c1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinator.java
@@ -25,12 +25,11 @@
import com.android.systemui.ForegroundServiceController;
import com.android.systemui.appops.AppOpsController;
import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.statusbar.notification.collection.NotifCollection;
-import com.android.systemui.statusbar.notification.collection.NotifCollectionListener;
-import com.android.systemui.statusbar.notification.collection.NotifLifetimeExtender;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
import java.util.HashMap;
import java.util.Map;
@@ -57,7 +56,7 @@
private final AppOpsController mAppOpsController;
private final Handler mMainHandler;
- private NotifCollection mNotifCollection;
+ private NotifPipeline mNotifPipeline;
@Inject
public ForegroundCoordinator(
@@ -70,20 +69,20 @@
}
@Override
- public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) {
- mNotifCollection = notifCollection;
+ public void attach(NotifPipeline pipeline) {
+ mNotifPipeline = pipeline;
// extend the lifetime of foreground notification services to show for at least 5 seconds
- mNotifCollection.addNotificationLifetimeExtender(mForegroundLifetimeExtender);
+ mNotifPipeline.addNotificationLifetimeExtender(mForegroundLifetimeExtender);
// listen for new notifications to add appOps
- mNotifCollection.addCollectionListener(mNotifCollectionListener);
+ mNotifPipeline.addCollectionListener(mNotifCollectionListener);
// when appOps change, update any relevant notifications to update appOps for
mAppOpsController.addCallback(ForegroundServiceController.APP_OPS, this::onAppOpsChanged);
// filter out foreground service notifications that aren't necessary anymore
- notifListBuilder.addPreGroupFilter(mNotifFilter);
+ mNotifPipeline.addPreGroupFilter(mNotifFilter);
}
/**
@@ -230,7 +229,7 @@
}
private NotificationEntry findNotificationEntryWithKey(String key) {
- for (NotificationEntry entry : mNotifCollection.getNotifs()) {
+ for (NotificationEntry entry : mNotifPipeline.getActiveNotifs()) {
if (entry.getKey().equals(key)) {
return entry;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
index 9312c22..a26ee545 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
@@ -39,10 +39,10 @@
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.ListEntry;
-import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import javax.inject.Inject;
@@ -62,6 +62,7 @@
private final BroadcastDispatcher mBroadcastDispatcher;
private final StatusBarStateController mStatusBarStateController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+ private final HighPriorityProvider mHighPriorityProvider;
@Inject
public KeyguardCoordinator(
@@ -71,21 +72,22 @@
NotificationLockscreenUserManager lockscreenUserManager,
BroadcastDispatcher broadcastDispatcher,
StatusBarStateController statusBarStateController,
- KeyguardUpdateMonitor keyguardUpdateMonitor) {
+ KeyguardUpdateMonitor keyguardUpdateMonitor,
+ HighPriorityProvider highPriorityProvider) {
mContext = context;
mMainHandler = mainThreadHandler;
mKeyguardStateController = keyguardStateController;
mLockscreenUserManager = lockscreenUserManager;
-
mBroadcastDispatcher = broadcastDispatcher;
mStatusBarStateController = statusBarStateController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+ mHighPriorityProvider = highPriorityProvider;
}
@Override
- public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) {
+ public void attach(NotifPipeline pipeline) {
setupInvalidateNotifListCallbacks();
- notifListBuilder.addPreRenderFilter(mNotifFilter);
+ pipeline.addPreRenderFilter(mNotifFilter);
}
private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
@@ -151,7 +153,7 @@
}
if (NotificationUtils.useNewInterruptionModel(mContext)
&& hideSilentNotificationsOnLockscreen()) {
- return entry.isHighPriority();
+ return mHighPriorityProvider.isHighPriority(entry);
} else {
return entry.getRepresentativeEntry() != null
&& !entry.getRepresentativeEntry().getRanking().isAmbient();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
index 13247193..8d0dd5b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java
@@ -16,12 +16,14 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
+import com.android.systemui.DumpController;
import com.android.systemui.Dumpable;
-import com.android.systemui.statusbar.notification.collection.NotifCollection;
-import com.android.systemui.statusbar.notification.collection.NotifCollectionListener;
-import com.android.systemui.statusbar.notification.collection.NotifLifetimeExtender;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
+import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -32,47 +34,67 @@
import javax.inject.Singleton;
/**
- * Handles the attachment of the {@link NotifListBuilder} and {@link NotifCollection} to the
- * {@link Coordinator}s, so that the Coordinators can register their respective callbacks.
+ * Handles the attachment of {@link Coordinator}s to the {@link NotifPipeline} so that the
+ * Coordinators can register their respective callbacks.
*/
@Singleton
public class NotifCoordinators implements Dumpable {
private static final String TAG = "NotifCoordinators";
private final List<Coordinator> mCoordinators = new ArrayList<>();
-
+ private final List<NotifSection> mOrderedSections = new ArrayList<>();
/**
* Creates all the coordinators.
*/
@Inject
public NotifCoordinators(
+ DumpController dumpController,
+ FeatureFlags featureFlags,
KeyguardCoordinator keyguardCoordinator,
RankingCoordinator rankingCoordinator,
ForegroundCoordinator foregroundCoordinator,
- DeviceProvisionedCoordinator deviceProvisionedCoordinator) {
+ DeviceProvisionedCoordinator deviceProvisionedCoordinator,
+ PreparationCoordinator preparationCoordinator) {
+ dumpController.registerDumpable(TAG, this);
+
mCoordinators.add(keyguardCoordinator);
mCoordinators.add(rankingCoordinator);
mCoordinators.add(foregroundCoordinator);
mCoordinators.add(deviceProvisionedCoordinator);
+ if (featureFlags.isNewNotifPipelineRenderingEnabled()) {
+ mCoordinators.add(preparationCoordinator);
+ }
// TODO: add new Coordinators here! (b/145134683, b/112656837)
+
+ // TODO: add the sections in a particular ORDER (HeadsUp < People < Alerting)
+ for (Coordinator c : mCoordinators) {
+ if (c.getSection() != null) {
+ mOrderedSections.add(c.getSection());
+ }
+ }
}
/**
- * Sends the initialized notifListBuilder and notifCollection to each
- * coordinator to indicate the notifListBuilder is ready to accept {@link Pluggable}s
- * and the notifCollection is ready to accept {@link NotifCollectionListener}s and
- * {@link NotifLifetimeExtender}s.
+ * Sends the pipeline to each coordinator when the pipeline is ready to accept
+ * {@link Pluggable}s, {@link NotifCollectionListener}s and {@link NotifLifetimeExtender}s.
*/
- public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) {
+ public void attach(NotifPipeline pipeline) {
for (Coordinator c : mCoordinators) {
- c.attach(notifCollection, notifListBuilder);
+ c.attach(pipeline);
}
+
+ pipeline.setSections(mOrderedSections);
}
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println();
pw.println(TAG + ":");
for (Coordinator c : mCoordinators) {
pw.println("\t" + c.getClass());
}
+
+ for (NotifSection s : mOrderedSections) {
+ pw.println("\t" + s.getName());
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
new file mode 100644
index 0000000..20c9cbc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -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 com.android.systemui.statusbar.notification.collection.coordinator;
+
+import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.logging.NotifEvent;
+import com.android.systemui.statusbar.notification.logging.NotifLog;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Kicks off notification inflation and view rebinding when a notification is added or updated.
+ * Aborts inflation when a notification is removed.
+ *
+ * If a notification is not done inflating, this coordinator will filter the notification out
+ * from the NotifListBuilder.
+ */
+@Singleton
+public class PreparationCoordinator implements Coordinator {
+ private static final String TAG = "PreparationCoordinator";
+
+ private final NotifLog mNotifLog;
+ private final NotifInflater mNotifInflater;
+ private final List<NotificationEntry> mPendingNotifications = new ArrayList<>();
+
+ @Inject
+ public PreparationCoordinator(NotifLog notifLog, NotifInflaterImpl notifInflater) {
+ mNotifLog = notifLog;
+ mNotifInflater = notifInflater;
+ mNotifInflater.setInflationCallback(mInflationCallback);
+ }
+
+ @Override
+ public void attach(NotifPipeline pipeline) {
+ pipeline.addCollectionListener(mNotifCollectionListener);
+ pipeline.addPreRenderFilter(mNotifInflationErrorFilter);
+ pipeline.addPreRenderFilter(mNotifInflatingFilter);
+ }
+
+ private final NotifCollectionListener mNotifCollectionListener = new NotifCollectionListener() {
+ @Override
+ public void onEntryAdded(NotificationEntry entry) {
+ inflateEntry(entry, "entryAdded");
+ }
+
+ @Override
+ public void onEntryUpdated(NotificationEntry entry) {
+ rebind(entry, "entryUpdated");
+ }
+
+ @Override
+ public void onEntryRemoved(NotificationEntry entry, int reason, boolean removedByUser) {
+ abortInflation(entry, "entryRemoved reason=" + reason);
+ }
+ };
+
+ private final NotifFilter mNotifInflationErrorFilter = new NotifFilter(
+ TAG + "InflationError") {
+ /**
+ * Filters out notifications that threw an error when attempting to inflate.
+ */
+ @Override
+ public boolean shouldFilterOut(NotificationEntry entry, long now) {
+ if (entry.hasInflationError()) {
+ mPendingNotifications.remove(entry);
+ return true;
+ }
+ return false;
+ }
+ };
+
+ private final NotifFilter mNotifInflatingFilter = new NotifFilter(TAG + "Inflating") {
+ /**
+ * Filters out notifications that haven't been inflated yet
+ */
+ @Override
+ public boolean shouldFilterOut(NotificationEntry entry, long now) {
+ return mPendingNotifications.contains(entry);
+ }
+ };
+
+ private final NotifInflater.InflationCallback mInflationCallback =
+ new NotifInflater.InflationCallback() {
+ @Override
+ public void onInflationFinished(NotificationEntry entry) {
+ mNotifLog.log(NotifEvent.INFLATED, entry);
+ mPendingNotifications.remove(entry);
+ mNotifInflatingFilter.invalidateList();
+ }
+ };
+
+ private void inflateEntry(NotificationEntry entry, String reason) {
+ abortInflation(entry, reason);
+ mPendingNotifications.add(entry);
+ mNotifInflater.inflateViews(entry);
+ }
+
+ private void rebind(NotificationEntry entry, String reason) {
+ mNotifInflater.rebindViews(entry);
+ }
+
+ private void abortInflation(NotificationEntry entry, String reason) {
+ mNotifLog.log(NotifEvent.INFLATION_ABORTED, reason);
+ entry.abortTask();
+ mPendingNotifications.remove(entry);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
index 0751aa8..7e9e760 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
@@ -17,9 +17,8 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import javax.inject.Inject;
@@ -43,10 +42,10 @@
}
@Override
- public void attach(NotifCollection notifCollection, NotifListBuilder notifListBuilder) {
+ public void attach(NotifPipeline pipeline) {
mStatusBarStateController.addCallback(mStatusBarStateCallback);
- notifListBuilder.addPreGroupFilter(mNotifFilter);
+ pipeline.addPreGroupFilter(mNotifFilter);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.java
new file mode 100644
index 0000000..ea0ece4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.java
@@ -0,0 +1,56 @@
+/*
+ * 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.systemui.statusbar.notification.collection.inflation;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.coordinator.PreparationCoordinator;
+
+/**
+ * Used by the {@link PreparationCoordinator}. When notifications are added or updated, the
+ * NotifInflater is asked to (re)inflated and prepare their views. This inflation occurs off the
+ * main thread. When the inflation is finished, NotifInflater will trigger its InflationCallback.
+ */
+public interface NotifInflater {
+
+ /**
+ * Callback used when inflation is finished.
+ */
+ void setInflationCallback(InflationCallback callback);
+
+ /**
+ * Called to rebind the entry's views.
+ */
+ void rebindViews(NotificationEntry entry);
+
+ /**
+ * Called to inflate the views of an entry. Views are not considered inflated until all of its
+ * views are bound. Once all views are inflated, the InflationCallback is triggered.
+ */
+ void inflateViews(NotificationEntry entry);
+
+ /**
+ * Request to stop the inflation of an entry. For example, called when a notification is
+ * removed and no longer needs to be inflated.
+ */
+ void abortInflation(NotificationEntry entry);
+
+ /**
+ * Callback once all the views are inflated and bound for a given NotificationEntry.
+ */
+ interface InflationCallback {
+ void onInflationFinished(NotificationEntry entry);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinder.java
similarity index 90%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinder.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinder.java
index 7504e86..3f500644 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinder.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,13 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.collection;
+package com.android.systemui.statusbar.notification.collection.inflation;
import android.annotation.Nullable;
import com.android.systemui.statusbar.NotificationUiAdjustment;
import com.android.systemui.statusbar.notification.InflationException;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
/**
* Used by the {@link NotificationEntryManager}. When notifications are added or updated, the binder
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
similarity index 80%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index 6c93618..5cd3e94 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.collection;
+package com.android.systemui.statusbar.notification.collection.inflation;
+import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
@@ -29,7 +30,6 @@
import android.view.ViewGroup;
import com.android.internal.util.NotificationMessagingUtil;
-import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -39,86 +39,102 @@
import com.android.systemui.statusbar.notification.InflationException;
import com.android.systemui.statusbar.notification.NotificationClicker;
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
+import com.android.systemui.statusbar.notification.people.NotificationPersonExtractorPluginBoundary;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifierImpl;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder;
import com.android.systemui.statusbar.notification.row.RowInflaterTask;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.StatusBar;
+import com.android.systemui.statusbar.policy.ExtensionControllerImpl;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import java.util.Objects;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.inject.Singleton;
+
/** Handles inflating and updating views for notifications. */
+@Singleton
public class NotificationRowBinderImpl implements NotificationRowBinder {
private static final String TAG = "NotificationViewManager";
- private final NotificationGroupManager mGroupManager =
- Dependency.get(NotificationGroupManager.class);
- private final NotificationGutsManager mGutsManager =
- Dependency.get(NotificationGutsManager.class);
- private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider =
- Dependency.get(NotificationInterruptionStateProvider.class);
-
+ private final NotificationGroupManager mGroupManager;
+ private final NotificationGutsManager mGutsManager;
+ private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
private final Context mContext;
+ private final NotificationRowContentBinder mRowContentBinder;
private final NotificationMessagingUtil mMessagingUtil;
private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger =
this::logNotificationExpansion;
+ private final NotificationRemoteInputManager mNotificationRemoteInputManager;
+ private final NotificationLockscreenUserManager mNotificationLockscreenUserManager;
private final boolean mAllowLongPress;
private final KeyguardBypassController mKeyguardBypassController;
private final StatusBarStateController mStatusBarStateController;
- private NotificationRemoteInputManager mRemoteInputManager;
private NotificationPresenter mPresenter;
private NotificationListContainer mListContainer;
private HeadsUpManager mHeadsUpManager;
- private NotificationContentInflater.InflationCallback mInflationCallback;
+ private NotificationRowContentBinder.InflationCallback mInflationCallback;
private ExpandableNotificationRow.OnAppOpsClickListener mOnAppOpsClickListener;
private BindRowCallback mBindRowCallback;
private NotificationClicker mNotificationClicker;
private final NotificationLogger mNotificationLogger;
+ @Inject
public NotificationRowBinderImpl(
Context context,
- boolean allowLongPress,
+ NotificationRemoteInputManager notificationRemoteInputManager,
+ NotificationLockscreenUserManager notificationLockscreenUserManager,
+ NotificationRowContentBinder rowContentBinder,
+ @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
KeyguardBypassController keyguardBypassController,
StatusBarStateController statusBarStateController,
+ NotificationGroupManager notificationGroupManager,
+ NotificationGutsManager notificationGutsManager,
+ NotificationInterruptionStateProvider notificationInterruptionStateProvider,
NotificationLogger logger) {
mContext = context;
+ mRowContentBinder = rowContentBinder;
mMessagingUtil = new NotificationMessagingUtil(context);
+ mNotificationRemoteInputManager = notificationRemoteInputManager;
+ mNotificationLockscreenUserManager = notificationLockscreenUserManager;
mAllowLongPress = allowLongPress;
mKeyguardBypassController = keyguardBypassController;
mStatusBarStateController = statusBarStateController;
+ mGroupManager = notificationGroupManager;
+ mGutsManager = notificationGutsManager;
+ mNotificationInterruptionStateProvider = notificationInterruptionStateProvider;
mNotificationLogger = logger;
}
- private NotificationRemoteInputManager getRemoteInputManager() {
- if (mRemoteInputManager == null) {
- mRemoteInputManager = Dependency.get(NotificationRemoteInputManager.class);
- }
- return mRemoteInputManager;
- }
-
/**
* Sets up late-bound dependencies for this component.
*/
public void setUpWithPresenter(NotificationPresenter presenter,
NotificationListContainer listContainer,
HeadsUpManager headsUpManager,
- NotificationContentInflater.InflationCallback inflationCallback,
BindRowCallback bindRowCallback) {
mPresenter = presenter;
mListContainer = listContainer;
mHeadsUpManager = headsUpManager;
- mInflationCallback = inflationCallback;
mBindRowCallback = bindRowCallback;
mOnAppOpsClickListener = mGutsManager::openGuts;
}
+ public void setInflationCallback(NotificationRowContentBinder.InflationCallback callback) {
+ mInflationCallback = callback;
+ }
+
public void setNotificationClicker(NotificationClicker clicker) {
mNotificationClicker = clicker;
}
@@ -154,19 +170,6 @@
private void bindRow(NotificationEntry entry, PackageManager pmUser,
StatusBarNotification sbn, ExpandableNotificationRow row,
Runnable onDismissRunnable) {
- row.setExpansionLogger(mExpansionLogger, entry.getSbn().getKey());
- row.setBypassController(mKeyguardBypassController);
- row.setStatusBarStateController(mStatusBarStateController);
- row.setGroupManager(mGroupManager);
- row.setHeadsUpManager(mHeadsUpManager);
- row.setOnExpandClickListener(mPresenter);
- row.setInflationCallback(mInflationCallback);
- if (mAllowLongPress) {
- row.setLongPressListener(mGutsManager::openGuts);
- }
- mListContainer.bindRow(row);
- getRemoteInputManager().bindRow(row);
-
// Get the app name.
// Note that Notification.Builder#bindHeaderAppName has similar logic
// but since this field is used in the guts, it must be accurate.
@@ -184,15 +187,33 @@
} catch (PackageManager.NameNotFoundException e) {
// Do nothing
}
- row.setAppName(appname);
+
+ row.initialize(
+ appname,
+ sbn.getKey(),
+ mExpansionLogger,
+ mKeyguardBypassController,
+ mGroupManager,
+ mHeadsUpManager,
+ mRowContentBinder,
+ mPresenter);
+
+ // TODO: Either move these into ExpandableNotificationRow#initialize or out of row entirely
+ row.setStatusBarStateController(mStatusBarStateController);
+ row.setInflationCallback(mInflationCallback);
+ row.setAppOpsOnClickListener(mOnAppOpsClickListener);
+ if (mAllowLongPress) {
+ row.setLongPressListener(mGutsManager::openGuts);
+ }
+ mListContainer.bindRow(row);
+ mNotificationRemoteInputManager.bindRow(row);
+
row.setOnDismissRunnable(onDismissRunnable);
row.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
if (ENABLE_REMOTE_INPUT) {
row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
}
- row.setAppOpsOnClickListener(mOnAppOpsClickListener);
-
mBindRowCallback.onBindRow(entry, pmUser, sbn, row);
}
@@ -261,8 +282,7 @@
if (mNotificationInterruptionStateProvider.shouldHeadsUp(entry)) {
row.setInflationFlags(FLAG_CONTENT_VIEW_HEADS_UP);
}
- row.setNeedsRedaction(
- Dependency.get(NotificationLockscreenUserManager.class).needsRedaction(entry));
+ row.setNeedsRedaction(mNotificationLockscreenUserManager.needsRedaction(entry));
row.inflateViews();
// bind the click event to the content area
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/FakePipelineConsumer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/FakePipelineConsumer.java
index 986ee17..15f312d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/FakePipelineConsumer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/FakePipelineConsumer.java
@@ -19,8 +19,8 @@
import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.ListEntry;
-import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.ShadeListBuilder;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -36,7 +36,7 @@
private List<ListEntry> mEntries = Collections.emptyList();
/** Attach the consumer to the pipeline. */
- public void attach(NotifListBuilderImpl listBuilder) {
+ public void attach(ShadeListBuilder listBuilder) {
listBuilder.setOnRenderListListener(this::onBuildComplete);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NewNotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
similarity index 62%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NewNotifPipeline.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
index 5e0bd4f..959b002 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NewNotifPipeline.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
@@ -20,11 +20,15 @@
import com.android.systemui.DumpController;
import com.android.systemui.Dumpable;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
-import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
+import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.ShadeListBuilder;
+import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer;
import com.android.systemui.statusbar.notification.collection.coordinator.NotifCoordinators;
-import com.android.systemui.statusbar.notification.collection.notifcollection.GroupCoalescer;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -36,41 +40,56 @@
* Initialization code for the new notification pipeline.
*/
@Singleton
-public class NewNotifPipeline implements Dumpable {
+public class NotifPipelineInitializer implements Dumpable {
+ private final NotifPipeline mPipelineWrapper;
private final GroupCoalescer mGroupCoalescer;
private final NotifCollection mNotifCollection;
- private final NotifListBuilderImpl mNotifPipeline;
+ private final ShadeListBuilder mListBuilder;
private final NotifCoordinators mNotifPluggableCoordinators;
+ private final NotifInflaterImpl mNotifInflater;
private final DumpController mDumpController;
+ private final FeatureFlags mFeatureFlags;
private final FakePipelineConsumer mFakePipelineConsumer = new FakePipelineConsumer();
@Inject
- public NewNotifPipeline(
+ public NotifPipelineInitializer(
+ NotifPipeline pipelineWrapper,
GroupCoalescer groupCoalescer,
NotifCollection notifCollection,
- NotifListBuilderImpl notifPipeline,
+ ShadeListBuilder listBuilder,
NotifCoordinators notifCoordinators,
- DumpController dumpController) {
+ NotifInflaterImpl notifInflater,
+ DumpController dumpController,
+ FeatureFlags featureFlags) {
+ mPipelineWrapper = pipelineWrapper;
mGroupCoalescer = groupCoalescer;
mNotifCollection = notifCollection;
- mNotifPipeline = notifPipeline;
+ mListBuilder = listBuilder;
mNotifPluggableCoordinators = notifCoordinators;
mDumpController = dumpController;
+ mNotifInflater = notifInflater;
+ mFeatureFlags = featureFlags;
}
/** Hooks the new pipeline up to NotificationManager */
public void initialize(
- NotificationListener notificationService) {
+ NotificationListener notificationService,
+ NotificationRowBinderImpl rowBinder) {
mDumpController.registerDumpable("NotifPipeline", this);
+ // Setup inflation
+ if (mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+ mNotifInflater.setRowBinder(rowBinder);
+ }
+
// Wire up coordinators
- mFakePipelineConsumer.attach(mNotifPipeline);
- mNotifPluggableCoordinators.attach(mNotifCollection, mNotifPipeline);
+ mNotifPluggableCoordinators.attach(mPipelineWrapper);
// Wire up pipeline
- mNotifPipeline.attach(mNotifCollection);
+ mFakePipelineConsumer.attach(mListBuilder);
+ mListBuilder.attach(mNotifCollection);
mNotifCollection.attach(mGroupCoalescer);
mGroupCoalescer.attach(notificationService);
@@ -84,5 +103,5 @@
mGroupCoalescer.dump(fd, pw, args);
}
- private static final String TAG = "NewNotifPipeline";
+ private static final String TAG = "NotifPipeline";
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifListBuilder.java
deleted file mode 100644
index 7580924..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifListBuilder.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * 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 com.android.systemui.statusbar.notification.collection.listbuilder;
-
-import com.android.systemui.statusbar.notification.collection.ListEntry;
-import com.android.systemui.statusbar.notification.collection.NotifCollection;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.SectionsProvider;
-
-import java.util.List;
-
-/**
- * The system that constructs the current "notification list", the list of notifications that are
- * currently being displayed to the user.
- *
- * The pipeline proceeds through a series of stages in order to produce the final list (see below).
- * Each stage exposes hooks and listeners for other code to participate.
- *
- * This list differs from the canonical one we receive from system server in a few ways:
- * - Filtered: Some notifications are filtered out. For example, we filter out notifications whose
- * views haven't been inflated yet. We also filter out some notifications if we're on the lock
- * screen. To participate, see {@link #addFilter(NotifFilter)}.
- * - Grouped: Notifications that are part of the same group are clustered together into a single
- * GroupEntry. These groups are then transformed in order to remove children or completely split
- * them apart. To participate, see {@link #addPromoter(NotifPromoter)}.
- * - Sorted: All top-level notifications are sorted. To participate, see
- * {@link #setSectionsProvider(SectionsProvider)} and {@link #setComparators(List)}
- *
- * The exact order of all hooks is as follows:
- * 0. Collection listeners are fired (see {@link NotifCollection}).
- * 1. NotifFilters are called on each notification currently in NotifCollection.
- * 2. Initial grouping is performed (NotificationEntries will have their parents set
- * appropriately).
- * 3. OnBeforeTransformGroupListeners are fired
- * 4. NotifPromoters are called on each notification with a parent
- * 5. OnBeforeSortListeners are fired
- * 6. SectionsProvider is called on each top-level entry in the list
- * 7. The top-level entries are sorted using the provided NotifComparators (plus some additional
- * built-in logic).
- * 8. OnBeforeRenderListListeners are fired
- * 9. The list is handed off to the view layer to be rendered.
- */
-public interface NotifListBuilder {
-
- /**
- * Registers a filter with the pipeline before grouping, promoting and sorting occurs. Filters
- * are called on each notification in the order that they were registered. If any filter
- * returns true, the notification is removed from the pipeline (and no other filters are
- * called on that notif).
- */
- void addPreGroupFilter(NotifFilter filter);
-
- /**
- * Registers a promoter with the pipeline. Promoters are able to promote child notifications to
- * top-level, i.e. move a notification that would be a child of a group and make it appear
- * ungrouped. Promoters are called on each child notification in the order that they are
- * registered. If any promoter returns true, the notification is removed from the group (and no
- * other promoters are called on it).
- */
- void addPromoter(NotifPromoter promoter);
-
- /**
- * Assigns sections to each top-level entry, where a section is simply an integer. Sections are
- * the primary metric by which top-level entries are sorted; NotifComparators are only consulted
- * when two entries are in the same section. The pipeline doesn't assign any particular meaning
- * to section IDs -- from it's perspective they're just numbers and it sorts them by a simple
- * numerical comparison.
- */
- void setSectionsProvider(SectionsProvider provider);
-
- /**
- * Comparators that are used to sort top-level entries that share the same section. The
- * comparators are executed in order until one of them returns a non-zero result. If all return
- * zero, the pipeline falls back to sorting by rank (and, failing that, Notification.when).
- */
- void setComparators(List<NotifComparator> comparators);
-
- /**
- * Registers a filter with the pipeline to filter right before rendering the list (after
- * pre-group filtering, grouping, promoting and sorting occurs). Filters are
- * called on each notification in the order that they were registered. If any filter returns
- * true, the notification is removed from the pipeline (and no other filters are called on that
- * notif).
- */
- void addPreRenderFilter(NotifFilter filter);
-
- /**
- * Called after notifications have been filtered and after the initial grouping has been
- * performed but before NotifPromoters have had a chance to promote children out of groups.
- */
- void addOnBeforeTransformGroupsListener(OnBeforeTransformGroupsListener listener);
-
- /**
- * Called after notifs have been filtered and groups have been determined but before sections
- * have been determined or the notifs have been sorted.
- */
- void addOnBeforeSortListener(OnBeforeSortListener listener);
-
- /**
- * Called at the end of the pipeline after the notif list has been finalized but before it has
- * been handed off to the view layer.
- */
- void addOnBeforeRenderListListener(OnBeforeRenderListListener listener);
-
- /**
- * Returns a read-only view in to the current notification list. If this method is called
- * during pipeline execution it will return the current state of the list, which will likely
- * be only partially-generated.
- */
- List<ListEntry> getActiveNotifs();
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeRenderListListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeRenderListListener.java
index f6ca12d..44a27a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeRenderListListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeRenderListListener.java
@@ -17,10 +17,11 @@
package com.android.systemui.statusbar.notification.collection.listbuilder;
import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import java.util.List;
-/** See {@link NotifListBuilder#addOnBeforeRenderListListener(OnBeforeRenderListListener)} */
+/** See {@link NotifPipeline#addOnBeforeRenderListListener(OnBeforeRenderListListener)} */
public interface OnBeforeRenderListListener {
/**
* Called at the end of the pipeline after the notif list has been finalized but before it has
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeSortListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeSortListener.java
index 7be7ac0..56cfe5c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeSortListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeSortListener.java
@@ -17,10 +17,11 @@
package com.android.systemui.statusbar.notification.collection.listbuilder;
import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import java.util.List;
-/** See {@link NotifListBuilder#addOnBeforeSortListener(OnBeforeSortListener)} */
+/** See {@link NotifPipeline#addOnBeforeSortListener(OnBeforeSortListener)} */
public interface OnBeforeSortListener {
/**
* Called after the notif list has been filtered and grouped but before sections have been
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java
index d7a0815..0dc4df0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/OnBeforeTransformGroupsListener.java
@@ -17,13 +17,14 @@
package com.android.systemui.statusbar.notification.collection.listbuilder;
import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
import java.util.List;
/**
* See
- * {@link NotifListBuilder#addOnBeforeTransformGroupsListener(OnBeforeTransformGroupsListener)}
+ * {@link NotifPipeline#addOnBeforeTransformGroupsListener(OnBeforeTransformGroupsListener)}
*/
public interface OnBeforeTransformGroupsListener {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java
index 084d038..1897ba2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/PipelineState.java
@@ -18,13 +18,13 @@
import android.annotation.IntDef;
-import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
+import com.android.systemui.statusbar.notification.collection.ShadeListBuilder;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
- * Used by {@link NotifListBuilderImpl} to track its internal state machine.
+ * Used by {@link ShadeListBuilder} to track its internal state machine.
*/
public class PipelineState {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java
index a191c83..0d150ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifComparator.java
@@ -17,13 +17,13 @@
package com.android.systemui.statusbar.notification.collection.listbuilder.pluggable;
import com.android.systemui.statusbar.notification.collection.ListEntry;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import java.util.Comparator;
import java.util.List;
/**
- * Pluggable for participating in notif sorting. See {@link NotifListBuilder#setComparators(List)}.
+ * Pluggable for participating in notif sorting. See {@link NotifPipeline#setComparators(List)}.
*/
public abstract class NotifComparator
extends Pluggable<NotifComparator>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java
index e6189ed..8f575cd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifFilter.java
@@ -16,12 +16,12 @@
package com.android.systemui.statusbar.notification.collection.listbuilder.pluggable;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
/**
* Pluggable for participating in notif filtering.
- * See {@link NotifListBuilder#addPreGroupFilter} and {@link NotifListBuilder#addPreRenderFilter}.
+ * See {@link NotifPipeline#addPreGroupFilter} and {@link NotifPipeline#addPreRenderFilter}.
*/
public abstract class NotifFilter extends Pluggable<NotifFilter> {
protected NotifFilter(String name) {
@@ -35,9 +35,9 @@
* however. If another filter returns true before yours, we'll skip straight to the next notif.
*
* @param entry The entry in question.
- * If this filter is registered via {@link NotifListBuilder#addPreGroupFilter},
+ * If this filter is registered via {@link NotifPipeline#addPreGroupFilter},
* this entry will not have any grouping nor sorting information.
- * If this filter is registered via {@link NotifListBuilder#addPreRenderFilter},
+ * If this filter is registered via {@link NotifPipeline#addPreRenderFilter},
* this entry will have grouping and sorting information.
* @param now A timestamp in SystemClock.uptimeMillis that represents "now" for the purposes of
* pipeline execution. This value will be the same for all pluggable calls made
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifPromoter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifPromoter.java
index 84e16f4..5fce446 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifPromoter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifPromoter.java
@@ -16,13 +16,13 @@
package com.android.systemui.statusbar.notification.collection.listbuilder.pluggable;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
/**
* Pluggable for participating in notif promotion. Notif promoters can upgrade notifications
* from being children of a group to top-level notifications. See
- * {@link NotifListBuilder#addPromoter(NotifPromoter)}.
+ * {@link NotifPipeline#addPromoter}.
*/
public abstract class NotifPromoter extends Pluggable<NotifPromoter> {
protected NotifPromoter(String name) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSection.java
new file mode 100644
index 0000000..fe5ba3c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifSection.java
@@ -0,0 +1,37 @@
+/*
+ * 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.systemui.statusbar.notification.collection.listbuilder.pluggable;
+
+import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.ShadeListBuilder;
+
+/**
+ * Pluggable for participating in notif sectioning. See {@link ShadeListBuilder#setSections}.
+ */
+public abstract class NotifSection extends Pluggable<NotifSection> {
+ protected NotifSection(String name) {
+ super(name);
+ }
+
+ /**
+ * If returns true, this notification is considered within this section.
+ * Sectioning is performed on each top level notification each time the pipeline is run.
+ * However, this doesn't necessarily mean that your section will get called on each top-level
+ * notification. The first section to return true determines the section of the notification.
+ */
+ public abstract boolean isInSection(ListEntry entry);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java
index f9ce197..4270408 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java
@@ -18,10 +18,10 @@
import android.annotation.Nullable;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
/**
- * Generic superclass for chunks of code that can plug into the {@link NotifListBuilder}.
+ * Generic superclass for chunks of code that can plug into the {@link NotifPipeline}.
*
* A pluggable is fundamentally three things:
* 1. A name (for debugging purposes)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/SectionsProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/SectionsProvider.java
deleted file mode 100644
index 11ea850..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/SectionsProvider.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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 com.android.systemui.statusbar.notification.collection.listbuilder.pluggable;
-
-import com.android.systemui.statusbar.notification.collection.ListEntry;
-
-/**
- * Interface for sorting notifications into "sections", such as a heads-upping section, people
- * section, alerting section, silent section, etc.
- */
-public abstract class SectionsProvider extends Pluggable<SectionsProvider> {
-
- protected SectionsProvider(String name) {
- super(name);
- }
-
- /**
- * Returns the section that this entry belongs to. A section can be any non-negative integer.
- * When entries are sorted, they are first sorted by section and then by any remainining
- * comparators.
- */
- public abstract int getSection(ListEntry entry);
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/CollectionReadyForBuildListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CollectionReadyForBuildListener.java
similarity index 82%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/CollectionReadyForBuildListener.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CollectionReadyForBuildListener.java
index 87aaea0..4023474 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/CollectionReadyForBuildListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CollectionReadyForBuildListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,7 +14,9 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.collection;
+package com.android.systemui.statusbar.notification.collection.notifcollection;
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import java.util.Collection;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/DismissedByUserStats.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/DismissedByUserStats.java
similarity index 91%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/DismissedByUserStats.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/DismissedByUserStats.java
index ecce6ea..b268686 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/DismissedByUserStats.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/DismissedByUserStats.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.collection;
+package com.android.systemui.statusbar.notification.collection.notifcollection;
import android.service.notification.NotificationStats.DismissalSentiment;
import android.service.notification.NotificationStats.DismissalSurface;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
similarity index 71%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionListener.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
index 032620e..9cbc7d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollectionListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,9 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.collection;
+package com.android.systemui.statusbar.notification.collection.notifcollection;
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
/**
* Listener interface for {@link NotifCollection}.
@@ -36,7 +38,9 @@
}
/**
- * Called immediately after a notification has been removed from the collection.
+ * Called whenever a notification is retracted by system server. This method is not called
+ * immediately after a user dismisses a notification: we wait until we receive confirmation from
+ * system server before considering the notification removed.
*/
default void onEntryRemoved(
NotificationEntry entry,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLifetimeExtender.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifLifetimeExtender.java
similarity index 89%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLifetimeExtender.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifLifetimeExtender.java
index 2c7b138..05f5ea8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLifetimeExtender.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifLifetimeExtender.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,9 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.collection;
+package com.android.systemui.statusbar.notification.collection.notifcollection;
+import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
/**
* A way for other code to temporarily extend the lifetime of a notification after it has been
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DerivedMember.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DerivedMember.java
deleted file mode 100644
index 815e6f7..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DerivedMember.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * 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 com.android.systemui.statusbar.notification.collection.provider;
-/**
- * Caches a computed value until invalidate() is called
- * @param <Parent> Object used to computeValue
- * @param <Value> type of value to cache until invalidate is called
- */
-public abstract class DerivedMember<Parent, Value> {
- private Value mValue;
- protected abstract Value computeValue(Parent parent);
-
- /**
- * Gets the last cached value, else recomputes the value.
- */
- public Value get(Parent parent) {
- if (mValue == null) {
- mValue = computeValue(parent);
- }
- return mValue;
- }
-
- /**
- * Resets the cached value.
- * Next time "get" is called, the value is recomputed.
- */
- public void invalidate() {
- mValue = null;
- }
-
- /**
- * Called when a NotificationEntry's status bar notification has updated.
- * Derived members can invalidate here.
- */
- public void onSbnUpdated() {}
-
- /**
- * Called when a NotificationEntry's Ranking has updated.
- * Derived members can invalidate here.
- */
- public void onRankingUpdated() {}
-
- /**
- * Called when a ListEntry's grouping information (parent or children) has changed.
- * Derived members can invalidate here.
- */
- public void onGroupingUpdated() {}
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java
new file mode 100644
index 0000000..ccd7fa3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/HighPriorityProvider.java
@@ -0,0 +1,110 @@
+/*
+ * 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.systemui.statusbar.notification.collection.provider;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+
+import com.android.systemui.statusbar.notification.collection.GroupEntry;
+import com.android.systemui.statusbar.notification.collection.ListEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Determines whether a notification is considered 'high priority'.
+ *
+ * Notifications that are high priority are visible on the lock screen/status bar and in the top
+ * section in the shade.
+ */
+@Singleton
+public class HighPriorityProvider {
+ private final PeopleNotificationIdentifier mPeopleNotificationIdentifier;
+
+ @Inject
+ public HighPriorityProvider(PeopleNotificationIdentifier peopleNotificationIdentifier) {
+ mPeopleNotificationIdentifier = peopleNotificationIdentifier;
+ }
+
+ /**
+ * @return true if the ListEntry is high priority, else false
+ *
+ * A NotificationEntry is considered high priority if it:
+ * - has importance greater than or equal to IMPORTANCE_DEFAULT
+ * OR
+ * - their importance has NOT been set to a low priority option by the user AND the
+ * notification fulfills one of the following:
+ * - has a person associated with it
+ * - has a media session associated with it
+ * - has messaging style
+ *
+ * A GroupEntry is considered high priority if its representativeEntry (summary) or children are
+ * high priority
+ */
+ public boolean isHighPriority(ListEntry entry) {
+ if (entry == null) {
+ return false;
+ }
+
+ final NotificationEntry notifEntry = entry.getRepresentativeEntry();
+ return notifEntry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_DEFAULT
+ || hasHighPriorityCharacteristics(notifEntry)
+ || hasHighPriorityChild(entry);
+ }
+
+
+ private boolean hasHighPriorityChild(ListEntry entry) {
+ if (entry instanceof GroupEntry) {
+ for (NotificationEntry child : ((GroupEntry) entry).getChildren()) {
+ if (isHighPriority(child)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean hasHighPriorityCharacteristics(NotificationEntry entry) {
+ return !hasUserSetImportance(entry)
+ && (isImportantOngoing(entry)
+ || entry.getSbn().getNotification().hasMediaSession()
+ || isPeopleNotification(entry)
+ || isMessagingStyle(entry));
+ }
+
+ private boolean isImportantOngoing(NotificationEntry entry) {
+ return entry.getSbn().getNotification().isForegroundService()
+ && entry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_LOW;
+ }
+
+ private boolean isMessagingStyle(NotificationEntry entry) {
+ return Notification.MessagingStyle.class.equals(
+ entry.getSbn().getNotification().getNotificationStyle());
+ }
+
+ private boolean isPeopleNotification(NotificationEntry entry) {
+ return mPeopleNotificationIdentifier.isPeopleNotification(
+ entry.getSbn(), entry.getChannel());
+ }
+
+ private boolean hasUserSetImportance(NotificationEntry entry) {
+ return entry.getRanking().getChannel() != null
+ && entry.getRanking().getChannel().hasUserSetImportance();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProvider.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProvider.java
deleted file mode 100644
index 76e256b..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProvider.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * 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 com.android.systemui.statusbar.notification.collection.provider;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.Person;
-
-import com.android.systemui.Dependency;
-import com.android.systemui.statusbar.notification.collection.GroupEntry;
-import com.android.systemui.statusbar.notification.collection.ListEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.phone.NotificationGroupManager;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Whether the ListEntry is shown to the user as a high priority notification: visible on
- * the lock screen/status bar and in the top section in the shade.
- *
- * A NotificationEntry is considered high priority if it:
- * - has importance greater than or equal to IMPORTANCE_DEFAULT
- * OR
- * - their importance has NOT been set to a low priority option by the user AND the notification
- * fulfills one of the following:
- * - has a person associated with it
- * - has a media session associated with it
- * - has messaging style
- *
- * A GroupEntry is considered high priority if its representativeEntry (summary) or children are
- * high priority
- */
-public class IsHighPriorityProvider extends DerivedMember<ListEntry, Boolean> {
- // TODO: (b/145659174) remove groupManager when moving to NewNotifPipeline. Logic
- // replaced in GroupEntry and NotifListBuilderImpl
- private final NotificationGroupManager mGroupManager;
-
-
- public IsHighPriorityProvider() {
- // TODO: (b/145659174) remove
- mGroupManager = Dependency.get(NotificationGroupManager.class);
- }
-
- @Override
- protected Boolean computeValue(ListEntry entry) {
- if (entry == null) {
- return false;
- }
-
- return isHighPriority(entry);
- }
-
- private boolean isHighPriority(ListEntry listEntry) {
- // requires groups have been set (AFTER PipelineState.STATE_TRANSFORMING)
- final NotificationEntry notifEntry = listEntry.getRepresentativeEntry();
- return notifEntry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_DEFAULT
- || hasHighPriorityCharacteristics(notifEntry)
- || hasHighPriorityChild(listEntry);
-
- }
-
- private boolean hasHighPriorityChild(ListEntry entry) {
- // TODO: (b/145659174) remove
- if (entry instanceof NotificationEntry) {
- NotificationEntry notifEntry = (NotificationEntry) entry;
- if (mGroupManager.isSummaryOfGroup(notifEntry.getSbn())) {
- List<NotificationEntry> logicalChildren =
- mGroupManager.getLogicalChildren(notifEntry.getSbn());
- for (NotificationEntry child : logicalChildren) {
- if (child.isHighPriority()) {
- return true;
- }
- }
- }
- }
-
- if (entry instanceof GroupEntry) {
- for (NotificationEntry child : ((GroupEntry) entry).getChildren()) {
- if (child.isHighPriority()) {
- return true;
- }
- }
- }
- return false;
- }
-
- private boolean hasHighPriorityCharacteristics(NotificationEntry entry) {
- return !hasUserSetImportance(entry)
- && (isImportantOngoing(entry)
- || entry.getSbn().getNotification().hasMediaSession()
- || hasPerson(entry)
- || isMessagingStyle(entry));
- }
-
- private boolean isImportantOngoing(NotificationEntry entry) {
- return entry.getSbn().getNotification().isForegroundService()
- && entry.getRanking().getImportance() >= NotificationManager.IMPORTANCE_LOW;
- }
-
- private boolean isMessagingStyle(NotificationEntry entry) {
- return Notification.MessagingStyle.class.equals(
- entry.getSbn().getNotification().getNotificationStyle());
- }
-
- private boolean hasPerson(NotificationEntry entry) {
- // TODO: cache favorite and recent contacts to check contact affinity
- Notification notification = entry.getSbn().getNotification();
- ArrayList<Person> people = notification.extras != null
- ? notification.extras.getParcelableArrayList(Notification.EXTRA_PEOPLE_LIST)
- : new ArrayList<>();
- return people != null && !people.isEmpty();
- }
-
- private boolean hasUserSetImportance(NotificationEntry entry) {
- return entry.getRanking().getChannel() != null
- && entry.getRanking().getChannel().hasUserSetImportance();
- }
-
- @Override
- public void onSbnUpdated() {
- invalidate();
- }
-
- @Override
- public void onRankingUpdated() {
- invalidate();
- }
-
- @Override
- public void onGroupingUpdated() {
- invalidate();
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
index e4a57d7..9adceb7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotifEvent.java
@@ -22,8 +22,8 @@
import com.android.systemui.log.RichEvent;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.listbuilder.NotifListBuilder;
-import com.android.systemui.statusbar.notification.collection.notifcollection.GroupCoalescer;
+import com.android.systemui.statusbar.notification.collection.ShadeListBuilder;
+import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -67,7 +67,7 @@
}
/**
- * @return if this event occurred in {@link NotifListBuilder}
+ * @return if this event occurred in {@link ShadeListBuilder}
*/
static boolean isListBuilderEvent(@EventType int type) {
return isBetweenInclusive(type, 0, TOTAL_LIST_BUILDER_EVENT_TYPES);
@@ -94,7 +94,7 @@
LIST_BUILD_COMPLETE,
PRE_GROUP_FILTER_INVALIDATED,
PROMOTER_INVALIDATED,
- SECTIONS_PROVIDER_INVALIDATED,
+ SECTION_INVALIDATED,
COMPARATOR_INVALIDATED,
PARENT_CHANGED,
FILTER_CHANGED,
@@ -112,7 +112,12 @@
LIFETIME_EXTENDED,
REMOVE_INTERCEPTED,
INFLATION_ABORTED,
- INFLATED
+ INFLATED,
+
+ // GroupCoalescer
+ COALESCED_EVENT,
+ EARLY_BATCH_EMIT,
+ EMIT_EVENT_BATCH
})
@Retention(RetentionPolicy.SOURCE)
public @interface EventType {}
@@ -127,12 +132,13 @@
"ListBuildComplete",
"FilterInvalidated",
"PromoterInvalidated",
- "SectionsProviderInvalidated",
+ "SectionInvalidated",
"ComparatorInvalidated",
"ParentChanged",
"FilterChanged",
"PromoterChanged",
"FinalFilterInvalidated",
+ "SectionerChanged",
// NEM event labels:
"NotifAdded",
@@ -147,14 +153,17 @@
"InflationAborted",
"Inflated",
+ // GroupCoalescer labels:
"CoalescedEvent",
- "EarlyBatchEmit"
+ "EarlyBatchEmit",
+ "EmitEventBatch",
+ "BatchMaxTimeout"
};
private static final int TOTAL_EVENT_LABELS = EVENT_LABELS.length;
/**
- * Events related to {@link NotifListBuilder}
+ * Events related to {@link ShadeListBuilder}
*/
public static final int WARN = 0;
public static final int ON_BUILD_LIST = 1;
@@ -163,36 +172,41 @@
public static final int LIST_BUILD_COMPLETE = 4;
public static final int PRE_GROUP_FILTER_INVALIDATED = 5;
public static final int PROMOTER_INVALIDATED = 6;
- public static final int SECTIONS_PROVIDER_INVALIDATED = 7;
+ public static final int SECTION_INVALIDATED = 7;
public static final int COMPARATOR_INVALIDATED = 8;
public static final int PARENT_CHANGED = 9;
public static final int FILTER_CHANGED = 10;
public static final int PROMOTER_CHANGED = 11;
public static final int PRE_RENDER_FILTER_INVALIDATED = 12;
- private static final int TOTAL_LIST_BUILDER_EVENT_TYPES = 13;
+ public static final int SECTION_CHANGED = 13;
+ private static final int TOTAL_LIST_BUILDER_EVENT_TYPES = 14;
/**
* Events related to {@link NotificationEntryManager}
*/
- public static final int NOTIF_ADDED = TOTAL_LIST_BUILDER_EVENT_TYPES;
- public static final int NOTIF_REMOVED = TOTAL_LIST_BUILDER_EVENT_TYPES + 1;
- public static final int NOTIF_UPDATED = TOTAL_LIST_BUILDER_EVENT_TYPES + 2;
- public static final int FILTER = TOTAL_LIST_BUILDER_EVENT_TYPES + 3;
- public static final int SORT = TOTAL_LIST_BUILDER_EVENT_TYPES + 4;
- public static final int FILTER_AND_SORT = TOTAL_LIST_BUILDER_EVENT_TYPES + 5;
- public static final int NOTIF_VISIBILITY_CHANGED = TOTAL_LIST_BUILDER_EVENT_TYPES + 6;
- public static final int LIFETIME_EXTENDED = TOTAL_LIST_BUILDER_EVENT_TYPES + 7;
+ private static final int NEM_EVENT_START_INDEX = TOTAL_LIST_BUILDER_EVENT_TYPES;
+ public static final int NOTIF_ADDED = NEM_EVENT_START_INDEX;
+ public static final int NOTIF_REMOVED = NEM_EVENT_START_INDEX + 1;
+ public static final int NOTIF_UPDATED = NEM_EVENT_START_INDEX + 2;
+ public static final int FILTER = NEM_EVENT_START_INDEX + 3;
+ public static final int SORT = NEM_EVENT_START_INDEX + 4;
+ public static final int FILTER_AND_SORT = NEM_EVENT_START_INDEX + 5;
+ public static final int NOTIF_VISIBILITY_CHANGED = NEM_EVENT_START_INDEX + 6;
+ public static final int LIFETIME_EXTENDED = NEM_EVENT_START_INDEX + 7;
// unable to remove notif - removal intercepted by {@link NotificationRemoveInterceptor}
- public static final int REMOVE_INTERCEPTED = TOTAL_LIST_BUILDER_EVENT_TYPES + 8;
- public static final int INFLATION_ABORTED = TOTAL_LIST_BUILDER_EVENT_TYPES + 9;
- public static final int INFLATED = TOTAL_LIST_BUILDER_EVENT_TYPES + 10;
+ public static final int REMOVE_INTERCEPTED = NEM_EVENT_START_INDEX + 8;
+ public static final int INFLATION_ABORTED = NEM_EVENT_START_INDEX + 9;
+ public static final int INFLATED = NEM_EVENT_START_INDEX + 10;
private static final int TOTAL_NEM_EVENT_TYPES = 11;
/**
* Events related to {@link GroupCoalescer}
*/
- public static final int COALESCED_EVENT = TOTAL_NEM_EVENT_TYPES;
- public static final int EARLY_BATCH_EMIT = TOTAL_NEM_EVENT_TYPES + 1;
- public static final int EMIT_EVENT_BATCH = TOTAL_NEM_EVENT_TYPES + 2;
- private static final int TOTAL_COALESCER_EVENT_TYPES = 2;
+ private static final int COALESCER_EVENT_START_INDEX = NEM_EVENT_START_INDEX
+ + TOTAL_NEM_EVENT_TYPES;
+ public static final int COALESCED_EVENT = COALESCER_EVENT_START_INDEX;
+ public static final int EARLY_BATCH_EMIT = COALESCER_EVENT_START_INDEX + 1;
+ public static final int EMIT_EVENT_BATCH = COALESCER_EVENT_START_INDEX + 2;
+ public static final int BATCH_MAX_TIMEOUT = COALESCER_EVENT_START_INDEX + 3;
+ private static final int TOTAL_COALESCER_EVENT_TYPES = 3;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index 3e1b5bd..89e5f55 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -294,6 +294,9 @@
}
}
+ /**
+ * Logs Notification inflation error
+ */
private void logNotificationError(
StatusBarNotification notification,
Exception exception) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHub.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHub.kt
index 2c0c942..e81d361 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHub.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHub.kt
@@ -37,7 +37,8 @@
val key: PersonKey,
val name: CharSequence,
val avatar: Drawable,
- val clickIntent: PendingIntent
+ val clickIntent: PendingIntent,
+ val userId: Int
)
/** Unique identifier for a Person in PeopleHub. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
index 784673e..88b4147 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
@@ -17,28 +17,27 @@
package com.android.systemui.statusbar.notification.people
import android.app.Notification
-import android.content.Context
-import android.graphics.Canvas
-import android.graphics.ColorFilter
-import android.graphics.PixelFormat
-import android.graphics.drawable.BitmapDrawable
+import android.content.pm.UserInfo
import android.graphics.drawable.Drawable
-import android.os.UserHandle
+import android.os.UserManager
import android.service.notification.StatusBarNotification
-import android.util.TypedValue
+import android.util.SparseArray
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import com.android.internal.statusbar.NotificationVisibility
import com.android.internal.widget.MessagingGroup
-import com.android.launcher3.icons.BaseIconFactory
import com.android.systemui.R
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.NotificationPersonExtractorPlugin
+import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.notification.NotificationEntryListener
import com.android.systemui.statusbar.notification.NotificationEntryManager
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.policy.ExtensionController
import java.util.ArrayDeque
+import java.util.concurrent.Executor
import javax.inject.Inject
import javax.inject.Singleton
@@ -52,8 +51,7 @@
@Singleton
class NotificationPersonExtractorPluginBoundary @Inject constructor(
- extensionController: ExtensionController,
- private val context: Context
+ extensionController: ExtensionController
) : NotificationPersonExtractor {
private var plugin: NotificationPersonExtractorPlugin? = null
@@ -70,9 +68,8 @@
}
override fun extractPerson(sbn: StatusBarNotification) =
- plugin?.extractPerson(sbn)?.let { data ->
- val badged = addBadgeToDrawable(data.avatar, context, sbn.packageName, sbn.user)
- PersonModel(data.key, data.name, badged, data.clickIntent)
+ plugin?.extractPerson(sbn)?.run {
+ PersonModel(key, name, avatar, clickIntent, sbn.user.identifier)
}
override fun extractPersonKey(sbn: StatusBarNotification) = plugin?.extractPersonKey(sbn)
@@ -84,11 +81,16 @@
@Singleton
class PeopleHubDataSourceImpl @Inject constructor(
private val notificationEntryManager: NotificationEntryManager,
- private val peopleHubManager: PeopleHubManager,
- private val extractor: NotificationPersonExtractor
+ private val extractor: NotificationPersonExtractor,
+ private val userManager: UserManager,
+ @Background private val bgExecutor: Executor,
+ @Main private val mainExecutor: Executor,
+ private val notifLockscreenUserMgr: NotificationLockscreenUserManager
) : DataSource<PeopleHubModel> {
+ private var userChangeSubscription: Subscription? = null
private val dataListeners = mutableListOf<DataListener<PeopleHubModel>>()
+ private val peopleHubManagerForUser = SparseArray<PeopleHubManager>()
private val notificationEntryListener = object : NotificationEntryListener {
override fun onEntryInflated(entry: NotificationEntry, inflatedFlags: Int) =
@@ -106,31 +108,56 @@
}
private fun removeVisibleEntry(entry: NotificationEntry) {
- val key = extractor.extractPersonKey(entry.sbn) ?: entry.extractPersonKey()
- if (key?.let(peopleHubManager::removeActivePerson) == true) {
- updateUi()
+ (extractor.extractPersonKey(entry.sbn) ?: entry.extractPersonKey())?.let { key ->
+ val userId = entry.sbn.user.identifier
+ bgExecutor.execute {
+ val parentId = userManager.getProfileParent(userId)?.id ?: userId
+ mainExecutor.execute {
+ if (peopleHubManagerForUser[parentId]?.removeActivePerson(key) == true) {
+ updateUi()
+ }
+ }
+ }
}
}
private fun addVisibleEntry(entry: NotificationEntry) {
- val personModel = extractor.extractPerson(entry.sbn) ?: entry.extractPerson()
- if (personModel?.let(peopleHubManager::addActivePerson) == true) {
- updateUi()
+ (extractor.extractPerson(entry.sbn) ?: entry.extractPerson())?.let { personModel ->
+ val userId = entry.sbn.user.identifier
+ bgExecutor.execute {
+ val parentId = userManager.getProfileParent(userId)?.id ?: userId
+ mainExecutor.execute {
+ val manager = peopleHubManagerForUser[parentId]
+ ?: PeopleHubManager().also { peopleHubManagerForUser.put(parentId, it) }
+ if (manager.addActivePerson(personModel)) {
+ updateUi()
+ }
+ }
+ }
}
}
override fun registerListener(listener: DataListener<PeopleHubModel>): Subscription {
- val registerWithNotificationEntryManager = dataListeners.isEmpty()
+ val register = dataListeners.isEmpty()
dataListeners.add(listener)
- if (registerWithNotificationEntryManager) {
+ if (register) {
+ userChangeSubscription = notifLockscreenUserMgr.registerListener(
+ object : NotificationLockscreenUserManager.UserChangedListener {
+ override fun onUserChanged(userId: Int) = updateUi()
+ override fun onCurrentProfilesChanged(
+ currentProfiles: SparseArray<UserInfo>?
+ ) = updateUi()
+ })
notificationEntryManager.addNotificationEntryListener(notificationEntryListener)
} else {
- listener.onDataChanged(peopleHubManager.getPeopleHubModel())
+ getPeopleHubModelForCurrentUser()?.let(listener::onDataChanged)
}
return object : Subscription {
override fun unsubscribe() {
dataListeners.remove(listener)
if (dataListeners.isEmpty()) {
+ userChangeSubscription?.unsubscribe()
+ userChangeSubscription = null
notificationEntryManager
.removeNotificationEntryListener(notificationEntryListener)
}
@@ -138,16 +165,36 @@
}
}
+ private fun getPeopleHubModelForCurrentUser(): PeopleHubModel? {
+ val currentUserId = notifLockscreenUserMgr.currentUserId
+ val model = peopleHubManagerForUser[currentUserId]?.getPeopleHubModel()
+ ?: return null
+ val currentProfiles = notifLockscreenUserMgr.currentProfiles
+ return model.copy(people = model.people.filter { person ->
+ currentProfiles[person.userId]?.isQuietModeEnabled == false
+ })
+ }
+
private fun updateUi() {
- val model = peopleHubManager.getPeopleHubModel()
+ val model = getPeopleHubModelForCurrentUser() ?: return
for (listener in dataListeners) {
listener.onDataChanged(model)
}
}
}
-@Singleton
-class PeopleHubManager @Inject constructor() {
+private fun NotificationLockscreenUserManager.registerListener(
+ listener: NotificationLockscreenUserManager.UserChangedListener
+): Subscription {
+ addUserChangedListener(listener)
+ return object : Subscription {
+ override fun unsubscribe() {
+ removeUserChangedListener(listener)
+ }
+ }
+}
+
+class PeopleHubManager {
private val activePeople = mutableMapOf<PersonKey, PersonModel>()
private val inactivePeople = ArrayDeque<PersonModel>(MAX_STORED_INACTIVE_PEOPLE)
@@ -157,7 +204,7 @@
if (inactivePeople.size >= MAX_STORED_INACTIVE_PEOPLE) {
inactivePeople.removeLast()
}
- inactivePeople.push(data)
+ inactivePeople.add(data)
return true
}
return false
@@ -190,63 +237,7 @@
?: extras.getString(Notification.EXTRA_TITLE)
?: return null
val drawable = extractAvatarFromRow(this) ?: return null
- val badgedAvatar = addBadgeToDrawable(drawable, row.context, sbn.packageName, sbn.user)
- return PersonModel(key, name, badgedAvatar, clickIntent)
-}
-
-private fun addBadgeToDrawable(
- drawable: Drawable,
- context: Context,
- packageName: String,
- user: UserHandle
-): Drawable {
- val pm = context.packageManager
- val appInfo = pm.getApplicationInfoAsUser(packageName, 0, user)
- return object : Drawable() {
- override fun draw(canvas: Canvas) {
- val iconBounds = getBounds()
- val factory = object : BaseIconFactory(
- context,
- 0 /* unused */,
- iconBounds.width(),
- true) {}
- val badge = factory.createBadgedIconBitmap(
- appInfo.loadIcon(pm),
- user,
- true,
- appInfo.isInstantApp,
- null)
- val badgeDrawable = BitmapDrawable(context.resources, badge.icon)
- .apply {
- alpha = drawable.alpha
- colorFilter = drawable.colorFilter
- val badgeWidth = TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_DIP,
- 15f,
- context.resources.displayMetrics
- ).toInt()
- setBounds(
- iconBounds.left + (iconBounds.width() - badgeWidth),
- iconBounds.top + (iconBounds.height() - badgeWidth),
- iconBounds.right,
- iconBounds.bottom)
- }
- drawable.bounds = iconBounds
- drawable.draw(canvas)
- badgeDrawable.draw(canvas)
- }
-
- override fun setAlpha(alpha: Int) {
- drawable.alpha = alpha
- }
-
- override fun setColorFilter(colorFilter: ColorFilter?) {
- drawable.colorFilter = colorFilter
- }
-
- @PixelFormat.Opacity
- override fun getOpacity(): Int = PixelFormat.OPAQUE
- }
+ return PersonModel(key, name, drawable, clickIntent, sbn.user.identifier)
}
fun extractAvatarFromRow(entry: NotificationEntry): Drawable? =
@@ -272,4 +263,4 @@
if (isMessagingNotification()) key else null
private fun NotificationEntry.isMessagingNotification() =
- sbn.notification.notificationStyle == Notification.MessagingStyle::class.java
\ No newline at end of file
+ sbn.notification.notificationStyle == Notification.MessagingStyle::class.java
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
index 78eaf3e..5c90211 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt
@@ -17,20 +17,30 @@
package com.android.systemui.statusbar.notification.people
import android.app.Notification
+import android.content.Context
+import android.app.NotificationChannel
import android.service.notification.StatusBarNotification
+import android.util.FeatureFlagUtils
import javax.inject.Inject
import javax.inject.Singleton
interface PeopleNotificationIdentifier {
- fun isPeopleNotification(sbn: StatusBarNotification): Boolean
+ fun isPeopleNotification(sbn: StatusBarNotification, channel: NotificationChannel): Boolean
}
@Singleton
class PeopleNotificationIdentifierImpl @Inject constructor(
- private val personExtractor: NotificationPersonExtractor
+ private val personExtractor: NotificationPersonExtractor,
+ private val context: Context
) : PeopleNotificationIdentifier {
- override fun isPeopleNotification(sbn: StatusBarNotification) =
- sbn.notification.notificationStyle == Notification.MessagingStyle::class.java ||
- personExtractor.isPersonNotification(sbn)
+ override fun isPeopleNotification(sbn: StatusBarNotification, channel: NotificationChannel) =
+ ((sbn.notification.notificationStyle == Notification.MessagingStyle::class.java &&
+ (sbn.notification.shortcutId != null ||
+ FeatureFlagUtils.isEnabled(
+ context,
+ FeatureFlagUtils.NOTIF_CONVO_BYPASS_SHORTCUT_REQ
+ ))) ||
+ personExtractor.isPersonNotification(sbn)) &&
+ !channel.isDemoted
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 3c247df..b71beda 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -65,7 +65,6 @@
import android.widget.Chronometer;
import android.widget.FrameLayout;
import android.widget.ImageView;
-import android.widget.RemoteViews;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
@@ -150,7 +149,7 @@
private StatusBarStateController mStatusbarStateController;
private KeyguardBypassController mBypassController;
private LayoutListener mLayoutListener;
- private final NotificationContentInflater mNotificationInflater;
+ private NotificationRowContentBinder mNotificationContentBinder;
private int mIconTransformContentShift;
private int mIconTransformContentShiftNoIcon;
private int mMaxHeadsUpHeightBeforeN;
@@ -464,7 +463,7 @@
* Inflate views based off the inflation flags set. Inflation happens asynchronously.
*/
public void inflateViews() {
- mNotificationInflater.bindContent(mEntry, this, mInflationFlags, mBindParams,
+ mNotificationContentBinder.bindContent(mEntry, this, mInflationFlags, mBindParams,
false /* forceInflate */, mInflationCallback);
}
@@ -478,7 +477,7 @@
// View should not be reinflated in the future
clearInflationFlags(inflationFlag);
Runnable freeViewRunnable =
- () -> mNotificationInflater.unbindContent(mEntry, this, inflationFlag);
+ () -> mNotificationContentBinder.unbindContent(mEntry, this, inflationFlag);
switch (inflationFlag) {
case FLAG_CONTENT_VIEW_HEADS_UP:
getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_HEADSUP,
@@ -742,23 +741,10 @@
return mIsHeadsUp || mHeadsupDisappearRunning;
}
-
- public void setGroupManager(NotificationGroupManager groupManager) {
- mGroupManager = groupManager;
- mPrivateLayout.setGroupManager(groupManager);
- }
-
public void setRemoteInputController(RemoteInputController r) {
mPrivateLayout.setRemoteInputController(r);
}
- public void setAppName(String appName) {
- mAppName = appName;
- if (mMenuRow != null && mMenuRow.getMenuView() != null) {
- mMenuRow.setAppName(mAppName);
- }
- }
-
public void addChildNotification(ExpandableNotificationRow row) {
addChildNotification(row, -1);
}
@@ -852,7 +838,7 @@
mIsChildInGroup = isChildInGroup;
if (mIsLowPriority) {
int flags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED;
- mNotificationInflater.bindContent(mEntry, this, flags, mBindParams,
+ mNotificationContentBinder.bindContent(mEntry, this, flags, mBindParams,
false /* forceInflate */, mInflationCallback);
}
}
@@ -1105,10 +1091,6 @@
return mPrivateLayout.getContractedNotificationHeader();
}
- public void setOnExpandClickListener(OnExpandClickListener onExpandClickListener) {
- mOnExpandClickListener = onExpandClickListener;
- }
-
public void setLongPressListener(LongPressListener longPressListener) {
mLongPressListener = longPressListener;
}
@@ -1131,10 +1113,6 @@
}
}
- public void setHeadsUpManager(HeadsUpManager headsUpManager) {
- mHeadsUpManager = headsUpManager;
- }
-
public HeadsUpManager getHeadsUpManager() {
return mHeadsUpManager;
}
@@ -1172,6 +1150,7 @@
mMenuRow = plugin;
if (mMenuRow.shouldUseDefaultMenuItems()) {
ArrayList<MenuItem> items = new ArrayList<>();
+ items.add(NotificationMenuRow.createConversationItem(mContext));
items.add(NotificationMenuRow.createInfoItem(mContext));
items.add(NotificationMenuRow.createSnoozeItem(mContext));
items.add(NotificationMenuRow.createAppOpsItem(mContext));
@@ -1185,7 +1164,7 @@
@Override
public void onPluginDisconnected(NotificationMenuRowPlugin plugin) {
boolean existed = mMenuRow.getMenuView() != null;
- mMenuRow = new NotificationMenuRow(mContext); // Back to default
+ mMenuRow = new NotificationMenuRow(mContext);
if (existed) {
createMenu();
}
@@ -1259,7 +1238,7 @@
l.reInflateViews();
}
mEntry.getSbn().clearPackageContext();
- mNotificationInflater.bindContent(mEntry, this, mInflationFlags, mBindParams,
+ mNotificationContentBinder.bindContent(mEntry, this, mInflationFlags, mBindParams,
true /* forceInflate */, mInflationCallback);
}
@@ -1634,10 +1613,6 @@
mBindParams.usesIncreasedHeadsUpHeight = use;
}
- public void setRemoteViewClickHandler(RemoteViews.OnClickHandler remoteViewClickHandler) {
- mNotificationInflater.setRemoteViewClickHandler(remoteViewClickHandler);
- }
-
/**
* Set callback for notification content inflation
*
@@ -1652,7 +1627,7 @@
mNeedsRedaction = needsRedaction;
if (needsRedaction) {
setInflationFlags(FLAG_CONTENT_VIEW_PUBLIC);
- mNotificationInflater.bindContent(mEntry, this, FLAG_CONTENT_VIEW_PUBLIC,
+ mNotificationContentBinder.bindContent(mEntry, this, FLAG_CONTENT_VIEW_PUBLIC,
mBindParams, false /* forceInflate */, mInflationCallback);
} else {
clearInflationFlags(FLAG_CONTENT_VIEW_PUBLIC);
@@ -1661,18 +1636,12 @@
}
}
- @VisibleForTesting
- public NotificationContentInflater getNotificationInflater() {
- return mNotificationInflater;
- }
-
public interface ExpansionLogger {
void logNotificationExpansion(String key, boolean userAction, boolean expanded);
}
public ExpandableNotificationRow(Context context, AttributeSet attrs) {
super(context, attrs);
- mNotificationInflater = new NotificationContentInflater();
mMenuRow = new NotificationMenuRow(mContext);
mImageResolver = new NotificationInlineImageResolver(context,
new NotificationInlineImageCache());
@@ -1680,8 +1649,30 @@
initDimens();
}
- public void setBypassController(KeyguardBypassController bypassController) {
+ /**
+ * Initialize row.
+ */
+ public void initialize(
+ String appName,
+ String notificationKey,
+ ExpansionLogger logger,
+ KeyguardBypassController bypassController,
+ NotificationGroupManager groupManager,
+ HeadsUpManager headsUpManager,
+ NotificationRowContentBinder rowContentBinder,
+ OnExpandClickListener onExpandClickListener) {
+ mAppName = appName;
+ if (mMenuRow != null && mMenuRow.getMenuView() != null) {
+ mMenuRow.setAppName(mAppName);
+ }
+ mLogger = logger;
+ mLoggingKey = notificationKey;
mBypassController = bypassController;
+ mGroupManager = groupManager;
+ mPrivateLayout.setGroupManager(groupManager);
+ mHeadsUpManager = headsUpManager;
+ mNotificationContentBinder = rowContentBinder;
+ mOnExpandClickListener = onExpandClickListener;
}
public void setStatusBarStateController(StatusBarStateController statusBarStateController) {
@@ -1730,6 +1721,8 @@
*/
public void reset() {
mShowingPublicInitialized = false;
+ unDismiss();
+ resetTranslation();
onHeightReset();
requestLayout();
}
@@ -2920,11 +2913,6 @@
return 0;
}
- public void setExpansionLogger(ExpansionLogger logger, String key) {
- mLogger = logger;
- mLoggingKey = key;
- }
-
public void onExpandedByGesture(boolean userExpanded) {
int event = MetricsEvent.ACTION_NOTIFICATION_GESTURE_EXPANDER;
if (mGroupManager.isSummaryOfGroup(mEntry.getSbn())) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCache.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCache.java
new file mode 100644
index 0000000..c11c60f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCache.java
@@ -0,0 +1,75 @@
+/*
+ * 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.systemui.statusbar.notification.row;
+
+import android.widget.RemoteViews;
+
+import androidx.annotation.Nullable;
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
+
+/**
+ * Caches {@link RemoteViews} for a notification's content views.
+ */
+public interface NotifRemoteViewCache {
+
+ /**
+ * Whether the notification has the remote view cached
+ *
+ * @param entry notification
+ * @param flag inflation flag for content view
+ * @return true if the remote view is cached
+ */
+ boolean hasCachedView(NotificationEntry entry, @InflationFlag int flag);
+
+ /**
+ * Get the remote view for the content flag specified.
+ *
+ * @param entry notification
+ * @param flag inflation flag for the content view
+ * @return the remote view if it is cached, null otherwise
+ */
+ @Nullable RemoteViews getCachedView(NotificationEntry entry, @InflationFlag int flag);
+
+ /**
+ * Cache a remote view for a given content flag on a notification.
+ *
+ * @param entry notification
+ * @param flag inflation flag for the content view
+ * @param remoteView remote view to store
+ */
+ void putCachedView(
+ NotificationEntry entry,
+ @InflationFlag int flag,
+ RemoteViews remoteView);
+
+ /**
+ * Remove a cached remote view for a given content flag on a notification.
+ *
+ * @param entry notification
+ * @param flag inflation flag for the content view
+ */
+ void removeCachedView(NotificationEntry entry, @InflationFlag int flag);
+
+ /**
+ * Clear a notification's remote view cache.
+ *
+ * @param entry notification
+ */
+ void clearCache(NotificationEntry entry);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java
new file mode 100644
index 0000000..a6e5c2b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java
@@ -0,0 +1,110 @@
+/*
+ * 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.systemui.statusbar.notification.row;
+
+import android.util.ArrayMap;
+import android.util.SparseArray;
+import android.widget.RemoteViews;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.statusbar.notification.NotificationEntryListener;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
+
+import java.util.Map;
+
+import javax.inject.Inject;
+
+/**
+ * Implementation of remote view cache that keeps remote views cached for all active notifications.
+ */
+public class NotifRemoteViewCacheImpl implements NotifRemoteViewCache {
+ private final Map<NotificationEntry, SparseArray<RemoteViews>> mNotifCachedContentViews =
+ new ArrayMap<>();
+
+ @Inject
+ NotifRemoteViewCacheImpl(NotificationEntryManager entryManager) {
+ entryManager.addNotificationEntryListener(mEntryListener);
+ }
+
+ @Override
+ public boolean hasCachedView(NotificationEntry entry, @InflationFlag int flag) {
+ return getCachedView(entry, flag) != null;
+ }
+
+ @Override
+ public @Nullable RemoteViews getCachedView(NotificationEntry entry, @InflationFlag int flag) {
+ SparseArray<RemoteViews> contentViews = mNotifCachedContentViews.get(entry);
+ if (contentViews == null) {
+ return null;
+ }
+ return contentViews.get(flag);
+ }
+
+ @Override
+ public void putCachedView(
+ NotificationEntry entry,
+ @InflationFlag int flag,
+ RemoteViews remoteView) {
+ /**
+ * TODO: We should be more strict here in the future (i.e. throw an exception) if the
+ * content views aren't created. We don't do that right now because we have edge cases
+ * where we may bind/unbind content after a notification is removed.
+ */
+ SparseArray<RemoteViews> contentViews = mNotifCachedContentViews.get(entry);
+ if (contentViews == null) {
+ return;
+ }
+ contentViews.put(flag, remoteView);
+ }
+
+ @Override
+ public void removeCachedView(NotificationEntry entry, @InflationFlag int flag) {
+ SparseArray<RemoteViews> contentViews = mNotifCachedContentViews.get(entry);
+ if (contentViews == null) {
+ return;
+ }
+ contentViews.remove(flag);
+ }
+
+ @Override
+ public void clearCache(NotificationEntry entry) {
+ SparseArray<RemoteViews> contentViews = mNotifCachedContentViews.get(entry);
+ if (contentViews == null) {
+ return;
+ }
+ contentViews.clear();
+ }
+
+ private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
+ @Override
+ public void onPendingEntryAdded(NotificationEntry entry) {
+ mNotifCachedContentViews.put(entry, new SparseArray<>());
+ }
+
+ @Override
+ public void onEntryRemoved(
+ NotificationEntry entry,
+ @Nullable NotificationVisibility visibility,
+ boolean removedByUser) {
+ mNotifCachedContentViews.remove(entry);
+ }
+ };
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 172b72e..e1a6747 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.row;
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP;
@@ -26,7 +27,6 @@
import android.os.AsyncTask;
import android.os.CancellationSignal;
import android.service.notification.StatusBarNotification;
-import android.util.ArrayMap;
import android.util.Log;
import android.view.View;
import android.widget.RemoteViews;
@@ -35,6 +35,7 @@
import com.android.internal.widget.ImageMessageConsumer;
import com.android.systemui.Dependency;
import com.android.systemui.statusbar.InflationTask;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.InflationException;
import com.android.systemui.statusbar.notification.MediaNotificationProcessor;
@@ -49,17 +50,30 @@
import java.util.HashMap;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
/**
* {@link NotificationContentInflater} binds content to a {@link ExpandableNotificationRow} by
* asynchronously building the content's {@link RemoteViews} and applying it to the row.
*/
+@Singleton
+@VisibleForTesting(visibility = PACKAGE)
public class NotificationContentInflater implements NotificationRowContentBinder {
public static final String TAG = "NotifContentInflater";
- private RemoteViews.OnClickHandler mRemoteViewClickHandler;
private boolean mInflateSynchronously = false;
- private final ArrayMap<Integer, RemoteViews> mCachedContentViews = new ArrayMap<>();
+ private final NotificationRemoteInputManager mRemoteInputManager;
+ private final NotifRemoteViewCache mRemoteViewCache;
+
+ @Inject
+ public NotificationContentInflater(
+ NotifRemoteViewCache remoteViewCache,
+ NotificationRemoteInputManager remoteInputManager) {
+ mRemoteViewCache = remoteViewCache;
+ mRemoteInputManager = remoteInputManager;
+ }
@Override
public void bindContent(
@@ -76,27 +90,27 @@
return;
}
- StatusBarNotification sbn = row.getEntry().getSbn();
+ StatusBarNotification sbn = entry.getSbn();
// To check if the notification has inline image and preload inline image if necessary.
row.getImageResolver().preloadImages(sbn.getNotification());
if (forceInflate) {
- mCachedContentViews.clear();
+ mRemoteViewCache.clearCache(entry);
}
AsyncInflationTask task = new AsyncInflationTask(
- sbn,
mInflateSynchronously,
contentToBind,
- mCachedContentViews,
+ mRemoteViewCache,
+ entry,
row,
bindParams.isLowPriority,
bindParams.isChildInGroup,
bindParams.usesIncreasedHeight,
bindParams.usesIncreasedHeadsUpHeight,
callback,
- mRemoteViewClickHandler);
+ mRemoteInputManager.getRemoteViewsOnClickHandler());
if (mInflateSynchronously) {
task.onPostExecute(task.doInBackground());
} else {
@@ -123,13 +137,15 @@
result = inflateSmartReplyViews(result, reInflateFlags, entry,
row.getContext(), packageContext, row.getHeadsUpManager(),
row.getExistingSmartRepliesAndActions());
+
apply(
inflateSynchronously,
result,
reInflateFlags,
- mCachedContentViews,
+ mRemoteViewCache,
+ entry,
row,
- mRemoteViewClickHandler,
+ mRemoteInputManager.getRemoteViewsOnClickHandler(),
null);
return result;
}
@@ -149,7 +165,7 @@
int curFlag = 1;
while (contentToUnbind != 0) {
if ((contentToUnbind & curFlag) != 0) {
- freeNotificationView(row, curFlag);
+ freeNotificationView(entry, row, curFlag);
}
contentToUnbind &= ~curFlag;
curFlag = curFlag << 1;
@@ -157,34 +173,25 @@
}
/**
- * Set click handler for notification remote views
- *
- * @param remoteViewClickHandler click handler for remote views
- */
- public void setRemoteViewClickHandler(RemoteViews.OnClickHandler remoteViewClickHandler) {
- mRemoteViewClickHandler = remoteViewClickHandler;
- }
-
- /**
* Frees the content view associated with the inflation flag. Will only succeed if the
* view is safe to remove.
*
* @param inflateFlag the flag corresponding to the content view which should be freed
*/
- private void freeNotificationView(ExpandableNotificationRow row,
+ private void freeNotificationView(NotificationEntry entry, ExpandableNotificationRow row,
@InflationFlag int inflateFlag) {
switch (inflateFlag) {
case FLAG_CONTENT_VIEW_HEADS_UP:
if (row.getPrivateLayout().isContentViewInactive(VISIBLE_TYPE_HEADSUP)) {
row.getPrivateLayout().setHeadsUpChild(null);
- mCachedContentViews.remove(FLAG_CONTENT_VIEW_HEADS_UP);
+ mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP);
row.getPrivateLayout().setHeadsUpInflatedSmartReplies(null);
}
break;
case FLAG_CONTENT_VIEW_PUBLIC:
if (row.getPublicLayout().isContentViewInactive(VISIBLE_TYPE_CONTRACTED)) {
row.getPublicLayout().setContractedChild(null);
- mCachedContentViews.remove(FLAG_CONTENT_VIEW_PUBLIC);
+ mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC);
}
break;
case FLAG_CONTENT_VIEW_CONTRACTED:
@@ -245,11 +252,12 @@
return result;
}
- public static CancellationSignal apply(
+ private static CancellationSignal apply(
boolean inflateSynchronously,
InflationProgress result,
@InflationFlag int reInflateFlags,
- ArrayMap<Integer, RemoteViews> cachedContentViews,
+ NotifRemoteViewCache remoteViewCache,
+ NotificationEntry entry,
ExpandableNotificationRow row,
RemoteViews.OnClickHandler remoteViewClickHandler,
@Nullable InflationCallback callback) {
@@ -261,7 +269,7 @@
if ((reInflateFlags & flag) != 0) {
boolean isNewView =
!canReapplyRemoteView(result.newContentView,
- cachedContentViews.get(FLAG_CONTENT_VIEW_CONTRACTED));
+ remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED));
ApplyCallback applyCallback = new ApplyCallback() {
@Override
public void setResultView(View v) {
@@ -273,8 +281,8 @@
return result.newContentView;
}
};
- applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, cachedContentViews,
- row, isNewView, remoteViewClickHandler, callback, privateLayout,
+ applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache,
+ entry, row, isNewView, remoteViewClickHandler, callback, privateLayout,
privateLayout.getContractedChild(), privateLayout.getVisibleWrapper(
NotificationContentView.VISIBLE_TYPE_CONTRACTED),
runningInflations, applyCallback);
@@ -285,7 +293,7 @@
if (result.newExpandedView != null) {
boolean isNewView =
!canReapplyRemoteView(result.newExpandedView,
- cachedContentViews.get(FLAG_CONTENT_VIEW_EXPANDED));
+ remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED));
ApplyCallback applyCallback = new ApplyCallback() {
@Override
public void setResultView(View v) {
@@ -297,8 +305,8 @@
return result.newExpandedView;
}
};
- applyRemoteView(inflateSynchronously, result, reInflateFlags, flag,
- cachedContentViews, row, isNewView, remoteViewClickHandler,
+ applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache,
+ entry, row, isNewView, remoteViewClickHandler,
callback, privateLayout, privateLayout.getExpandedChild(),
privateLayout.getVisibleWrapper(
NotificationContentView.VISIBLE_TYPE_EXPANDED), runningInflations,
@@ -311,7 +319,7 @@
if (result.newHeadsUpView != null) {
boolean isNewView =
!canReapplyRemoteView(result.newHeadsUpView,
- cachedContentViews.get(FLAG_CONTENT_VIEW_HEADS_UP));
+ remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP));
ApplyCallback applyCallback = new ApplyCallback() {
@Override
public void setResultView(View v) {
@@ -323,8 +331,8 @@
return result.newHeadsUpView;
}
};
- applyRemoteView(inflateSynchronously, result, reInflateFlags, flag,
- cachedContentViews, row, isNewView, remoteViewClickHandler,
+ applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache,
+ entry, row, isNewView, remoteViewClickHandler,
callback, privateLayout, privateLayout.getHeadsUpChild(),
privateLayout.getVisibleWrapper(
VISIBLE_TYPE_HEADSUP), runningInflations,
@@ -336,7 +344,7 @@
if ((reInflateFlags & flag) != 0) {
boolean isNewView =
!canReapplyRemoteView(result.newPublicView,
- cachedContentViews.get(FLAG_CONTENT_VIEW_PUBLIC));
+ remoteViewCache.getCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC));
ApplyCallback applyCallback = new ApplyCallback() {
@Override
public void setResultView(View v) {
@@ -348,15 +356,16 @@
return result.newPublicView;
}
};
- applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, cachedContentViews,
- row, isNewView, remoteViewClickHandler, callback,
+ applyRemoteView(inflateSynchronously, result, reInflateFlags, flag, remoteViewCache,
+ entry, row, isNewView, remoteViewClickHandler, callback,
publicLayout, publicLayout.getContractedChild(),
publicLayout.getVisibleWrapper(NotificationContentView.VISIBLE_TYPE_CONTRACTED),
runningInflations, applyCallback);
}
// Let's try to finish, maybe nobody is even inflating anything
- finishIfDone(result, reInflateFlags, cachedContentViews, runningInflations, callback, row);
+ finishIfDone(result, reInflateFlags, remoteViewCache, runningInflations, callback, entry,
+ row);
CancellationSignal cancellationSignal = new CancellationSignal();
cancellationSignal.setOnCancelListener(
() -> runningInflations.values().forEach(CancellationSignal::cancel));
@@ -369,7 +378,8 @@
final InflationProgress result,
final @InflationFlag int reInflateFlags,
@InflationFlag int inflationId,
- final ArrayMap<Integer, RemoteViews> cachedContentViews,
+ final NotifRemoteViewCache remoteViewCache,
+ final NotificationEntry entry,
final ExpandableNotificationRow row,
boolean isNewView,
RemoteViews.OnClickHandler remoteViewClickHandler,
@@ -397,7 +407,7 @@
existingWrapper.onReinflated();
}
} catch (Exception e) {
- handleInflationError(runningInflations, e, row.getEntry().getSbn(), callback);
+ handleInflationError(runningInflations, e, row.getEntry(), callback);
// Add a running inflation to make sure we don't trigger callbacks.
// Safe to do because only happens in tests.
runningInflations.put(inflationId, new CancellationSignal());
@@ -422,8 +432,8 @@
existingWrapper.onReinflated();
}
runningInflations.remove(inflationId);
- finishIfDone(result, reInflateFlags, cachedContentViews, runningInflations,
- callback, row);
+ finishIfDone(result, reInflateFlags, remoteViewCache, runningInflations,
+ callback, entry, row);
}
@Override
@@ -448,7 +458,7 @@
onViewApplied(newView);
} catch (Exception anotherException) {
runningInflations.remove(inflationId);
- handleInflationError(runningInflations, e, row.getEntry().getSbn(),
+ handleInflationError(runningInflations, e, row.getEntry(),
callback);
}
}
@@ -474,7 +484,7 @@
private static void handleInflationError(
HashMap<Integer, CancellationSignal> runningInflations, Exception e,
- StatusBarNotification notification, @Nullable InflationCallback callback) {
+ NotificationEntry notification, @Nullable InflationCallback callback) {
Assert.isMainThread();
runningInflations.values().forEach(CancellationSignal::cancel);
if (callback != null) {
@@ -488,11 +498,11 @@
* @return true if the inflation was finished
*/
private static boolean finishIfDone(InflationProgress result,
- @InflationFlag int reInflateFlags, ArrayMap<Integer, RemoteViews> cachedContentViews,
+ @InflationFlag int reInflateFlags, NotifRemoteViewCache remoteViewCache,
HashMap<Integer, CancellationSignal> runningInflations,
- @Nullable InflationCallback endListener, ExpandableNotificationRow row) {
+ @Nullable InflationCallback endListener, NotificationEntry entry,
+ ExpandableNotificationRow row) {
Assert.isMainThread();
- NotificationEntry entry = row.getEntry();
NotificationContentView privateLayout = row.getPrivateLayout();
NotificationContentView publicLayout = row.getPublicLayout();
if (runningInflations.isEmpty()) {
@@ -500,23 +510,27 @@
if (result.inflatedContentView != null) {
// New view case
privateLayout.setContractedChild(result.inflatedContentView);
- cachedContentViews.put(FLAG_CONTENT_VIEW_CONTRACTED, result.newContentView);
- } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_CONTRACTED) != null) {
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED,
+ result.newContentView);
+ } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED)) {
// Reinflation case. Only update if it's still cached (i.e. view has not been
// freed while inflating).
- cachedContentViews.put(FLAG_CONTENT_VIEW_CONTRACTED, result.newContentView);
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED,
+ result.newContentView);
}
}
if ((reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0) {
if (result.inflatedExpandedView != null) {
privateLayout.setExpandedChild(result.inflatedExpandedView);
- cachedContentViews.put(FLAG_CONTENT_VIEW_EXPANDED, result.newExpandedView);
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED,
+ result.newExpandedView);
} else if (result.newExpandedView == null) {
privateLayout.setExpandedChild(null);
- cachedContentViews.put(FLAG_CONTENT_VIEW_EXPANDED, null);
- } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_EXPANDED) != null) {
- cachedContentViews.put(FLAG_CONTENT_VIEW_EXPANDED, result.newExpandedView);
+ remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED);
+ } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED)) {
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED,
+ result.newExpandedView);
}
if (result.newExpandedView != null) {
privateLayout.setExpandedInflatedSmartReplies(
@@ -530,12 +544,14 @@
if ((reInflateFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
if (result.inflatedHeadsUpView != null) {
privateLayout.setHeadsUpChild(result.inflatedHeadsUpView);
- cachedContentViews.put(FLAG_CONTENT_VIEW_HEADS_UP, result.newHeadsUpView);
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP,
+ result.newHeadsUpView);
} else if (result.newHeadsUpView == null) {
privateLayout.setHeadsUpChild(null);
- cachedContentViews.put(FLAG_CONTENT_VIEW_HEADS_UP, null);
- } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_HEADS_UP) != null) {
- cachedContentViews.put(FLAG_CONTENT_VIEW_HEADS_UP, result.newHeadsUpView);
+ remoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP);
+ } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP)) {
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_HEADS_UP,
+ result.newHeadsUpView);
}
if (result.newHeadsUpView != null) {
privateLayout.setHeadsUpInflatedSmartReplies(
@@ -548,16 +564,18 @@
if ((reInflateFlags & FLAG_CONTENT_VIEW_PUBLIC) != 0) {
if (result.inflatedPublicView != null) {
publicLayout.setContractedChild(result.inflatedPublicView);
- cachedContentViews.put(FLAG_CONTENT_VIEW_PUBLIC, result.newPublicView);
- } else if (cachedContentViews.get(FLAG_CONTENT_VIEW_PUBLIC) != null) {
- cachedContentViews.put(FLAG_CONTENT_VIEW_PUBLIC, result.newPublicView);
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC,
+ result.newPublicView);
+ } else if (remoteViewCache.hasCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC)) {
+ remoteViewCache.putCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC,
+ result.newPublicView);
}
}
entry.headsUpStatusBarText = result.headsUpStatusBarText;
entry.headsUpStatusBarTextPublic = result.headsUpStatusBarTextPublic;
if (endListener != null) {
- endListener.onAsyncInflationFinished(row.getEntry(), reInflateFlags);
+ endListener.onAsyncInflationFinished(entry, reInflateFlags);
}
return true;
}
@@ -615,7 +633,7 @@
public static class AsyncInflationTask extends AsyncTask<Void, Void, InflationProgress>
implements InflationCallback, InflationTask {
- private final StatusBarNotification mSbn;
+ private final NotificationEntry mEntry;
private final Context mContext;
private final boolean mInflateSynchronously;
private final boolean mIsLowPriority;
@@ -624,17 +642,17 @@
private final InflationCallback mCallback;
private final boolean mUsesIncreasedHeadsUpHeight;
private @InflationFlag int mReInflateFlags;
- private final ArrayMap<Integer, RemoteViews> mCachedContentViews;
+ private final NotifRemoteViewCache mRemoteViewCache;
private ExpandableNotificationRow mRow;
private Exception mError;
private RemoteViews.OnClickHandler mRemoteViewClickHandler;
private CancellationSignal mCancellationSignal;
private AsyncInflationTask(
- StatusBarNotification notification,
boolean inflateSynchronously,
@InflationFlag int reInflateFlags,
- ArrayMap<Integer, RemoteViews> cachedContentViews,
+ NotifRemoteViewCache cache,
+ NotificationEntry entry,
ExpandableNotificationRow row,
boolean isLowPriority,
boolean isChildInGroup,
@@ -642,11 +660,11 @@
boolean usesIncreasedHeadsUpHeight,
InflationCallback callback,
RemoteViews.OnClickHandler remoteViewClickHandler) {
+ mEntry = entry;
mRow = row;
- mSbn = notification;
mInflateSynchronously = inflateSynchronously;
mReInflateFlags = reInflateFlags;
- mCachedContentViews = cachedContentViews;
+ mRemoteViewCache = cache;
mContext = mRow.getContext();
mIsLowPriority = isLowPriority;
mIsChildInGroup = isChildInGroup;
@@ -654,7 +672,6 @@
mUsesIncreasedHeadsUpHeight = usesIncreasedHeadsUpHeight;
mRemoteViewClickHandler = remoteViewClickHandler;
mCallback = callback;
- NotificationEntry entry = row.getEntry();
entry.setInflationTask(this);
}
@@ -667,12 +684,13 @@
@Override
protected InflationProgress doInBackground(Void... params) {
try {
+ final StatusBarNotification sbn = mEntry.getSbn();
final Notification.Builder recoveredBuilder
= Notification.Builder.recoverBuilder(mContext,
- mSbn.getNotification());
+ sbn.getNotification());
- Context packageContext = mSbn.getPackageContext(mContext);
- Notification notification = mSbn.getNotification();
+ Context packageContext = sbn.getPackageContext(mContext);
+ Notification notification = sbn.getNotification();
if (notification.isMediaNotification()) {
MediaNotificationProcessor processor = new MediaNotificationProcessor(mContext,
packageContext);
@@ -681,7 +699,7 @@
InflationProgress inflationProgress = createRemoteViews(mReInflateFlags,
recoveredBuilder, mIsLowPriority, mIsChildInGroup, mUsesIncreasedHeight,
mUsesIncreasedHeadsUpHeight, packageContext);
- return inflateSmartReplyViews(inflationProgress, mReInflateFlags, mRow.getEntry(),
+ return inflateSmartReplyViews(inflationProgress, mReInflateFlags, mEntry,
mRow.getContext(), packageContext, mRow.getHeadsUpManager(),
mRow.getExistingSmartRepliesAndActions());
} catch (Exception e) {
@@ -694,20 +712,20 @@
protected void onPostExecute(InflationProgress result) {
if (mError == null) {
mCancellationSignal = apply(mInflateSynchronously, result, mReInflateFlags,
- mCachedContentViews, mRow, mRemoteViewClickHandler, this);
+ mRemoteViewCache, mEntry, mRow, mRemoteViewClickHandler, this);
} else {
handleError(mError);
}
}
private void handleError(Exception e) {
- mRow.getEntry().onInflationTaskFinished();
- StatusBarNotification sbn = mRow.getEntry().getSbn();
+ mEntry.onInflationTaskFinished();
+ StatusBarNotification sbn = mEntry.getSbn();
final String ident = sbn.getPackageName() + "/0x"
+ Integer.toHexString(sbn.getId());
Log.e(StatusBar.TAG, "couldn't inflate view for notification " + ident, e);
if (mCallback != null) {
- mCallback.handleInflationException(sbn,
+ mCallback.handleInflationException(mRow.getEntry(),
new InflationException("Couldn't inflate contentViews" + e));
}
}
@@ -729,17 +747,17 @@
}
@Override
- public void handleInflationException(StatusBarNotification notification, Exception e) {
+ public void handleInflationException(NotificationEntry entry, Exception e) {
handleError(e);
}
@Override
public void onAsyncInflationFinished(NotificationEntry entry,
@InflationFlag int inflatedFlags) {
- mRow.getEntry().onInflationTaskFinished();
+ mEntry.onInflationTaskFinished();
mRow.onNotificationUpdated();
if (mCallback != null) {
- mCallback.onAsyncInflationFinished(mRow.getEntry(), inflatedFlags);
+ mCallback.onAsyncInflationFinished(mEntry, inflatedFlags);
}
// Notify the resolver that the inflation task has finished,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
new file mode 100644
index 0000000..3b106cb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -0,0 +1,642 @@
+/*
+ * 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.systemui.statusbar.notification.row;
+
+import static android.app.Notification.EXTRA_IS_GROUP_CONVERSATION;
+import static android.app.NotificationChannel.PLACEHOLDER_CONVERSATION_ID;
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
+import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
+
+import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_BUBBLE;
+import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_DEMOTE;
+import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_FAVORITE;
+import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_HOME;
+import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_MUTE;
+import static com.android.systemui.statusbar.notification.row.NotificationConversationInfo.UpdateChannelRunnable.ACTION_SNOOZE;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.app.INotificationManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Slog;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+import java.lang.annotation.Retention;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * The guts of a conversation notification revealed when performing a long press.
+ */
+public class NotificationConversationInfo extends LinearLayout implements
+ NotificationGuts.GutsContent {
+ private static final String TAG = "ConversationGuts";
+
+
+ private INotificationManager mINotificationManager;
+ private LauncherApps mLauncherApps;
+ ShortcutManager mShortcutManager;
+ private PackageManager mPm;
+ private VisualStabilityManager mVisualStabilityManager;
+
+ private String mPackageName;
+ private String mAppName;
+ private int mAppUid;
+ private String mDelegatePkg;
+ private NotificationChannel mNotificationChannel;
+ private ShortcutInfo mShortcutInfo;
+ private String mConversationId;
+ private NotificationEntry mEntry;
+ private StatusBarNotification mSbn;
+ private boolean mIsDeviceProvisioned;
+
+ private int mStartingChannelImportance;
+ private boolean mStartedAsBubble;
+ private boolean mIsBubbleable;
+ // TODO: remove when launcher api works
+ @VisibleForTesting
+ boolean mShowHomeScreen = false;
+
+ private @UpdateChannelRunnable.Action int mSelectedAction = -1;
+
+ private OnSnoozeClickListener mOnSnoozeClickListener;
+ private OnSettingsClickListener mOnSettingsClickListener;
+ private OnAppSettingsClickListener mAppSettingsClickListener;
+ private NotificationGuts mGutsContainer;
+ private BubbleController mBubbleController;
+
+ @VisibleForTesting
+ boolean mSkipPost = false;
+
+ private OnClickListener mOnBubbleClick = v -> {
+ mSelectedAction = ACTION_BUBBLE;
+ if (mStartedAsBubble) {
+ mBubbleController.onUserDemotedBubbleFromNotification(mEntry);
+ } else {
+ mBubbleController.onUserCreatedBubbleFromNotification(mEntry);
+ }
+ closeControls(v, true);
+ };
+
+ private OnClickListener mOnHomeClick = v -> {
+ mSelectedAction = ACTION_HOME;
+ mShortcutManager.requestPinShortcut(mShortcutInfo, null);
+ closeControls(v, true);
+ };
+
+ private OnClickListener mOnFavoriteClick = v -> {
+ mSelectedAction = ACTION_FAVORITE;
+ closeControls(v, true);
+ };
+
+ private OnClickListener mOnSnoozeClick = v -> {
+ mSelectedAction = ACTION_SNOOZE;
+ mOnSnoozeClickListener.onClick(v, 1);
+ closeControls(v, true);
+ };
+
+ private OnClickListener mOnMuteClick = v -> {
+ mSelectedAction = ACTION_MUTE;
+ closeControls(v, true);
+ };
+
+ private OnClickListener mOnDemoteClick = v -> {
+ mSelectedAction = ACTION_DEMOTE;
+ closeControls(v, true);
+ };
+
+ public NotificationConversationInfo(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public interface OnSettingsClickListener {
+ void onClick(View v, NotificationChannel channel, int appUid);
+ }
+
+ public interface OnAppSettingsClickListener {
+ void onClick(View v, Intent intent);
+ }
+
+ public interface OnSnoozeClickListener {
+ void onClick(View v, int hoursToSnooze);
+ }
+
+ public void bindNotification(
+ ShortcutManager shortcutManager,
+ LauncherApps launcherApps,
+ PackageManager pm,
+ INotificationManager iNotificationManager,
+ VisualStabilityManager visualStabilityManager,
+ String pkg,
+ NotificationChannel notificationChannel,
+ NotificationEntry entry,
+ OnSettingsClickListener onSettingsClick,
+ OnAppSettingsClickListener onAppSettingsClick,
+ OnSnoozeClickListener onSnoozeClickListener,
+ boolean isDeviceProvisioned) {
+ mSelectedAction = -1;
+ mINotificationManager = iNotificationManager;
+ mVisualStabilityManager = visualStabilityManager;
+ mBubbleController = Dependency.get(BubbleController.class);
+ mPackageName = pkg;
+ mEntry = entry;
+ mSbn = entry.getSbn();
+ mPm = pm;
+ mAppSettingsClickListener = onAppSettingsClick;
+ mAppName = mPackageName;
+ mOnSettingsClickListener = onSettingsClick;
+ mNotificationChannel = notificationChannel;
+ mStartingChannelImportance = mNotificationChannel.getImportance();
+ mAppUid = mSbn.getUid();
+ mDelegatePkg = mSbn.getOpPkg();
+ mIsDeviceProvisioned = isDeviceProvisioned;
+ mOnSnoozeClickListener = onSnoozeClickListener;
+
+ mShortcutManager = shortcutManager;
+ mLauncherApps = launcherApps;
+ mConversationId = mNotificationChannel.getConversationId();
+ if (TextUtils.isEmpty(mNotificationChannel.getConversationId())) {
+ mConversationId = mSbn.getNotification().getShortcutId();
+ }
+ // TODO: flag this when flag exists
+ if (TextUtils.isEmpty(mConversationId)) {
+ mConversationId = mSbn.getId() + mSbn.getTag() + PLACEHOLDER_CONVERSATION_ID;
+ }
+ // TODO: consider querying this earlier in the notification pipeline and passing it in
+ LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery()
+ .setPackage(mPackageName)
+ .setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED)
+ .setShortcutIds(Arrays.asList(mConversationId));
+ List<ShortcutInfo> shortcuts = mLauncherApps.getShortcuts(query, mSbn.getUser());
+ if (shortcuts != null && !shortcuts.isEmpty()) {
+ mShortcutInfo = shortcuts.get(0);
+ }
+
+ mIsBubbleable = mEntry.getBubbleMetadata() != null;
+ mStartedAsBubble = mEntry.isBubble();
+
+ createConversationChannelIfNeeded();
+
+ bindHeader();
+ bindActions();
+
+ }
+
+ void createConversationChannelIfNeeded() {
+ // If this channel is not already a customized conversation channel, create
+ // a custom channel
+ if (TextUtils.isEmpty(mNotificationChannel.getConversationId())) {
+ try {
+ // TODO: associate this key with this channel service side so the customization
+ // isn't forgotten on the next update
+ mINotificationManager.createConversationNotificationChannelForPackage(
+ mPackageName, mAppUid, mNotificationChannel, mConversationId);
+ mNotificationChannel = mINotificationManager.getConversationNotificationChannel(
+ mContext.getOpPackageName(), UserHandle.getUserId(mAppUid), mPackageName,
+ mNotificationChannel.getId(), false, mConversationId);
+
+ // TODO: ask LA to pin the shortcut once api exists for pinning one shortcut at a
+ // time
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Could not create conversation channel", e);
+ }
+ }
+ }
+
+ private void bindActions() {
+ // TODO: figure out what should happen for non-configurable channels
+
+ Button bubble = findViewById(R.id.bubble);
+ bubble.setVisibility(mIsBubbleable ? VISIBLE : GONE);
+ bubble.setOnClickListener(mOnBubbleClick);
+ if (mStartedAsBubble) {
+ bubble.setText(R.string.notification_conversation_unbubble);
+ } else {
+ bubble.setText(R.string.notification_conversation_bubble);
+ }
+
+ Button home = findViewById(R.id.home);
+ home.setOnClickListener(mOnHomeClick);
+ home.setVisibility(mShowHomeScreen && mShortcutInfo != null
+ && mShortcutManager.isRequestPinShortcutSupported()
+ ? VISIBLE : GONE);
+
+ Button favorite = findViewById(R.id.fave);
+ favorite.setOnClickListener(mOnFavoriteClick);
+ if (mNotificationChannel.canBypassDnd()) {
+ favorite.setText(R.string.notification_conversation_unfavorite);
+ favorite.setCompoundDrawablesRelative(
+ mContext.getDrawable(R.drawable.ic_star), null, null, null);
+ } else {
+ favorite.setText(R.string.notification_conversation_favorite);
+ favorite.setCompoundDrawablesRelative(
+ mContext.getDrawable(R.drawable.ic_star_border), null, null, null);
+ }
+
+ Button snooze = findViewById(R.id.snooze);
+ snooze.setOnClickListener(mOnSnoozeClick);
+
+ Button mute = findViewById(R.id.mute);
+ mute.setOnClickListener(mOnMuteClick);
+ if (mStartingChannelImportance >= IMPORTANCE_DEFAULT
+ || mStartingChannelImportance == IMPORTANCE_UNSPECIFIED) {
+ mute.setText(R.string.notification_conversation_mute);
+ favorite.setCompoundDrawablesRelative(
+ mContext.getDrawable(R.drawable.ic_notifications_silence), null, null, null);
+ } else {
+ mute.setText(R.string.notification_conversation_unmute);
+ favorite.setCompoundDrawablesRelative(
+ mContext.getDrawable(R.drawable.ic_notifications_alert), null, null, null);
+ }
+
+ ImageButton demote = findViewById(R.id.demote);
+ demote.setOnClickListener(mOnDemoteClick);
+ }
+
+ private void bindHeader() {
+ bindConversationDetails();
+
+ // Delegate
+ bindDelegate();
+
+ // Set up app settings link (i.e. Customize)
+ View settingsLinkView = findViewById(R.id.app_settings);
+ Intent settingsIntent = getAppSettingsIntent(mPm, mPackageName,
+ mNotificationChannel,
+ mSbn.getId(), mSbn.getTag());
+ if (settingsIntent != null
+ && !TextUtils.isEmpty(mSbn.getNotification().getSettingsText())) {
+ settingsLinkView.setVisibility(VISIBLE);
+ settingsLinkView.setOnClickListener((View view) -> {
+ mAppSettingsClickListener.onClick(view, settingsIntent);
+ });
+ } else {
+ settingsLinkView.setVisibility(View.GONE);
+ }
+
+ // System Settings button.
+ final View settingsButton = findViewById(R.id.info);
+ settingsButton.setOnClickListener(getSettingsOnClickListener());
+ settingsButton.setVisibility(settingsButton.hasOnClickListeners() ? VISIBLE : GONE);
+ }
+
+ private OnClickListener getSettingsOnClickListener() {
+ if (mAppUid >= 0 && mOnSettingsClickListener != null && mIsDeviceProvisioned) {
+ final int appUidF = mAppUid;
+ return ((View view) -> {
+ mOnSettingsClickListener.onClick(view, mNotificationChannel, appUidF);
+ });
+ }
+ return null;
+ }
+
+ private void bindConversationDetails() {
+ final TextView channelName = findViewById(R.id.parent_channel_name);
+ channelName.setText(mNotificationChannel.getName());
+
+ bindGroup();
+ bindName();
+ bindPackage();
+ bindIcon();
+
+ }
+
+ private void bindIcon() {
+ ImageView image = findViewById(R.id.conversation_icon);
+ if (mShortcutInfo != null) {
+ image.setImageDrawable(mLauncherApps.getShortcutBadgedIconDrawable(mShortcutInfo,
+ mContext.getResources().getDisplayMetrics().densityDpi));
+ } else {
+ // TODO: flag this behavior
+ if (mSbn.getNotification().extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION, false)) {
+ // TODO: maybe use a generic group icon, or a composite of recent senders
+ image.setImageDrawable(mPm.getDefaultActivityIcon());
+ } else {
+ final List<Notification.MessagingStyle.Message> messages =
+ Notification.MessagingStyle.Message.getMessagesFromBundleArray(
+ (Parcelable[]) mSbn.getNotification().extras.get(
+ Notification.EXTRA_MESSAGES));
+
+ final Notification.MessagingStyle.Message latestMessage =
+ Notification.MessagingStyle.findLatestIncomingMessage(messages);
+ Icon personIcon = latestMessage.getSenderPerson().getIcon();
+ if (personIcon != null) {
+ image.setImageIcon(latestMessage.getSenderPerson().getIcon());
+ } else {
+ // TODO: choose something better
+ image.setImageDrawable(mPm.getDefaultActivityIcon());
+ }
+ }
+ }
+ }
+
+ private void bindName() {
+ TextView name = findViewById(R.id.name);
+ if (mShortcutInfo != null) {
+ name.setText(mShortcutInfo.getShortLabel());
+ } else {
+ // TODO: flag this behavior
+ Bundle extras = mSbn.getNotification().extras;
+ String nameString = extras.getString(Notification.EXTRA_CONVERSATION_TITLE);
+ if (TextUtils.isEmpty(nameString)) {
+ nameString = extras.getString(Notification.EXTRA_TITLE);
+ }
+ name.setText(nameString);
+ }
+ }
+
+ private void bindPackage() {
+ ApplicationInfo info;
+ try {
+ info = mPm.getApplicationInfo(
+ mPackageName,
+ PackageManager.MATCH_UNINSTALLED_PACKAGES
+ | PackageManager.MATCH_DISABLED_COMPONENTS
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE);
+ if (info != null) {
+ mAppName = String.valueOf(mPm.getApplicationLabel(info));
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ }
+ ((TextView) findViewById(R.id.pkg_name)).setText(mAppName);
+ }
+
+ private void bindDelegate() {
+ TextView delegateView = findViewById(R.id.delegate_name);
+ TextView dividerView = findViewById(R.id.pkg_divider);
+
+ if (!TextUtils.equals(mPackageName, mDelegatePkg)) {
+ // this notification was posted by a delegate!
+ delegateView.setVisibility(View.VISIBLE);
+ dividerView.setVisibility(View.VISIBLE);
+ } else {
+ delegateView.setVisibility(View.GONE);
+ dividerView.setVisibility(View.GONE);
+ }
+ }
+
+ private void bindGroup() {
+ // Set group information if this channel has an associated group.
+ CharSequence groupName = null;
+ if (mNotificationChannel != null && mNotificationChannel.getGroup() != null) {
+ try {
+ final NotificationChannelGroup notificationChannelGroup =
+ mINotificationManager.getNotificationChannelGroupForPackage(
+ mNotificationChannel.getGroup(), mPackageName, mAppUid);
+ if (notificationChannelGroup != null) {
+ groupName = notificationChannelGroup.getName();
+ }
+ } catch (RemoteException e) {
+ }
+ }
+ TextView groupNameView = findViewById(R.id.group_name);
+ View groupDivider = findViewById(R.id.group_divider);
+ if (groupName != null) {
+ groupNameView.setText(groupName);
+ groupNameView.setVisibility(VISIBLE);
+ groupDivider.setVisibility(VISIBLE);
+ } else {
+ groupNameView.setVisibility(GONE);
+ groupDivider.setVisibility(GONE);
+ }
+ }
+
+ @Override
+ public boolean post(Runnable action) {
+ if (mSkipPost) {
+ action.run();
+ return true;
+ } else {
+ return super.post(action);
+ }
+ }
+
+ @Override
+ public void onFinishedClosing() {
+ // TODO: do we need to do anything here?
+ }
+
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ if (mGutsContainer != null &&
+ event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
+ if (mGutsContainer.isExposed()) {
+ event.getText().add(mContext.getString(
+ R.string.notification_channel_controls_opened_accessibility, mAppName));
+ } else {
+ event.getText().add(mContext.getString(
+ R.string.notification_channel_controls_closed_accessibility, mAppName));
+ }
+ }
+ }
+
+ private Intent getAppSettingsIntent(PackageManager pm, String packageName,
+ NotificationChannel channel, int id, String tag) {
+ Intent intent = new Intent(Intent.ACTION_MAIN)
+ .addCategory(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES)
+ .setPackage(packageName);
+ final List<ResolveInfo> resolveInfos = pm.queryIntentActivities(
+ intent,
+ PackageManager.MATCH_DEFAULT_ONLY
+ );
+ if (resolveInfos == null || resolveInfos.size() == 0 || resolveInfos.get(0) == null) {
+ return null;
+ }
+ final ActivityInfo activityInfo = resolveInfos.get(0).activityInfo;
+ intent.setClassName(activityInfo.packageName, activityInfo.name);
+ if (channel != null) {
+ intent.putExtra(Notification.EXTRA_CHANNEL_ID, channel.getId());
+ }
+ intent.putExtra(Notification.EXTRA_NOTIFICATION_ID, id);
+ intent.putExtra(Notification.EXTRA_NOTIFICATION_TAG, tag);
+ return intent;
+ }
+
+ /**
+ * Closes the controls and commits the updated importance values (indirectly).
+ *
+ * <p><b>Note,</b> this will only get called once the view is dismissing. This means that the
+ * user does not have the ability to undo the action anymore.
+ */
+ @VisibleForTesting
+ void closeControls(View v, boolean save) {
+ int[] parentLoc = new int[2];
+ int[] targetLoc = new int[2];
+ mGutsContainer.getLocationOnScreen(parentLoc);
+ v.getLocationOnScreen(targetLoc);
+ final int centerX = v.getWidth() / 2;
+ final int centerY = v.getHeight() / 2;
+ final int x = targetLoc[0] - parentLoc[0] + centerX;
+ final int y = targetLoc[1] - parentLoc[1] + centerY;
+ mGutsContainer.closeControls(x, y, save, false /* force */);
+ }
+
+ @Override
+ public void setGutsParent(NotificationGuts guts) {
+ mGutsContainer = guts;
+ }
+
+ @Override
+ public boolean willBeRemoved() {
+ return false;
+ }
+
+ @Override
+ public boolean shouldBeSaved() {
+ return mSelectedAction > -1;
+ }
+
+ @Override
+ public View getContentView() {
+ return this;
+ }
+
+ @Override
+ public boolean handleCloseControls(boolean save, boolean force) {
+ if (save && mSelectedAction > -1) {
+ Handler bgHandler = new Handler(Dependency.get(Dependency.BG_LOOPER));
+ bgHandler.post(
+ new UpdateChannelRunnable(mINotificationManager, mPackageName,
+ mAppUid, mSelectedAction, mNotificationChannel));
+ mVisualStabilityManager.temporarilyAllowReordering();
+ }
+ return false;
+ }
+
+ @Override
+ public int getActualHeight() {
+ return getHeight();
+ }
+
+ @VisibleForTesting
+ public boolean isAnimating() {
+ return false;
+ }
+
+ static class UpdateChannelRunnable implements Runnable {
+
+ @Retention(SOURCE)
+ @IntDef({ACTION_BUBBLE, ACTION_HOME, ACTION_FAVORITE, ACTION_SNOOZE, ACTION_MUTE,
+ ACTION_DEMOTE})
+ private @interface Action {}
+ static final int ACTION_BUBBLE = 0;
+ static final int ACTION_HOME = 1;
+ static final int ACTION_FAVORITE = 2;
+ static final int ACTION_SNOOZE = 3;
+ static final int ACTION_MUTE = 4;
+ static final int ACTION_DEMOTE = 5;
+
+ private final INotificationManager mINotificationManager;
+ private final String mAppPkg;
+ private final int mAppUid;
+ private NotificationChannel mChannelToUpdate;
+ private final @Action int mAction;
+
+ public UpdateChannelRunnable(INotificationManager notificationManager,
+ String packageName, int appUid, @Action int action,
+ @NonNull NotificationChannel channelToUpdate) {
+ mINotificationManager = notificationManager;
+ mAppPkg = packageName;
+ mAppUid = appUid;
+ mChannelToUpdate = channelToUpdate;
+ mAction = action;
+ }
+
+ @Override
+ public void run() {
+ try {
+ switch (mAction) {
+ case ACTION_BUBBLE:
+ mChannelToUpdate.setAllowBubbles(!mChannelToUpdate.canBubble());
+ break;
+ case ACTION_FAVORITE:
+ // TODO: extend beyond DND
+ mChannelToUpdate.setBypassDnd(!mChannelToUpdate.canBypassDnd());
+ break;
+ case ACTION_MUTE:
+ if (mChannelToUpdate.getImportance() == IMPORTANCE_UNSPECIFIED
+ || mChannelToUpdate.getImportance() >= IMPORTANCE_DEFAULT) {
+ mChannelToUpdate.setImportance(IMPORTANCE_LOW);
+ } else {
+ mChannelToUpdate.setImportance(Math.max(
+ mChannelToUpdate.getOriginalImportance(), IMPORTANCE_DEFAULT));
+ }
+ break;
+ case ACTION_DEMOTE:
+ mChannelToUpdate.setDemoted(!mChannelToUpdate.isDemoted());
+ break;
+
+ }
+
+ if (mAction != ACTION_HOME && mAction != ACTION_SNOOZE) {
+ mINotificationManager.updateNotificationChannelForPackage(
+ mAppPkg, mAppUid, mChannelToUpdate);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to update notification channel", e);
+ }
+ }
+ }
+
+ @Retention(SOURCE)
+ @IntDef({BEHAVIOR_ALERTING, BEHAVIOR_SILENT, BEHAVIOR_BUBBLE})
+ private @interface AlertingBehavior {}
+ private static final int BEHAVIOR_ALERTING = 0;
+ private static final int BEHAVIOR_SILENT = 1;
+ private static final int BEHAVIOR_BUBBLE = 2;
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
index 6f2abba..6789c81 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java
@@ -23,7 +23,9 @@
import android.app.NotificationChannel;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
+import android.content.pm.ShortcutManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -53,6 +55,7 @@
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.row.NotificationInfo.CheckSaveListener;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -81,6 +84,7 @@
private final Context mContext;
private final VisualStabilityManager mVisualStabilityManager;
private final AccessibilityManager mAccessibilityManager;
+ private final HighPriorityProvider mHighPriorityProvider;
// Dependencies:
private final NotificationLockscreenUserManager mLockscreenUserManager =
@@ -109,12 +113,14 @@
@Inject
public NotificationGutsManager(Context context, VisualStabilityManager visualStabilityManager,
Lazy<StatusBar> statusBarLazy, @Main Handler mainHandler,
- AccessibilityManager accessibilityManager) {
+ AccessibilityManager accessibilityManager,
+ HighPriorityProvider highPriorityProvider) {
mContext = context;
mVisualStabilityManager = visualStabilityManager;
mStatusBarLazy = statusBarLazy;
mMainHandler = mainHandler;
mAccessibilityManager = accessibilityManager;
+ mHighPriorityProvider = highPriorityProvider;
}
public void setUpWithPresenter(NotificationPresenter presenter,
@@ -224,6 +230,9 @@
initializeAppOpsInfo(row, (AppOpsInfo) gutsView);
} else if (gutsView instanceof NotificationInfo) {
initializeNotificationInfo(row, (NotificationInfo) gutsView);
+ } else if (gutsView instanceof NotificationConversationInfo) {
+ initializeConversationNotificationInfo(
+ row, (NotificationConversationInfo) gutsView);
}
return true;
} catch (Exception e) {
@@ -331,7 +340,66 @@
row.getIsNonblockable(),
isForBlockingHelper,
row.getEntry().getImportance(),
- row.getEntry().isHighPriority());
+ mHighPriorityProvider.isHighPriority(row.getEntry()));
+ }
+
+ /**
+ * Sets up the {@link NotificationConversationInfo} inside the notification row's guts.
+ * @param row view to set up the guts for
+ * @param notificationInfoView view to set up/bind within {@code row}
+ */
+ @VisibleForTesting
+ void initializeConversationNotificationInfo(
+ final ExpandableNotificationRow row,
+ NotificationConversationInfo notificationInfoView) throws Exception {
+ NotificationGuts guts = row.getGuts();
+ StatusBarNotification sbn = row.getEntry().getSbn();
+ String packageName = sbn.getPackageName();
+ // Settings link is only valid for notifications that specify a non-system user
+ NotificationConversationInfo.OnSettingsClickListener onSettingsClick = null;
+ UserHandle userHandle = sbn.getUser();
+ PackageManager pmUser = StatusBar.getPackageManagerForUser(
+ mContext, userHandle.getIdentifier());
+ LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class);
+ ShortcutManager shortcutManager = mContext.getSystemService(ShortcutManager.class);
+ INotificationManager iNotificationManager = INotificationManager.Stub.asInterface(
+ ServiceManager.getService(Context.NOTIFICATION_SERVICE));
+ final NotificationConversationInfo.OnAppSettingsClickListener onAppSettingsClick =
+ (View v, Intent intent) -> {
+ mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_APP_NOTE_SETTINGS);
+ guts.resetFalsingCheck();
+ mNotificationActivityStarter.startNotificationGutsIntent(intent, sbn.getUid(),
+ row);
+ };
+
+ final NotificationConversationInfo.OnSnoozeClickListener onSnoozeClickListener =
+ (View v, int hours) -> {
+ mListContainer.getSwipeActionHelper().snooze(sbn, hours);
+ };
+
+ if (!userHandle.equals(UserHandle.ALL)
+ || mLockscreenUserManager.getCurrentUserId() == UserHandle.USER_SYSTEM) {
+ onSettingsClick = (View v, NotificationChannel channel, int appUid) -> {
+ mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO);
+ guts.resetFalsingCheck();
+ mOnSettingsClickListener.onSettingsClick(sbn.getKey());
+ startAppNotificationSettingsActivity(packageName, appUid, channel, row);
+ };
+ }
+
+ notificationInfoView.bindNotification(
+ shortcutManager,
+ launcherApps,
+ pmUser,
+ iNotificationManager,
+ mVisualStabilityManager,
+ packageName,
+ row.getEntry().getChannel(),
+ row.getEntry(),
+ onSettingsClick,
+ onAppSettingsClick,
+ onSnoozeClickListener,
+ mDeviceProvisionedController.isDeviceProvisioned());
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
index b4ccb56..212cba6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationMenuRow.java
@@ -46,6 +46,7 @@
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.statusbar.AlphaOptimizedImageView;
import com.android.systemui.statusbar.notification.row.NotificationGuts.GutsContent;
+import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import java.util.ArrayList;
@@ -82,7 +83,6 @@
private OnMenuEventListener mMenuListener;
private boolean mDismissRtl;
private boolean mIsForeground;
- private final boolean mIsUsingBidirectionalSwipe;
private ValueAnimator mFadeAnimator;
private boolean mAnimating;
@@ -115,19 +115,11 @@
private boolean mIsUserTouching;
public NotificationMenuRow(Context context) {
- //TODO: (b/131242807) not using bidirectional swipe for now
- this(context, false);
- }
-
- // Only needed for testing until we want to turn bidirectional swipe back on
- @VisibleForTesting
- NotificationMenuRow(Context context, boolean isUsingBidirectionalSwipe) {
mContext = context;
mShouldShowMenu = context.getResources().getBoolean(R.bool.config_showNotificationGear);
mHandler = new Handler(Looper.getMainLooper());
mLeftMenuItems = new ArrayList<>();
mRightMenuItems = new ArrayList<>();
- mIsUsingBidirectionalSwipe = isUsingBidirectionalSwipe;
}
@Override
@@ -268,23 +260,18 @@
mSnoozeItem = createSnoozeItem(mContext);
}
mAppOpsItem = createAppOpsItem(mContext);
- if (mIsUsingBidirectionalSwipe) {
- mInfoItem = createInfoItem(mContext, !mParent.getEntry().isHighPriority());
+ if (mParent.getEntry().getBucket() == NotificationSectionsManager.BUCKET_PEOPLE) {
+ mInfoItem = createConversationItem(mContext);
} else {
mInfoItem = createInfoItem(mContext);
}
- if (!mIsUsingBidirectionalSwipe) {
- if (!isForeground && showSnooze) {
- mRightMenuItems.add(mSnoozeItem);
- }
- mRightMenuItems.add(mInfoItem);
- mRightMenuItems.add(mAppOpsItem);
- mLeftMenuItems.addAll(mRightMenuItems);
- } else {
- ArrayList<MenuItem> menuItems = mDismissRtl ? mLeftMenuItems : mRightMenuItems;
- menuItems.add(mInfoItem);
+ if (!isForeground && showSnooze) {
+ mRightMenuItems.add(mSnoozeItem);
}
+ mRightMenuItems.add(mInfoItem);
+ mRightMenuItems.add(mAppOpsItem);
+ mLeftMenuItems.addAll(mRightMenuItems);
populateMenuViews();
if (resetState) {
@@ -631,12 +618,12 @@
@Override
public boolean shouldShowGutsOnSnapOpen() {
- return mIsUsingBidirectionalSwipe;
+ return false;
}
@Override
public MenuItem menuItemToExposeOnSnap() {
- return mIsUsingBidirectionalSwipe ? mInfoItem : null;
+ return null;
}
@Override
@@ -662,6 +649,16 @@
return snooze;
}
+ static NotificationMenuItem createConversationItem(Context context) {
+ Resources res = context.getResources();
+ String infoDescription = res.getString(R.string.notification_menu_gear_description);
+ NotificationConversationInfo infoContent =
+ (NotificationConversationInfo) LayoutInflater.from(context).inflate(
+ R.layout.notification_conversation_info, null, false);
+ return new NotificationMenuItem(context, infoDescription, infoContent,
+ R.drawable.ic_settings);
+ }
+
static NotificationMenuItem createInfoItem(Context context) {
Resources res = context.getResources();
String infoDescription = res.getString(R.string.notification_menu_gear_description);
@@ -671,17 +668,6 @@
R.drawable.ic_settings);
}
- static NotificationMenuItem createInfoItem(Context context, boolean isCurrentlySilent) {
- Resources res = context.getResources();
- String infoDescription = res.getString(R.string.notification_menu_gear_description);
- NotificationInfo infoContent = (NotificationInfo) LayoutInflater.from(context).inflate(
- R.layout.notification_info, null, false);
- int iconResId = isCurrentlySilent
- ? R.drawable.ic_notifications_silence
- : R.drawable.ic_notifications_alert;
- return new NotificationMenuItem(context, infoDescription, infoContent, iconResId);
- }
-
static MenuItem createAppOpsItem(Context context) {
AppOpsInfo appOpsContent = (AppOpsInfo) LayoutInflater.from(context).inflate(
R.layout.app_ops_info, null, false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
index 2fe54c0..9b95bff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
@@ -19,7 +19,6 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.service.notification.StatusBarNotification;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -138,10 +137,10 @@
/**
* Callback for when there is an inflation exception
*
- * @param notification notification which failed to inflate content
+ * @param entry notification which failed to inflate content
* @param e exception
*/
- void handleInflationException(StatusBarNotification notification, Exception e);
+ void handleInflationException(NotificationEntry entry, Exception e);
/**
* Callback for after the content views finish inflating.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
new file mode 100644
index 0000000..df8653c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java
@@ -0,0 +1,44 @@
+/*
+ * 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.systemui.statusbar.notification.row;
+
+import javax.inject.Singleton;
+
+import dagger.Binds;
+import dagger.Module;
+
+/**
+ * Dagger Module containing notification row and view inflation implementations.
+ */
+@Module
+public abstract class NotificationRowModule {
+ /**
+ * Provides notification row content binder instance.
+ */
+ @Binds
+ @Singleton
+ public abstract NotificationRowContentBinder provideNotificationRowContentBinder(
+ NotificationContentInflater contentBinderImpl);
+
+ /**
+ * Provides notification remote view cache instance.
+ */
+ @Binds
+ @Singleton
+ public abstract NotifRemoteViewCache provideNotifRemoteViewCache(
+ NotifRemoteViewCacheImpl cacheImpl);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
index 352ba0f..76fdfc6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
@@ -28,6 +28,7 @@
import android.media.session.PlaybackState;
import android.metrics.LogMaker;
import android.os.Handler;
+import android.service.notification.StatusBarNotification;
import android.text.format.DateUtils;
import android.view.LayoutInflater;
import android.view.View;
@@ -176,27 +177,30 @@
final MediaSession.Token token = mRow.getEntry().getSbn().getNotification().extras
.getParcelable(Notification.EXTRA_MEDIA_SESSION);
- if (Utils.useQsMediaPlayer(mContext)) {
+ if (Utils.useQsMediaPlayer(mContext) && token != null) {
final int[] compactActions = mRow.getEntry().getSbn().getNotification().extras
.getIntArray(Notification.EXTRA_COMPACT_ACTIONS);
int tintColor = getNotificationHeader().getOriginalIconColor();
StatusBarWindowController ctrl = Dependency.get(StatusBarWindowController.class);
QuickQSPanel panel = ctrl.getStatusBarView().findViewById(
com.android.systemui.R.id.quick_qs_panel);
+ StatusBarNotification sbn = mRow.getEntry().getSbn();
+ Notification notif = sbn.getNotification();
panel.getMediaPlayer().setMediaSession(token,
- mRow.getEntry().getSbn().getNotification().getSmallIcon(),
+ notif.getSmallIcon(),
tintColor,
mBackgroundColor,
mActions,
- compactActions);
+ compactActions,
+ notif.contentIntent);
QSPanel bigPanel = ctrl.getStatusBarView().findViewById(
com.android.systemui.R.id.quick_settings_panel);
bigPanel.addMediaSession(token,
- mRow.getEntry().getSbn().getNotification().getSmallIcon(),
+ notif.getSmallIcon(),
tintColor,
mBackgroundColor,
mActions,
- mRow.getEntry().getSbn());
+ sbn);
}
boolean showCompactSeekbar = mMediaManager.getShowCompactMediaSeekbar();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
index 2761689..09c1fad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.java
@@ -187,18 +187,18 @@
@Override
public boolean beginsSection(@NonNull View view, @Nullable View previous) {
boolean begin = false;
- if (view instanceof ExpandableNotificationRow) {
- if (previous instanceof ExpandableNotificationRow) {
+ if (view instanceof ActivatableNotificationView) {
+ if (previous instanceof ActivatableNotificationView) {
// If we're drawing the first non-person notification, break out a section
- ExpandableNotificationRow curr = (ExpandableNotificationRow) view;
- ExpandableNotificationRow prev = (ExpandableNotificationRow) previous;
+ ActivatableNotificationView curr = (ActivatableNotificationView) view;
+ ActivatableNotificationView prev = (ActivatableNotificationView) previous;
- begin = curr.getEntry().getBucket() != prev.getEntry().getBucket();
+ begin = getBucket(curr) != getBucket(prev);
}
}
if (!begin) {
- begin = view == mGentleHeader || previous == mPeopleHubView;
+ begin = view == mGentleHeader || view == mPeopleHubView;
}
return begin;
@@ -230,29 +230,42 @@
return;
}
- int lastPersonIndex = -1;
- int firstGentleNotifIndex = -1;
+ boolean peopleNotificationsPresent = false;
+ int firstNonHeadsUpIndex = -1;
+ int firstGentleIndex = -1;
+ int notifCount = 0;
final int n = mParent.getChildCount();
for (int i = 0; i < n; i++) {
View child = mParent.getChildAt(i);
- if (child instanceof ExpandableNotificationRow
- && child.getVisibility() != View.GONE) {
+ if (child instanceof ExpandableNotificationRow && child.getVisibility() != View.GONE) {
+ notifCount++;
ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ if (firstNonHeadsUpIndex == -1 && !row.isHeadsUp()) {
+ firstNonHeadsUpIndex = i;
+ }
if (row.getEntry().getBucket() == BUCKET_PEOPLE) {
- lastPersonIndex = i;
+ peopleNotificationsPresent = true;
}
if (row.getEntry().getBucket() == BUCKET_SILENT) {
- firstGentleNotifIndex = i;
+ firstGentleIndex = i;
break;
}
}
}
- // make room for peopleHub
- firstGentleNotifIndex += adjustPeopleHubVisibilityAndPosition(lastPersonIndex);
+ if (firstNonHeadsUpIndex == -1) {
+ firstNonHeadsUpIndex = firstGentleIndex != -1 ? firstGentleIndex : notifCount;
+ }
- adjustGentleHeaderVisibilityAndPosition(firstGentleNotifIndex);
+ // make room for peopleHub
+ int offset = adjustPeopleHubVisibilityAndPosition(
+ firstNonHeadsUpIndex, peopleNotificationsPresent);
+ if (firstGentleIndex != -1) {
+ firstGentleIndex += offset;
+ }
+
+ adjustGentleHeaderVisibilityAndPosition(firstGentleIndex);
mGentleHeader.setAreThereDismissableGentleNotifs(
mParent.hasActiveClearableNotifications(ROWS_GENTLE));
@@ -294,13 +307,15 @@
}
}
- private int adjustPeopleHubVisibilityAndPosition(int lastPersonIndex) {
- final boolean showPeopleHeader = mPeopleHubVisible
- && mNumberOfSections > 2
- && mStatusBarStateController.getState() != StatusBarState.KEYGUARD;
+ private int adjustPeopleHubVisibilityAndPosition(
+ int targetIndex, boolean peopleNotificationsPresent) {
+ final boolean showPeopleHeader = mNumberOfSections > 2
+ && mStatusBarStateController.getState() != StatusBarState.KEYGUARD
+ && (peopleNotificationsPresent || mPeopleHubVisible);
final int currentHubIndex = mParent.indexOfChild(mPeopleHubView);
final boolean currentlyVisible = currentHubIndex >= 0;
- int targetIndex = lastPersonIndex + 1;
+
+ mPeopleHubView.setCanSwipe(showPeopleHeader && !peopleNotificationsPresent);
if (!showPeopleHeader) {
if (currentlyVisible) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 71342c5..dc2d99c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -134,7 +134,7 @@
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.NotificationGroupManager.OnGroupChangeListener;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
-import com.android.systemui.statusbar.phone.NotificationPanelView;
+import com.android.systemui.statusbar.phone.NotificationPanelViewController;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -496,8 +496,7 @@
protected boolean mClearAllEnabled;
private Interpolator mHideXInterpolator = Interpolators.FAST_OUT_SLOW_IN;
- private NotificationPanelView mNotificationPanel;
- private final ShadeController mShadeController = Dependency.get(ShadeController.class);
+ private NotificationPanelViewController mNotificationPanelController;
private final NotificationGutsManager mNotificationGutsManager;
private final NotificationSectionsManager mSectionsManager;
@@ -5519,7 +5518,8 @@
if (viewsToRemove.isEmpty()) {
if (closeShade) {
- mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
+ Dependency.get(ShadeController.class).animateCollapsePanels(
+ CommandQueue.FLAG_EXCLUDE_NONE);
}
return;
}
@@ -5577,11 +5577,12 @@
final Runnable onSlideAwayAnimationComplete = () -> {
if (closeShade) {
- mShadeController.addPostCollapseAction(() -> {
+ Dependency.get(ShadeController.class).addPostCollapseAction(() -> {
setDismissAllInProgress(false);
onAnimationComplete.run();
});
- mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
+ Dependency.get(ShadeController.class).animateCollapsePanels(
+ CommandQueue.FLAG_EXCLUDE_NONE);
} else {
setDismissAllInProgress(false);
onAnimationComplete.run();
@@ -5657,8 +5658,9 @@
}
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
- public void setNotificationPanel(NotificationPanelView notificationPanelView) {
- mNotificationPanel = notificationPanelView;
+ public void setNotificationPanelController(
+ NotificationPanelViewController notificationPanelViewController) {
+ mNotificationPanelController = notificationPanelViewController;
}
public void updateIconAreaViews() {
@@ -6205,6 +6207,11 @@
}
@Override
+ public void onSnooze(StatusBarNotification sbn, int hours) {
+ mStatusBar.setNotificationSnoozed(sbn, hours);
+ }
+
+ @Override
public boolean shouldDismissQuickly() {
return NotificationStackScrollLayout.this.isExpanded() && mAmbientState.isFullyAwake();
}
@@ -6402,7 +6409,7 @@
if (!mAmbientState.isDozing() || startingChild != null) {
// We have notifications, go to locked shade.
- mShadeController.goToLockedShade(startingChild);
+ Dependency.get(ShadeController.class).goToLockedShade(startingChild);
if (startingChild instanceof ExpandableNotificationRow) {
ExpandableNotificationRow row = (ExpandableNotificationRow) startingChild;
row.onExpandedByGesture(true /* drag down is always an open */);
@@ -6441,7 +6448,7 @@
@Override
public void setEmptyDragAmount(float amount) {
- mNotificationPanel.setEmptyDragAmount(amount);
+ mNotificationPanelController.setEmptyDragAmount(amount);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
index 6d0fcc3..6c0655e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelper.java
@@ -31,6 +31,7 @@
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
class NotificationSwipeHelper extends SwipeHelper implements NotificationSwipeActionHelper {
@@ -281,6 +282,11 @@
mCallback.onSnooze(sbn, snoozeOption);
}
+ @Override
+ public void snooze(StatusBarNotification sbn, int hours) {
+ mCallback.onSnooze(sbn, hours);
+ }
+
@VisibleForTesting
protected void handleMenuCoveredOrDismissed() {
View exposedMenuView = getExposedMenuView();
@@ -298,8 +304,8 @@
@Override
public Animator getViewTranslationAnimator(View v, float target,
ValueAnimator.AnimatorUpdateListener listener) {
- if (v instanceof SwipeableView) {
- return ((SwipeableView) v).getTranslateViewAnimator(target, listener);
+ if (v instanceof ExpandableNotificationRow) {
+ return ((ExpandableNotificationRow) v).getTranslateViewAnimator(target, listener);
} else {
return superGetViewTranslationAnimator(v, target, listener);
}
@@ -446,6 +452,8 @@
void onSnooze(StatusBarNotification sbn, SnoozeOption snoozeOption);
+ void onSnooze(StatusBarNotification sbn, int hours);
+
void onDismiss();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt
index a079606..e5717ae 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/PeopleHubView.kt
@@ -16,38 +16,22 @@
package com.android.systemui.statusbar.notification.stack
-import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
-import android.animation.ObjectAnimator
-import android.animation.ValueAnimator
import android.content.Context
import android.util.AttributeSet
-import android.util.FloatProperty
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
-import android.widget.LinearLayout
-import android.widget.TextView
import com.android.systemui.R
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin
import com.android.systemui.statusbar.notification.people.DataListener
import com.android.systemui.statusbar.notification.people.PersonViewModel
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView
-private val TRANSLATE_CONTENT = object : FloatProperty<PeopleHubView>("translate") {
- override fun setValue(view: PeopleHubView, value: Float) {
- view.translation = value
- }
-
- override fun get(view: PeopleHubView) = view.translation
-}
-
class PeopleHubView(context: Context, attrs: AttributeSet) :
ActivatableNotificationView(context, attrs), SwipeableView {
private lateinit var contents: ViewGroup
private lateinit var personControllers: List<PersonDataListenerImpl>
- private var translateAnim: ObjectAnimator? = null
val personViewAdapters: Sequence<DataListener<PersonViewModel?>>
get() = personControllers.asSequence()
@@ -56,9 +40,10 @@
super.onFinishInflate()
contents = requireViewById(R.id.people_list)
personControllers = (0 until contents.childCount)
+ .reversed()
.asSequence()
.mapNotNull { idx ->
- (contents.getChildAt(idx) as? LinearLayout)?.let(::PersonDataListenerImpl)
+ (contents.getChildAt(idx) as? ImageView)?.let(::PersonDataListenerImpl)
}
.toList()
}
@@ -69,41 +54,32 @@
override fun createMenu(): NotificationMenuRowPlugin? = null
- override fun getTranslateViewAnimator(
- leftTarget: Float,
- listener: ValueAnimator.AnimatorUpdateListener?
- ): Animator =
- ObjectAnimator
- .ofFloat(this, TRANSLATE_CONTENT, leftTarget)
- .apply {
- listener?.let { addUpdateListener(listener) }
- addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(anim: Animator) {
- translateAnim = null
- }
- })
- }
- .also {
- translateAnim?.cancel()
- translateAnim = it
- }
-
override fun resetTranslation() {
- translateAnim?.cancel()
translationX = 0f
}
- private inner class PersonDataListenerImpl(val viewGroup: ViewGroup) :
+ override fun setTranslation(translation: Float) {
+ if (canSwipe) {
+ super.setTranslation(translation)
+ }
+ }
+
+ var canSwipe: Boolean = true
+ set(value) {
+ if (field != value) {
+ if (field) {
+ resetTranslation()
+ }
+ field = value
+ }
+ }
+
+ private inner class PersonDataListenerImpl(val avatarView: ImageView) :
DataListener<PersonViewModel?> {
- val nameView = viewGroup.requireViewById<TextView>(R.id.person_name)
- val avatarView = viewGroup.requireViewById<ImageView>(R.id.person_icon)
-
override fun onDataChanged(data: PersonViewModel?) {
- viewGroup.visibility = data?.let { View.VISIBLE } ?: View.INVISIBLE
- nameView.text = data?.name
avatarView.setImageDrawable(data?.icon)
- viewGroup.setOnClickListener { data?.onClick?.invoke() }
+ avatarView.setOnClickListener { data?.onClick?.invoke() }
}
}
}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SwipeableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SwipeableView.java
index 6c6ef61..49e59a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SwipeableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/SwipeableView.java
@@ -16,9 +16,6 @@
package com.android.systemui.statusbar.notification.stack;
-import android.animation.Animator;
-import android.animation.ValueAnimator;
-
import androidx.annotation.Nullable;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -32,10 +29,6 @@
/** Optionally creates a menu for this view. */
@Nullable NotificationMenuRowPlugin createMenu();
- /** Animator for translating the view, simulating a swipe. */
- Animator getTranslateViewAnimator(
- float leftTarget, ValueAnimator.AnimatorUpdateListener listener);
-
/** Sets the translation amount for an in-progress swipe. */
void setTranslation(float translate);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index 8b31da4..e03db2c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -230,7 +230,7 @@
// The shelf will be hidden when dozing with a custom clock, we must show notification
// icons in this occasion.
if (mStatusBarStateController.isDozing()
- && mStatusBarComponent.getPanel().hasCustomClock()) {
+ && mStatusBarComponent.getPanelController().hasCustomClock()) {
state |= DISABLE_CLOCK | DISABLE_SYSTEM_INFO;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index 09ebb64..accd2a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -70,7 +70,6 @@
boolean mWakeLockScreenPerformsAuth = SystemProperties.getBoolean(
"persist.sysui.wake_performs_auth", true);
private boolean mDozingRequested;
- private boolean mDozing;
private boolean mPulsing;
private WakefulnessLifecycle mWakefulnessLifecycle;
private final SysuiStatusBarStateController mStatusBarStateController;
@@ -92,7 +91,7 @@
private final LockscreenLockIconController mLockscreenLockIconController;
private NotificationIconAreaController mNotificationIconAreaController;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
- private NotificationPanelView mNotificationPanel;
+ private NotificationPanelViewController mNotificationPanel;
private View mAmbientIndicationContainer;
private StatusBar mStatusBar;
@@ -142,7 +141,7 @@
NotificationIconAreaController notificationIconAreaController,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
StatusBarWindowViewController statusBarWindowViewController,
- NotificationPanelView notificationPanel, View ambientIndicationContainer) {
+ NotificationPanelViewController notificationPanel, View ambientIndicationContainer) {
mStatusBar = statusBar;
mNotificationIconAreaController = notificationIconAreaController;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
@@ -196,8 +195,8 @@
public void startDozing() {
if (!mDozingRequested) {
mDozingRequested = true;
- mDozeLog.traceDozing(mDozing);
updateDozing();
+ mDozeLog.traceDozing(mStatusBarStateController.isDozing());
mStatusBar.updateIsKeyguard();
}
}
@@ -281,8 +280,8 @@
public void stopDozing() {
if (mDozingRequested) {
mDozingRequested = false;
- mDozeLog.traceDozing(mDozing);
updateDozing();
+ mDozeLog.traceDozing(mStatusBarStateController.isDozing());
}
}
@@ -292,7 +291,7 @@
mDozeLog.tracePulseTouchDisabledByProx(ignore);
}
mIgnoreTouchWhilePulsing = ignore;
- if (mDozing && ignore) {
+ if (mStatusBarStateController.isDozing() && ignore) {
mStatusBarWindowViewController.cancelCurrentTouch();
}
}
@@ -448,10 +447,6 @@
return mAnimateScreenOff;
}
- public void setDozing(boolean dozing) {
- mDozing = dozing;
- }
-
boolean getIgnoreTouchWhilePulsing() {
return mIgnoreTouchWhilePulsing;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
index f25f910..9840a7b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
@@ -97,6 +97,8 @@
// The edge width where touch down is allowed
private int mEdgeWidth;
+ // The bottom gesture area height
+ private int mBottomGestureHeight;
// The slop to distinguish between horizontal and vertical motion
private final float mTouchSlop;
// Duration after which we consider the event as longpress.
@@ -174,6 +176,8 @@
public void updateCurrentUserResources(Resources res) {
mEdgeWidth = res.getDimensionPixelSize(
com.android.internal.R.dimen.config_backGestureInset);
+ mBottomGestureHeight = res.getDimensionPixelSize(
+ com.android.internal.R.dimen.navigation_bar_gesture_height);
}
/**
@@ -316,6 +320,11 @@
return false;
}
+ // Disallow if we are in the bottom gesture area
+ if (y >= (mDisplaySize.y - mBottomGestureHeight)) {
+ return false;
+ }
+
// Always allow if the user is in a transient sticky immersive state
if (mIsNavBarShownTransiently) {
return true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index 8e5a912..7b20a7b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -58,7 +58,7 @@
private final View mClockView;
private final View mOperatorNameView;
private final DarkIconDispatcher mDarkIconDispatcher;
- private final NotificationPanelView mPanelView;
+ private final NotificationPanelViewController mNotificationPanelViewController;
private final Consumer<ExpandableNotificationRow>
mSetTrackingHeadsUp = this::setTrackingHeadsUp;
private final Runnable mUpdatePanelTranslation = this::updatePanelTranslation;
@@ -96,13 +96,14 @@
SysuiStatusBarStateController statusBarStateController,
KeyguardBypassController keyguardBypassController,
KeyguardStateController keyguardStateController,
- NotificationWakeUpCoordinator wakeUpCoordinator, CommandQueue commandQueue) {
+ NotificationWakeUpCoordinator wakeUpCoordinator, CommandQueue commandQueue,
+ NotificationPanelViewController notificationPanelViewController) {
this(notificationIconAreaController, headsUpManager, statusBarStateController,
keyguardBypassController, wakeUpCoordinator, keyguardStateController,
commandQueue,
statusbarView.findViewById(R.id.heads_up_status_bar_view),
statusbarView.findViewById(R.id.notification_stack_scroller),
- statusbarView.findViewById(R.id.notification_panel),
+ notificationPanelViewController,
statusbarView.findViewById(R.id.clock),
statusbarView.findViewById(R.id.operator_name_frame),
statusbarView.findViewById(R.id.centered_icon_area));
@@ -119,7 +120,7 @@
CommandQueue commandQueue,
HeadsUpStatusBarView headsUpStatusBarView,
NotificationStackScrollLayout stackScroller,
- NotificationPanelView panelView,
+ NotificationPanelViewController notificationPanelViewController,
View clockView,
View operatorNameView,
View centeredIconView) {
@@ -131,10 +132,10 @@
headsUpStatusBarView.setOnDrawingRectChangedListener(
() -> updateIsolatedIconLocation(true /* requireUpdate */));
mStackScroller = stackScroller;
- mPanelView = panelView;
- panelView.addTrackingHeadsUpListener(mSetTrackingHeadsUp);
- panelView.addVerticalTranslationListener(mUpdatePanelTranslation);
- panelView.setHeadsUpAppearanceController(this);
+ mNotificationPanelViewController = notificationPanelViewController;
+ notificationPanelViewController.addTrackingHeadsUpListener(mSetTrackingHeadsUp);
+ notificationPanelViewController.addVerticalTranslationListener(mUpdatePanelTranslation);
+ notificationPanelViewController.setHeadsUpAppearanceController(this);
mStackScroller.addOnExpandedHeightChangedListener(mSetExpandedHeight);
mStackScroller.addOnLayoutChangeListener(mStackScrollLayoutChangeListener);
mStackScroller.setHeadsUpAppearanceController(this);
@@ -169,9 +170,9 @@
mHeadsUpManager.removeListener(this);
mHeadsUpStatusBarView.setOnDrawingRectChangedListener(null);
mWakeUpCoordinator.removeListener(this);
- mPanelView.removeTrackingHeadsUpListener(mSetTrackingHeadsUp);
- mPanelView.removeVerticalTranslationListener(mUpdatePanelTranslation);
- mPanelView.setHeadsUpAppearanceController(null);
+ mNotificationPanelViewController.removeTrackingHeadsUpListener(mSetTrackingHeadsUp);
+ mNotificationPanelViewController.removeVerticalTranslationListener(mUpdatePanelTranslation);
+ mNotificationPanelViewController.setHeadsUpAppearanceController(null);
mStackScroller.removeOnExpandedHeightChangedListener(mSetExpandedHeight);
mStackScroller.removeOnLayoutChangeListener(mStackScrollLayoutChangeListener);
mDarkIconDispatcher.removeDarkReceiver(this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
index ac06d9d..c282cb8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
@@ -39,12 +39,12 @@
private boolean mTouchingHeadsUpView;
private boolean mTrackingHeadsUp;
private boolean mCollapseSnoozes;
- private NotificationPanelView mPanel;
+ private NotificationPanelViewController mPanel;
private ExpandableNotificationRow mPickedChild;
public HeadsUpTouchHelper(HeadsUpManagerPhone headsUpManager,
Callback callback,
- NotificationPanelView notificationPanelView) {
+ NotificationPanelViewController notificationPanelView) {
mHeadsUpManager = headsUpManager;
mCallback = callback;
mPanel = notificationPanelView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index d95d2b7..d3e44ea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -155,7 +155,6 @@
};
private boolean mLeftIsVoiceAssist;
- private AssistManager mAssistManager;
private Drawable mLeftAssistIcon;
private IntentButton mRightButton = new DefaultRightButton();
@@ -254,7 +253,6 @@
mActivityStarter = Dependency.get(ActivityStarter.class);
mFlashlightController = Dependency.get(FlashlightController.class);
mAccessibilityController = Dependency.get(AccessibilityController.class);
- mAssistManager = Dependency.get(AssistManager.class);
mActivityIntentHelper = new ActivityIntentHelper(getContext());
updateLeftAffordance();
}
@@ -551,7 +549,7 @@
Runnable runnable = new Runnable() {
@Override
public void run() {
- mAssistManager.launchVoiceAssistFromKeyguard();
+ Dependency.get(AssistManager.class).launchVoiceAssistFromKeyguard();
}
};
if (!mKeyguardStateController.canDismissLockScreen()) {
@@ -565,7 +563,7 @@
}
private boolean canLaunchVoiceAssist() {
- return mAssistManager.canVoiceAssistBeLaunchedFromKeyguard();
+ return Dependency.get(AssistManager.class).canVoiceAssistBeLaunchedFromKeyguard();
}
private void launchPhone() {
@@ -647,7 +645,7 @@
}
if (mLeftIsVoiceAssist) {
mLeftPreview = mPreviewInflater.inflatePreviewFromService(
- mAssistManager.getVoiceInteractorComponentName());
+ Dependency.get(AssistManager.class).getVoiceInteractorComponentName());
} else {
mLeftPreview = mPreviewInflater.inflatePreview(mLeftButton.getIntent());
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
index f7d52b5..ad1aa83 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt
@@ -98,7 +98,12 @@
bypassEnabled = tunerService.getValue(key, dismissByDefault) != 0
}
}, Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD)
- lockscreenUserManager.addUserChangedListener { pendingUnlockType = null }
+ lockscreenUserManager.addUserChangedListener(
+ object : NotificationLockscreenUserManager.UserChangedListener {
+ override fun onUserChanged(userId: Int) {
+ pendingUnlockType = null
+ }
+ })
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 4e91e4c..a3f14ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -90,7 +90,7 @@
private int mContainerTopPadding;
/**
- * @see NotificationPanelView#getExpandedFraction()
+ * @see NotificationPanelViewController#getExpandedFraction()
*/
private float mPanelExpansion;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index d4cf272..3e3ef0c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -27,6 +27,7 @@
import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_NAVIGATION_BARS;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
+import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NAV_BAR_HANDLE_FORCE_OPAQUE;
import static com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
@@ -64,6 +65,7 @@
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.provider.DeviceConfig;
import android.provider.Settings;
import android.telecom.TelecomManager;
import android.text.TextUtils;
@@ -157,7 +159,6 @@
private int mNavigationIconHints = 0;
private @TransitionMode int mNavigationBarMode;
private AccessibilityManager mAccessibilityManager;
- private MagnificationContentObserver mMagnificationObserver;
private ContentResolver mContentResolver;
private boolean mAssistantAvailable;
@@ -176,6 +177,8 @@
private Locale mLocale;
private int mLayoutDirection;
+ private boolean mForceNavBarHandleOpaque;
+
/** @see android.view.WindowInsetsController#setSystemBarsAppearance(int) */
private @Appearance int mAppearance;
@@ -228,14 +231,17 @@
@Override
public void onNavBarButtonAlphaChanged(float alpha, boolean animate) {
ButtonDispatcher buttonDispatcher = null;
+ boolean forceVisible = false;
if (QuickStepContract.isSwipeUpMode(mNavBarMode)) {
buttonDispatcher = mNavigationBarView.getBackButton();
} else if (QuickStepContract.isGesturalMode(mNavBarMode)) {
+ forceVisible = mForceNavBarHandleOpaque;
buttonDispatcher = mNavigationBarView.getHomeHandle();
}
if (buttonDispatcher != null) {
- buttonDispatcher.setVisibility(alpha > 0 ? View.VISIBLE : View.INVISIBLE);
- buttonDispatcher.setAlpha(alpha, animate);
+ buttonDispatcher.setVisibility(
+ (forceVisible || alpha > 0) ? View.VISIBLE : View.INVISIBLE);
+ buttonDispatcher.setAlpha(forceVisible ? 1f : alpha, animate);
}
}
};
@@ -292,6 +298,21 @@
mDivider = divider;
mRecentsOptional = recentsOptional;
mHandler = mainHandler;
+
+ mForceNavBarHandleOpaque = DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_SYSTEMUI,
+ NAV_BAR_HANDLE_FORCE_OPAQUE,
+ /* defaultValue = */ false);
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI, mHandler::post,
+ new DeviceConfig.OnPropertiesChangedListener() {
+ @Override
+ public void onPropertiesChanged(DeviceConfig.Properties properties) {
+ if (properties.getKeyset().contains(NAV_BAR_HANDLE_FORCE_OPAQUE)) {
+ mForceNavBarHandleOpaque = properties.getBoolean(
+ NAV_BAR_HANDLE_FORCE_OPAQUE, /* defaultValue = */ false);
+ }
+ }
+ });
}
// ----- Fragment Lifecycle Callbacks -----
@@ -303,11 +324,6 @@
mWindowManager = getContext().getSystemService(WindowManager.class);
mAccessibilityManager = getContext().getSystemService(AccessibilityManager.class);
mContentResolver = getContext().getContentResolver();
- mMagnificationObserver = new MagnificationContentObserver(
- getContext().getMainThreadHandler());
- mContentResolver.registerContentObserver(Settings.Secure.getUriFor(
- Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED), false,
- mMagnificationObserver, UserHandle.USER_ALL);
mContentResolver.registerContentObserver(
Settings.Secure.getUriFor(Settings.Secure.ASSISTANT),
false /* notifyForDescendants */, mAssistContentObserver, UserHandle.USER_ALL);
@@ -329,7 +345,6 @@
super.onDestroy();
mNavigationModeController.removeListener(this);
mAccessibilityManagerWrapper.removeCallback(mAccessibilityListener);
- mContentResolver.unregisterContentObserver(mMagnificationObserver);
mContentResolver.unregisterContentObserver(mAssistContentObserver);
}
@@ -350,7 +365,7 @@
mIsOnDefaultDisplay = mDisplayId == Display.DEFAULT_DISPLAY;
}
- mNavigationBarView.setComponents(mStatusBarLazy.get().getPanel(), mAssistManager);
+ mNavigationBarView.setComponents(mStatusBarLazy.get().getPanelController());
mNavigationBarView.setDisabledFlags(mDisabledFlags1);
mNavigationBarView.setOnVerticalChangedListener(this::onVerticalChanged);
mNavigationBarView.setOnTouchListener(this::onNavigationTouch);
@@ -366,8 +381,8 @@
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_USER_SWITCHED);
- mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, Handler.getMain(),
- UserHandle.ALL);
+ mBroadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter,
+ Handler.getMain(), UserHandle.ALL);
notifyNavigationBarScreenOn();
mOverviewProxyService.addCallback(mOverviewProxyListener);
@@ -969,28 +984,18 @@
* @param outFeedbackEnabled if non-null, sets it to true if accessibility feedback is enabled.
*/
public int getA11yButtonState(@Nullable boolean[] outFeedbackEnabled) {
- int requestingServices = 0;
- try {
- if (Settings.Secure.getIntForUser(mContentResolver,
- Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
- UserHandle.USER_CURRENT) == 1) {
- requestingServices++;
- }
- } catch (Settings.SettingNotFoundException e) {
- }
-
boolean feedbackEnabled = false;
// AccessibilityManagerService resolves services for the current user since the local
// AccessibilityManager is created from a Context with the INTERACT_ACROSS_USERS permission
final List<AccessibilityServiceInfo> services =
mAccessibilityManager.getEnabledAccessibilityServiceList(
AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
+ final List<String> a11yButtonTargets =
+ mAccessibilityManager.getAccessibilityShortcutTargets(
+ AccessibilityManager.ACCESSIBILITY_BUTTON);
+ final int requestingServices = a11yButtonTargets.size();
for (int i = services.size() - 1; i >= 0; --i) {
AccessibilityServiceInfo info = services.get(i);
- if ((info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0) {
- requestingServices++;
- }
-
if (info.feedbackType != 0 && info.feedbackType !=
AccessibilityServiceInfo.FEEDBACK_GENERIC) {
feedbackEnabled = true;
@@ -1114,18 +1119,6 @@
private final AccessibilityServicesStateChangeListener mAccessibilityListener =
this::updateAccessibilityServicesState;
- private class MagnificationContentObserver extends ContentObserver {
-
- public MagnificationContentObserver(Handler handler) {
- super(handler);
- }
-
- @Override
- public void onChange(boolean selfChange) {
- NavigationBarFragment.this.updateAccessibilityServicesState(mAccessibilityManager);
- }
- }
-
private final Consumer<Integer> mRotationWatcher = rotation -> {
if (mNavigationBarView != null
&& mNavigationBarView.needsReorient(rotation)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 5a1b20d..ba9ba6c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -67,7 +67,6 @@
import com.android.systemui.Interpolators;
import com.android.systemui.R;
import com.android.systemui.assist.AssistHandleViewController;
-import com.android.systemui.assist.AssistManager;
import com.android.systemui.model.SysUiState;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.recents.Recents;
@@ -148,7 +147,7 @@
private NavigationBarInflaterView mNavigationInflaterView;
private RecentsOnboarding mRecentsOnboarding;
- private NotificationPanelView mPanelView;
+ private NotificationPanelViewController mPanelView;
private FloatingRotationButton mFloatingRotationButton;
private RotationButtonController mRotationButtonController;
@@ -349,7 +348,7 @@
return mBarTransitions.getLightTransitionsController();
}
- public void setComponents(NotificationPanelView panel, AssistManager assistManager) {
+ public void setComponents(NotificationPanelViewController panel) {
mPanelView = panel;
updatePanelSystemUiStateFlags();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
index fe0739f..896b6e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
@@ -32,7 +32,6 @@
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.NotificationContentInflater.AsyncInflationTask;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
import com.android.systemui.statusbar.phone.NotificationGroupManager.NotificationGroup;
import com.android.systemui.statusbar.phone.NotificationGroupManager.OnGroupChangeListener;
@@ -428,7 +427,7 @@
* The notification is still pending inflation but we've decided that we no longer need
* the content view (e.g. suppression might have changed and we decided we need to transfer
* back). However, there is no way to abort just this inflation if other inflation requests
- * have started (see {@link AsyncInflationTask#supersedeTask(InflationTask)}). So instead
+ * have started (see {@link InflationTask#supersedeTask(InflationTask)}). So instead
* we just flag it as aborted and free when it's inflated.
*/
boolean mAbortOnInflation;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
index e11fc1b..8c947ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupManager.java
@@ -116,7 +116,13 @@
*/
private void onEntryRemovedInternal(NotificationEntry removed,
final StatusBarNotification sbn) {
- String groupKey = getGroupKey(sbn);
+ onEntryRemovedInternal(removed, sbn.getGroupKey(), sbn.isGroup(),
+ sbn.getNotification().isGroupSummary());
+ }
+
+ private void onEntryRemovedInternal(NotificationEntry removed, String notifGroupKey, boolean
+ isGroup, boolean isGroupSummary) {
+ String groupKey = getGroupKey(removed.getKey(), notifGroupKey);
final NotificationGroup group = mGroupMap.get(groupKey);
if (group == null) {
// When an app posts 2 different notifications as summary of the same group, then a
@@ -125,7 +131,7 @@
// the close future. See b/23676310 for reference.
return;
}
- if (isGroupChild(sbn)) {
+ if (isGroupChild(removed.getKey(), isGroup, isGroupSummary)) {
group.children.remove(removed.getKey());
} else {
group.summary = null;
@@ -229,7 +235,7 @@
private int getNumberOfIsolatedChildren(String groupKey) {
int count = 0;
for (StatusBarNotification sbn : mIsolatedEntries.values()) {
- if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn)) {
+ if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn.getKey())) {
count++;
}
}
@@ -238,31 +244,47 @@
private NotificationEntry getIsolatedChild(String groupKey) {
for (StatusBarNotification sbn : mIsolatedEntries.values()) {
- if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn)) {
+ if (sbn.getGroupKey().equals(groupKey) && isIsolated(sbn.getKey())) {
return mGroupMap.get(sbn.getKey()).summary;
}
}
return null;
}
- public void onEntryUpdated(NotificationEntry entry,
- StatusBarNotification oldNotification) {
- String oldKey = oldNotification.getGroupKey();
- String newKey = entry.getSbn().getGroupKey();
- boolean groupKeysChanged = !oldKey.equals(newKey);
- boolean wasGroupChild = isGroupChild(oldNotification);
+ /**
+ * Update an entry's group information
+ * @param entry notification entry to update
+ * @param oldNotification previous notification info before this update
+ */
+ public void onEntryUpdated(NotificationEntry entry, StatusBarNotification oldNotification) {
+ onEntryUpdated(entry, oldNotification.getGroupKey(), oldNotification.isGroup(),
+ oldNotification.getNotification().isGroupSummary());
+ }
+
+ /**
+ * Updates an entry's group information
+ * @param entry notification entry to update
+ * @param oldGroupKey the notification's previous group key before this update
+ * @param oldIsGroup whether this notification was a group before this update
+ * @param oldIsGroupSummary whether this notification was a group summary before this update
+ */
+ public void onEntryUpdated(NotificationEntry entry, String oldGroupKey, boolean oldIsGroup,
+ boolean oldIsGroupSummary) {
+ String newGroupKey = entry.getSbn().getGroupKey();
+ boolean groupKeysChanged = !oldGroupKey.equals(newGroupKey);
+ boolean wasGroupChild = isGroupChild(entry.getKey(), oldIsGroup, oldIsGroupSummary);
boolean isGroupChild = isGroupChild(entry.getSbn());
mIsUpdatingUnchangedGroup = !groupKeysChanged && wasGroupChild == isGroupChild;
- if (mGroupMap.get(getGroupKey(oldNotification)) != null) {
- onEntryRemovedInternal(entry, oldNotification);
+ if (mGroupMap.get(getGroupKey(entry.getKey(), oldGroupKey)) != null) {
+ onEntryRemovedInternal(entry, oldGroupKey, oldIsGroup, oldIsGroupSummary);
}
onEntryAdded(entry);
mIsUpdatingUnchangedGroup = false;
- if (isIsolated(entry.getSbn())) {
+ if (isIsolated(entry.getSbn().getKey())) {
mIsolatedEntries.put(entry.getKey(), entry.getSbn());
if (groupKeysChanged) {
- updateSuppression(mGroupMap.get(oldKey));
- updateSuppression(mGroupMap.get(newKey));
+ updateSuppression(mGroupMap.get(oldGroupKey));
+ updateSuppression(mGroupMap.get(newGroupKey));
}
} else if (!wasGroupChild && isGroupChild) {
onEntryBecomingChild(entry);
@@ -418,10 +440,14 @@
* @return the key of the notification
*/
public String getGroupKey(StatusBarNotification sbn) {
- if (isIsolated(sbn)) {
- return sbn.getKey();
+ return getGroupKey(sbn.getKey(), sbn.getGroupKey());
+ }
+
+ private String getGroupKey(String key, String groupKey) {
+ if (isIsolated(key)) {
+ return key;
}
- return sbn.getGroupKey();
+ return groupKey;
}
/** @return group expansion state after toggling. */
@@ -434,8 +460,8 @@
return group.expanded;
}
- private boolean isIsolated(StatusBarNotification sbn) {
- return mIsolatedEntries.containsKey(sbn.getKey());
+ private boolean isIsolated(String sbnKey) {
+ return mIsolatedEntries.containsKey(sbnKey);
}
/**
@@ -445,7 +471,7 @@
* @return true if it is visually a group summary
*/
public boolean isGroupSummary(StatusBarNotification sbn) {
- if (isIsolated(sbn)) {
+ if (isIsolated(sbn.getKey())) {
return true;
}
return sbn.getNotification().isGroupSummary();
@@ -458,10 +484,14 @@
* @return true if it is visually a group child
*/
public boolean isGroupChild(StatusBarNotification sbn) {
- if (isIsolated(sbn)) {
+ return isGroupChild(sbn.getKey(), sbn.isGroup(), sbn.getNotification().isGroupSummary());
+ }
+
+ private boolean isGroupChild(String key, boolean isGroup, boolean isGroupSummary) {
+ if (isIsolated(key)) {
return false;
}
- return sbn.isGroup() && !sbn.getNotification().isGroupSummary();
+ return isGroup && !isGroupSummary;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index c74286d..0f3af09 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -16,118 +16,15 @@
package com.android.systemui.statusbar.phone;
-import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
-import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
-import static com.android.systemui.util.InjectionInflationController.VIEW_CONTEXT;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.app.ActivityManager;
-import android.app.Fragment;
-import android.app.StatusBarManager;
import android.content.Context;
-import android.content.pm.ResolveInfo;
-import android.content.res.Configuration;
-import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
-import android.graphics.PointF;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.hardware.biometrics.BiometricSourceType;
-import android.os.PowerManager;
-import android.os.SystemClock;
-import android.provider.DeviceConfig;
-import android.provider.Settings;
import android.util.AttributeSet;
-import android.util.Log;
-import android.util.MathUtils;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowInsets;
-import android.view.accessibility.AccessibilityManager;
-import android.widget.FrameLayout;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.keyguard.KeyguardClockSwitch;
-import com.android.keyguard.KeyguardStatusView;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
-import com.android.systemui.DejankUtils;
-import com.android.systemui.Dependency;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.doze.DozeLog;
-import com.android.systemui.fragments.FragmentHostManager;
-import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.HomeControlsPlugin;
-import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.plugins.qs.QS;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
-import com.android.systemui.qs.QSFragment;
-import com.android.systemui.shared.plugins.PluginManager;
-import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.FlingAnimationUtils;
-import com.android.systemui.statusbar.GestureRecorder;
-import com.android.systemui.statusbar.KeyguardAffordanceView;
-import com.android.systemui.statusbar.KeyguardIndicationController;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.NotificationShelf;
-import com.android.systemui.statusbar.PulseExpansionHandler;
-import com.android.systemui.statusbar.RemoteInputController;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
-import com.android.systemui.statusbar.notification.AnimatableProperty;
-import com.android.systemui.statusbar.notification.DynamicPrivacyController;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.PropertyAnimator;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.ExpandableView;
-import com.android.systemui.statusbar.notification.stack.AnimationProperties;
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
-import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
-import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
-import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.systemui.util.InjectionInflationController;
-import com.android.systemui.util.Utils;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.function.Consumer;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-public class NotificationPanelView extends PanelView implements
- ExpandableView.OnHeightChangedListener,
- View.OnClickListener, NotificationStackScrollLayout.OnOverscrollTopChangedListener,
- KeyguardAffordanceHelper.Callback, NotificationStackScrollLayout.OnEmptySpaceClickListener,
- OnHeadsUpChangedListener, QS.HeightListener, ZenModeController.Callback,
- ConfigurationController.ConfigurationListener, StateListener,
- PulseExpansionHandler.ExpansionCallback, DynamicPrivacyController.Listener,
- NotificationWakeUpCoordinator.WakeUpListener {
+public class NotificationPanelView extends PanelView {
private static final boolean DEBUG = false;
@@ -136,2873 +33,34 @@
*/
public static final int FLING_EXPAND = 0;
- /**
- * Fling collapsing QS, potentially stopping when QS becomes QQS.
- */
- public static final int FLING_COLLAPSE = 1;
-
- /**
- * Fling until QS is completely hidden.
- */
- public static final int FLING_HIDE = 2;
- private final DozeParameters mDozeParameters;
-
- private double mQqsSplitFraction;
-
- // Cap and total height of Roboto font. Needs to be adjusted when font for the big clock is
- // changed.
- private static final int CAP_HEIGHT = 1456;
- private static final int FONT_HEIGHT = 2163;
-
- /**
- * Maximum time before which we will expand the panel even for slow motions when getting a
- * touch passed over from launcher.
- */
- private static final int MAX_TIME_TO_OPEN_WHEN_FLINGING_FROM_LAUNCHER = 300;
-
static final String COUNTER_PANEL_OPEN = "panel_open";
static final String COUNTER_PANEL_OPEN_QS = "panel_open_qs";
- private static final String COUNTER_PANEL_OPEN_PEEK = "panel_open_peek";
- private static final Rect mDummyDirtyRect = new Rect(0, 0, 1, 1);
- private static final Rect mEmptyRect = new Rect();
-
- private static final AnimationProperties CLOCK_ANIMATION_PROPERTIES = new AnimationProperties()
- .setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
- private static final AnimatableProperty KEYGUARD_HEADS_UP_SHOWING_AMOUNT
- = AnimatableProperty.from("KEYGUARD_HEADS_UP_SHOWING_AMOUNT",
- NotificationPanelView::setKeyguardHeadsUpShowingAmount,
- NotificationPanelView::getKeyguardHeadsUpShowingAmount,
- R.id.keyguard_hun_animator_tag,
- R.id.keyguard_hun_animator_end_tag,
- R.id.keyguard_hun_animator_start_tag);
- private static final AnimationProperties KEYGUARD_HUN_PROPERTIES =
- new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
- @VisibleForTesting
- final KeyguardUpdateMonitorCallback mKeyguardUpdateCallback =
- new KeyguardUpdateMonitorCallback() {
-
- @Override
- public void onBiometricAuthenticated(int userId,
- BiometricSourceType biometricSourceType) {
- if (mFirstBypassAttempt && mUpdateMonitor.isUnlockingWithBiometricAllowed()) {
- mDelayShowingKeyguardStatusBar = true;
- }
- }
-
- @Override
- public void onBiometricRunningStateChanged(boolean running,
- BiometricSourceType biometricSourceType) {
- boolean keyguardOrShadeLocked = mBarState == StatusBarState.KEYGUARD
- || mBarState == StatusBarState.SHADE_LOCKED;
- if (!running && mFirstBypassAttempt && keyguardOrShadeLocked && !mDozing
- && !mDelayShowingKeyguardStatusBar) {
- mFirstBypassAttempt = false;
- animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
- }
- }
-
- @Override
- public void onFinishedGoingToSleep(int why) {
- mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
- mDelayShowingKeyguardStatusBar = false;
- }
- };
- private final KeyguardStateController.Callback mKeyguardMonitorCallback =
- new KeyguardStateController.Callback() {
- @Override
- public void onKeyguardFadingAwayChanged() {
- if (!mKeyguardStateController.isKeyguardFadingAway()) {
- mFirstBypassAttempt = false;
- mDelayShowingKeyguardStatusBar = false;
- }
- }
- };
-
- private final InjectionInflationController mInjectionInflationController;
- private final PowerManager mPowerManager;
- private final AccessibilityManager mAccessibilityManager;
- private final NotificationWakeUpCoordinator mWakeUpCoordinator;
- private final PulseExpansionHandler mPulseExpansionHandler;
- private final KeyguardBypassController mKeyguardBypassController;
- private final KeyguardUpdateMonitor mUpdateMonitor;
-
- @VisibleForTesting
- protected KeyguardAffordanceHelper mAffordanceHelper;
- private KeyguardUserSwitcher mKeyguardUserSwitcher;
- @VisibleForTesting
- protected KeyguardStatusBarView mKeyguardStatusBar;
- @VisibleForTesting
- protected ViewGroup mBigClockContainer;
- private QS mQs;
- @VisibleForTesting
- protected FrameLayout mQsFrame;
- @VisibleForTesting
- protected KeyguardStatusView mKeyguardStatusView;
- private View mQsNavbarScrim;
- protected NotificationsQuickSettingsContainer mNotificationContainerParent;
- protected NotificationStackScrollLayout mNotificationStackScroller;
- protected FrameLayout mHomeControlsLayout;
- private boolean mAnimateNextPositionUpdate;
-
- private int mTrackingPointer;
- private VelocityTracker mQsVelocityTracker;
- private boolean mQsTracking;
-
- /**
- * If set, the ongoing touch gesture might both trigger the expansion in {@link PanelView} and
- * the expansion for quick settings.
- */
- private boolean mConflictingQsExpansionGesture;
-
- /**
- * Whether we are currently handling a motion gesture in #onInterceptTouchEvent, but haven't
- * intercepted yet.
- */
- private boolean mIntercepting;
- private boolean mPanelExpanded;
- private boolean mQsExpanded;
- private boolean mQsExpandedWhenExpandingStarted;
- private boolean mQsFullyExpanded;
- private boolean mKeyguardShowing;
- private boolean mDozing;
- private boolean mDozingOnDown;
- protected int mBarState;
- private float mInitialHeightOnTouch;
- private float mInitialTouchX;
- private float mInitialTouchY;
- private float mLastTouchX;
- private float mLastTouchY;
- protected float mQsExpansionHeight;
- protected int mQsMinExpansionHeight;
- protected int mQsMaxExpansionHeight;
- private int mQsPeekHeight;
- private boolean mStackScrollerOverscrolling;
- private boolean mQsExpansionFromOverscroll;
- private float mLastOverscroll;
- protected boolean mQsExpansionEnabled = true;
- private ValueAnimator mQsExpansionAnimator;
- private FlingAnimationUtils mFlingAnimationUtils;
- private int mStatusBarMinHeight;
- private int mNotificationsHeaderCollideDistance;
- private int mUnlockMoveDistance;
- private float mEmptyDragAmount;
- private float mDownX;
- private float mDownY;
-
- private final KeyguardClockPositionAlgorithm mClockPositionAlgorithm =
- new KeyguardClockPositionAlgorithm();
- private final KeyguardClockPositionAlgorithm.Result mClockPositionResult =
- new KeyguardClockPositionAlgorithm.Result();
- private boolean mIsExpanding;
-
- private boolean mBlockTouches;
- // Used for two finger gesture as well as accessibility shortcut to QS.
- private boolean mQsExpandImmediate;
- private boolean mTwoFingerQsExpandPossible;
-
- /**
- * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still
- * need to take this into account in our panel height calculation.
- */
- private boolean mQsAnimatorExpand;
- private boolean mIsLaunchTransitionFinished;
- private boolean mIsLaunchTransitionRunning;
- private Runnable mLaunchAnimationEndRunnable;
- private boolean mOnlyAffordanceInThisMotion;
- private boolean mKeyguardStatusViewAnimating;
- private ValueAnimator mQsSizeChangeAnimator;
-
- private boolean mShowEmptyShadeView;
-
- private boolean mQsScrimEnabled = true;
- private boolean mLastAnnouncementWasQuickSettings;
- private boolean mQsTouchAboveFalsingThreshold;
- private int mQsFalsingThreshold;
-
- private float mKeyguardStatusBarAnimateAlpha = 1f;
- private int mOldLayoutDirection;
- private HeadsUpTouchHelper mHeadsUpTouchHelper;
- private boolean mIsExpansionFromHeadsUp;
- private boolean mListenForHeadsUp;
- private int mNavigationBarBottomHeight;
- private boolean mExpandingFromHeadsUp;
- private boolean mCollapsedOnDown;
- private int mPositionMinSideMargin;
- private int mMaxFadeoutHeight;
- private int mLastOrientation = -1;
- private boolean mClosingWithAlphaFadeOut;
- private boolean mHeadsUpAnimatingAway;
- private boolean mLaunchingAffordance;
- private boolean mAffordanceHasPreview;
- private FalsingManager mFalsingManager;
- private String mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
-
- private Runnable mHeadsUpExistenceChangedRunnable = new Runnable() {
- @Override
- public void run() {
- setHeadsUpAnimatingAway(false);
- notifyBarPanelExpansionChanged();
- }
- };
- private NotificationGroupManager mGroupManager;
- private boolean mShowIconsWhenExpanded;
- private int mIndicationBottomPadding;
- private int mAmbientIndicationBottomPadding;
- private boolean mIsFullWidth;
- private boolean mBlockingExpansionForCurrentTouch;
-
- /**
- * Following variables maintain state of events when input focus transfer may occur.
- */
- private boolean mExpectingSynthesizedDown; // expecting to see synthesized DOWN event
- private boolean mLastEventSynthesizedDown; // last event was synthesized DOWN event
-
- /**
- * Current dark amount that follows regular interpolation curve of animation.
- */
- private float mInterpolatedDarkAmount;
-
- /**
- * Dark amount that animates from 0 to 1 or vice-versa in linear manner, even if the
- * interpolation curve is different.
- */
- private float mLinearDarkAmount;
-
- private boolean mPulsing;
- private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
- private boolean mNoVisibleNotifications = true;
- private boolean mUserSetupComplete;
- private int mQsNotificationTopPadding;
- private float mExpandOffset;
- private boolean mHideIconsDuringNotificationLaunch = true;
- private int mStackScrollerMeasuringPass;
- private ArrayList<Consumer<ExpandableNotificationRow>> mTrackingHeadsUpListeners
- = new ArrayList<>();
- private ArrayList<Runnable> mVerticalTranslationListener = new ArrayList<>();
- private HeadsUpAppearanceController mHeadsUpAppearanceController;
-
- private int mPanelAlpha;
private int mCurrentPanelAlpha;
private final Paint mAlphaPaint = new Paint();
- private Runnable mPanelAlphaEndAction;
- private float mBottomAreaShadeAlpha;
- private final ValueAnimator mBottomAreaShadeAlphaAnimator;
- private AnimatorListenerAdapter mAnimatorListenerAdapter = new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- if (mPanelAlphaEndAction != null) {
- mPanelAlphaEndAction.run();
- }
- }
- };
- private final AnimatableProperty PANEL_ALPHA = AnimatableProperty.from(
- "panelAlpha",
- NotificationPanelView::setPanelAlphaInternal,
- NotificationPanelView::getCurrentPanelAlpha,
- R.id.panel_alpha_animator_tag,
- R.id.panel_alpha_animator_start_tag,
- R.id.panel_alpha_animator_end_tag);
- private final AnimationProperties PANEL_ALPHA_OUT_PROPERTIES = new AnimationProperties()
- .setDuration(150)
- .setCustomInterpolator(PANEL_ALPHA.getProperty(), Interpolators.ALPHA_OUT);
- private final AnimationProperties PANEL_ALPHA_IN_PROPERTIES = new AnimationProperties()
- .setDuration(200)
- .setAnimationFinishListener(mAnimatorListenerAdapter)
- .setCustomInterpolator(PANEL_ALPHA.getProperty(), Interpolators.ALPHA_IN);
- private final NotificationEntryManager mEntryManager;
+ private boolean mDozing;
+ private RtlChangeListener mRtlChangeListener;
- private final CommandQueue mCommandQueue;
- private final NotificationLockscreenUserManager mLockscreenUserManager;
- private final ShadeController mShadeController;
- private int mDisplayId;
-
- /**
- * Cache the resource id of the theme to avoid unnecessary work in onThemeChanged.
- *
- * onThemeChanged is forced when the theme might not have changed. So, to avoid unncessary
- * work, check the current id with the cached id.
- */
- private int mThemeResId;
- private KeyguardIndicationController mKeyguardIndicationController;
- private Consumer<Boolean> mAffordanceLaunchListener;
- private int mShelfHeight;
- private Runnable mOnReinflationListener;
- private int mDarkIconSize;
- private int mHeadsUpInset;
- private boolean mHeadsUpPinnedMode;
- private float mKeyguardHeadsUpShowingAmount = 0.0f;
- private boolean mShowingKeyguardHeadsUp;
- private boolean mAllowExpandForSmallExpansion;
- private Runnable mExpandAfterLayoutRunnable;
-
- /**
- * If face auth with bypass is running for the first time after you turn on the screen.
- * (From aod or screen off)
- */
- private boolean mFirstBypassAttempt;
- /**
- * If auth happens successfully during {@code mFirstBypassAttempt}, and we should wait until
- * the keyguard is dismissed to show the status bar.
- */
- private boolean mDelayShowingKeyguardStatusBar;
-
- private PluginManager mPluginManager;
- private FrameLayout mPluginFrame;
- private NPVPluginManager mNPVPluginManager;
-
- @Inject
- public NotificationPanelView(@Named(VIEW_CONTEXT) Context context, AttributeSet attrs,
- InjectionInflationController injectionInflationController,
- NotificationWakeUpCoordinator coordinator, PulseExpansionHandler pulseExpansionHandler,
- DynamicPrivacyController dynamicPrivacyController,
- KeyguardBypassController bypassController, FalsingManager falsingManager,
- PluginManager pluginManager, ShadeController shadeController,
- NotificationLockscreenUserManager notificationLockscreenUserManager,
- NotificationEntryManager notificationEntryManager,
- KeyguardStateController keyguardStateController,
- StatusBarStateController statusBarStateController, DozeLog dozeLog,
- DozeParameters dozeParameters, CommandQueue commandQueue) {
- super(context, attrs, falsingManager, dozeLog, keyguardStateController,
- (SysuiStatusBarStateController) statusBarStateController);
+ public NotificationPanelView(Context context, AttributeSet attrs) {
+ super(context, attrs);
setWillNotDraw(!DEBUG);
- mInjectionInflationController = injectionInflationController;
- mFalsingManager = falsingManager;
- mPowerManager = context.getSystemService(PowerManager.class);
- mWakeUpCoordinator = coordinator;
- mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
- setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
mAlphaPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY));
- setPanelAlpha(255, false /* animate */);
- mCommandQueue = commandQueue;
- mDisplayId = context.getDisplayId();
- mPulseExpansionHandler = pulseExpansionHandler;
- mDozeParameters = dozeParameters;
- pulseExpansionHandler.setPulseExpandAbortListener(() -> {
- if (mQs != null) {
- mQs.animateHeaderSlidingOut();
- }
- });
- mThemeResId = context.getThemeResId();
- mKeyguardBypassController = bypassController;
- mUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
- mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
- mKeyguardStateController.addCallback(mKeyguardMonitorCallback);
- dynamicPrivacyController.addListener(this);
-
- mBottomAreaShadeAlphaAnimator = ValueAnimator.ofFloat(1f, 0);
- mBottomAreaShadeAlphaAnimator.addUpdateListener(animation -> {
- mBottomAreaShadeAlpha = (float) animation.getAnimatedValue();
- updateKeyguardBottomAreaAlpha();
- });
- mBottomAreaShadeAlphaAnimator.setDuration(160);
- mBottomAreaShadeAlphaAnimator.setInterpolator(Interpolators.ALPHA_OUT);
- mPluginManager = pluginManager;
- mShadeController = shadeController;
- mLockscreenUserManager = notificationLockscreenUserManager;
- mEntryManager = notificationEntryManager;
setBackgroundColor(Color.TRANSPARENT);
}
- /**
- * Returns if there's a custom clock being presented.
- */
- public boolean hasCustomClock() {
- return mKeyguardStatusView.hasCustomClock();
- }
-
- private void setStatusBar(StatusBar bar) {
- mStatusBar = bar;
- mKeyguardBottomArea.setStatusBar(mStatusBar);
- }
-
- /**
- * Call after this view has been fully inflated and had its children attached.
- */
- public void onChildrenAttached() {
- loadDimens();
- mKeyguardStatusBar = findViewById(R.id.keyguard_header);
- mKeyguardStatusView = findViewById(R.id.keyguard_status_view);
-
- KeyguardClockSwitch keyguardClockSwitch = findViewById(R.id.keyguard_clock_container);
- mBigClockContainer = findViewById(R.id.big_clock_container);
- keyguardClockSwitch.setBigClockContainer(mBigClockContainer);
-
- mHomeControlsLayout = findViewById(R.id.home_controls_layout);
- mNotificationContainerParent = findViewById(R.id.notification_container_parent);
- mNotificationStackScroller = findViewById(R.id.notification_stack_scroller);
- mNotificationStackScroller.setOnHeightChangedListener(this);
- mNotificationStackScroller.setOverscrollTopChangedListener(this);
- mNotificationStackScroller.setOnEmptySpaceClickListener(this);
- addTrackingHeadsUpListener(mNotificationStackScroller::setTrackingHeadsUp);
- mKeyguardBottomArea = findViewById(R.id.keyguard_bottom_area);
- mQsNavbarScrim = findViewById(R.id.qs_navbar_scrim);
- mLastOrientation = getResources().getConfiguration().orientation;
- mPluginFrame = findViewById(R.id.plugin_frame);
- if (Settings.System.getInt(
- mContext.getContentResolver(), "npv_plugin_flag", 0) == 1) {
- mNPVPluginManager = new NPVPluginManager(mPluginFrame, mPluginManager);
- }
-
-
- initBottomArea();
-
- mWakeUpCoordinator.setStackScroller(mNotificationStackScroller);
- mQsFrame = findViewById(R.id.qs_frame);
- mPulseExpansionHandler.setUp(mNotificationStackScroller, this, mShadeController);
- mWakeUpCoordinator.addListener(new NotificationWakeUpCoordinator.WakeUpListener() {
- @Override
- public void onFullyHiddenChanged(boolean isFullyHidden) {
- updateKeyguardStatusBarForHeadsUp();
- }
-
- @Override
- public void onPulseExpansionChanged(boolean expandingChanged) {
- if (mKeyguardBypassController.getBypassEnabled()) {
- // Position the notifications while dragging down while pulsing
- requestScrollerTopPaddingUpdate(false /* animate */);
- updateQSPulseExpansion();
- }
- }
- });
-
- mPluginManager.addPluginListener(
- new PluginListener<HomeControlsPlugin>() {
-
- @Override
- public void onPluginConnected(HomeControlsPlugin plugin,
- Context pluginContext) {
- plugin.sendParentGroup(mHomeControlsLayout);
- }
-
- @Override
- public void onPluginDisconnected(HomeControlsPlugin plugin) {
-
- }
- }, HomeControlsPlugin.class, false);
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- FragmentHostManager.get(this).addTagListener(QS.TAG, mFragmentListener);
- Dependency.get(StatusBarStateController.class).addCallback(this);
- Dependency.get(ZenModeController.class).addCallback(this);
- Dependency.get(ConfigurationController.class).addCallback(this);
- mUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
- // Theme might have changed between inflating this view and attaching it to the window, so
- // force a call to onThemeChanged
- onThemeChanged();
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- FragmentHostManager.get(this).removeTagListener(QS.TAG, mFragmentListener);
- Dependency.get(StatusBarStateController.class).removeCallback(this);
- Dependency.get(ZenModeController.class).removeCallback(this);
- Dependency.get(ConfigurationController.class).removeCallback(this);
- mUpdateMonitor.removeCallback(mKeyguardUpdateCallback);
- }
-
- @Override
- protected void loadDimens() {
- super.loadDimens();
- mFlingAnimationUtils = new FlingAnimationUtils(getResources().getDisplayMetrics(), 0.4f);
- mStatusBarMinHeight = getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.status_bar_height);
- mQsPeekHeight = getResources().getDimensionPixelSize(R.dimen.qs_peek_height);
- mNotificationsHeaderCollideDistance =
- getResources().getDimensionPixelSize(R.dimen.header_notifications_collide_distance);
- mUnlockMoveDistance = getResources().getDimensionPixelOffset(R.dimen.unlock_move_distance);
- mClockPositionAlgorithm.loadDimens(getResources());
- mQsFalsingThreshold = getResources().getDimensionPixelSize(
- R.dimen.qs_falsing_threshold);
- mPositionMinSideMargin = getResources().getDimensionPixelSize(
- R.dimen.notification_panel_min_side_margin);
- mMaxFadeoutHeight = getResources().getDimensionPixelSize(
- R.dimen.max_notification_fadeout_height);
- mIndicationBottomPadding = getResources().getDimensionPixelSize(
- R.dimen.keyguard_indication_bottom_padding);
- mQsNotificationTopPadding = getResources().getDimensionPixelSize(
- R.dimen.qs_notification_padding);
- mShelfHeight = getResources().getDimensionPixelSize(R.dimen.notification_shelf_height);
- mDarkIconSize = getResources().getDimensionPixelSize(
- R.dimen.status_bar_icon_drawing_size_dark);
- int statusbarHeight = getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.status_bar_height);
- mHeadsUpInset = statusbarHeight + getResources().getDimensionPixelSize(
- R.dimen.heads_up_status_bar_padding);
- mQqsSplitFraction = ((float) getResources().getInteger(R.integer.qqs_split_fraction)) / (
- getResources().getInteger(R.integer.qqs_split_fraction)
- + getResources().getInteger(R.integer.qs_split_fraction));
- }
-
- /**
- * @see #launchCamera(boolean, int)
- * @see #setLaunchingAffordance(boolean)
- */
- public void setLaunchAffordanceListener(Consumer<Boolean> listener) {
- mAffordanceLaunchListener = listener;
- }
-
- public void updateResources() {
- Resources res = getResources();
- int qsWidth = res.getDimensionPixelSize(R.dimen.qs_panel_width);
- int panelGravity = getResources().getInteger(R.integer.notification_panel_layout_gravity);
- FrameLayout.LayoutParams lp =
- (FrameLayout.LayoutParams) mQsFrame.getLayoutParams();
- if (lp.width != qsWidth || lp.gravity != panelGravity) {
- lp.width = qsWidth;
- lp.gravity = panelGravity;
- mQsFrame.setLayoutParams(lp);
- }
-
- int panelWidth = res.getDimensionPixelSize(R.dimen.notification_panel_width);
- lp = (FrameLayout.LayoutParams) mNotificationStackScroller.getLayoutParams();
- if (lp.width != panelWidth || lp.gravity != panelGravity) {
- lp.width = panelWidth;
- lp.gravity = panelGravity;
- mNotificationStackScroller.setLayoutParams(lp);
- }
- int sideMargin = res.getDimensionPixelOffset(R.dimen.notification_side_paddings);
- int topMargin = sideMargin;
- lp = (FrameLayout.LayoutParams) mPluginFrame.getLayoutParams();
- if (lp.width != qsWidth || lp.gravity != panelGravity || lp.leftMargin != sideMargin
- || lp.rightMargin != sideMargin || lp.topMargin != topMargin) {
- lp.width = qsWidth;
- lp.gravity = panelGravity;
- lp.leftMargin = sideMargin;
- lp.rightMargin = sideMargin;
- lp.topMargin = topMargin;
- mPluginFrame.setLayoutParams(lp);
- }
- }
-
- @Override
- public void onDensityOrFontScaleChanged() {
- updateShowEmptyShadeView();
- }
-
- @Override
- public void onThemeChanged() {
- final int themeResId = getContext().getThemeResId();
- if (mThemeResId == themeResId) {
- return;
- }
- mThemeResId = themeResId;
-
- reInflateViews();
- }
-
- @Override
- public void onOverlayChanged() {
- reInflateViews();
- }
-
- private void reInflateViews() {
- updateShowEmptyShadeView();
-
- // Re-inflate the status view group.
- int index = indexOfChild(mKeyguardStatusView);
- removeView(mKeyguardStatusView);
- mKeyguardStatusView = (KeyguardStatusView) mInjectionInflationController
- .injectable(LayoutInflater.from(mContext)).inflate(
- R.layout.keyguard_status_view,
- this,
- false);
- addView(mKeyguardStatusView, index);
-
- // Re-associate the clock container with the keyguard clock switch.
- mBigClockContainer.removeAllViews();
- KeyguardClockSwitch keyguardClockSwitch = findViewById(R.id.keyguard_clock_container);
- keyguardClockSwitch.setBigClockContainer(mBigClockContainer);
-
- // Update keyguard bottom area
- index = indexOfChild(mKeyguardBottomArea);
- removeView(mKeyguardBottomArea);
- KeyguardBottomAreaView oldBottomArea = mKeyguardBottomArea;
- mKeyguardBottomArea = (KeyguardBottomAreaView) mInjectionInflationController
- .injectable(LayoutInflater.from(mContext)).inflate(
- R.layout.keyguard_bottom_area,
- this,
- false);
- mKeyguardBottomArea.initFrom(oldBottomArea);
- addView(mKeyguardBottomArea, index);
- initBottomArea();
- mKeyguardIndicationController.setIndicationArea(mKeyguardBottomArea);
- onDozeAmountChanged(mStatusBarStateController.getDozeAmount(),
- mStatusBarStateController.getInterpolatedDozeAmount());
-
- if (mKeyguardStatusBar != null) {
- mKeyguardStatusBar.onThemeChanged();
- }
-
- setKeyguardStatusViewVisibility(mBarState, false, false);
- setKeyguardBottomAreaVisibility(mBarState, false);
- if (mOnReinflationListener != null) {
- mOnReinflationListener.run();
- }
- reinflatePluginContainer();
- }
-
- @Override
- public void onUiModeChanged() {
- reinflatePluginContainer();
- }
-
- private void reinflatePluginContainer() {
- int index = indexOfChild(mPluginFrame);
- removeView(mPluginFrame);
- mPluginFrame = (FrameLayout) mInjectionInflationController
- .injectable(LayoutInflater.from(mContext)).inflate(
- R.layout.status_bar_expanded_plugin_frame,
- this,
- false);
- addView(mPluginFrame, index);
-
- Resources res = getResources();
- int qsWidth = res.getDimensionPixelSize(R.dimen.qs_panel_width);
- int panelGravity = getResources().getInteger(R.integer.notification_panel_layout_gravity);
- FrameLayout.LayoutParams lp;
- int sideMargin = res.getDimensionPixelOffset(R.dimen.notification_side_paddings);
- int topMargin =
- res.getDimensionPixelOffset(com.android.internal.R.dimen.quick_qs_total_height);
- if (Utils.useQsMediaPlayer(mContext)) {
- topMargin = res.getDimensionPixelOffset(
- com.android.internal.R.dimen.quick_qs_total_height_with_media);
- }
- lp = (FrameLayout.LayoutParams) mPluginFrame.getLayoutParams();
- if (lp.width != qsWidth || lp.gravity != panelGravity || lp.leftMargin != sideMargin
- || lp.rightMargin != sideMargin || lp.topMargin != topMargin) {
- lp.width = qsWidth;
- lp.gravity = panelGravity;
- lp.leftMargin = sideMargin;
- lp.rightMargin = sideMargin;
- lp.topMargin = topMargin;
- mPluginFrame.setLayoutParams(lp);
- }
-
- if (mNPVPluginManager != null) mNPVPluginManager.replaceFrameLayout(mPluginFrame);
- }
-
- private void initBottomArea() {
- mAffordanceHelper = new KeyguardAffordanceHelper(this, getContext(), mFalsingManager);
- mKeyguardBottomArea.setAffordanceHelper(mAffordanceHelper);
- mKeyguardBottomArea.setStatusBar(mStatusBar);
- mKeyguardBottomArea.setUserSetupComplete(mUserSetupComplete);
- }
-
- public void setKeyguardIndicationController(KeyguardIndicationController indicationController) {
- mKeyguardIndicationController = indicationController;
- mKeyguardIndicationController.setIndicationArea(mKeyguardBottomArea);
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- DejankUtils.startDetectingBlockingIpcs("NVP#onLayout");
- super.onLayout(changed, left, top, right, bottom);
- setIsFullWidth(mNotificationStackScroller.getWidth() == getWidth());
-
- // Update Clock Pivot
- mKeyguardStatusView.setPivotX(getWidth() / 2);
- mKeyguardStatusView.setPivotY((FONT_HEIGHT - CAP_HEIGHT) / 2048f *
- mKeyguardStatusView.getClockTextSize());
-
- // Calculate quick setting heights.
- int oldMaxHeight = mQsMaxExpansionHeight;
- if (mQs != null) {
- mQsMinExpansionHeight = mKeyguardShowing ? 0 : mQs.getQsMinExpansionHeight();
- if (mNPVPluginManager != null) {
- mNPVPluginManager.setYOffset(mQsMinExpansionHeight);
- mQsMinExpansionHeight += mNPVPluginManager.getHeight();
- }
- mQsMaxExpansionHeight = mQs.getDesiredHeight();
- mNotificationStackScroller.setMaxTopPadding(
- mQsMaxExpansionHeight + mQsNotificationTopPadding);
- }
- positionClockAndNotifications();
- if (mQsExpanded && mQsFullyExpanded) {
- mQsExpansionHeight = mQsMaxExpansionHeight;
- requestScrollerTopPaddingUpdate(false /* animate */);
- requestPanelHeightUpdate();
-
- // Size has changed, start an animation.
- if (mQsMaxExpansionHeight != oldMaxHeight) {
- startQsSizeChangeAnimation(oldMaxHeight, mQsMaxExpansionHeight);
- }
- } else if (!mQsExpanded) {
- setQsExpansion(mQsMinExpansionHeight + mLastOverscroll);
- }
- updateExpandedHeight(getExpandedHeight());
- updateHeader();
-
- // If we are running a size change animation, the animation takes care of the height of
- // the container. However, if we are not animating, we always need to make the QS container
- // the desired height so when closing the QS detail, it stays smaller after the size change
- // animation is finished but the detail view is still being animated away (this animation
- // takes longer than the size change animation).
- if (mQsSizeChangeAnimator == null && mQs != null) {
- mQs.setHeightOverride(mQs.getDesiredHeight());
- }
- updateMaxHeadsUpTranslation();
- updateGestureExclusionRect();
- if (mExpandAfterLayoutRunnable != null) {
- mExpandAfterLayoutRunnable.run();
- mExpandAfterLayoutRunnable = null;
- }
- DejankUtils.stopDetectingBlockingIpcs("NVP#onLayout");
- }
-
- private void updateGestureExclusionRect() {
- Rect exclusionRect = calculateGestureExclusionRect();
- setSystemGestureExclusionRects(exclusionRect.isEmpty()
- ? Collections.EMPTY_LIST
- : Collections.singletonList(exclusionRect));
- }
-
- private Rect calculateGestureExclusionRect() {
- Rect exclusionRect = null;
- Region touchableRegion = mHeadsUpManager.calculateTouchableRegion();
- if (isFullyCollapsed() && touchableRegion != null) {
- // Note: The heads up manager also calculates the non-pinned touchable region
- exclusionRect = touchableRegion.getBounds();
- }
- return exclusionRect != null
- ? exclusionRect
- : mEmptyRect;
- }
-
- private void setIsFullWidth(boolean isFullWidth) {
- mIsFullWidth = isFullWidth;
- mNotificationStackScroller.setIsFullWidth(isFullWidth);
- }
-
- private void startQsSizeChangeAnimation(int oldHeight, final int newHeight) {
- if (mQsSizeChangeAnimator != null) {
- oldHeight = (int) mQsSizeChangeAnimator.getAnimatedValue();
- mQsSizeChangeAnimator.cancel();
- }
- mQsSizeChangeAnimator = ValueAnimator.ofInt(oldHeight, newHeight);
- mQsSizeChangeAnimator.setDuration(300);
- mQsSizeChangeAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- mQsSizeChangeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- requestScrollerTopPaddingUpdate(false /* animate */);
- requestPanelHeightUpdate();
- int height = (int) mQsSizeChangeAnimator.getAnimatedValue();
- mQs.setHeightOverride(height);
- }
- });
- mQsSizeChangeAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mQsSizeChangeAnimator = null;
- }
- });
- mQsSizeChangeAnimator.start();
- }
-
- /**
- * Positions the clock and notifications dynamically depending on how many notifications are
- * showing.
- */
- private void positionClockAndNotifications() {
- boolean animate = mNotificationStackScroller.isAddOrRemoveAnimationPending();
- boolean animateClock = animate || mAnimateNextPositionUpdate;
- int stackScrollerPadding;
- if (mBarState != StatusBarState.KEYGUARD) {
- stackScrollerPadding = getUnlockedStackScrollerPadding();
- } else {
- int totalHeight = getHeight();
- int bottomPadding = Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding);
- int clockPreferredY = mKeyguardStatusView.getClockPreferredY(totalHeight);
- boolean bypassEnabled = mKeyguardBypassController.getBypassEnabled();
- final boolean hasVisibleNotifications =
- !bypassEnabled && mNotificationStackScroller.getVisibleNotificationCount() != 0;
- mKeyguardStatusView.setHasVisibleNotifications(hasVisibleNotifications);
- mClockPositionAlgorithm.setup(
- mStatusBarMinHeight,
- totalHeight - bottomPadding,
- mNotificationStackScroller.getIntrinsicContentHeight(),
- getExpandedFraction(),
- totalHeight,
- (int) (mKeyguardStatusView.getHeight()
- - mShelfHeight / 2.0f - mDarkIconSize / 2.0f),
- clockPreferredY,
- hasCustomClock(),
- hasVisibleNotifications,
- mInterpolatedDarkAmount,
- mEmptyDragAmount,
- bypassEnabled,
- getUnlockedStackScrollerPadding());
- mClockPositionAlgorithm.run(mClockPositionResult);
- PropertyAnimator.setProperty(mKeyguardStatusView, AnimatableProperty.X,
- mClockPositionResult.clockX, CLOCK_ANIMATION_PROPERTIES, animateClock);
- PropertyAnimator.setProperty(mKeyguardStatusView, AnimatableProperty.Y,
- mClockPositionResult.clockY, CLOCK_ANIMATION_PROPERTIES, animateClock);
- updateNotificationTranslucency();
- updateClock();
- stackScrollerPadding = mClockPositionResult.stackScrollerPaddingExpanded;
- }
- mNotificationStackScroller.setIntrinsicPadding(stackScrollerPadding);
- mKeyguardBottomArea.setAntiBurnInOffsetX(mClockPositionResult.clockX);
-
- mStackScrollerMeasuringPass++;
- requestScrollerTopPaddingUpdate(animate);
- mStackScrollerMeasuringPass = 0;
- mAnimateNextPositionUpdate = false;
- }
-
- /**
- * @return the padding of the stackscroller when unlocked
- */
- private int getUnlockedStackScrollerPadding() {
- return (mQs != null ? mQs.getHeader().getHeight() : 0) + mQsPeekHeight
- + mQsNotificationTopPadding;
- }
-
- /**
- * @param maximum the maximum to return at most
- * @return the maximum keyguard notifications that can fit on the screen
- */
- public int computeMaxKeyguardNotifications(int maximum) {
- float minPadding = mClockPositionAlgorithm.getMinStackScrollerPadding();
- int notificationPadding = Math.max(1, getResources().getDimensionPixelSize(
- R.dimen.notification_divider_height));
- NotificationShelf shelf = mNotificationStackScroller.getNotificationShelf();
- float shelfSize = shelf.getVisibility() == GONE ? 0
- : shelf.getIntrinsicHeight() + notificationPadding;
- float availableSpace = mNotificationStackScroller.getHeight() - minPadding - shelfSize
- - Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding)
- - mKeyguardStatusView.getLogoutButtonHeight();
- int count = 0;
- for (int i = 0; i < mNotificationStackScroller.getChildCount(); i++) {
- ExpandableView child = (ExpandableView) mNotificationStackScroller.getChildAt(i);
- if (!(child instanceof ExpandableNotificationRow)) {
- continue;
- }
- ExpandableNotificationRow row = (ExpandableNotificationRow) child;
- boolean suppressedSummary = mGroupManager != null
- && mGroupManager.isSummaryOfSuppressedGroup(row.getEntry().getSbn());
- if (suppressedSummary) {
- continue;
- }
- if (!mLockscreenUserManager.shouldShowOnKeyguard(row.getEntry())) {
- continue;
- }
- if (row.isRemoved()) {
- continue;
- }
- availableSpace -= child.getMinHeight(true /* ignoreTemporaryStates */)
- + notificationPadding;
- if (availableSpace >= 0 && count < maximum) {
- count++;
- } else if (availableSpace > -shelfSize) {
- // if we are exactly the last view, then we can show us still!
- for (int j = i + 1; j < mNotificationStackScroller.getChildCount(); j++) {
- if (mNotificationStackScroller.getChildAt(j)
- instanceof ExpandableNotificationRow) {
- return count;
- }
- }
- count++;
- return count;
- } else {
- return count;
- }
- }
- return count;
- }
-
- private void updateClock() {
- if (!mKeyguardStatusViewAnimating) {
- mKeyguardStatusView.setAlpha(mClockPositionResult.clockAlpha);
- }
- }
-
- public void animateToFullShade(long delay) {
- mNotificationStackScroller.goToFullShade(delay);
- requestLayout();
- mAnimateNextPositionUpdate = true;
- }
-
- public void setQsExpansionEnabled(boolean qsExpansionEnabled) {
- mQsExpansionEnabled = qsExpansionEnabled;
- if (mQs == null) return;
- mQs.setHeaderClickable(qsExpansionEnabled);
- }
-
- @Override
- public void resetViews(boolean animate) {
- mIsLaunchTransitionFinished = false;
- mBlockTouches = false;
- if (!mLaunchingAffordance) {
- mAffordanceHelper.reset(false);
- mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
- }
- mStatusBar.getGutsManager().closeAndSaveGuts(true /* leavebehind */, true /* force */,
- true /* controls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
- if (animate) {
- animateCloseQs(true /* animateAway */);
- } else {
- closeQs();
- }
- mNotificationStackScroller.setOverScrollAmount(0f, true /* onTop */, animate,
- !animate /* cancelAnimators */);
- mNotificationStackScroller.resetScrollPosition();
- }
-
- @Override
- public void collapse(boolean delayed, float speedUpFactor) {
- if (!canPanelBeCollapsed()) {
- return;
- }
-
- if (mQsExpanded) {
- mQsExpandImmediate = true;
- mNotificationStackScroller.setShouldShowShelfOnly(true);
- }
- super.collapse(delayed, speedUpFactor);
- }
-
- public void closeQs() {
- cancelQsAnimation();
- setQsExpansion(mQsMinExpansionHeight);
- }
-
- /**
- * Animate QS closing by flinging it.
- * If QS is expanded, it will collapse into QQS and stop.
- *
- * @param animateAway Do not stop when QS becomes QQS. Fling until QS isn't visible anymore.
- */
- public void animateCloseQs(boolean animateAway) {
- if (mQsExpansionAnimator != null) {
- if (!mQsAnimatorExpand) {
- return;
- }
- float height = mQsExpansionHeight;
- mQsExpansionAnimator.cancel();
- setQsExpansion(height);
- }
- flingSettings(0 /* vel */, animateAway ? FLING_HIDE : FLING_COLLAPSE);
- }
-
- public void expandWithQs() {
- if (mQsExpansionEnabled) {
- mQsExpandImmediate = true;
- mNotificationStackScroller.setShouldShowShelfOnly(true);
- }
- if (isFullyCollapsed()) {
- expand(true /* animate */);
- } else {
- flingSettings(0 /* velocity */, FLING_EXPAND);
- }
- }
-
- public void expandWithoutQs() {
- if (isQsExpanded()) {
- flingSettings(0 /* velocity */, FLING_COLLAPSE);
- } else {
- expand(true /* animate */);
- }
- }
-
- @Override
- public void fling(float vel, boolean expand) {
- GestureRecorder gr = ((PhoneStatusBarView) mBar).mBar.getGestureRecorder();
- if (gr != null) {
- gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel);
- }
- super.fling(vel, expand);
- }
-
- @Override
- protected void flingToHeight(float vel, boolean expand, float target,
- float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) {
- mHeadsUpTouchHelper.notifyFling(!expand);
- setClosingWithAlphaFadeout(!expand && !isOnKeyguard() && getFadeoutAlpha() == 1.0f);
- super.flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
- }
-
- @Override
- public boolean onInterceptTouchEvent(MotionEvent event) {
- if (mBlockTouches || mQsFullyExpanded && mQs.onInterceptTouchEvent(event)) {
- return false;
- }
- initDownStates(event);
- // Do not let touches go to shade or QS if the bouncer is visible,
- // but still let user swipe down to expand the panel, dismissing the bouncer.
- if (mStatusBar.isBouncerShowing()) {
- return true;
- }
- if (mBar.panelEnabled() && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
- mIsExpansionFromHeadsUp = true;
- MetricsLogger.count(mContext, COUNTER_PANEL_OPEN, 1);
- MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_PEEK, 1);
- return true;
- }
- if (!shouldQuickSettingsIntercept(mDownX, mDownY, 0)
- && mPulseExpansionHandler.onInterceptTouchEvent(event)) {
- return true;
- }
-
- if (!isFullyCollapsed() && onQsIntercept(event)) {
- return true;
- }
- return super.onInterceptTouchEvent(event);
- }
-
- private boolean onQsIntercept(MotionEvent event) {
- int pointerIndex = event.findPointerIndex(mTrackingPointer);
- if (pointerIndex < 0) {
- pointerIndex = 0;
- mTrackingPointer = event.getPointerId(pointerIndex);
- }
- final float x = event.getX(pointerIndex);
- final float y = event.getY(pointerIndex);
-
- switch (event.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- mIntercepting = true;
- mInitialTouchY = y;
- mInitialTouchX = x;
- initVelocityTracker();
- trackMovement(event);
- if (shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) {
- getParent().requestDisallowInterceptTouchEvent(true);
- }
- if (mQsExpansionAnimator != null) {
- onQsExpansionStarted();
- mInitialHeightOnTouch = mQsExpansionHeight;
- mQsTracking = true;
- mIntercepting = false;
- mNotificationStackScroller.cancelLongPress();
- }
- break;
- case MotionEvent.ACTION_POINTER_UP:
- final int upPointer = event.getPointerId(event.getActionIndex());
- if (mTrackingPointer == upPointer) {
- // gesture is ongoing, find a new pointer to track
- final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
- mTrackingPointer = event.getPointerId(newIndex);
- mInitialTouchX = event.getX(newIndex);
- mInitialTouchY = event.getY(newIndex);
- }
- break;
-
- case MotionEvent.ACTION_MOVE:
- final float h = y - mInitialTouchY;
- trackMovement(event);
- if (mQsTracking) {
-
- // Already tracking because onOverscrolled was called. We need to update here
- // so we don't stop for a frame until the next touch event gets handled in
- // onTouchEvent.
- setQsExpansion(h + mInitialHeightOnTouch);
- trackMovement(event);
- mIntercepting = false;
- return true;
- }
- if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX)
- && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) {
- mQsTracking = true;
- onQsExpansionStarted();
- notifyExpandingFinished();
- mInitialHeightOnTouch = mQsExpansionHeight;
- mInitialTouchY = y;
- mInitialTouchX = x;
- mIntercepting = false;
- mNotificationStackScroller.cancelLongPress();
- return true;
- }
- break;
-
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
- trackMovement(event);
- if (mQsTracking) {
- flingQsWithCurrentVelocity(y,
- event.getActionMasked() == MotionEvent.ACTION_CANCEL);
- mQsTracking = false;
- }
- mIntercepting = false;
- break;
- }
- return false;
- }
-
- @Override
- protected boolean isInContentBounds(float x, float y) {
- float stackScrollerX = mNotificationStackScroller.getX();
- return !mNotificationStackScroller.isBelowLastNotification(x - stackScrollerX, y)
- && stackScrollerX < x && x < stackScrollerX + mNotificationStackScroller.getWidth();
- }
-
- private void initDownStates(MotionEvent event) {
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
- mOnlyAffordanceInThisMotion = false;
- mQsTouchAboveFalsingThreshold = mQsFullyExpanded;
- mDozingOnDown = isDozing();
- mDownX = event.getX();
- mDownY = event.getY();
- mCollapsedOnDown = isFullyCollapsed();
- mListenForHeadsUp = mCollapsedOnDown && mHeadsUpManager.hasPinnedHeadsUp();
- mAllowExpandForSmallExpansion = mExpectingSynthesizedDown;
- mTouchSlopExceededBeforeDown = mExpectingSynthesizedDown;
- if (mExpectingSynthesizedDown) {
- mLastEventSynthesizedDown = true;
- } else {
- // down but not synthesized motion event.
- mLastEventSynthesizedDown = false;
- }
- } else {
- // not down event at all.
- mLastEventSynthesizedDown = false;
- }
- }
-
- private void flingQsWithCurrentVelocity(float y, boolean isCancelMotionEvent) {
- float vel = getCurrentQSVelocity();
- final boolean expandsQs = flingExpandsQs(vel);
- if (expandsQs) {
- logQsSwipeDown(y);
- }
- flingSettings(vel, expandsQs && !isCancelMotionEvent ? FLING_EXPAND : FLING_COLLAPSE);
- }
-
- private void logQsSwipeDown(float y) {
- float vel = getCurrentQSVelocity();
- final int gesture = mBarState == StatusBarState.KEYGUARD
- ? MetricsEvent.ACTION_LS_QS
- : MetricsEvent.ACTION_SHADE_QS_PULL;
- mLockscreenGestureLogger.write(gesture,
- (int) ((y - mInitialTouchY) / mStatusBar.getDisplayDensity()),
- (int) (vel / mStatusBar.getDisplayDensity()));
- }
-
- private boolean flingExpandsQs(float vel) {
- if (mFalsingManager.isUnlockingDisabled() || isFalseTouch()) {
- return false;
- }
- if (Math.abs(vel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
- return getQsExpansionFraction() > 0.5f;
- } else {
- return vel > 0;
- }
- }
-
- private boolean isFalseTouch() {
- if (!needsAntiFalsing()) {
- return false;
- }
- if (mFalsingManager.isClassifierEnabled()) {
- return mFalsingManager.isFalseTouch();
- }
- return !mQsTouchAboveFalsingThreshold;
- }
-
- private float getQsExpansionFraction() {
- return Math.min(1f, (mQsExpansionHeight - mQsMinExpansionHeight)
- / (mQsMaxExpansionHeight - mQsMinExpansionHeight));
- }
-
- @Override
- protected boolean shouldExpandWhenNotFlinging() {
- if (super.shouldExpandWhenNotFlinging()) {
- return true;
- }
- if (mAllowExpandForSmallExpansion) {
- // When we get a touch that came over from launcher, the velocity isn't always correct
- // Let's err on expanding if the gesture has been reasonably slow
- long timeSinceDown = SystemClock.uptimeMillis() - mDownTime;
- return timeSinceDown <= MAX_TIME_TO_OPEN_WHEN_FLINGING_FROM_LAUNCHER;
- }
- return false;
- }
-
- @Override
- protected float getOpeningHeight() {
- return mNotificationStackScroller.getOpeningHeight();
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- if (mBlockTouches || (mQs != null && mQs.isCustomizing())) {
- return false;
- }
-
- // Do not allow panel expansion if bouncer is scrimmed, otherwise user would be able to
- // pull down QS or expand the shade.
- if (mStatusBar.isBouncerShowingScrimmed()) {
- return false;
- }
-
- // Make sure the next touch won't the blocked after the current ends.
- if (event.getAction() == MotionEvent.ACTION_UP
- || event.getAction() == MotionEvent.ACTION_CANCEL) {
- mBlockingExpansionForCurrentTouch = false;
- }
- // When touch focus transfer happens, ACTION_DOWN->ACTION_UP may happen immediately
- // without any ACTION_MOVE event.
- // In such case, simply expand the panel instead of being stuck at the bottom bar.
- if (mLastEventSynthesizedDown && event.getAction() == MotionEvent.ACTION_UP) {
- expand(true /* animate */);
- }
- initDownStates(event);
- if (!mIsExpanding && !shouldQuickSettingsIntercept(mDownX, mDownY, 0)
- && mPulseExpansionHandler.onTouchEvent(event)) {
- // We're expanding all the other ones shouldn't get this anymore
- return true;
- }
- if (mListenForHeadsUp && !mHeadsUpTouchHelper.isTrackingHeadsUp()
- && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
- mIsExpansionFromHeadsUp = true;
- MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_PEEK, 1);
- }
- boolean handled = false;
- if ((!mIsExpanding || mHintAnimationRunning)
- && !mQsExpanded
- && mBarState != StatusBarState.SHADE
- && !mDozing) {
- handled |= mAffordanceHelper.onTouchEvent(event);
- }
- if (mOnlyAffordanceInThisMotion) {
- return true;
- }
- handled |= mHeadsUpTouchHelper.onTouchEvent(event);
-
- if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && handleQsTouch(event)) {
- return true;
- }
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
- MetricsLogger.count(mContext, COUNTER_PANEL_OPEN, 1);
- updateVerticalPanelPosition(event.getX());
- handled = true;
- }
- handled |= super.onTouchEvent(event);
- return !mDozing || mPulsing || handled;
- }
-
- private boolean handleQsTouch(MotionEvent event) {
- final int action = event.getActionMasked();
- if (action == MotionEvent.ACTION_DOWN && getExpandedFraction() == 1f
- && mBarState != StatusBarState.KEYGUARD && !mQsExpanded
- && mQsExpansionEnabled) {
-
- // Down in the empty area while fully expanded - go to QS.
- mQsTracking = true;
- mConflictingQsExpansionGesture = true;
- onQsExpansionStarted();
- mInitialHeightOnTouch = mQsExpansionHeight;
- mInitialTouchY = event.getX();
- mInitialTouchX = event.getY();
- }
- if (!isFullyCollapsed()) {
- handleQsDown(event);
- }
- if (!mQsExpandImmediate && mQsTracking) {
- onQsTouch(event);
- if (!mConflictingQsExpansionGesture) {
- return true;
- }
- }
- if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
- mConflictingQsExpansionGesture = false;
- }
- if (action == MotionEvent.ACTION_DOWN && isFullyCollapsed()
- && mQsExpansionEnabled) {
- mTwoFingerQsExpandPossible = true;
- }
- if (mTwoFingerQsExpandPossible && isOpenQsEvent(event)
- && event.getY(event.getActionIndex()) < mStatusBarMinHeight) {
- MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_QS, 1);
- mQsExpandImmediate = true;
- mNotificationStackScroller.setShouldShowShelfOnly(true);
- requestPanelHeightUpdate();
-
- // Normally, we start listening when the panel is expanded, but here we need to start
- // earlier so the state is already up to date when dragging down.
- setListening(true);
- }
- if (isQsSplitEnabled() && !mKeyguardShowing) {
- if (mQsExpandImmediate) {
- mNotificationStackScroller.setVisibility(View.GONE);
- mQsFrame.setVisibility(View.VISIBLE);
- mHomeControlsLayout.setVisibility(View.VISIBLE);
- } else {
- mNotificationStackScroller.setVisibility(View.VISIBLE);
- mQsFrame.setVisibility(View.GONE);
- mHomeControlsLayout.setVisibility(View.GONE);
- }
- }
- return false;
- }
-
- private boolean isInQsArea(float x, float y) {
- return (x >= mQsFrame.getX()
- && x <= mQsFrame.getX() + mQsFrame.getWidth())
- && (y <= mNotificationStackScroller.getBottomMostNotificationBottom()
- || y <= mQs.getView().getY() + mQs.getView().getHeight());
- }
-
- private boolean isOnQsEndArea(float x) {
- if (!isQsSplitEnabled()) return false;
- if (getLayoutDirection() == LAYOUT_DIRECTION_LTR) {
- return x >= mQsFrame.getX() + mQqsSplitFraction * mQsFrame.getWidth()
- && x <= mQsFrame.getX() + mQsFrame.getWidth();
- } else {
- return x >= mQsFrame.getX()
- && x <= mQsFrame.getX() + (1 - mQqsSplitFraction) * mQsFrame.getWidth();
- }
- }
-
- private boolean isOpenQsEvent(MotionEvent event) {
- final int pointerCount = event.getPointerCount();
- final int action = event.getActionMasked();
-
- final boolean twoFingerDrag = action == MotionEvent.ACTION_POINTER_DOWN
- && pointerCount == 2;
-
- final boolean stylusButtonClickDrag = action == MotionEvent.ACTION_DOWN
- && (event.isButtonPressed(MotionEvent.BUTTON_STYLUS_PRIMARY)
- || event.isButtonPressed(MotionEvent.BUTTON_STYLUS_SECONDARY));
-
- final boolean mouseButtonClickDrag = action == MotionEvent.ACTION_DOWN
- && (event.isButtonPressed(MotionEvent.BUTTON_SECONDARY)
- || event.isButtonPressed(MotionEvent.BUTTON_TERTIARY));
-
- final boolean onHeaderRight = isOnQsEndArea(event.getX());
-
- return twoFingerDrag || stylusButtonClickDrag || mouseButtonClickDrag || onHeaderRight;
- }
-
- private void handleQsDown(MotionEvent event) {
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN
- && shouldQuickSettingsIntercept(event.getX(), event.getY(), -1)) {
- mFalsingManager.onQsDown();
- mQsTracking = true;
- onQsExpansionStarted();
- mInitialHeightOnTouch = mQsExpansionHeight;
- mInitialTouchY = event.getX();
- mInitialTouchX = event.getY();
-
- // If we interrupt an expansion gesture here, make sure to update the state correctly.
- notifyExpandingFinished();
- }
- }
-
- /**
- * Input focus transfer is about to happen.
- */
- public void startWaitingForOpenPanelGesture() {
- if (!isFullyCollapsed()) {
- return;
- }
- mExpectingSynthesizedDown = true;
- onTrackingStarted();
- updatePanelExpanded();
- }
-
- /**
- * Called when this view is no longer waiting for input focus transfer.
- *
- * There are two scenarios behind this function call. First, input focus transfer
- * has successfully happened and this view already received synthetic DOWN event.
- * (mExpectingSynthesizedDown == false). Do nothing.
- *
- * Second, before input focus transfer finished, user may have lifted finger
- * in previous window and this window never received synthetic DOWN event.
- * (mExpectingSynthesizedDown == true).
- * In this case, we use the velocity to trigger fling event.
- *
- * @param velocity unit is in px / millis
- */
- public void stopWaitingForOpenPanelGesture(final float velocity) {
- if (mExpectingSynthesizedDown) {
- mExpectingSynthesizedDown = false;
- maybeVibrateOnOpening();
- Runnable runnable = () -> fling(velocity > 1f ? 1000f * velocity : 0,
- true /* expand */);
- if (mStatusBar.getStatusBarWindow().getHeight()
- != mStatusBar.getStatusBarHeight()) {
- // The panel is already expanded to its full size, let's expand directly
- runnable.run();
- } else {
- mExpandAfterLayoutRunnable = runnable;
- }
- onTrackingStopped(false);
- }
- }
-
- @Override
- protected boolean flingExpands(float vel, float vectorVel, float x, float y) {
- boolean expands = super.flingExpands(vel, vectorVel, x, y);
-
- // If we are already running a QS expansion, make sure that we keep the panel open.
- if (mQsExpansionAnimator != null) {
- expands = true;
- }
- return expands;
- }
-
- @Override
- protected boolean shouldGestureWaitForTouchSlop() {
- if (mExpectingSynthesizedDown) {
- mExpectingSynthesizedDown = false;
- return false;
- }
- return isFullyCollapsed() || mBarState != StatusBarState.SHADE;
- }
-
- @Override
- protected boolean shouldGestureIgnoreXTouchSlop(float x, float y) {
- return !mAffordanceHelper.isOnAffordanceIcon(x, y);
- }
-
- private void onQsTouch(MotionEvent event) {
- int pointerIndex = event.findPointerIndex(mTrackingPointer);
- if (pointerIndex < 0) {
- pointerIndex = 0;
- mTrackingPointer = event.getPointerId(pointerIndex);
- }
- final float y = event.getY(pointerIndex);
- final float x = event.getX(pointerIndex);
- final float h = y - mInitialTouchY;
-
- switch (event.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- mQsTracking = true;
- mInitialTouchY = y;
- mInitialTouchX = x;
- onQsExpansionStarted();
- mInitialHeightOnTouch = mQsExpansionHeight;
- initVelocityTracker();
- trackMovement(event);
- break;
-
- case MotionEvent.ACTION_POINTER_UP:
- final int upPointer = event.getPointerId(event.getActionIndex());
- if (mTrackingPointer == upPointer) {
- // gesture is ongoing, find a new pointer to track
- final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
- final float newY = event.getY(newIndex);
- final float newX = event.getX(newIndex);
- mTrackingPointer = event.getPointerId(newIndex);
- mInitialHeightOnTouch = mQsExpansionHeight;
- mInitialTouchY = newY;
- mInitialTouchX = newX;
- }
- break;
-
- case MotionEvent.ACTION_MOVE:
- setQsExpansion(h + mInitialHeightOnTouch);
- if (h >= getFalsingThreshold()) {
- mQsTouchAboveFalsingThreshold = true;
- }
- trackMovement(event);
- break;
-
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- mQsTracking = false;
- mTrackingPointer = -1;
- trackMovement(event);
- float fraction = getQsExpansionFraction();
- if (fraction != 0f || y >= mInitialTouchY) {
- flingQsWithCurrentVelocity(y,
- event.getActionMasked() == MotionEvent.ACTION_CANCEL);
- }
- if (mQsVelocityTracker != null) {
- mQsVelocityTracker.recycle();
- mQsVelocityTracker = null;
- }
- break;
- }
- }
-
- private int getFalsingThreshold() {
- float factor = mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
- return (int) (mQsFalsingThreshold * factor);
- }
-
- @Override
- public void onOverscrollTopChanged(float amount, boolean isRubberbanded) {
- cancelQsAnimation();
- if (!mQsExpansionEnabled) {
- amount = 0f;
- }
- float rounded = amount >= 1f ? amount : 0f;
- setOverScrolling(rounded != 0f && isRubberbanded);
- mQsExpansionFromOverscroll = rounded != 0f;
- mLastOverscroll = rounded;
- updateQsState();
- setQsExpansion(mQsMinExpansionHeight + rounded);
- }
-
- @Override
- public void flingTopOverscroll(float velocity, boolean open) {
- mLastOverscroll = 0f;
- mQsExpansionFromOverscroll = false;
- setQsExpansion(mQsExpansionHeight);
- flingSettings(!mQsExpansionEnabled && open ? 0f : velocity,
- open && mQsExpansionEnabled ? FLING_EXPAND : FLING_COLLAPSE,
- new Runnable() {
- @Override
- public void run() {
- mStackScrollerOverscrolling = false;
- setOverScrolling(false);
- updateQsState();
- }
- }, false /* isClick */);
- }
-
- private void setOverScrolling(boolean overscrolling) {
- mStackScrollerOverscrolling = overscrolling;
- if (mQs == null) return;
- mQs.setOverscrolling(overscrolling);
- }
-
- private void onQsExpansionStarted() {
- onQsExpansionStarted(0);
- }
-
- protected void onQsExpansionStarted(int overscrollAmount) {
- cancelQsAnimation();
- cancelHeightAnimator();
-
- // Reset scroll position and apply that position to the expanded height.
- float height = mQsExpansionHeight - overscrollAmount;
- setQsExpansion(height);
- requestPanelHeightUpdate();
- mNotificationStackScroller.checkSnoozeLeavebehind();
-
- // When expanding QS, let's authenticate the user if possible,
- // this will speed up notification actions.
- if (height == 0) {
- mStatusBar.requestFaceAuth();
- }
- }
-
- private void setQsExpanded(boolean expanded) {
- boolean changed = mQsExpanded != expanded;
- if (changed) {
- mQsExpanded = expanded;
- updateQsState();
- requestPanelHeightUpdate();
- mFalsingManager.setQsExpanded(expanded);
- mStatusBar.setQsExpanded(expanded);
- mNotificationContainerParent.setQsExpanded(expanded);
- mPulseExpansionHandler.setQsExpanded(expanded);
- mKeyguardBypassController.setQSExpanded(expanded);
- }
- }
-
- @Override
- public void onStateChanged(int statusBarState) {
- boolean goingToFullShade = mStatusBarStateController.goingToFullShade();
- boolean keyguardFadingAway = mKeyguardStateController.isKeyguardFadingAway();
- int oldState = mBarState;
- boolean keyguardShowing = statusBarState == StatusBarState.KEYGUARD;
- setKeyguardStatusViewVisibility(statusBarState, keyguardFadingAway, goingToFullShade);
- setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
-
- mBarState = statusBarState;
- mKeyguardShowing = keyguardShowing;
- if (mKeyguardShowing && isQsSplitEnabled()) {
- mNotificationStackScroller.setVisibility(View.VISIBLE);
- mQsFrame.setVisibility(View.VISIBLE);
- mHomeControlsLayout.setVisibility(View.GONE);
- }
-
- if (oldState == StatusBarState.KEYGUARD
- && (goingToFullShade || statusBarState == StatusBarState.SHADE_LOCKED)) {
- animateKeyguardStatusBarOut();
- long delay = mBarState == StatusBarState.SHADE_LOCKED
- ? 0 : mKeyguardStateController.calculateGoingToFullShadeDelay();
- mQs.animateHeaderSlidingIn(delay);
- } else if (oldState == StatusBarState.SHADE_LOCKED
- && statusBarState == StatusBarState.KEYGUARD) {
- animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
- mNotificationStackScroller.resetScrollPosition();
- // Only animate header if the header is visible. If not, it will partially animate out
- // the top of QS
- if (!mQsExpanded) {
- mQs.animateHeaderSlidingOut();
- }
- } else {
- mKeyguardStatusBar.setAlpha(1f);
- mKeyguardStatusBar.setVisibility(keyguardShowing ? View.VISIBLE : View.INVISIBLE);
- ((PhoneStatusBarView) mBar).maybeShowDivider(keyguardShowing);
- if (keyguardShowing && oldState != mBarState) {
- if (mQs != null) {
- mQs.hideImmediately();
- }
- }
- }
- updateKeyguardStatusBarForHeadsUp();
- if (keyguardShowing) {
- updateDozingVisibilities(false /* animate */);
- }
- // THe update needs to happen after the headerSlide in above, otherwise the translation
- // would reset
- updateQSPulseExpansion();
- maybeAnimateBottomAreaAlpha();
- resetHorizontalPanelPosition();
- updateQsState();
- }
-
- private void maybeAnimateBottomAreaAlpha() {
- mBottomAreaShadeAlphaAnimator.cancel();
- if (mBarState == StatusBarState.SHADE_LOCKED) {
- mBottomAreaShadeAlphaAnimator.start();
- } else {
- mBottomAreaShadeAlpha = 1f;
- }
- }
-
- private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = new Runnable() {
- @Override
- public void run() {
- mKeyguardStatusViewAnimating = false;
- mKeyguardStatusView.setVisibility(View.INVISIBLE);
- }
- };
-
- private final Runnable mAnimateKeyguardStatusViewGoneEndRunnable = new Runnable() {
- @Override
- public void run() {
- mKeyguardStatusViewAnimating = false;
- mKeyguardStatusView.setVisibility(View.GONE);
- }
- };
-
- private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = new Runnable() {
- @Override
- public void run() {
- mKeyguardStatusViewAnimating = false;
- }
- };
-
- private final Runnable mAnimateKeyguardStatusBarInvisibleEndRunnable = new Runnable() {
- @Override
- public void run() {
- mKeyguardStatusBar.setVisibility(View.INVISIBLE);
- mKeyguardStatusBar.setAlpha(1f);
- mKeyguardStatusBarAnimateAlpha = 1f;
- }
- };
-
- private void animateKeyguardStatusBarOut() {
- ValueAnimator anim = ValueAnimator.ofFloat(mKeyguardStatusBar.getAlpha(), 0f);
- anim.addUpdateListener(mStatusBarAnimateAlphaListener);
- anim.setStartDelay(mKeyguardStateController.isKeyguardFadingAway()
- ? mKeyguardStateController.getKeyguardFadingAwayDelay()
- : 0);
-
- long duration;
- if (mKeyguardStateController.isKeyguardFadingAway()) {
- duration = mKeyguardStateController.getShortenedFadingAwayDuration();
- } else {
- duration = StackStateAnimator.ANIMATION_DURATION_STANDARD;
- }
- anim.setDuration(duration);
-
- anim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
- anim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mAnimateKeyguardStatusBarInvisibleEndRunnable.run();
- }
- });
- anim.start();
- }
-
- private final ValueAnimator.AnimatorUpdateListener mStatusBarAnimateAlphaListener =
- new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- mKeyguardStatusBarAnimateAlpha = (float) animation.getAnimatedValue();
- updateHeaderKeyguardAlpha();
- }
- };
-
- private void animateKeyguardStatusBarIn(long duration) {
- mKeyguardStatusBar.setVisibility(View.VISIBLE);
- mKeyguardStatusBar.setAlpha(0f);
- ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
- anim.addUpdateListener(mStatusBarAnimateAlphaListener);
- anim.setDuration(duration);
- anim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
- anim.start();
- }
-
- private final Runnable mAnimateKeyguardBottomAreaInvisibleEndRunnable = new Runnable() {
- @Override
- public void run() {
- mKeyguardBottomArea.setVisibility(View.GONE);
- }
- };
-
- private void setKeyguardBottomAreaVisibility(int statusBarState, boolean goingToFullShade) {
- mKeyguardBottomArea.animate().cancel();
- if (goingToFullShade) {
- mKeyguardBottomArea.animate()
- .alpha(0f)
- .setStartDelay(mKeyguardStateController.getKeyguardFadingAwayDelay())
- .setDuration(mKeyguardStateController.getShortenedFadingAwayDuration())
- .setInterpolator(Interpolators.ALPHA_OUT)
- .withEndAction(mAnimateKeyguardBottomAreaInvisibleEndRunnable)
- .start();
- } else if (statusBarState == StatusBarState.KEYGUARD
- || statusBarState == StatusBarState.SHADE_LOCKED) {
- mKeyguardBottomArea.setVisibility(View.VISIBLE);
- mKeyguardBottomArea.setAlpha(1f);
- } else {
- mKeyguardBottomArea.setVisibility(View.GONE);
- }
- }
-
- private void setKeyguardStatusViewVisibility(int statusBarState, boolean keyguardFadingAway,
- boolean goingToFullShade) {
- mKeyguardStatusView.animate().cancel();
- mKeyguardStatusViewAnimating = false;
- if ((!keyguardFadingAway && mBarState == StatusBarState.KEYGUARD
- && statusBarState != StatusBarState.KEYGUARD) || goingToFullShade) {
- mKeyguardStatusViewAnimating = true;
- mKeyguardStatusView.animate()
- .alpha(0f)
- .setStartDelay(0)
- .setDuration(160)
- .setInterpolator(Interpolators.ALPHA_OUT)
- .withEndAction(mAnimateKeyguardStatusViewGoneEndRunnable);
- if (keyguardFadingAway) {
- mKeyguardStatusView.animate()
- .setStartDelay(mKeyguardStateController.getKeyguardFadingAwayDelay())
- .setDuration(mKeyguardStateController.getShortenedFadingAwayDuration())
- .start();
- }
- } else if (mBarState == StatusBarState.SHADE_LOCKED
- && statusBarState == StatusBarState.KEYGUARD) {
- mKeyguardStatusView.setVisibility(View.VISIBLE);
- mKeyguardStatusViewAnimating = true;
- mKeyguardStatusView.setAlpha(0f);
- mKeyguardStatusView.animate()
- .alpha(1f)
- .setStartDelay(0)
- .setDuration(320)
- .setInterpolator(Interpolators.ALPHA_IN)
- .withEndAction(mAnimateKeyguardStatusViewVisibleEndRunnable);
- } else if (statusBarState == StatusBarState.KEYGUARD) {
- if (keyguardFadingAway) {
- mKeyguardStatusViewAnimating = true;
- mKeyguardStatusView.animate()
- .alpha(0)
- .translationYBy(-getHeight() * 0.05f)
- .setInterpolator(Interpolators.FAST_OUT_LINEAR_IN)
- .setDuration(125)
- .setStartDelay(0)
- .withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable)
- .start();
- } else {
- mKeyguardStatusView.setVisibility(View.VISIBLE);
- mKeyguardStatusView.setAlpha(1f);
- }
- } else {
- mKeyguardStatusView.setVisibility(View.GONE);
- mKeyguardStatusView.setAlpha(1f);
- }
- }
-
- private void updateQsState() {
- mNotificationStackScroller.setQsExpanded(mQsExpanded);
- mNotificationStackScroller.setScrollingEnabled(
- mBarState != StatusBarState.KEYGUARD && (!mQsExpanded
- || mQsExpansionFromOverscroll));
- updateEmptyShadeView();
- if (mNPVPluginManager != null) {
- mNPVPluginManager.changeVisibility((mBarState != StatusBarState.KEYGUARD)
- ? View.VISIBLE
- : View.INVISIBLE);
- }
- mQsNavbarScrim.setVisibility(mBarState == StatusBarState.SHADE && mQsExpanded
- && !mStackScrollerOverscrolling && mQsScrimEnabled
- ? View.VISIBLE
- : View.INVISIBLE);
- if (mKeyguardUserSwitcher != null && mQsExpanded && !mStackScrollerOverscrolling) {
- mKeyguardUserSwitcher.hideIfNotSimple(true /* animate */);
- }
- if (mQs == null) return;
- mQs.setExpanded(mQsExpanded);
- }
-
- private void setQsExpansion(float height) {
- height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight);
- mQsFullyExpanded = height == mQsMaxExpansionHeight && mQsMaxExpansionHeight != 0;
- if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling
- && !mDozing) {
- setQsExpanded(true);
- } else if (height <= mQsMinExpansionHeight && mQsExpanded) {
- setQsExpanded(false);
- }
- mQsExpansionHeight = height;
- updateQsExpansion();
- requestScrollerTopPaddingUpdate(false /* animate */);
- updateHeaderKeyguardAlpha();
- if (mBarState == StatusBarState.SHADE_LOCKED
- || mBarState == StatusBarState.KEYGUARD) {
- updateKeyguardBottomAreaAlpha();
- updateBigClockAlpha();
- }
- if (mBarState == StatusBarState.SHADE && mQsExpanded
- && !mStackScrollerOverscrolling && mQsScrimEnabled) {
- mQsNavbarScrim.setAlpha(getQsExpansionFraction());
- }
-
- if (mAccessibilityManager.isEnabled()) {
- setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
- }
-
- if (!mFalsingManager.isUnlockingDisabled() && mQsFullyExpanded
- && mFalsingManager.shouldEnforceBouncer()) {
- mStatusBar.executeRunnableDismissingKeyguard(null, null /* cancelAction */,
- false /* dismissShade */, true /* afterKeyguardGone */, false /* deferred */);
- }
- for (int i = 0; i < mExpansionListeners.size(); i++) {
- mExpansionListeners.get(i).onQsExpansionChanged(mQsMaxExpansionHeight != 0
- ? mQsExpansionHeight / mQsMaxExpansionHeight : 0);
- }
- if (DEBUG) {
- invalidate();
- }
- }
-
- protected void updateQsExpansion() {
- if (mQs == null) return;
- float qsExpansionFraction = getQsExpansionFraction();
- mQs.setQsExpansion(qsExpansionFraction, getHeaderTranslation());
- int heightDiff = mQs.getDesiredHeight() - mQs.getQsMinExpansionHeight();
- if (mNPVPluginManager != null) {
- mNPVPluginManager.setExpansion(qsExpansionFraction, getHeaderTranslation(), heightDiff);
- }
- mNotificationStackScroller.setQsExpansionFraction(qsExpansionFraction);
- }
-
- private String determineAccessibilityPaneTitle() {
- if (mQs != null && mQs.isCustomizing()) {
- return getContext().getString(R.string.accessibility_desc_quick_settings_edit);
- } else if (mQsExpansionHeight != 0.0f && mQsFullyExpanded) {
- // Upon initialisation when we are not layouted yet we don't want to announce that we
- // are fully expanded, hence the != 0.0f check.
- return getContext().getString(R.string.accessibility_desc_quick_settings);
- } else if (mBarState == StatusBarState.KEYGUARD) {
- return getContext().getString(R.string.accessibility_desc_lock_screen);
- } else {
- return getContext().getString(R.string.accessibility_desc_notification_shade);
- }
- }
-
- private float calculateQsTopPadding() {
- if (mKeyguardShowing
- && (mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted)) {
-
- // Either QS pushes the notifications down when fully expanded, or QS is fully above the
- // notifications (mostly on tablets). maxNotificationPadding denotes the normal top
- // padding on Keyguard, maxQsPadding denotes the top padding from the quick settings
- // panel. We need to take the maximum and linearly interpolate with the panel expansion
- // for a nice motion.
- int maxNotificationPadding = getKeyguardNotificationStaticPadding();
- int maxQsPadding = mQsMaxExpansionHeight + mQsNotificationTopPadding;
- int max = mBarState == StatusBarState.KEYGUARD
- ? Math.max(maxNotificationPadding, maxQsPadding)
- : maxQsPadding;
- return (int) MathUtils.lerp((float) mQsMinExpansionHeight, (float) max,
- getExpandedFraction());
- } else if (mQsSizeChangeAnimator != null) {
- return Math.max((int) mQsSizeChangeAnimator.getAnimatedValue(),
- getKeyguardNotificationStaticPadding());
- } else if (mKeyguardShowing) {
- // We can only do the smoother transition on Keyguard when we also are not collapsing
- // from a scrolled quick settings.
- return MathUtils.lerp((float) getKeyguardNotificationStaticPadding(),
- (float) (mQsMaxExpansionHeight + mQsNotificationTopPadding),
- getQsExpansionFraction());
- } else {
- return mQsExpansionHeight + mQsNotificationTopPadding;
- }
- }
-
- /**
- * @return the topPadding of notifications when on keyguard not respecting quick settings
- * expansion
- */
- private int getKeyguardNotificationStaticPadding() {
- if (!mKeyguardShowing) {
- return 0;
- }
- if (!mKeyguardBypassController.getBypassEnabled()) {
- return mClockPositionResult.stackScrollerPadding;
- }
- int collapsedPosition = mHeadsUpInset;
- if (!mNotificationStackScroller.isPulseExpanding()) {
- return collapsedPosition;
- } else {
- int expandedPosition = mClockPositionResult.stackScrollerPadding;
- return (int) MathUtils.lerp(collapsedPosition, expandedPosition,
- mNotificationStackScroller.calculateAppearFractionBypass());
- }
- }
-
-
- protected void requestScrollerTopPaddingUpdate(boolean animate) {
- mNotificationStackScroller.updateTopPadding(calculateQsTopPadding(), animate);
- if (mKeyguardShowing && mKeyguardBypassController.getBypassEnabled()) {
- // update the position of the header
- updateQsExpansion();
- }
- }
-
-
- private void updateQSPulseExpansion() {
- if (mQs != null) {
- mQs.setShowCollapsedOnKeyguard(mKeyguardShowing
- && mKeyguardBypassController.getBypassEnabled()
- && mNotificationStackScroller.isPulseExpanding());
- }
- }
-
- private void trackMovement(MotionEvent event) {
- if (mQsVelocityTracker != null) mQsVelocityTracker.addMovement(event);
- mLastTouchX = event.getX();
- mLastTouchY = event.getY();
- }
-
- private void initVelocityTracker() {
- if (mQsVelocityTracker != null) {
- mQsVelocityTracker.recycle();
- }
- mQsVelocityTracker = VelocityTracker.obtain();
- }
-
- private float getCurrentQSVelocity() {
- if (mQsVelocityTracker == null) {
- return 0;
- }
- mQsVelocityTracker.computeCurrentVelocity(1000);
- return mQsVelocityTracker.getYVelocity();
- }
-
- private void cancelQsAnimation() {
- if (mQsExpansionAnimator != null) {
- mQsExpansionAnimator.cancel();
- }
- }
-
- /**
- * @see #flingSettings(float, int, Runnable, boolean)
- */
- public void flingSettings(float vel, int type) {
- flingSettings(vel, type, null, false /* isClick */);
- }
-
- /**
- * Animates QS or QQS as if the user had swiped up or down.
- *
- * @param vel Finger velocity or 0 when not initiated by touch events.
- * @param type Either {@link #FLING_EXPAND}, {@link #FLING_COLLAPSE} or {@link #FLING_HIDE}.
- * @param onFinishRunnable Runnable to be executed at the end of animation.
- * @param isClick If originated by click (different interpolator and duration.)
- */
- protected void flingSettings(float vel, int type, final Runnable onFinishRunnable,
- boolean isClick) {
- float target;
- switch (type) {
- case FLING_EXPAND:
- target = mQsMaxExpansionHeight;
- break;
- case FLING_COLLAPSE:
- target = mQsMinExpansionHeight;
- break;
- case FLING_HIDE:
- default:
- target = 0;
- }
- if (target == mQsExpansionHeight) {
- if (onFinishRunnable != null) {
- onFinishRunnable.run();
- }
- return;
- }
-
- // If we move in the opposite direction, reset velocity and use a different duration.
- boolean oppositeDirection = false;
- boolean expanding = type == FLING_EXPAND;
- if (vel > 0 && !expanding || vel < 0 && expanding) {
- vel = 0;
- oppositeDirection = true;
- }
- ValueAnimator animator = ValueAnimator.ofFloat(mQsExpansionHeight, target);
- if (isClick) {
- animator.setInterpolator(Interpolators.TOUCH_RESPONSE);
- animator.setDuration(368);
- } else {
- mFlingAnimationUtils.apply(animator, mQsExpansionHeight, target, vel);
- }
- if (oppositeDirection) {
- animator.setDuration(350);
- }
- animator.addUpdateListener(animation -> {
- setQsExpansion((Float) animation.getAnimatedValue());
- });
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mNotificationStackScroller.resetCheckSnoozeLeavebehind();
- mQsExpansionAnimator = null;
- if (onFinishRunnable != null) {
- onFinishRunnable.run();
- }
- }
- });
- animator.start();
- mQsExpansionAnimator = animator;
- mQsAnimatorExpand = expanding;
- }
-
- /**
- * @return Whether we should intercept a gesture to open Quick Settings.
- */
- private boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) {
- if (!mQsExpansionEnabled || mCollapsedOnDown
- || (mKeyguardShowing && mKeyguardBypassController.getBypassEnabled())) {
- return false;
- }
- View header = mKeyguardShowing || mQs == null ? mKeyguardStatusBar : mQs.getHeader();
- final boolean onHeader = x >= mQsFrame.getX()
- && x <= mQsFrame.getX() + mQsFrame.getWidth()
- && y >= header.getTop() && y <= header.getBottom();
- if (mQsExpanded) {
- return onHeader || (yDiff < 0 && isInQsArea(x, y));
- } else {
- return onHeader;
- }
- }
-
- @Override
- protected boolean isScrolledToBottom() {
- if (!isInSettings()) {
- return mBarState == StatusBarState.KEYGUARD
- || mNotificationStackScroller.isScrolledToBottom();
- } else {
- return true;
- }
- }
-
- @Override
- protected int getMaxPanelHeight() {
- if (mKeyguardBypassController.getBypassEnabled() && mBarState == StatusBarState.KEYGUARD) {
- return getMaxPanelHeightBypass();
- } else {
- return getMaxPanelHeightNonBypass();
- }
- }
-
- private int getMaxPanelHeightNonBypass() {
- int min = mStatusBarMinHeight;
- if (!(mBarState == StatusBarState.KEYGUARD)
- && mNotificationStackScroller.getNotGoneChildCount() == 0) {
- int minHeight = (int) (mQsMinExpansionHeight + getOverExpansionAmount());
- min = Math.max(min, minHeight);
- }
- int maxHeight;
- if (mQsExpandImmediate || mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted
- || mPulsing) {
- maxHeight = calculatePanelHeightQsExpanded();
- } else {
- maxHeight = calculatePanelHeightShade();
- }
- maxHeight = Math.max(maxHeight, min);
- return maxHeight;
- }
-
- private int getMaxPanelHeightBypass() {
- int position = mClockPositionAlgorithm.getExpandedClockPosition()
- + mKeyguardStatusView.getHeight();
- if (mNotificationStackScroller.getVisibleNotificationCount() != 0) {
- position += mShelfHeight / 2.0f + mDarkIconSize / 2.0f;
- }
- return position;
- }
-
- public boolean isInSettings() {
- return mQsExpanded;
- }
-
- public boolean isExpanding() {
- return mIsExpanding;
- }
-
- @Override
- protected void onHeightUpdated(float expandedHeight) {
- if (!mQsExpanded || mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted) {
- // Updating the clock position will set the top padding which might
- // trigger a new panel height and re-position the clock.
- // This is a circular dependency and should be avoided, otherwise we'll have
- // a stack overflow.
- if (mStackScrollerMeasuringPass > 2) {
- if (DEBUG) Log.d(TAG, "Unstable notification panel height. Aborting.");
- } else {
- positionClockAndNotifications();
- }
- }
- if (mQsExpandImmediate || mQsExpanded && !mQsTracking && mQsExpansionAnimator == null
- && !mQsExpansionFromOverscroll) {
- float t;
- if (mKeyguardShowing) {
-
- // On Keyguard, interpolate the QS expansion linearly to the panel expansion
- t = expandedHeight / (getMaxPanelHeight());
- } else {
- // In Shade, interpolate linearly such that QS is closed whenever panel height is
- // minimum QS expansion + minStackHeight
- float panelHeightQsCollapsed = mNotificationStackScroller.getIntrinsicPadding()
- + mNotificationStackScroller.getLayoutMinHeight();
- float panelHeightQsExpanded = calculatePanelHeightQsExpanded();
- t = (expandedHeight - panelHeightQsCollapsed)
- / (panelHeightQsExpanded - panelHeightQsCollapsed);
- }
- float targetHeight = mQsMinExpansionHeight
- + t * (mQsMaxExpansionHeight - mQsMinExpansionHeight);
- setQsExpansion(targetHeight);
- mHomeControlsLayout.setTranslationY(targetHeight);
- }
- updateExpandedHeight(expandedHeight);
- updateHeader();
- updateNotificationTranslucency();
- updatePanelExpanded();
- updateGestureExclusionRect();
- if (DEBUG) {
- invalidate();
- }
- }
-
- private void updatePanelExpanded() {
- boolean isExpanded = !isFullyCollapsed() || mExpectingSynthesizedDown;
- if (mPanelExpanded != isExpanded) {
- mHeadsUpManager.setIsPanelExpanded(isExpanded);
- mStatusBar.setPanelExpanded(isExpanded);
- mPanelExpanded = isExpanded;
- }
- }
-
- private int calculatePanelHeightShade() {
- int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin();
- int maxHeight = mNotificationStackScroller.getHeight() - emptyBottomMargin;
- maxHeight += mNotificationStackScroller.getTopPaddingOverflow();
-
- if (mBarState == StatusBarState.KEYGUARD) {
- int minKeyguardPanelBottom = mClockPositionAlgorithm.getExpandedClockPosition()
- + mKeyguardStatusView.getHeight()
- + mNotificationStackScroller.getIntrinsicContentHeight();
- return Math.max(maxHeight, minKeyguardPanelBottom);
- } else {
- return maxHeight;
- }
- }
-
- private int calculatePanelHeightQsExpanded() {
- float notificationHeight = mNotificationStackScroller.getHeight()
- - mNotificationStackScroller.getEmptyBottomMargin()
- - mNotificationStackScroller.getTopPadding();
-
- // When only empty shade view is visible in QS collapsed state, simulate that we would have
- // it in expanded QS state as well so we don't run into troubles when fading the view in/out
- // and expanding/collapsing the whole panel from/to quick settings.
- if (mNotificationStackScroller.getNotGoneChildCount() == 0
- && mShowEmptyShadeView) {
- notificationHeight = mNotificationStackScroller.getEmptyShadeViewHeight();
- }
- int maxQsHeight = mQsMaxExpansionHeight;
-
- if (mKeyguardShowing) {
- maxQsHeight += mQsNotificationTopPadding;
- }
-
- // If an animation is changing the size of the QS panel, take the animated value.
- if (mQsSizeChangeAnimator != null) {
- maxQsHeight = (int) mQsSizeChangeAnimator.getAnimatedValue();
- }
- float totalHeight = Math.max(
- maxQsHeight, mBarState == StatusBarState.KEYGUARD
- ? mClockPositionResult.stackScrollerPadding : 0)
- + notificationHeight + mNotificationStackScroller.getTopPaddingOverflow();
- if (totalHeight > mNotificationStackScroller.getHeight()) {
- float fullyCollapsedHeight = maxQsHeight
- + mNotificationStackScroller.getLayoutMinHeight();
- totalHeight = Math.max(fullyCollapsedHeight, mNotificationStackScroller.getHeight());
- }
- return (int) totalHeight;
- }
-
- private void updateNotificationTranslucency() {
- float alpha = 1f;
- if (mClosingWithAlphaFadeOut && !mExpandingFromHeadsUp &&
- !mHeadsUpManager.hasPinnedHeadsUp()) {
- alpha = getFadeoutAlpha();
- }
- if (mBarState == StatusBarState.KEYGUARD && !mHintAnimationRunning
- && !mKeyguardBypassController.getBypassEnabled()) {
- alpha *= mClockPositionResult.clockAlpha;
- }
- mNotificationStackScroller.setAlpha(alpha);
- }
-
- private float getFadeoutAlpha() {
- float alpha;
- if (mQsMinExpansionHeight == 0) {
- return 1.0f;
- }
- alpha = getExpandedHeight() / mQsMinExpansionHeight;
- alpha = Math.max(0, Math.min(alpha, 1));
- alpha = (float) Math.pow(alpha, 0.75);
- return alpha;
- }
-
- @Override
- protected float getOverExpansionAmount() {
- return mNotificationStackScroller.getCurrentOverScrollAmount(true /* top */);
- }
-
- @Override
- protected float getOverExpansionPixels() {
- return mNotificationStackScroller.getCurrentOverScrolledPixels(true /* top */);
- }
-
- /**
- * Hides the header when notifications are colliding with it.
- */
- private void updateHeader() {
- if (mBarState == StatusBarState.KEYGUARD) {
- updateHeaderKeyguardAlpha();
- }
- updateQsExpansion();
- }
-
- protected float getHeaderTranslation() {
- if (mBarState == StatusBarState.KEYGUARD && !mKeyguardBypassController.getBypassEnabled()) {
- return -mQs.getQsMinExpansionHeight();
- }
- float appearAmount = mNotificationStackScroller.calculateAppearFraction(mExpandedHeight);
- float startHeight = -mQsExpansionHeight;
- if (mKeyguardBypassController.getBypassEnabled() && isOnKeyguard()
- && mNotificationStackScroller.isPulseExpanding()) {
- if (!mPulseExpansionHandler.isExpanding()
- && !mPulseExpansionHandler.getLeavingLockscreen()) {
- // If we aborted the expansion we need to make sure the header doesn't reappear
- // again after the header has animated away
- appearAmount = 0;
- } else {
- appearAmount = mNotificationStackScroller.calculateAppearFractionBypass();
- }
- startHeight = -mQs.getQsMinExpansionHeight();
- if (mNPVPluginManager != null) startHeight -= mNPVPluginManager.getHeight();
- }
- float translation = MathUtils.lerp(startHeight, 0,
- Math.min(1.0f, appearAmount))
- + mExpandOffset;
- return Math.min(0, translation);
- }
-
- /**
- * @return the alpha to be used to fade out the contents on Keyguard (status bar, bottom area)
- * during swiping up
- */
- private float getKeyguardContentsAlpha() {
- float alpha;
- if (mBarState == StatusBarState.KEYGUARD) {
-
- // When on Keyguard, we hide the header as soon as we expanded close enough to the
- // header
- alpha = getExpandedHeight()
- /
- (mKeyguardStatusBar.getHeight() + mNotificationsHeaderCollideDistance);
- } else {
-
- // In SHADE_LOCKED, the top card is already really close to the header. Hide it as
- // soon as we start translating the stack.
- alpha = getExpandedHeight() / mKeyguardStatusBar.getHeight();
- }
- alpha = MathUtils.saturate(alpha);
- alpha = (float) Math.pow(alpha, 0.75);
- return alpha;
- }
-
- private void updateHeaderKeyguardAlpha() {
- if (!mKeyguardShowing) {
- return;
- }
- float alphaQsExpansion = 1 - Math.min(1, getQsExpansionFraction() * 2);
- float newAlpha = Math.min(getKeyguardContentsAlpha(), alphaQsExpansion)
- * mKeyguardStatusBarAnimateAlpha;
- newAlpha *= 1.0f - mKeyguardHeadsUpShowingAmount;
- mKeyguardStatusBar.setAlpha(newAlpha);
- boolean hideForBypass = mFirstBypassAttempt && mUpdateMonitor.shouldListenForFace()
- || mDelayShowingKeyguardStatusBar;
- mKeyguardStatusBar.setVisibility(newAlpha != 0f && !mDozing && !hideForBypass
- ? VISIBLE : INVISIBLE);
- }
-
- private void updateKeyguardBottomAreaAlpha() {
- // There are two possible panel expansion behaviors:
- // • User dragging up to unlock: we want to fade out as quick as possible
- // (ALPHA_EXPANSION_THRESHOLD) to avoid seeing the bouncer over the bottom area.
- // • User tapping on lock screen: bouncer won't be visible but panel expansion will
- // change due to "unlock hint animation." In this case, fading out the bottom area
- // would also hide the message that says "swipe to unlock," we don't want to do that.
- float expansionAlpha = MathUtils.map(isUnlockHintRunning()
- ? 0 : KeyguardBouncer.ALPHA_EXPANSION_THRESHOLD, 1f,
- 0f, 1f, getExpandedFraction());
- float alpha = Math.min(expansionAlpha, 1 - getQsExpansionFraction());
- alpha *= mBottomAreaShadeAlpha;
- mKeyguardBottomArea.setAffordanceAlpha(alpha);
- mKeyguardBottomArea.setImportantForAccessibility(alpha == 0f
- ? IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
- : IMPORTANT_FOR_ACCESSIBILITY_AUTO);
- View ambientIndicationContainer = mStatusBar.getAmbientIndicationContainer();
- if (ambientIndicationContainer != null) {
- ambientIndicationContainer.setAlpha(alpha);
- }
- }
-
- /**
- * Custom clock fades away when user drags up to unlock or pulls down quick settings.
- *
- * Updates alpha of custom clock to match the alpha of the KeyguardBottomArea. See
- * {@link updateKeyguardBottomAreaAlpha}.
- */
- private void updateBigClockAlpha() {
- float expansionAlpha = MathUtils.map(isUnlockHintRunning()
- ? 0 : KeyguardBouncer.ALPHA_EXPANSION_THRESHOLD, 1f, 0f, 1f, getExpandedFraction());
- float alpha = Math.min(expansionAlpha, 1 - getQsExpansionFraction());
- mBigClockContainer.setAlpha(alpha);
- }
-
- @Override
- protected void onExpandingStarted() {
- super.onExpandingStarted();
- mNotificationStackScroller.onExpansionStarted();
- mIsExpanding = true;
- mQsExpandedWhenExpandingStarted = mQsFullyExpanded;
- if (mQsExpanded) {
- onQsExpansionStarted();
- }
- // Since there are QS tiles in the header now, we need to make sure we start listening
- // immediately so they can be up to date.
- if (mQs == null) return;
- mQs.setHeaderListening(true);
- }
-
- @Override
- protected void onExpandingFinished() {
- super.onExpandingFinished();
- mNotificationStackScroller.onExpansionStopped();
- mHeadsUpManager.onExpandingFinished();
- mIsExpanding = false;
- if (isFullyCollapsed()) {
- DejankUtils.postAfterTraversal(new Runnable() {
- @Override
- public void run() {
- setListening(false);
- }
- });
-
- // Workaround b/22639032: Make sure we invalidate something because else RenderThread
- // thinks we are actually drawing a frame put in reality we don't, so RT doesn't go
- // ahead with rendering and we jank.
- postOnAnimation(new Runnable() {
- @Override
- public void run() {
- getParent().invalidateChild(NotificationPanelView.this, mDummyDirtyRect);
- }
- });
- } else {
- setListening(true);
- }
- mQsExpandImmediate = false;
- mNotificationStackScroller.setShouldShowShelfOnly(false);
- mTwoFingerQsExpandPossible = false;
- mIsExpansionFromHeadsUp = false;
- notifyListenersTrackingHeadsUp(null);
- mExpandingFromHeadsUp = false;
- setPanelScrimMinFraction(0.0f);
- }
-
- private void notifyListenersTrackingHeadsUp(ExpandableNotificationRow pickedChild) {
- for (int i = 0; i < mTrackingHeadsUpListeners.size(); i++) {
- Consumer<ExpandableNotificationRow> listener
- = mTrackingHeadsUpListeners.get(i);
- listener.accept(pickedChild);
- }
- }
-
- private void setListening(boolean listening) {
- mKeyguardStatusBar.setListening(listening);
- if (mQs == null) return;
- mQs.setListening(listening);
- if (mNPVPluginManager != null) mNPVPluginManager.setListening(listening);
- }
-
- @Override
- public void expand(boolean animate) {
- super.expand(animate);
- setListening(true);
- }
-
- @Override
- protected void setOverExpansion(float overExpansion, boolean isPixels) {
- if (mConflictingQsExpansionGesture || mQsExpandImmediate) {
- return;
- }
- if (mBarState != StatusBarState.KEYGUARD) {
- mNotificationStackScroller.setOnHeightChangedListener(null);
- if (isPixels) {
- mNotificationStackScroller.setOverScrolledPixels(
- overExpansion, true /* onTop */, false /* animate */);
- } else {
- mNotificationStackScroller.setOverScrollAmount(
- overExpansion, true /* onTop */, false /* animate */);
- }
- mNotificationStackScroller.setOnHeightChangedListener(this);
- }
- }
-
- @Override
- protected void onTrackingStarted() {
- mFalsingManager.onTrackingStarted(!mKeyguardStateController.canDismissLockScreen());
- super.onTrackingStarted();
- if (mQsFullyExpanded) {
- mQsExpandImmediate = true;
- mNotificationStackScroller.setShouldShowShelfOnly(true);
- }
- if (mBarState == StatusBarState.KEYGUARD
- || mBarState == StatusBarState.SHADE_LOCKED) {
- mAffordanceHelper.animateHideLeftRightIcon();
- }
- mNotificationStackScroller.onPanelTrackingStarted();
- }
-
- @Override
- protected void onTrackingStopped(boolean expand) {
- mFalsingManager.onTrackingStopped();
- super.onTrackingStopped(expand);
- if (expand) {
- mNotificationStackScroller.setOverScrolledPixels(
- 0.0f, true /* onTop */, true /* animate */);
- }
- mNotificationStackScroller.onPanelTrackingStopped();
- if (expand && (mBarState == StatusBarState.KEYGUARD
- || mBarState == StatusBarState.SHADE_LOCKED)) {
- if (!mHintAnimationRunning) {
- mAffordanceHelper.reset(true);
- }
- }
- }
-
- @Override
- public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
-
- // Block update if we are in quick settings and just the top padding changed
- // (i.e. view == null).
- if (view == null && mQsExpanded) {
- return;
- }
- if (needsAnimation && mInterpolatedDarkAmount == 0) {
- mAnimateNextPositionUpdate = true;
- }
- ExpandableView firstChildNotGone = mNotificationStackScroller.getFirstChildNotGone();
- ExpandableNotificationRow firstRow = firstChildNotGone instanceof ExpandableNotificationRow
- ? (ExpandableNotificationRow) firstChildNotGone
- : null;
- if (firstRow != null
- && (view == firstRow || (firstRow.getNotificationParent() == firstRow))) {
- requestScrollerTopPaddingUpdate(false /* animate */);
- }
- requestPanelHeightUpdate();
- }
-
- @Override
- public void onReset(ExpandableView view) {
- }
-
- public void onQsHeightChanged() {
- mQsMaxExpansionHeight = mQs != null ? mQs.getDesiredHeight() : 0;
- if (mQsExpanded && mQsFullyExpanded) {
- mQsExpansionHeight = mQsMaxExpansionHeight;
- requestScrollerTopPaddingUpdate(false /* animate */);
- requestPanelHeightUpdate();
- }
- if (mAccessibilityManager.isEnabled()) {
- setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
- }
- mNotificationStackScroller.setMaxTopPadding(
- mQsMaxExpansionHeight + mQsNotificationTopPadding);
- }
-
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- mAffordanceHelper.onConfigurationChanged();
- if (newConfig.orientation != mLastOrientation) {
- resetHorizontalPanelPosition();
- }
- mLastOrientation = newConfig.orientation;
- }
-
- @Override
- public WindowInsets onApplyWindowInsets(WindowInsets insets) {
- mNavigationBarBottomHeight = insets.getStableInsetBottom();
- updateMaxHeadsUpTranslation();
- return insets;
- }
-
- private void updateMaxHeadsUpTranslation() {
- mNotificationStackScroller.setHeadsUpBoundaries(getHeight(), mNavigationBarBottomHeight);
- }
-
@Override
public void onRtlPropertiesChanged(int layoutDirection) {
- if (layoutDirection != mOldLayoutDirection) {
- mAffordanceHelper.onRtlPropertiesChanged();
- mOldLayoutDirection = layoutDirection;
+ if (mRtlChangeListener != null) {
+ mRtlChangeListener.onRtlPropertielsChanged(layoutDirection);
}
}
@Override
- public void onClick(View v) {
- onQsExpansionStarted();
- if (mQsExpanded) {
- flingSettings(0 /* vel */, FLING_COLLAPSE, null /* onFinishRunnable */,
- true /* isClick */);
- } else if (mQsExpansionEnabled) {
- mLockscreenGestureLogger.write(MetricsEvent.ACTION_SHADE_QS_TAP, 0, 0);
- flingSettings(0 /* vel */, FLING_EXPAND, null /* onFinishRunnable */,
- true /* isClick */);
- }
- }
-
- @Override
- public void onAnimationToSideStarted(boolean rightPage, float translation, float vel) {
- boolean start = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? rightPage : !rightPage;
- mIsLaunchTransitionRunning = true;
- mLaunchAnimationEndRunnable = null;
- float displayDensity = mStatusBar.getDisplayDensity();
- int lengthDp = Math.abs((int) (translation / displayDensity));
- int velocityDp = Math.abs((int) (vel / displayDensity));
- if (start) {
- mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_DIALER, lengthDp, velocityDp);
-
- mFalsingManager.onLeftAffordanceOn();
- if (mFalsingManager.shouldEnforceBouncer()) {
- mStatusBar.executeRunnableDismissingKeyguard(new Runnable() {
- @Override
- public void run() {
- mKeyguardBottomArea.launchLeftAffordance();
- }
- }, null, true /* dismissShade */, false /* afterKeyguardGone */,
- true /* deferred */);
- } else {
- mKeyguardBottomArea.launchLeftAffordance();
- }
- } else {
- if (KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE.equals(
- mLastCameraLaunchSource)) {
- mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_CAMERA, lengthDp, velocityDp);
- }
- mFalsingManager.onCameraOn();
- if (mFalsingManager.shouldEnforceBouncer()) {
- mStatusBar.executeRunnableDismissingKeyguard(new Runnable() {
- @Override
- public void run() {
- mKeyguardBottomArea.launchCamera(mLastCameraLaunchSource);
- }
- }, null, true /* dismissShade */, false /* afterKeyguardGone */,
- true /* deferred */);
- }
- else {
- mKeyguardBottomArea.launchCamera(mLastCameraLaunchSource);
- }
- }
- mStatusBar.startLaunchTransitionTimeout();
- mBlockTouches = true;
- }
-
- @Override
- public void onAnimationToSideEnded() {
- mIsLaunchTransitionRunning = false;
- mIsLaunchTransitionFinished = true;
- if (mLaunchAnimationEndRunnable != null) {
- mLaunchAnimationEndRunnable.run();
- mLaunchAnimationEndRunnable = null;
- }
- mStatusBar.readyForKeyguardDone();
- }
-
- @Override
- protected void startUnlockHintAnimation() {
- if (mPowerManager.isPowerSaveMode()) {
- onUnlockHintStarted();
- onUnlockHintFinished();
- return;
- }
- super.startUnlockHintAnimation();
- }
-
- @Override
- public float getMaxTranslationDistance() {
- return (float) Math.hypot(getWidth(), getHeight());
- }
-
- @Override
- public void onSwipingStarted(boolean rightIcon) {
- mFalsingManager.onAffordanceSwipingStarted(rightIcon);
- boolean camera = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? !rightIcon
- : rightIcon;
- if (camera) {
- mKeyguardBottomArea.bindCameraPrewarmService();
- }
- requestDisallowInterceptTouchEvent(true);
- mOnlyAffordanceInThisMotion = true;
- mQsTracking = false;
- }
-
- @Override
- public void onSwipingAborted() {
- mFalsingManager.onAffordanceSwipingAborted();
- mKeyguardBottomArea.unbindCameraPrewarmService(false /* launched */);
- }
-
- @Override
- public void onIconClicked(boolean rightIcon) {
- if (mHintAnimationRunning) {
- return;
- }
- mHintAnimationRunning = true;
- mAffordanceHelper.startHintAnimation(rightIcon, new Runnable() {
- @Override
- public void run() {
- mHintAnimationRunning = false;
- mStatusBar.onHintFinished();
- }
- });
- rightIcon = getLayoutDirection() == LAYOUT_DIRECTION_RTL ? !rightIcon : rightIcon;
- if (rightIcon) {
- mStatusBar.onCameraHintStarted();
- } else {
- if (mKeyguardBottomArea.isLeftVoiceAssist()) {
- mStatusBar.onVoiceAssistHintStarted();
- } else {
- mStatusBar.onPhoneHintStarted();
- }
- }
- }
-
- @Override
- protected void onUnlockHintFinished() {
- super.onUnlockHintFinished();
- mNotificationStackScroller.setUnlockHintRunning(false);
- }
-
- @Override
- protected void onUnlockHintStarted() {
- super.onUnlockHintStarted();
- mNotificationStackScroller.setUnlockHintRunning(true);
- }
-
- @Override
- public KeyguardAffordanceView getLeftIcon() {
- return getLayoutDirection() == LAYOUT_DIRECTION_RTL
- ? mKeyguardBottomArea.getRightView()
- : mKeyguardBottomArea.getLeftView();
- }
-
- @Override
- public KeyguardAffordanceView getRightIcon() {
- return getLayoutDirection() == LAYOUT_DIRECTION_RTL
- ? mKeyguardBottomArea.getLeftView()
- : mKeyguardBottomArea.getRightView();
- }
-
- @Override
- public View getLeftPreview() {
- return getLayoutDirection() == LAYOUT_DIRECTION_RTL
- ? mKeyguardBottomArea.getRightPreview()
- : mKeyguardBottomArea.getLeftPreview();
- }
-
- @Override
- public View getRightPreview() {
- return getLayoutDirection() == LAYOUT_DIRECTION_RTL
- ? mKeyguardBottomArea.getLeftPreview()
- : mKeyguardBottomArea.getRightPreview();
- }
-
- @Override
- public float getAffordanceFalsingFactor() {
- return mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
- }
-
- @Override
- public boolean needsAntiFalsing() {
- return mBarState == StatusBarState.KEYGUARD;
- }
-
- @Override
- protected float getPeekHeight() {
- if (mNotificationStackScroller.getNotGoneChildCount() > 0) {
- return mNotificationStackScroller.getPeekHeight();
- } else {
- return mQsMinExpansionHeight;
- }
- }
-
- @Override
- protected boolean shouldUseDismissingAnimation() {
- return mBarState != StatusBarState.SHADE
- && (mKeyguardStateController.canDismissLockScreen() || !isTracking());
- }
-
- @Override
- protected boolean fullyExpandedClearAllVisible() {
- return mNotificationStackScroller.isFooterViewNotGone()
- && mNotificationStackScroller.isScrolledToBottom() && !mQsExpandImmediate;
- }
-
- @Override
- protected boolean isClearAllVisible() {
- return mNotificationStackScroller.isFooterViewContentVisible();
- }
-
- @Override
- protected int getClearAllHeight() {
- return mNotificationStackScroller.getFooterViewHeight();
- }
-
- @Override
- protected boolean isTrackingBlocked() {
- return mConflictingQsExpansionGesture && mQsExpanded || mBlockingExpansionForCurrentTouch;
- }
-
- public boolean isQsExpanded() {
- return mQsExpanded;
- }
-
- public boolean isQsDetailShowing() {
- return mQs.isShowingDetail();
- }
-
- public void closeQsDetail() {
- mQs.closeDetail();
- }
-
- @Override
public boolean shouldDelayChildPressedState() {
return true;
}
- public boolean isLaunchTransitionFinished() {
- return mIsLaunchTransitionFinished;
- }
-
- public boolean isLaunchTransitionRunning() {
- return mIsLaunchTransitionRunning;
- }
-
- public void setLaunchTransitionEndRunnable(Runnable r) {
- mLaunchAnimationEndRunnable = r;
- }
-
- public void setEmptyDragAmount(float amount) {
- mEmptyDragAmount = amount * 0.2f;
- positionClockAndNotifications();
- }
-
- private void updateDozingVisibilities(boolean animate) {
- mKeyguardBottomArea.setDozing(mDozing, animate);
- if (!mDozing && animate) {
- animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
- }
- }
-
- @Override
- public boolean isDozing() {
- return mDozing;
- }
-
- public void showEmptyShadeView(boolean emptyShadeViewVisible) {
- mShowEmptyShadeView = emptyShadeViewVisible;
- updateEmptyShadeView();
- }
-
- private void updateEmptyShadeView() {
- // Hide "No notifications" in QS.
- mNotificationStackScroller.updateEmptyShadeView(mShowEmptyShadeView && !mQsExpanded);
- }
-
- public void setQsScrimEnabled(boolean qsScrimEnabled) {
- boolean changed = mQsScrimEnabled != qsScrimEnabled;
- mQsScrimEnabled = qsScrimEnabled;
- if (changed) {
- updateQsState();
- }
- }
-
- public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
- mKeyguardUserSwitcher = keyguardUserSwitcher;
- }
-
- public void onScreenTurningOn() {
- mKeyguardStatusView.dozeTimeTick();
- }
-
- @Override
- public void onEmptySpaceClicked(float x, float y) {
- onEmptySpaceClick(x);
- }
-
- @Override
- protected boolean onMiddleClicked() {
- switch (mBarState) {
- case StatusBarState.KEYGUARD:
- if (!mDozingOnDown) {
- if (mKeyguardBypassController.getBypassEnabled()) {
- mUpdateMonitor.requestFaceAuth();
- } else {
- mLockscreenGestureLogger.write(
- MetricsEvent.ACTION_LS_HINT,
- 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
- startUnlockHintAnimation();
- }
- }
- return true;
- case StatusBarState.SHADE_LOCKED:
- if (!mQsExpanded) {
- mStatusBarStateController.setState(StatusBarState.KEYGUARD);
- }
- return true;
- case StatusBarState.SHADE:
-
- // This gets called in the middle of the touch handling, where the state is still
- // that we are tracking the panel. Collapse the panel after this is done.
- post(mPostCollapseRunnable);
- return false;
- default:
- return true;
- }
- }
-
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
@@ -3011,250 +69,18 @@
}
}
- public float getCurrentPanelAlpha() {
+ float getCurrentPanelAlpha() {
return mCurrentPanelAlpha;
}
- public boolean setPanelAlpha(int alpha, boolean animate) {
- if (mPanelAlpha != alpha) {
- mPanelAlpha = alpha;
- PropertyAnimator.setProperty(this, PANEL_ALPHA, alpha,
- alpha == 255 ? PANEL_ALPHA_IN_PROPERTIES : PANEL_ALPHA_OUT_PROPERTIES, animate);
- return true;
- }
- return false;
- }
-
- public void setPanelAlphaInternal(float alpha) {
+ void setPanelAlphaInternal(float alpha) {
mCurrentPanelAlpha = (int) alpha;
mAlphaPaint.setARGB(mCurrentPanelAlpha, 255, 255, 255);
invalidate();
}
- public void setPanelAlphaEndAction(Runnable r) {
- mPanelAlphaEndAction = r;
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
-
- if (DEBUG) {
- Paint p = new Paint();
- p.setColor(Color.RED);
- p.setStrokeWidth(2);
- p.setStyle(Paint.Style.STROKE);
- canvas.drawLine(0, getMaxPanelHeight(), getWidth(), getMaxPanelHeight(), p);
- p.setColor(Color.BLUE);
- canvas.drawLine(0, getExpandedHeight(), getWidth(), getExpandedHeight(), p);
- p.setColor(Color.GREEN);
- canvas.drawLine(0, calculatePanelHeightQsExpanded(), getWidth(),
- calculatePanelHeightQsExpanded(), p);
- p.setColor(Color.YELLOW);
- canvas.drawLine(0, calculatePanelHeightShade(), getWidth(),
- calculatePanelHeightShade(), p);
- p.setColor(Color.MAGENTA);
- canvas.drawLine(0, calculateQsTopPadding(), getWidth(),
- calculateQsTopPadding(), p);
- p.setColor(Color.CYAN);
- canvas.drawLine(0, mClockPositionResult.stackScrollerPadding, getWidth(),
- mNotificationStackScroller.getTopPadding(), p);
- p.setColor(Color.GRAY);
- canvas.drawLine(0, mClockPositionResult.clockY, getWidth(),
- mClockPositionResult.clockY, p);
- }
- }
-
- @Override
- public void onHeadsUpPinnedModeChanged(final boolean inPinnedMode) {
- mNotificationStackScroller.setInHeadsUpPinnedMode(inPinnedMode);
- if (inPinnedMode) {
- mHeadsUpExistenceChangedRunnable.run();
- updateNotificationTranslucency();
- } else {
- setHeadsUpAnimatingAway(true);
- mNotificationStackScroller.runAfterAnimationFinished(
- mHeadsUpExistenceChangedRunnable);
- }
- updateGestureExclusionRect();
- mHeadsUpPinnedMode = inPinnedMode;
- updateHeadsUpVisibility();
- updateKeyguardStatusBarForHeadsUp();
- }
-
- private void updateKeyguardStatusBarForHeadsUp() {
- boolean showingKeyguardHeadsUp = mKeyguardShowing
- && mHeadsUpAppearanceController.shouldBeVisible();
- if (mShowingKeyguardHeadsUp != showingKeyguardHeadsUp) {
- mShowingKeyguardHeadsUp = showingKeyguardHeadsUp;
- if (mKeyguardShowing) {
- PropertyAnimator.setProperty(this, KEYGUARD_HEADS_UP_SHOWING_AMOUNT,
- showingKeyguardHeadsUp ? 1.0f : 0.0f, KEYGUARD_HUN_PROPERTIES,
- true /* animate */);
- } else {
- PropertyAnimator.applyImmediately(this, KEYGUARD_HEADS_UP_SHOWING_AMOUNT, 0.0f);
- }
- }
- }
-
- private void setKeyguardHeadsUpShowingAmount(float amount) {
- mKeyguardHeadsUpShowingAmount = amount;
- updateHeaderKeyguardAlpha();
- }
-
- private float getKeyguardHeadsUpShowingAmount() {
- return mKeyguardHeadsUpShowingAmount;
- }
-
- public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
- mHeadsUpAnimatingAway = headsUpAnimatingAway;
- mNotificationStackScroller.setHeadsUpAnimatingAway(headsUpAnimatingAway);
- updateHeadsUpVisibility();
- }
-
- private void updateHeadsUpVisibility() {
- ((PhoneStatusBarView) mBar).setHeadsUpVisible(mHeadsUpAnimatingAway || mHeadsUpPinnedMode);
- }
-
- @Override
- public void onHeadsUpPinned(NotificationEntry entry) {
- if (!isOnKeyguard()) {
- mNotificationStackScroller.generateHeadsUpAnimation(entry.getHeadsUpAnimationView(),
- true);
- }
- }
-
- @Override
- public void onHeadsUpUnPinned(NotificationEntry entry) {
-
- // When we're unpinning the notification via active edge they remain heads-upped,
- // we need to make sure that an animation happens in this case, otherwise the notification
- // will stick to the top without any interaction.
- if (isFullyCollapsed() && entry.isRowHeadsUp() && !isOnKeyguard()) {
- mNotificationStackScroller.generateHeadsUpAnimation(
- entry.getHeadsUpAnimationView(), false);
- entry.setHeadsUpIsVisible();
- }
- }
-
- @Override
- public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
- mNotificationStackScroller.generateHeadsUpAnimation(entry, isHeadsUp);
- }
-
- @Override
- public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
- super.setHeadsUpManager(headsUpManager);
- mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager,
- mNotificationStackScroller.getHeadsUpCallback(), this);
- }
-
- public void setTrackedHeadsUp(ExpandableNotificationRow pickedChild) {
- if (pickedChild != null) {
- notifyListenersTrackingHeadsUp(pickedChild);
- mExpandingFromHeadsUp = true;
- }
- // otherwise we update the state when the expansion is finished
- }
-
- @Override
- protected void onClosingFinished() {
- super.onClosingFinished();
- resetHorizontalPanelPosition();
- setClosingWithAlphaFadeout(false);
- }
-
- private void setClosingWithAlphaFadeout(boolean closing) {
- mClosingWithAlphaFadeOut = closing;
- mNotificationStackScroller.forceNoOverlappingRendering(closing);
- }
-
- /**
- * Updates the vertical position of the panel so it is positioned closer to the touch
- * responsible for opening the panel.
- *
- * @param x the x-coordinate the touch event
- */
- protected void updateVerticalPanelPosition(float x) {
- if (mNotificationStackScroller.getWidth() * 1.75f > getWidth()) {
- resetHorizontalPanelPosition();
- return;
- }
- float leftMost = mPositionMinSideMargin + mNotificationStackScroller.getWidth() / 2;
- float rightMost = getWidth() - mPositionMinSideMargin
- - mNotificationStackScroller.getWidth() / 2;
- if (Math.abs(x - getWidth() / 2) < mNotificationStackScroller.getWidth() / 4) {
- x = getWidth() / 2;
- }
- x = Math.min(rightMost, Math.max(leftMost, x));
- float center =
- mNotificationStackScroller.getLeft() + mNotificationStackScroller.getWidth() / 2;
- setHorizontalPanelTranslation(x - center);
- }
-
- private void resetHorizontalPanelPosition() {
- setHorizontalPanelTranslation(0f);
- }
-
- protected void setHorizontalPanelTranslation(float translation) {
- mNotificationStackScroller.setTranslationX(translation);
- mQsFrame.setTranslationX(translation);
- int size = mVerticalTranslationListener.size();
- for (int i = 0; i < size; i++) {
- mVerticalTranslationListener.get(i).run();
- }
- }
-
- protected void updateExpandedHeight(float expandedHeight) {
- if (mTracking) {
- mNotificationStackScroller.setExpandingVelocity(getCurrentExpandVelocity());
- }
- if (mKeyguardBypassController.getBypassEnabled() && isOnKeyguard()) {
- // The expandedHeight is always the full panel Height when bypassing
- expandedHeight = getMaxPanelHeightNonBypass();
- }
- mNotificationStackScroller.setExpandedHeight(expandedHeight);
- updateKeyguardBottomAreaAlpha();
- updateBigClockAlpha();
- updateStatusBarIcons();
- }
-
- /**
- * @return whether the notifications are displayed full width and don't have any margins on
- * the side.
- */
- public boolean isFullWidth() {
- return mIsFullWidth;
- }
-
- private void updateStatusBarIcons() {
- boolean showIconsWhenExpanded = (isPanelVisibleBecauseOfHeadsUp() || isFullWidth())
- && getExpandedHeight() < getOpeningHeight();
- if (showIconsWhenExpanded && mNoVisibleNotifications && isOnKeyguard()) {
- showIconsWhenExpanded = false;
- }
- if (showIconsWhenExpanded != mShowIconsWhenExpanded) {
- mShowIconsWhenExpanded = showIconsWhenExpanded;
- mCommandQueue.recomputeDisableFlags(mDisplayId, false);
- }
- }
-
- private boolean isOnKeyguard() {
- return mBarState == StatusBarState.KEYGUARD;
- }
-
- public void setPanelScrimMinFraction(float minFraction) {
- mBar.panelScrimMinFractionChanged(minFraction);
- }
-
- public void clearNotificationEffects() {
- mStatusBar.clearNotificationEffects();
- }
-
- @Override
- protected boolean isPanelVisibleBecauseOfHeadsUp() {
- return (mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpAnimatingAway)
- && mBarState == StatusBarState.SHADE;
+ public void setDozing(boolean dozing) {
+ mDozing = dozing;
}
@Override
@@ -3262,382 +88,11 @@
return !mDozing;
}
- public void launchCamera(boolean animate, int source) {
- if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP) {
- mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP;
- } else if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE) {
- mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_WIGGLE;
- } else if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER) {
- mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER;
- } else {
-
- // Default.
- mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
- }
-
- // If we are launching it when we are occluded already we don't want it to animate,
- // nor setting these flags, since the occluded state doesn't change anymore, hence it's
- // never reset.
- if (!isFullyCollapsed()) {
- setLaunchingAffordance(true);
- } else {
- animate = false;
- }
- mAffordanceHasPreview = mKeyguardBottomArea.getRightPreview() != null;
- mAffordanceHelper.launchAffordance(animate, getLayoutDirection() == LAYOUT_DIRECTION_RTL);
+ void setRtlChangeListener(RtlChangeListener listener) {
+ mRtlChangeListener = listener;
}
- public void onAffordanceLaunchEnded() {
- setLaunchingAffordance(false);
- }
-
- /**
- * Set whether we are currently launching an affordance. This is currently only set when
- * launched via a camera gesture.
- */
- private void setLaunchingAffordance(boolean launchingAffordance) {
- mLaunchingAffordance = launchingAffordance;
- getLeftIcon().setLaunchingAffordance(launchingAffordance);
- getRightIcon().setLaunchingAffordance(launchingAffordance);
- mKeyguardBypassController.setLaunchingAffordance(launchingAffordance);
- if (mAffordanceLaunchListener != null) {
- mAffordanceLaunchListener.accept(launchingAffordance);
- }
- }
-
- /**
- * Return true when a bottom affordance is launching an occluded activity with a splash screen.
- */
- public boolean isLaunchingAffordanceWithPreview() {
- return mLaunchingAffordance && mAffordanceHasPreview;
- }
-
- /**
- * Whether the camera application can be launched for the camera launch gesture.
- */
- public boolean canCameraGestureBeLaunched() {
- if (!mStatusBar.isCameraAllowedByAdmin()) {
- return false;
- }
-
- ResolveInfo resolveInfo = mKeyguardBottomArea.resolveCameraIntent();
- String packageToLaunch = (resolveInfo == null || resolveInfo.activityInfo == null)
- ? null : resolveInfo.activityInfo.packageName;
- return packageToLaunch != null &&
- (mBarState != StatusBarState.SHADE || !isForegroundApp(packageToLaunch))
- && !mAffordanceHelper.isSwipingInProgress();
- }
-
- /**
- * Return true if the applications with the package name is running in foreground.
- *
- * @param pkgName application package name.
- */
- private boolean isForegroundApp(String pkgName) {
- ActivityManager am = getContext().getSystemService(ActivityManager.class);
- List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(1);
- return !tasks.isEmpty() && pkgName.equals(tasks.get(0).topActivity.getPackageName());
- }
-
- private void setGroupManager(NotificationGroupManager groupManager) {
- mGroupManager = groupManager;
- }
-
- public boolean hideStatusBarIconsWhenExpanded() {
- if (mLaunchingNotification) {
- return mHideIconsDuringNotificationLaunch;
- }
- if (mHeadsUpAppearanceController != null
- && mHeadsUpAppearanceController.shouldBeVisible()) {
- return false;
- }
- return !isFullWidth() || !mShowIconsWhenExpanded;
- }
-
- private final FragmentListener mFragmentListener = new FragmentListener() {
- @Override
- public void onFragmentViewCreated(String tag, Fragment fragment) {
- mQs = (QS) fragment;
- mQs.setPanelView(NotificationPanelView.this);
- mQs.setExpandClickListener(NotificationPanelView.this);
- mQs.setHeaderClickable(mQsExpansionEnabled);
- updateQSPulseExpansion();
- mQs.setOverscrolling(mStackScrollerOverscrolling);
-
- // recompute internal state when qspanel height changes
- mQs.getView().addOnLayoutChangeListener(
- (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
- final int height = bottom - top;
- final int oldHeight = oldBottom - oldTop;
- if (height != oldHeight) {
- onQsHeightChanged();
- }
- });
- mNotificationStackScroller.setQsContainer((ViewGroup) mQs.getView());
- if (mQs instanceof QSFragment) {
- mKeyguardStatusBar.setQSPanel(((QSFragment) mQs).getQsPanel());
- }
- updateQsExpansion();
- }
-
- @Override
- public void onFragmentViewDestroyed(String tag, Fragment fragment) {
- // Manual handling of fragment lifecycle is only required because this bridges
- // non-fragment and fragment code. Once we are using a fragment for the notification
- // panel, mQs will not need to be null cause it will be tied to the same lifecycle.
- if (fragment == mQs) {
- mQs = null;
- }
- }
- };
-
- @Override
- public void setTouchAndAnimationDisabled(boolean disabled) {
- super.setTouchAndAnimationDisabled(disabled);
- if (disabled && mAffordanceHelper.isSwipingInProgress() && !mIsLaunchTransitionRunning) {
- mAffordanceHelper.reset(false /* animate */);
- }
- mNotificationStackScroller.setAnimationsEnabled(!disabled);
- }
-
- /**
- * Sets the dozing state.
- *
- * @param dozing {@code true} when dozing.
- * @param animate if transition should be animated.
- * @param wakeUpTouchLocation touch event location - if woken up by SLPI sensor.
- */
- public void setDozing(boolean dozing, boolean animate, PointF wakeUpTouchLocation) {
- if (dozing == mDozing) return;
- mDozing = dozing;
- mNotificationStackScroller.setDozing(mDozing, animate, wakeUpTouchLocation);
- mKeyguardBottomArea.setDozing(mDozing, animate);
-
- if (dozing) {
- mBottomAreaShadeAlphaAnimator.cancel();
- }
-
- if (mBarState == StatusBarState.KEYGUARD
- || mBarState == StatusBarState.SHADE_LOCKED) {
- updateDozingVisibilities(animate);
- }
-
- final float dozeAmount = dozing ? 1 : 0;
- mStatusBarStateController.setDozeAmount(dozeAmount, animate);
- }
-
- @Override
- public void onDozeAmountChanged(float linearAmount, float amount) {
- mInterpolatedDarkAmount = amount;
- mLinearDarkAmount = linearAmount;
- mKeyguardStatusView.setDarkAmount(mInterpolatedDarkAmount);
- mKeyguardBottomArea.setDarkAmount(mInterpolatedDarkAmount);
- positionClockAndNotifications();
- }
-
- public void setPulsing(boolean pulsing) {
- mPulsing = pulsing;
- final boolean animatePulse = !mDozeParameters.getDisplayNeedsBlanking()
- && mDozeParameters.getAlwaysOn();
- if (animatePulse) {
- mAnimateNextPositionUpdate = true;
- }
- // Do not animate the clock when waking up from a pulse.
- // The height callback will take care of pushing the clock to the right position.
- if (!mPulsing && !mDozing) {
- mAnimateNextPositionUpdate = false;
- }
- mNotificationStackScroller.setPulsing(pulsing, animatePulse);
- mKeyguardStatusView.setPulsing(pulsing);
- }
-
- public void setAmbientIndicationBottomPadding(int ambientIndicationBottomPadding) {
- if (mAmbientIndicationBottomPadding != ambientIndicationBottomPadding) {
- mAmbientIndicationBottomPadding = ambientIndicationBottomPadding;
- mStatusBar.updateKeyguardMaxNotifications();
- }
- }
-
- public void dozeTimeTick() {
- mKeyguardBottomArea.dozeTimeTick();
- mKeyguardStatusView.dozeTimeTick();
- if (mInterpolatedDarkAmount > 0) {
- positionClockAndNotifications();
- }
- }
-
- public void setStatusAccessibilityImportance(int mode) {
- mKeyguardStatusView.setImportantForAccessibility(mode);
- }
-
- /**
- * TODO: this should be removed.
- * It's not correct to pass this view forward because other classes will end up adding
- * children to it. Theme will be out of sync.
- *
- * @return bottom area view
- */
- public KeyguardBottomAreaView getKeyguardBottomAreaView() {
- return mKeyguardBottomArea;
- }
-
- public void setUserSetupComplete(boolean userSetupComplete) {
- mUserSetupComplete = userSetupComplete;
- mKeyguardBottomArea.setUserSetupComplete(userSetupComplete);
- }
-
- public void applyExpandAnimationParams(ExpandAnimationParameters params) {
- mExpandOffset = params != null ? params.getTopChange() : 0;
- updateQsExpansion();
- if (params != null) {
- boolean hideIcons = params.getProgress(
- ActivityLaunchAnimator.ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f;
- if (hideIcons != mHideIconsDuringNotificationLaunch) {
- mHideIconsDuringNotificationLaunch = hideIcons;
- if (!hideIcons) {
- mCommandQueue.recomputeDisableFlags(mDisplayId, true /* animate */);
- }
- }
- }
- }
-
- public void addTrackingHeadsUpListener(Consumer<ExpandableNotificationRow> listener) {
- mTrackingHeadsUpListeners.add(listener);
- }
-
- public void removeTrackingHeadsUpListener(Consumer<ExpandableNotificationRow> listener) {
- mTrackingHeadsUpListeners.remove(listener);
- }
-
- public void addVerticalTranslationListener(Runnable verticalTranslationListener) {
- mVerticalTranslationListener.add(verticalTranslationListener);
- }
-
- public void removeVerticalTranslationListener(Runnable verticalTranslationListener) {
- mVerticalTranslationListener.remove(verticalTranslationListener);
- }
-
- public void setHeadsUpAppearanceController(
- HeadsUpAppearanceController headsUpAppearanceController) {
- mHeadsUpAppearanceController = headsUpAppearanceController;
- }
-
- /**
- * Starts the animation before we dismiss Keyguard, i.e. an disappearing animation on the
- * security view of the bouncer.
- */
- public void onBouncerPreHideAnimation() {
- setKeyguardStatusViewVisibility(mBarState, true /* keyguardFadingAway */,
- false /* goingToFullShade */);
- }
-
- /**
- * Do not let the user drag the shade up and down for the current touch session.
- * This is necessary to avoid shade expansion while/after the bouncer is dismissed.
- */
- public void blockExpansionForCurrentTouch() {
- mBlockingExpansionForCurrentTouch = mTracking;
- }
-
- @Override
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- super.dump(fd, pw, args);
- pw.println(" gestureExclusionRect: " + calculateGestureExclusionRect());
- if (mKeyguardStatusBar != null) {
- mKeyguardStatusBar.dump(fd, pw, args);
- }
- if (mKeyguardStatusView != null) {
- mKeyguardStatusView.dump(fd, pw, args);
- }
- }
-
- public boolean hasActiveClearableNotifications() {
- return mNotificationStackScroller.hasActiveClearableNotifications(ROWS_ALL);
- }
-
- @Override
- public void onZenChanged(int zen) {
- updateShowEmptyShadeView();
- }
-
- private void updateShowEmptyShadeView() {
- boolean showEmptyShadeView =
- mBarState != StatusBarState.KEYGUARD && !mEntryManager.hasActiveNotifications();
- showEmptyShadeView(showEmptyShadeView);
- }
-
- public RemoteInputController.Delegate createRemoteInputDelegate() {
- return mNotificationStackScroller.createDelegate();
- }
-
- public void updateNotificationViews() {
- mNotificationStackScroller.updateSectionBoundaries();
- mNotificationStackScroller.updateSpeedBumpIndex();
- mNotificationStackScroller.updateFooter();
- updateShowEmptyShadeView();
- mNotificationStackScroller.updateIconAreaViews();
- }
-
- public void onUpdateRowStates() {
- mNotificationStackScroller.onUpdateRowStates();
- }
-
- public boolean hasPulsingNotifications() {
- return mNotificationStackScroller.hasPulsingNotifications();
- }
-
- public ActivatableNotificationView getActivatedChild() {
- return mNotificationStackScroller.getActivatedChild();
- }
-
- public void setActivatedChild(ActivatableNotificationView o) {
- mNotificationStackScroller.setActivatedChild(o);
- }
-
- public void runAfterAnimationFinished(Runnable r) {
- mNotificationStackScroller.runAfterAnimationFinished(r);
- }
-
- public void setScrollingEnabled(boolean b) {
- mNotificationStackScroller.setScrollingEnabled(b);
- }
-
- public void initDependencies(StatusBar statusBar, NotificationGroupManager groupManager,
- NotificationShelf notificationShelf,
- HeadsUpManagerPhone headsUpManager,
- NotificationIconAreaController notificationIconAreaController,
- ScrimController scrimController) {
- setStatusBar(statusBar);
- setGroupManager(mGroupManager);
- mNotificationStackScroller.setNotificationPanel(this);
- mNotificationStackScroller.setIconAreaController(notificationIconAreaController);
- mNotificationStackScroller.setStatusBar(statusBar);
- mNotificationStackScroller.setGroupManager(groupManager);
- mNotificationStackScroller.setShelf(notificationShelf);
- mNotificationStackScroller.setScrimController(scrimController);
- updateShowEmptyShadeView();
- }
-
- public void showTransientIndication(int id) {
- mKeyguardIndicationController.showTransientIndication(id);
- }
-
- @Override
- public void onDynamicPrivacyChanged() {
- // Do not request animation when pulsing or waking up, otherwise the clock wiill be out
- // of sync with the notification panel.
- if (mLinearDarkAmount != 0) {
- return;
- }
- mAnimateNextPositionUpdate = true;
- }
-
- public void setOnReinflationListener(Runnable onReinflationListener) {
- mOnReinflationListener = onReinflationListener;
- }
-
- public static boolean isQsSplitEnabled() {
- return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.QS_SPLIT_ENABLED, false);
+ interface RtlChangeListener {
+ void onRtlPropertielsChanged(int layoutDirection);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
new file mode 100644
index 0000000..90ec2a0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -0,0 +1,3741 @@
+/*
+ * 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 com.android.systemui.statusbar.phone;
+
+import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
+import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.app.ActivityManager;
+import android.app.Fragment;
+import android.app.StatusBarManager;
+import android.content.Context;
+import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.Paint;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.drawable.Drawable;
+import android.hardware.biometrics.BiometricSourceType;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.provider.DeviceConfig;
+import android.provider.Settings;
+import android.util.Log;
+import android.util.MathUtils;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewPropertyAnimator;
+import android.view.ViewTreeObserver;
+import android.view.WindowInsets;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.LatencyTracker;
+import com.android.keyguard.KeyguardClockSwitch;
+import com.android.keyguard.KeyguardStatusView;
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.keyguard.KeyguardUpdateMonitorCallback;
+import com.android.systemui.DejankUtils;
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.dagger.qualifiers.DisplayId;
+import com.android.systemui.doze.DozeLog;
+import com.android.systemui.fragments.FragmentHostManager;
+import com.android.systemui.fragments.FragmentHostManager.FragmentListener;
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.HomeControlsPlugin;
+import com.android.systemui.plugins.PluginListener;
+import com.android.systemui.plugins.qs.QS;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
+import com.android.systemui.qs.QSFragment;
+import com.android.systemui.shared.plugins.PluginManager;
+import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.FlingAnimationUtils;
+import com.android.systemui.statusbar.GestureRecorder;
+import com.android.systemui.statusbar.KeyguardAffordanceView;
+import com.android.systemui.statusbar.KeyguardIndicationController;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager;
+import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.PulseExpansionHandler;
+import com.android.systemui.statusbar.RemoteInputController;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.VibratorHelper;
+import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
+import com.android.systemui.statusbar.notification.DynamicPrivacyController;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.PropertyAnimator;
+import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.ExpandableView;
+import com.android.systemui.statusbar.notification.stack.AnimationProperties;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
+import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.statusbar.policy.KeyguardUserSwitcher;
+import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
+import com.android.systemui.statusbar.policy.ZenModeController;
+import com.android.systemui.util.InjectionInflationController;
+import com.android.systemui.util.Utils;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import javax.inject.Inject;
+
+@StatusBarComponent.StatusBarScope
+public class NotificationPanelViewController extends PanelViewController {
+
+ private static final boolean DEBUG = false;
+
+ /**
+ * Fling expanding QS.
+ */
+ private static final int FLING_EXPAND = 0;
+
+ /**
+ * Fling collapsing QS, potentially stopping when QS becomes QQS.
+ */
+ private static final int FLING_COLLAPSE = 1;
+
+ /**
+ * Fling until QS is completely hidden.
+ */
+ private static final int FLING_HIDE = 2;
+ private final DozeParameters mDozeParameters;
+ private final OnHeightChangedListener mOnHeightChangedListener = new OnHeightChangedListener();
+ private final OnClickListener mOnClickListener = new OnClickListener();
+ private final OnOverscrollTopChangedListener
+ mOnOverscrollTopChangedListener =
+ new OnOverscrollTopChangedListener();
+ private final KeyguardAffordanceHelperCallback
+ mKeyguardAffordanceHelperCallback =
+ new KeyguardAffordanceHelperCallback();
+ private final OnEmptySpaceClickListener
+ mOnEmptySpaceClickListener =
+ new OnEmptySpaceClickListener();
+ private final MyOnHeadsUpChangedListener
+ mOnHeadsUpChangedListener =
+ new MyOnHeadsUpChangedListener();
+ private final HeightListener mHeightListener = new HeightListener();
+ private final ZenModeControllerCallback
+ mZenModeControllerCallback =
+ new ZenModeControllerCallback();
+ private final ConfigurationListener mConfigurationListener = new ConfigurationListener();
+ private final StatusBarStateListener mStatusBarStateListener = new StatusBarStateListener();
+ private final ExpansionCallback mExpansionCallback = new ExpansionCallback();
+ private final NotificationPanelView mView;
+ private final MetricsLogger mMetricsLogger;
+ private final ActivityManager mActivityManager;
+ private final ZenModeController mZenModeController;
+ private final ConfigurationController mConfigurationController;
+ private final FlingAnimationUtils.Builder mFlingAnimationUtilsBuilder;
+
+ private double mQqsSplitFraction;
+
+ // Cap and total height of Roboto font. Needs to be adjusted when font for the big clock is
+ // changed.
+ private static final int CAP_HEIGHT = 1456;
+ private static final int FONT_HEIGHT = 2163;
+
+ /**
+ * Maximum time before which we will expand the panel even for slow motions when getting a
+ * touch passed over from launcher.
+ */
+ private static final int MAX_TIME_TO_OPEN_WHEN_FLINGING_FROM_LAUNCHER = 300;
+
+ private static final String COUNTER_PANEL_OPEN = "panel_open";
+ private static final String COUNTER_PANEL_OPEN_QS = "panel_open_qs";
+ private static final String COUNTER_PANEL_OPEN_PEEK = "panel_open_peek";
+
+ private static final Rect M_DUMMY_DIRTY_RECT = new Rect(0, 0, 1, 1);
+ private static final Rect EMPTY_RECT = new Rect();
+
+ private static final AnimationProperties
+ CLOCK_ANIMATION_PROPERTIES =
+ new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ private final AnimatableProperty KEYGUARD_HEADS_UP_SHOWING_AMOUNT = AnimatableProperty.from(
+ "KEYGUARD_HEADS_UP_SHOWING_AMOUNT",
+ (notificationPanelView, aFloat) -> setKeyguardHeadsUpShowingAmount(aFloat),
+ (Function<NotificationPanelView, Float>) notificationPanelView ->
+ getKeyguardHeadsUpShowingAmount(),
+ R.id.keyguard_hun_animator_tag, R.id.keyguard_hun_animator_end_tag,
+ R.id.keyguard_hun_animator_start_tag);
+ private static final AnimationProperties
+ KEYGUARD_HUN_PROPERTIES =
+ new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ @VisibleForTesting
+ final KeyguardUpdateMonitorCallback
+ mKeyguardUpdateCallback =
+ new KeyguardUpdateMonitorCallback() {
+
+ @Override
+ public void onBiometricAuthenticated(int userId,
+ BiometricSourceType biometricSourceType) {
+ if (mFirstBypassAttempt && mUpdateMonitor.isUnlockingWithBiometricAllowed()) {
+ mDelayShowingKeyguardStatusBar = true;
+ }
+ }
+
+ @Override
+ public void onBiometricRunningStateChanged(boolean running,
+ BiometricSourceType biometricSourceType) {
+ boolean
+ keyguardOrShadeLocked =
+ mBarState == StatusBarState.KEYGUARD
+ || mBarState == StatusBarState.SHADE_LOCKED;
+ if (!running && mFirstBypassAttempt && keyguardOrShadeLocked && !mDozing
+ && !mDelayShowingKeyguardStatusBar) {
+ mFirstBypassAttempt = false;
+ animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ }
+ }
+
+ @Override
+ public void onFinishedGoingToSleep(int why) {
+ mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
+ mDelayShowingKeyguardStatusBar = false;
+ }
+ };
+
+ private final InjectionInflationController mInjectionInflationController;
+ private final PowerManager mPowerManager;
+ private final AccessibilityManager mAccessibilityManager;
+ private final NotificationWakeUpCoordinator mWakeUpCoordinator;
+ private final PulseExpansionHandler mPulseExpansionHandler;
+ private final KeyguardBypassController mKeyguardBypassController;
+ private final KeyguardUpdateMonitor mUpdateMonitor;
+
+ private KeyguardAffordanceHelper mAffordanceHelper;
+ private KeyguardUserSwitcher mKeyguardUserSwitcher;
+ private KeyguardStatusBarView mKeyguardStatusBar;
+ private ViewGroup mBigClockContainer;
+ private QS mQs;
+ private FrameLayout mQsFrame;
+ private KeyguardStatusView mKeyguardStatusView;
+ private View mQsNavbarScrim;
+ private NotificationsQuickSettingsContainer mNotificationContainerParent;
+ private NotificationStackScrollLayout mNotificationStackScroller;
+ private FrameLayout mHomeControlsLayout;
+ private boolean mAnimateNextPositionUpdate;
+
+ private int mTrackingPointer;
+ private VelocityTracker mQsVelocityTracker;
+ private boolean mQsTracking;
+
+ /**
+ * If set, the ongoing touch gesture might both trigger the expansion in {@link PanelView} and
+ * the expansion for quick settings.
+ */
+ private boolean mConflictingQsExpansionGesture;
+
+ private boolean mPanelExpanded;
+ private boolean mQsExpanded;
+ private boolean mQsExpandedWhenExpandingStarted;
+ private boolean mQsFullyExpanded;
+ private boolean mKeyguardShowing;
+ private boolean mDozing;
+ private boolean mDozingOnDown;
+ private int mBarState;
+ private float mInitialHeightOnTouch;
+ private float mInitialTouchX;
+ private float mInitialTouchY;
+ private float mQsExpansionHeight;
+ private int mQsMinExpansionHeight;
+ private int mQsMaxExpansionHeight;
+ private int mQsPeekHeight;
+ private boolean mStackScrollerOverscrolling;
+ private boolean mQsExpansionFromOverscroll;
+ private float mLastOverscroll;
+ private boolean mQsExpansionEnabled = true;
+ private ValueAnimator mQsExpansionAnimator;
+ private FlingAnimationUtils mFlingAnimationUtils;
+ private int mStatusBarMinHeight;
+ private int mNotificationsHeaderCollideDistance;
+ private float mEmptyDragAmount;
+ private float mDownX;
+ private float mDownY;
+
+ private final KeyguardClockPositionAlgorithm
+ mClockPositionAlgorithm =
+ new KeyguardClockPositionAlgorithm();
+ private final KeyguardClockPositionAlgorithm.Result
+ mClockPositionResult =
+ new KeyguardClockPositionAlgorithm.Result();
+ private boolean mIsExpanding;
+
+ private boolean mBlockTouches;
+ // Used for two finger gesture as well as accessibility shortcut to QS.
+ private boolean mQsExpandImmediate;
+ private boolean mTwoFingerQsExpandPossible;
+
+ /**
+ * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still
+ * need to take this into account in our panel height calculation.
+ */
+ private boolean mQsAnimatorExpand;
+ private boolean mIsLaunchTransitionFinished;
+ private boolean mIsLaunchTransitionRunning;
+ private Runnable mLaunchAnimationEndRunnable;
+ private boolean mOnlyAffordanceInThisMotion;
+ private boolean mKeyguardStatusViewAnimating;
+ private ValueAnimator mQsSizeChangeAnimator;
+
+ private boolean mShowEmptyShadeView;
+
+ private boolean mQsScrimEnabled = true;
+ private boolean mQsTouchAboveFalsingThreshold;
+ private int mQsFalsingThreshold;
+
+ private float mKeyguardStatusBarAnimateAlpha = 1f;
+ private HeadsUpTouchHelper mHeadsUpTouchHelper;
+ private boolean mListenForHeadsUp;
+ private int mNavigationBarBottomHeight;
+ private boolean mExpandingFromHeadsUp;
+ private boolean mCollapsedOnDown;
+ private int mPositionMinSideMargin;
+ private int mLastOrientation = -1;
+ private boolean mClosingWithAlphaFadeOut;
+ private boolean mHeadsUpAnimatingAway;
+ private boolean mLaunchingAffordance;
+ private boolean mAffordanceHasPreview;
+ private FalsingManager mFalsingManager;
+ private String mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
+
+ private Runnable mHeadsUpExistenceChangedRunnable = () -> {
+ setHeadsUpAnimatingAway(false);
+ notifyBarPanelExpansionChanged();
+ };
+ private NotificationGroupManager mGroupManager;
+ private boolean mShowIconsWhenExpanded;
+ private int mIndicationBottomPadding;
+ private int mAmbientIndicationBottomPadding;
+ private boolean mIsFullWidth;
+ private boolean mBlockingExpansionForCurrentTouch;
+
+ /**
+ * Following variables maintain state of events when input focus transfer may occur.
+ */
+ private boolean mExpectingSynthesizedDown; // expecting to see synthesized DOWN event
+ private boolean mLastEventSynthesizedDown; // last event was synthesized DOWN event
+
+ /**
+ * Current dark amount that follows regular interpolation curve of animation.
+ */
+ private float mInterpolatedDarkAmount;
+
+ /**
+ * Dark amount that animates from 0 to 1 or vice-versa in linear manner, even if the
+ * interpolation curve is different.
+ */
+ private float mLinearDarkAmount;
+
+ private boolean mPulsing;
+ private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
+ private boolean mUserSetupComplete;
+ private int mQsNotificationTopPadding;
+ private float mExpandOffset;
+ private boolean mHideIconsDuringNotificationLaunch = true;
+ private int mStackScrollerMeasuringPass;
+ private ArrayList<Consumer<ExpandableNotificationRow>>
+ mTrackingHeadsUpListeners =
+ new ArrayList<>();
+ private ArrayList<Runnable> mVerticalTranslationListener = new ArrayList<>();
+ private HeadsUpAppearanceController mHeadsUpAppearanceController;
+
+ private int mPanelAlpha;
+ private Runnable mPanelAlphaEndAction;
+ private float mBottomAreaShadeAlpha;
+ private final ValueAnimator mBottomAreaShadeAlphaAnimator;
+ private AnimatorListenerAdapter mAnimatorListenerAdapter = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mPanelAlphaEndAction != null) {
+ mPanelAlphaEndAction.run();
+ }
+ }
+ };
+ private final AnimatableProperty mPanelAlphaAnimator = AnimatableProperty.from("panelAlpha",
+ NotificationPanelView::setPanelAlphaInternal,
+ NotificationPanelView::getCurrentPanelAlpha,
+ R.id.panel_alpha_animator_tag, R.id.panel_alpha_animator_start_tag,
+ R.id.panel_alpha_animator_end_tag);
+ private final AnimationProperties mPanelAlphaOutPropertiesAnimator =
+ new AnimationProperties().setDuration(150).setCustomInterpolator(
+ mPanelAlphaAnimator.getProperty(), Interpolators.ALPHA_OUT);
+ private final AnimationProperties mPanelAlphaInPropertiesAnimator =
+ new AnimationProperties().setDuration(200).setAnimationFinishListener(
+ mAnimatorListenerAdapter).setCustomInterpolator(
+ mPanelAlphaAnimator.getProperty(), Interpolators.ALPHA_IN);
+ private final NotificationEntryManager mEntryManager;
+
+ private final CommandQueue mCommandQueue;
+ private final NotificationLockscreenUserManager mLockscreenUserManager;
+ private final ShadeController mShadeController;
+ private int mDisplayId;
+
+ /**
+ * Cache the resource id of the theme to avoid unnecessary work in onThemeChanged.
+ *
+ * onThemeChanged is forced when the theme might not have changed. So, to avoid unncessary
+ * work, check the current id with the cached id.
+ */
+ private int mThemeResId;
+ private KeyguardIndicationController mKeyguardIndicationController;
+ private Consumer<Boolean> mAffordanceLaunchListener;
+ private int mShelfHeight;
+ private Runnable mOnReinflationListener;
+ private int mDarkIconSize;
+ private int mHeadsUpInset;
+ private boolean mHeadsUpPinnedMode;
+ private float mKeyguardHeadsUpShowingAmount = 0.0f;
+ private boolean mShowingKeyguardHeadsUp;
+ private boolean mAllowExpandForSmallExpansion;
+ private Runnable mExpandAfterLayoutRunnable;
+
+ /**
+ * If face auth with bypass is running for the first time after you turn on the screen.
+ * (From aod or screen off)
+ */
+ private boolean mFirstBypassAttempt;
+ /**
+ * If auth happens successfully during {@code mFirstBypassAttempt}, and we should wait until
+ * the keyguard is dismissed to show the status bar.
+ */
+ private boolean mDelayShowingKeyguardStatusBar;
+
+ private PluginManager mPluginManager;
+ private FrameLayout mPluginFrame;
+ private NPVPluginManager mNPVPluginManager;
+ private int mOldLayoutDirection;
+
+ @Inject
+ public NotificationPanelViewController(NotificationPanelView view,
+ InjectionInflationController injectionInflationController,
+ NotificationWakeUpCoordinator coordinator, PulseExpansionHandler pulseExpansionHandler,
+ DynamicPrivacyController dynamicPrivacyController,
+ KeyguardBypassController bypassController, FalsingManager falsingManager,
+ PluginManager pluginManager, ShadeController shadeController,
+ NotificationLockscreenUserManager notificationLockscreenUserManager,
+ NotificationEntryManager notificationEntryManager,
+ KeyguardStateController keyguardStateController,
+ StatusBarStateController statusBarStateController, DozeLog dozeLog,
+ DozeParameters dozeParameters, CommandQueue commandQueue, VibratorHelper vibratorHelper,
+ LatencyTracker latencyTracker, PowerManager powerManager,
+ AccessibilityManager accessibilityManager, @DisplayId int displayId,
+ KeyguardUpdateMonitor keyguardUpdateMonitor, MetricsLogger metricsLogger,
+ ActivityManager activityManager, ZenModeController zenModeController,
+ ConfigurationController configurationController,
+ FlingAnimationUtils.Builder flingAnimationUtilsBuilder) {
+ super(view, falsingManager, dozeLog, keyguardStateController,
+ (SysuiStatusBarStateController) statusBarStateController, vibratorHelper,
+ latencyTracker, flingAnimationUtilsBuilder);
+ mView = view;
+ mMetricsLogger = metricsLogger;
+ mActivityManager = activityManager;
+ mZenModeController = zenModeController;
+ mConfigurationController = configurationController;
+ mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilder;
+ mView.setWillNotDraw(!DEBUG);
+ mInjectionInflationController = injectionInflationController;
+ mFalsingManager = falsingManager;
+ mPowerManager = powerManager;
+ mWakeUpCoordinator = coordinator;
+ mAccessibilityManager = accessibilityManager;
+ mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
+ setPanelAlpha(255, false /* animate */);
+ mCommandQueue = commandQueue;
+ mDisplayId = displayId;
+ mPulseExpansionHandler = pulseExpansionHandler;
+ mDozeParameters = dozeParameters;
+ pulseExpansionHandler.setPulseExpandAbortListener(() -> {
+ if (mQs != null) {
+ mQs.animateHeaderSlidingOut();
+ }
+ });
+ mThemeResId = mView.getContext().getThemeResId();
+ mKeyguardBypassController = bypassController;
+ mUpdateMonitor = keyguardUpdateMonitor;
+ mFirstBypassAttempt = mKeyguardBypassController.getBypassEnabled();
+ KeyguardStateController.Callback
+ keyguardMonitorCallback =
+ new KeyguardStateController.Callback() {
+ @Override
+ public void onKeyguardFadingAwayChanged() {
+ if (!mKeyguardStateController.isKeyguardFadingAway()) {
+ mFirstBypassAttempt = false;
+ mDelayShowingKeyguardStatusBar = false;
+ }
+ }
+ };
+ mKeyguardStateController.addCallback(keyguardMonitorCallback);
+ DynamicPrivacyControlListener
+ dynamicPrivacyControlListener =
+ new DynamicPrivacyControlListener();
+ dynamicPrivacyController.addListener(dynamicPrivacyControlListener);
+
+ mBottomAreaShadeAlphaAnimator = ValueAnimator.ofFloat(1f, 0);
+ mBottomAreaShadeAlphaAnimator.addUpdateListener(animation -> {
+ mBottomAreaShadeAlpha = (float) animation.getAnimatedValue();
+ updateKeyguardBottomAreaAlpha();
+ });
+ mBottomAreaShadeAlphaAnimator.setDuration(160);
+ mBottomAreaShadeAlphaAnimator.setInterpolator(Interpolators.ALPHA_OUT);
+ mPluginManager = pluginManager;
+ mShadeController = shadeController;
+ mLockscreenUserManager = notificationLockscreenUserManager;
+ mEntryManager = notificationEntryManager;
+
+ mView.setBackgroundColor(Color.TRANSPARENT);
+ OnAttachStateChangeListener onAttachStateChangeListener = new OnAttachStateChangeListener();
+ mView.addOnAttachStateChangeListener(onAttachStateChangeListener);
+ if (mView.isAttachedToWindow()) {
+ onAttachStateChangeListener.onViewAttachedToWindow(mView);
+ }
+
+ mView.setOnApplyWindowInsetsListener(new OnApplyWindowInsetsListener());
+
+ if (DEBUG) {
+ mView.getOverlay().add(new DebugDrawable());
+ }
+
+ onFinishInflate();
+ }
+
+ private void onFinishInflate() {
+ loadDimens();
+ mKeyguardStatusBar = mView.findViewById(R.id.keyguard_header);
+ mKeyguardStatusView = mView.findViewById(R.id.keyguard_status_view);
+
+ KeyguardClockSwitch keyguardClockSwitch = mView.findViewById(R.id.keyguard_clock_container);
+ mBigClockContainer = mView.findViewById(R.id.big_clock_container);
+ keyguardClockSwitch.setBigClockContainer(mBigClockContainer);
+
+ mHomeControlsLayout = mView.findViewById(R.id.home_controls_layout);
+ mNotificationContainerParent = mView.findViewById(R.id.notification_container_parent);
+ mNotificationStackScroller = mView.findViewById(R.id.notification_stack_scroller);
+ mNotificationStackScroller.setOnHeightChangedListener(mOnHeightChangedListener);
+ mNotificationStackScroller.setOverscrollTopChangedListener(mOnOverscrollTopChangedListener);
+ mNotificationStackScroller.setOnEmptySpaceClickListener(mOnEmptySpaceClickListener);
+ addTrackingHeadsUpListener(mNotificationStackScroller::setTrackingHeadsUp);
+ mKeyguardBottomArea = mView.findViewById(R.id.keyguard_bottom_area);
+ mQsNavbarScrim = mView.findViewById(R.id.qs_navbar_scrim);
+ mLastOrientation = mResources.getConfiguration().orientation;
+ mPluginFrame = mView.findViewById(R.id.plugin_frame);
+ if (Settings.System.getInt(mView.getContext().getContentResolver(), "npv_plugin_flag", 0)
+ == 1) {
+ mNPVPluginManager = new NPVPluginManager(mPluginFrame, mPluginManager);
+ }
+
+
+ initBottomArea();
+
+ mWakeUpCoordinator.setStackScroller(mNotificationStackScroller);
+ mQsFrame = mView.findViewById(R.id.qs_frame);
+ mPulseExpansionHandler.setUp(
+ mNotificationStackScroller, mExpansionCallback, mShadeController);
+ mWakeUpCoordinator.addListener(new NotificationWakeUpCoordinator.WakeUpListener() {
+ @Override
+ public void onFullyHiddenChanged(boolean isFullyHidden) {
+ updateKeyguardStatusBarForHeadsUp();
+ }
+
+ @Override
+ public void onPulseExpansionChanged(boolean expandingChanged) {
+ if (mKeyguardBypassController.getBypassEnabled()) {
+ // Position the notifications while dragging down while pulsing
+ requestScrollerTopPaddingUpdate(false /* animate */);
+ updateQSPulseExpansion();
+ }
+ }
+ });
+
+ mPluginManager.addPluginListener(new PluginListener<HomeControlsPlugin>() {
+
+ @Override
+ public void onPluginConnected(HomeControlsPlugin plugin, Context pluginContext) {
+ plugin.sendParentGroup(mHomeControlsLayout);
+ }
+
+ @Override
+ public void onPluginDisconnected(HomeControlsPlugin plugin) {
+
+ }
+ }, HomeControlsPlugin.class, false);
+
+ mView.setRtlChangeListener(layoutDirection -> {
+ if (layoutDirection != mOldLayoutDirection) {
+ mAffordanceHelper.onRtlPropertiesChanged();
+ mOldLayoutDirection = layoutDirection;
+ }
+ });
+ }
+
+ @Override
+ protected void loadDimens() {
+ super.loadDimens();
+ mFlingAnimationUtils = mFlingAnimationUtilsBuilder.reset()
+ .setMaxLengthSeconds(0.4f).build();
+ mStatusBarMinHeight = mResources.getDimensionPixelSize(
+ com.android.internal.R.dimen.status_bar_height);
+ mQsPeekHeight = mResources.getDimensionPixelSize(R.dimen.qs_peek_height);
+ mNotificationsHeaderCollideDistance = mResources.getDimensionPixelSize(
+ R.dimen.header_notifications_collide_distance);
+ mClockPositionAlgorithm.loadDimens(mResources);
+ mQsFalsingThreshold = mResources.getDimensionPixelSize(R.dimen.qs_falsing_threshold);
+ mPositionMinSideMargin = mResources.getDimensionPixelSize(
+ R.dimen.notification_panel_min_side_margin);
+ mIndicationBottomPadding = mResources.getDimensionPixelSize(
+ R.dimen.keyguard_indication_bottom_padding);
+ mQsNotificationTopPadding = mResources.getDimensionPixelSize(
+ R.dimen.qs_notification_padding);
+ mShelfHeight = mResources.getDimensionPixelSize(R.dimen.notification_shelf_height);
+ mDarkIconSize = mResources.getDimensionPixelSize(R.dimen.status_bar_icon_drawing_size_dark);
+ int statusbarHeight = mResources.getDimensionPixelSize(
+ com.android.internal.R.dimen.status_bar_height);
+ mHeadsUpInset = statusbarHeight + mResources.getDimensionPixelSize(
+ R.dimen.heads_up_status_bar_padding);
+ mQqsSplitFraction = ((float) mResources.getInteger(R.integer.qqs_split_fraction)) / (
+ mResources.getInteger(R.integer.qqs_split_fraction) + mResources.getInteger(
+ R.integer.qs_split_fraction));
+ }
+
+ /**
+ * Returns if there's a custom clock being presented.
+ */
+ public boolean hasCustomClock() {
+ return mKeyguardStatusView.hasCustomClock();
+ }
+
+ private void setStatusBar(StatusBar bar) {
+ // TODO: this can be injected.
+ mStatusBar = bar;
+ mKeyguardBottomArea.setStatusBar(mStatusBar);
+ }
+ /**
+ * @see #launchCamera(boolean, int)
+ * @see #setLaunchingAffordance(boolean)
+ */
+ public void setLaunchAffordanceListener(Consumer<Boolean> listener) {
+ mAffordanceLaunchListener = listener;
+ }
+
+ public void updateResources() {
+ int qsWidth = mResources.getDimensionPixelSize(R.dimen.qs_panel_width);
+ int panelGravity = mResources.getInteger(R.integer.notification_panel_layout_gravity);
+ FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mQsFrame.getLayoutParams();
+ if (lp.width != qsWidth || lp.gravity != panelGravity) {
+ lp.width = qsWidth;
+ lp.gravity = panelGravity;
+ mQsFrame.setLayoutParams(lp);
+ }
+
+ int panelWidth = mResources.getDimensionPixelSize(R.dimen.notification_panel_width);
+ lp = (FrameLayout.LayoutParams) mNotificationStackScroller.getLayoutParams();
+ if (lp.width != panelWidth || lp.gravity != panelGravity) {
+ lp.width = panelWidth;
+ lp.gravity = panelGravity;
+ mNotificationStackScroller.setLayoutParams(lp);
+ }
+ int sideMargin = mResources.getDimensionPixelOffset(R.dimen.notification_side_paddings);
+ int topMargin = sideMargin;
+ lp = (FrameLayout.LayoutParams) mPluginFrame.getLayoutParams();
+ if (lp.width != qsWidth || lp.gravity != panelGravity || lp.leftMargin != sideMargin
+ || lp.rightMargin != sideMargin || lp.topMargin != topMargin) {
+ lp.width = qsWidth;
+ lp.gravity = panelGravity;
+ lp.leftMargin = sideMargin;
+ lp.rightMargin = sideMargin;
+ lp.topMargin = topMargin;
+ mPluginFrame.setLayoutParams(lp);
+ }
+ }
+
+ private void reInflateViews() {
+ updateShowEmptyShadeView();
+
+ // Re-inflate the status view group.
+ int index = mView.indexOfChild(mKeyguardStatusView);
+ mView.removeView(mKeyguardStatusView);
+ mKeyguardStatusView = (KeyguardStatusView) mInjectionInflationController.injectable(
+ LayoutInflater.from(mView.getContext())).inflate(
+ R.layout.keyguard_status_view, mView, false);
+ mView.addView(mKeyguardStatusView, index);
+
+ // Re-associate the clock container with the keyguard clock switch.
+ mBigClockContainer.removeAllViews();
+ KeyguardClockSwitch keyguardClockSwitch = mView.findViewById(R.id.keyguard_clock_container);
+ keyguardClockSwitch.setBigClockContainer(mBigClockContainer);
+
+ // Update keyguard bottom area
+ index = mView.indexOfChild(mKeyguardBottomArea);
+ mView.removeView(mKeyguardBottomArea);
+ KeyguardBottomAreaView oldBottomArea = mKeyguardBottomArea;
+ mKeyguardBottomArea = (KeyguardBottomAreaView) mInjectionInflationController.injectable(
+ LayoutInflater.from(mView.getContext())).inflate(
+ R.layout.keyguard_bottom_area, mView, false);
+ mKeyguardBottomArea.initFrom(oldBottomArea);
+ mView.addView(mKeyguardBottomArea, index);
+ initBottomArea();
+ mKeyguardIndicationController.setIndicationArea(mKeyguardBottomArea);
+ mStatusBarStateListener.onDozeAmountChanged(mStatusBarStateController.getDozeAmount(),
+ mStatusBarStateController.getInterpolatedDozeAmount());
+
+ if (mKeyguardStatusBar != null) {
+ mKeyguardStatusBar.onThemeChanged();
+ }
+
+ setKeyguardStatusViewVisibility(mBarState, false, false);
+ setKeyguardBottomAreaVisibility(mBarState, false);
+ if (mOnReinflationListener != null) {
+ mOnReinflationListener.run();
+ }
+ reinflatePluginContainer();
+ }
+
+ private void reinflatePluginContainer() {
+ int index = mView.indexOfChild(mPluginFrame);
+ mView.removeView(mPluginFrame);
+ mPluginFrame = (FrameLayout) mInjectionInflationController.injectable(
+ LayoutInflater.from(mView.getContext())).inflate(
+ R.layout.status_bar_expanded_plugin_frame, mView, false);
+ mView.addView(mPluginFrame, index);
+
+ Resources res = mView.getResources();
+ int qsWidth = res.getDimensionPixelSize(R.dimen.qs_panel_width);
+ int panelGravity = mView.getResources().getInteger(
+ R.integer.notification_panel_layout_gravity);
+ FrameLayout.LayoutParams lp;
+ int sideMargin = res.getDimensionPixelOffset(R.dimen.notification_side_paddings);
+ int topMargin = res.getDimensionPixelOffset(
+ com.android.internal.R.dimen.quick_qs_total_height);
+ if (Utils.useQsMediaPlayer(mView.getContext())) {
+ topMargin = res.getDimensionPixelOffset(
+ com.android.internal.R.dimen.quick_qs_total_height_with_media);
+ }
+ lp = (FrameLayout.LayoutParams) mPluginFrame.getLayoutParams();
+ if (lp.width != qsWidth || lp.gravity != panelGravity || lp.leftMargin != sideMargin
+ || lp.rightMargin != sideMargin || lp.topMargin != topMargin) {
+ lp.width = qsWidth;
+ lp.gravity = panelGravity;
+ lp.leftMargin = sideMargin;
+ lp.rightMargin = sideMargin;
+ lp.topMargin = topMargin;
+ mPluginFrame.setLayoutParams(lp);
+ }
+
+ if (mNPVPluginManager != null) mNPVPluginManager.replaceFrameLayout(mPluginFrame);
+ }
+
+ private void initBottomArea() {
+ mAffordanceHelper = new KeyguardAffordanceHelper(
+ mKeyguardAffordanceHelperCallback, mView.getContext(), mFalsingManager);
+ mKeyguardBottomArea.setAffordanceHelper(mAffordanceHelper);
+ mKeyguardBottomArea.setStatusBar(mStatusBar);
+ mKeyguardBottomArea.setUserSetupComplete(mUserSetupComplete);
+ }
+
+ public void setKeyguardIndicationController(KeyguardIndicationController indicationController) {
+ mKeyguardIndicationController = indicationController;
+ mKeyguardIndicationController.setIndicationArea(mKeyguardBottomArea);
+ }
+
+ private void updateGestureExclusionRect() {
+ Rect exclusionRect = calculateGestureExclusionRect();
+ mView.setSystemGestureExclusionRects(exclusionRect.isEmpty() ? Collections.EMPTY_LIST
+ : Collections.singletonList(exclusionRect));
+ }
+
+ private Rect calculateGestureExclusionRect() {
+ Rect exclusionRect = null;
+ Region touchableRegion = mHeadsUpManager.calculateTouchableRegion();
+ if (isFullyCollapsed() && touchableRegion != null) {
+ // Note: The heads up manager also calculates the non-pinned touchable region
+ exclusionRect = touchableRegion.getBounds();
+ }
+ return exclusionRect != null ? exclusionRect : EMPTY_RECT;
+ }
+
+ private void setIsFullWidth(boolean isFullWidth) {
+ mIsFullWidth = isFullWidth;
+ mNotificationStackScroller.setIsFullWidth(isFullWidth);
+ }
+
+ private void startQsSizeChangeAnimation(int oldHeight, final int newHeight) {
+ if (mQsSizeChangeAnimator != null) {
+ oldHeight = (int) mQsSizeChangeAnimator.getAnimatedValue();
+ mQsSizeChangeAnimator.cancel();
+ }
+ mQsSizeChangeAnimator = ValueAnimator.ofInt(oldHeight, newHeight);
+ mQsSizeChangeAnimator.setDuration(300);
+ mQsSizeChangeAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ mQsSizeChangeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ requestScrollerTopPaddingUpdate(false /* animate */);
+ requestPanelHeightUpdate();
+ int height = (int) mQsSizeChangeAnimator.getAnimatedValue();
+ mQs.setHeightOverride(height);
+ }
+ });
+ mQsSizeChangeAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mQsSizeChangeAnimator = null;
+ }
+ });
+ mQsSizeChangeAnimator.start();
+ }
+
+ /**
+ * Positions the clock and notifications dynamically depending on how many notifications are
+ * showing.
+ */
+ private void positionClockAndNotifications() {
+ boolean animate = mNotificationStackScroller.isAddOrRemoveAnimationPending();
+ boolean animateClock = animate || mAnimateNextPositionUpdate;
+ int stackScrollerPadding;
+ if (mBarState != StatusBarState.KEYGUARD) {
+ stackScrollerPadding = getUnlockedStackScrollerPadding();
+ } else {
+ int totalHeight = mView.getHeight();
+ int bottomPadding = Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding);
+ int clockPreferredY = mKeyguardStatusView.getClockPreferredY(totalHeight);
+ boolean bypassEnabled = mKeyguardBypassController.getBypassEnabled();
+ final boolean
+ hasVisibleNotifications =
+ !bypassEnabled && mNotificationStackScroller.getVisibleNotificationCount() != 0;
+ mKeyguardStatusView.setHasVisibleNotifications(hasVisibleNotifications);
+ mClockPositionAlgorithm.setup(mStatusBarMinHeight, totalHeight - bottomPadding,
+ mNotificationStackScroller.getIntrinsicContentHeight(), getExpandedFraction(),
+ totalHeight, (int) (mKeyguardStatusView.getHeight() - mShelfHeight / 2.0f
+ - mDarkIconSize / 2.0f), clockPreferredY, hasCustomClock(),
+ hasVisibleNotifications, mInterpolatedDarkAmount, mEmptyDragAmount,
+ bypassEnabled, getUnlockedStackScrollerPadding());
+ mClockPositionAlgorithm.run(mClockPositionResult);
+ PropertyAnimator.setProperty(mKeyguardStatusView, AnimatableProperty.X,
+ mClockPositionResult.clockX, CLOCK_ANIMATION_PROPERTIES, animateClock);
+ PropertyAnimator.setProperty(mKeyguardStatusView, AnimatableProperty.Y,
+ mClockPositionResult.clockY, CLOCK_ANIMATION_PROPERTIES, animateClock);
+ updateNotificationTranslucency();
+ updateClock();
+ stackScrollerPadding = mClockPositionResult.stackScrollerPaddingExpanded;
+ }
+ mNotificationStackScroller.setIntrinsicPadding(stackScrollerPadding);
+ mKeyguardBottomArea.setAntiBurnInOffsetX(mClockPositionResult.clockX);
+
+ mStackScrollerMeasuringPass++;
+ requestScrollerTopPaddingUpdate(animate);
+ mStackScrollerMeasuringPass = 0;
+ mAnimateNextPositionUpdate = false;
+ }
+
+ /**
+ * @return the padding of the stackscroller when unlocked
+ */
+ private int getUnlockedStackScrollerPadding() {
+ return (mQs != null ? mQs.getHeader().getHeight() : 0) + mQsPeekHeight
+ + mQsNotificationTopPadding;
+ }
+
+ /**
+ * @param maximum the maximum to return at most
+ * @return the maximum keyguard notifications that can fit on the screen
+ */
+ public int computeMaxKeyguardNotifications(int maximum) {
+ float minPadding = mClockPositionAlgorithm.getMinStackScrollerPadding();
+ int notificationPadding = Math.max(
+ 1, mResources.getDimensionPixelSize(R.dimen.notification_divider_height));
+ NotificationShelf shelf = mNotificationStackScroller.getNotificationShelf();
+ float
+ shelfSize =
+ shelf.getVisibility() == View.GONE ? 0
+ : shelf.getIntrinsicHeight() + notificationPadding;
+ float
+ availableSpace =
+ mNotificationStackScroller.getHeight() - minPadding - shelfSize - Math.max(
+ mIndicationBottomPadding, mAmbientIndicationBottomPadding)
+ - mKeyguardStatusView.getLogoutButtonHeight();
+ int count = 0;
+ for (int i = 0; i < mNotificationStackScroller.getChildCount(); i++) {
+ ExpandableView child = (ExpandableView) mNotificationStackScroller.getChildAt(i);
+ if (!(child instanceof ExpandableNotificationRow)) {
+ continue;
+ }
+ ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+ boolean
+ suppressedSummary =
+ mGroupManager != null && mGroupManager.isSummaryOfSuppressedGroup(
+ row.getEntry().getSbn());
+ if (suppressedSummary) {
+ continue;
+ }
+ if (!mLockscreenUserManager.shouldShowOnKeyguard(row.getEntry())) {
+ continue;
+ }
+ if (row.isRemoved()) {
+ continue;
+ }
+ availableSpace -= child.getMinHeight(true /* ignoreTemporaryStates */)
+ + notificationPadding;
+ if (availableSpace >= 0 && count < maximum) {
+ count++;
+ } else if (availableSpace > -shelfSize) {
+ // if we are exactly the last view, then we can show us still!
+ for (int j = i + 1; j < mNotificationStackScroller.getChildCount(); j++) {
+ if (mNotificationStackScroller.getChildAt(
+ j) instanceof ExpandableNotificationRow) {
+ return count;
+ }
+ }
+ count++;
+ return count;
+ } else {
+ return count;
+ }
+ }
+ return count;
+ }
+
+ private void updateClock() {
+ if (!mKeyguardStatusViewAnimating) {
+ mKeyguardStatusView.setAlpha(mClockPositionResult.clockAlpha);
+ }
+ }
+
+ public void animateToFullShade(long delay) {
+ mNotificationStackScroller.goToFullShade(delay);
+ mView.requestLayout();
+ mAnimateNextPositionUpdate = true;
+ }
+
+ public void setQsExpansionEnabled(boolean qsExpansionEnabled) {
+ mQsExpansionEnabled = qsExpansionEnabled;
+ if (mQs == null) return;
+ mQs.setHeaderClickable(qsExpansionEnabled);
+ }
+
+ @Override
+ public void resetViews(boolean animate) {
+ mIsLaunchTransitionFinished = false;
+ mBlockTouches = false;
+ if (!mLaunchingAffordance) {
+ mAffordanceHelper.reset(false);
+ mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
+ }
+ mStatusBar.getGutsManager().closeAndSaveGuts(true /* leavebehind */, true /* force */,
+ true /* controls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
+ if (animate) {
+ animateCloseQs(true /* animateAway */);
+ } else {
+ closeQs();
+ }
+ mNotificationStackScroller.setOverScrollAmount(0f, true /* onTop */, animate,
+ !animate /* cancelAnimators */);
+ mNotificationStackScroller.resetScrollPosition();
+ }
+
+ @Override
+ public void collapse(boolean delayed, float speedUpFactor) {
+ if (!canPanelBeCollapsed()) {
+ return;
+ }
+
+ if (mQsExpanded) {
+ mQsExpandImmediate = true;
+ mNotificationStackScroller.setShouldShowShelfOnly(true);
+ }
+ super.collapse(delayed, speedUpFactor);
+ }
+
+ public void closeQs() {
+ cancelQsAnimation();
+ setQsExpansion(mQsMinExpansionHeight);
+ }
+
+ public void cancelAnimation() {
+ mView.animate().cancel();
+ }
+
+
+ /**
+ * Animate QS closing by flinging it.
+ * If QS is expanded, it will collapse into QQS and stop.
+ *
+ * @param animateAway Do not stop when QS becomes QQS. Fling until QS isn't visible anymore.
+ */
+ public void animateCloseQs(boolean animateAway) {
+ if (mQsExpansionAnimator != null) {
+ if (!mQsAnimatorExpand) {
+ return;
+ }
+ float height = mQsExpansionHeight;
+ mQsExpansionAnimator.cancel();
+ setQsExpansion(height);
+ }
+ flingSettings(0 /* vel */, animateAway ? FLING_HIDE : FLING_COLLAPSE);
+ }
+
+ public void expandWithQs() {
+ if (mQsExpansionEnabled) {
+ mQsExpandImmediate = true;
+ mNotificationStackScroller.setShouldShowShelfOnly(true);
+ }
+ if (isFullyCollapsed()) {
+ expand(true /* animate */);
+ } else {
+ flingSettings(0 /* velocity */, FLING_EXPAND);
+ }
+ }
+
+ public void expandWithoutQs() {
+ if (isQsExpanded()) {
+ flingSettings(0 /* velocity */, FLING_COLLAPSE);
+ } else {
+ expand(true /* animate */);
+ }
+ }
+
+ @Override
+ public void fling(float vel, boolean expand) {
+ GestureRecorder gr = ((PhoneStatusBarView) mBar).mBar.getGestureRecorder();
+ if (gr != null) {
+ gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel);
+ }
+ super.fling(vel, expand);
+ }
+
+ @Override
+ protected void flingToHeight(float vel, boolean expand, float target,
+ float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) {
+ mHeadsUpTouchHelper.notifyFling(!expand);
+ setClosingWithAlphaFadeout(!expand && !isOnKeyguard() && getFadeoutAlpha() == 1.0f);
+ super.flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
+ }
+
+
+ private boolean onQsIntercept(MotionEvent event) {
+ int pointerIndex = event.findPointerIndex(mTrackingPointer);
+ if (pointerIndex < 0) {
+ pointerIndex = 0;
+ mTrackingPointer = event.getPointerId(pointerIndex);
+ }
+ final float x = event.getX(pointerIndex);
+ final float y = event.getY(pointerIndex);
+
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ mInitialTouchY = y;
+ mInitialTouchX = x;
+ initVelocityTracker();
+ trackMovement(event);
+ if (shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, 0)) {
+ mView.getParent().requestDisallowInterceptTouchEvent(true);
+ }
+ if (mQsExpansionAnimator != null) {
+ onQsExpansionStarted();
+ mInitialHeightOnTouch = mQsExpansionHeight;
+ mQsTracking = true;
+ mNotificationStackScroller.cancelLongPress();
+ }
+ break;
+ case MotionEvent.ACTION_POINTER_UP:
+ final int upPointer = event.getPointerId(event.getActionIndex());
+ if (mTrackingPointer == upPointer) {
+ // gesture is ongoing, find a new pointer to track
+ final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
+ mTrackingPointer = event.getPointerId(newIndex);
+ mInitialTouchX = event.getX(newIndex);
+ mInitialTouchY = event.getY(newIndex);
+ }
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ final float h = y - mInitialTouchY;
+ trackMovement(event);
+ if (mQsTracking) {
+
+ // Already tracking because onOverscrolled was called. We need to update here
+ // so we don't stop for a frame until the next touch event gets handled in
+ // onTouchEvent.
+ setQsExpansion(h + mInitialHeightOnTouch);
+ trackMovement(event);
+ return true;
+ }
+ if (Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX)
+ && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) {
+ mQsTracking = true;
+ onQsExpansionStarted();
+ notifyExpandingFinished();
+ mInitialHeightOnTouch = mQsExpansionHeight;
+ mInitialTouchY = y;
+ mInitialTouchX = x;
+ mNotificationStackScroller.cancelLongPress();
+ return true;
+ }
+ break;
+
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ trackMovement(event);
+ if (mQsTracking) {
+ flingQsWithCurrentVelocity(y,
+ event.getActionMasked() == MotionEvent.ACTION_CANCEL);
+ mQsTracking = false;
+ }
+ break;
+ }
+ return false;
+ }
+
+ @Override
+ protected boolean isInContentBounds(float x, float y) {
+ float stackScrollerX = mNotificationStackScroller.getX();
+ return !mNotificationStackScroller.isBelowLastNotification(x - stackScrollerX, y)
+ && stackScrollerX < x && x < stackScrollerX + mNotificationStackScroller.getWidth();
+ }
+
+ private void initDownStates(MotionEvent event) {
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ mOnlyAffordanceInThisMotion = false;
+ mQsTouchAboveFalsingThreshold = mQsFullyExpanded;
+ mDozingOnDown = isDozing();
+ mDownX = event.getX();
+ mDownY = event.getY();
+ mCollapsedOnDown = isFullyCollapsed();
+ mListenForHeadsUp = mCollapsedOnDown && mHeadsUpManager.hasPinnedHeadsUp();
+ mAllowExpandForSmallExpansion = mExpectingSynthesizedDown;
+ mTouchSlopExceededBeforeDown = mExpectingSynthesizedDown;
+ if (mExpectingSynthesizedDown) {
+ mLastEventSynthesizedDown = true;
+ } else {
+ // down but not synthesized motion event.
+ mLastEventSynthesizedDown = false;
+ }
+ } else {
+ // not down event at all.
+ mLastEventSynthesizedDown = false;
+ }
+ }
+
+ private void flingQsWithCurrentVelocity(float y, boolean isCancelMotionEvent) {
+ float vel = getCurrentQSVelocity();
+ final boolean expandsQs = flingExpandsQs(vel);
+ if (expandsQs) {
+ logQsSwipeDown(y);
+ }
+ flingSettings(vel, expandsQs && !isCancelMotionEvent ? FLING_EXPAND : FLING_COLLAPSE);
+ }
+
+ private void logQsSwipeDown(float y) {
+ float vel = getCurrentQSVelocity();
+ final int
+ gesture =
+ mBarState == StatusBarState.KEYGUARD ? MetricsEvent.ACTION_LS_QS
+ : MetricsEvent.ACTION_SHADE_QS_PULL;
+ mLockscreenGestureLogger.write(gesture,
+ (int) ((y - mInitialTouchY) / mStatusBar.getDisplayDensity()),
+ (int) (vel / mStatusBar.getDisplayDensity()));
+ }
+
+ private boolean flingExpandsQs(float vel) {
+ if (mFalsingManager.isUnlockingDisabled() || isFalseTouch()) {
+ return false;
+ }
+ if (Math.abs(vel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
+ return getQsExpansionFraction() > 0.5f;
+ } else {
+ return vel > 0;
+ }
+ }
+
+ private boolean isFalseTouch() {
+ if (!mKeyguardAffordanceHelperCallback.needsAntiFalsing()) {
+ return false;
+ }
+ if (mFalsingManager.isClassifierEnabled()) {
+ return mFalsingManager.isFalseTouch();
+ }
+ return !mQsTouchAboveFalsingThreshold;
+ }
+
+ private float getQsExpansionFraction() {
+ return Math.min(
+ 1f, (mQsExpansionHeight - mQsMinExpansionHeight) / (mQsMaxExpansionHeight
+ - mQsMinExpansionHeight));
+ }
+
+ @Override
+ protected boolean shouldExpandWhenNotFlinging() {
+ if (super.shouldExpandWhenNotFlinging()) {
+ return true;
+ }
+ if (mAllowExpandForSmallExpansion) {
+ // When we get a touch that came over from launcher, the velocity isn't always correct
+ // Let's err on expanding if the gesture has been reasonably slow
+ long timeSinceDown = SystemClock.uptimeMillis() - mDownTime;
+ return timeSinceDown <= MAX_TIME_TO_OPEN_WHEN_FLINGING_FROM_LAUNCHER;
+ }
+ return false;
+ }
+
+ @Override
+ protected float getOpeningHeight() {
+ return mNotificationStackScroller.getOpeningHeight();
+ }
+
+
+ private boolean handleQsTouch(MotionEvent event) {
+ final int action = event.getActionMasked();
+ if (action == MotionEvent.ACTION_DOWN && getExpandedFraction() == 1f
+ && mBarState != StatusBarState.KEYGUARD && !mQsExpanded && mQsExpansionEnabled) {
+
+ // Down in the empty area while fully expanded - go to QS.
+ mQsTracking = true;
+ mConflictingQsExpansionGesture = true;
+ onQsExpansionStarted();
+ mInitialHeightOnTouch = mQsExpansionHeight;
+ mInitialTouchY = event.getX();
+ mInitialTouchX = event.getY();
+ }
+ if (!isFullyCollapsed()) {
+ handleQsDown(event);
+ }
+ if (!mQsExpandImmediate && mQsTracking) {
+ onQsTouch(event);
+ if (!mConflictingQsExpansionGesture) {
+ return true;
+ }
+ }
+ if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
+ mConflictingQsExpansionGesture = false;
+ }
+ if (action == MotionEvent.ACTION_DOWN && isFullyCollapsed() && mQsExpansionEnabled) {
+ mTwoFingerQsExpandPossible = true;
+ }
+ if (mTwoFingerQsExpandPossible && isOpenQsEvent(event) && event.getY(event.getActionIndex())
+ < mStatusBarMinHeight) {
+ mMetricsLogger.count(COUNTER_PANEL_OPEN_QS, 1);
+ mQsExpandImmediate = true;
+ mNotificationStackScroller.setShouldShowShelfOnly(true);
+ requestPanelHeightUpdate();
+
+ // Normally, we start listening when the panel is expanded, but here we need to start
+ // earlier so the state is already up to date when dragging down.
+ setListening(true);
+ }
+ if (isQsSplitEnabled() && !mKeyguardShowing) {
+ if (mQsExpandImmediate) {
+ mNotificationStackScroller.setVisibility(View.GONE);
+ mQsFrame.setVisibility(View.VISIBLE);
+ mHomeControlsLayout.setVisibility(View.VISIBLE);
+ } else {
+ mNotificationStackScroller.setVisibility(View.VISIBLE);
+ mQsFrame.setVisibility(View.GONE);
+ mHomeControlsLayout.setVisibility(View.GONE);
+ }
+ }
+ return false;
+ }
+
+ private boolean isInQsArea(float x, float y) {
+ return (x >= mQsFrame.getX() && x <= mQsFrame.getX() + mQsFrame.getWidth()) && (
+ y <= mNotificationStackScroller.getBottomMostNotificationBottom()
+ || y <= mQs.getView().getY() + mQs.getView().getHeight());
+ }
+
+ private boolean isOnQsEndArea(float x) {
+ if (!isQsSplitEnabled()) return false;
+ if (mView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR) {
+ return x >= mQsFrame.getX() + mQqsSplitFraction * mQsFrame.getWidth()
+ && x <= mQsFrame.getX() + mQsFrame.getWidth();
+ } else {
+ return x >= mQsFrame.getX()
+ && x <= mQsFrame.getX() + (1 - mQqsSplitFraction) * mQsFrame.getWidth();
+ }
+ }
+
+ private boolean isOpenQsEvent(MotionEvent event) {
+ final int pointerCount = event.getPointerCount();
+ final int action = event.getActionMasked();
+
+ final boolean
+ twoFingerDrag =
+ action == MotionEvent.ACTION_POINTER_DOWN && pointerCount == 2;
+
+ final boolean
+ stylusButtonClickDrag =
+ action == MotionEvent.ACTION_DOWN && (event.isButtonPressed(
+ MotionEvent.BUTTON_STYLUS_PRIMARY) || event.isButtonPressed(
+ MotionEvent.BUTTON_STYLUS_SECONDARY));
+
+ final boolean
+ mouseButtonClickDrag =
+ action == MotionEvent.ACTION_DOWN && (event.isButtonPressed(
+ MotionEvent.BUTTON_SECONDARY) || event.isButtonPressed(
+ MotionEvent.BUTTON_TERTIARY));
+
+ final boolean onHeaderRight = isOnQsEndArea(event.getX());
+
+ return twoFingerDrag || stylusButtonClickDrag || mouseButtonClickDrag || onHeaderRight;
+ }
+
+ private void handleQsDown(MotionEvent event) {
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN && shouldQuickSettingsIntercept(
+ event.getX(), event.getY(), -1)) {
+ mFalsingManager.onQsDown();
+ mQsTracking = true;
+ onQsExpansionStarted();
+ mInitialHeightOnTouch = mQsExpansionHeight;
+ mInitialTouchY = event.getX();
+ mInitialTouchX = event.getY();
+
+ // If we interrupt an expansion gesture here, make sure to update the state correctly.
+ notifyExpandingFinished();
+ }
+ }
+
+ /**
+ * Input focus transfer is about to happen.
+ */
+ public void startWaitingForOpenPanelGesture() {
+ if (!isFullyCollapsed()) {
+ return;
+ }
+ mExpectingSynthesizedDown = true;
+ onTrackingStarted();
+ updatePanelExpanded();
+ }
+
+ /**
+ * Called when this view is no longer waiting for input focus transfer.
+ *
+ * There are two scenarios behind this function call. First, input focus transfer
+ * has successfully happened and this view already received synthetic DOWN event.
+ * (mExpectingSynthesizedDown == false). Do nothing.
+ *
+ * Second, before input focus transfer finished, user may have lifted finger
+ * in previous window and this window never received synthetic DOWN event.
+ * (mExpectingSynthesizedDown == true).
+ * In this case, we use the velocity to trigger fling event.
+ *
+ * @param velocity unit is in px / millis
+ */
+ public void stopWaitingForOpenPanelGesture(final float velocity) {
+ if (mExpectingSynthesizedDown) {
+ mExpectingSynthesizedDown = false;
+ maybeVibrateOnOpening();
+ Runnable runnable = () -> fling(velocity > 1f ? 1000f * velocity : 0,
+ true /* expand */);
+ if (mStatusBar.getStatusBarWindow().getHeight() != mStatusBar.getStatusBarHeight()) {
+ // The panel is already expanded to its full size, let's expand directly
+ runnable.run();
+ } else {
+ mExpandAfterLayoutRunnable = runnable;
+ }
+ onTrackingStopped(false);
+ }
+ }
+
+ @Override
+ protected boolean flingExpands(float vel, float vectorVel, float x, float y) {
+ boolean expands = super.flingExpands(vel, vectorVel, x, y);
+
+ // If we are already running a QS expansion, make sure that we keep the panel open.
+ if (mQsExpansionAnimator != null) {
+ expands = true;
+ }
+ return expands;
+ }
+
+ @Override
+ protected boolean shouldGestureWaitForTouchSlop() {
+ if (mExpectingSynthesizedDown) {
+ mExpectingSynthesizedDown = false;
+ return false;
+ }
+ return isFullyCollapsed() || mBarState != StatusBarState.SHADE;
+ }
+
+ @Override
+ protected boolean shouldGestureIgnoreXTouchSlop(float x, float y) {
+ return !mAffordanceHelper.isOnAffordanceIcon(x, y);
+ }
+
+ private void onQsTouch(MotionEvent event) {
+ int pointerIndex = event.findPointerIndex(mTrackingPointer);
+ if (pointerIndex < 0) {
+ pointerIndex = 0;
+ mTrackingPointer = event.getPointerId(pointerIndex);
+ }
+ final float y = event.getY(pointerIndex);
+ final float x = event.getX(pointerIndex);
+ final float h = y - mInitialTouchY;
+
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ mQsTracking = true;
+ mInitialTouchY = y;
+ mInitialTouchX = x;
+ onQsExpansionStarted();
+ mInitialHeightOnTouch = mQsExpansionHeight;
+ initVelocityTracker();
+ trackMovement(event);
+ break;
+
+ case MotionEvent.ACTION_POINTER_UP:
+ final int upPointer = event.getPointerId(event.getActionIndex());
+ if (mTrackingPointer == upPointer) {
+ // gesture is ongoing, find a new pointer to track
+ final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
+ final float newY = event.getY(newIndex);
+ final float newX = event.getX(newIndex);
+ mTrackingPointer = event.getPointerId(newIndex);
+ mInitialHeightOnTouch = mQsExpansionHeight;
+ mInitialTouchY = newY;
+ mInitialTouchX = newX;
+ }
+ break;
+
+ case MotionEvent.ACTION_MOVE:
+ setQsExpansion(h + mInitialHeightOnTouch);
+ if (h >= getFalsingThreshold()) {
+ mQsTouchAboveFalsingThreshold = true;
+ }
+ trackMovement(event);
+ break;
+
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ mQsTracking = false;
+ mTrackingPointer = -1;
+ trackMovement(event);
+ float fraction = getQsExpansionFraction();
+ if (fraction != 0f || y >= mInitialTouchY) {
+ flingQsWithCurrentVelocity(y,
+ event.getActionMasked() == MotionEvent.ACTION_CANCEL);
+ }
+ if (mQsVelocityTracker != null) {
+ mQsVelocityTracker.recycle();
+ mQsVelocityTracker = null;
+ }
+ break;
+ }
+ }
+
+ private int getFalsingThreshold() {
+ float factor = mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
+ return (int) (mQsFalsingThreshold * factor);
+ }
+
+ private void setOverScrolling(boolean overscrolling) {
+ mStackScrollerOverscrolling = overscrolling;
+ if (mQs == null) return;
+ mQs.setOverscrolling(overscrolling);
+ }
+
+ private void onQsExpansionStarted() {
+ onQsExpansionStarted(0);
+ }
+
+ protected void onQsExpansionStarted(int overscrollAmount) {
+ cancelQsAnimation();
+ cancelHeightAnimator();
+
+ // Reset scroll position and apply that position to the expanded height.
+ float height = mQsExpansionHeight - overscrollAmount;
+ setQsExpansion(height);
+ requestPanelHeightUpdate();
+ mNotificationStackScroller.checkSnoozeLeavebehind();
+
+ // When expanding QS, let's authenticate the user if possible,
+ // this will speed up notification actions.
+ if (height == 0) {
+ mStatusBar.requestFaceAuth();
+ }
+ }
+
+ private void setQsExpanded(boolean expanded) {
+ boolean changed = mQsExpanded != expanded;
+ if (changed) {
+ mQsExpanded = expanded;
+ updateQsState();
+ requestPanelHeightUpdate();
+ mFalsingManager.setQsExpanded(expanded);
+ mStatusBar.setQsExpanded(expanded);
+ mNotificationContainerParent.setQsExpanded(expanded);
+ mPulseExpansionHandler.setQsExpanded(expanded);
+ mKeyguardBypassController.setQSExpanded(expanded);
+ }
+ }
+
+ private void maybeAnimateBottomAreaAlpha() {
+ mBottomAreaShadeAlphaAnimator.cancel();
+ if (mBarState == StatusBarState.SHADE_LOCKED) {
+ mBottomAreaShadeAlphaAnimator.start();
+ } else {
+ mBottomAreaShadeAlpha = 1f;
+ }
+ }
+
+ private final Runnable mAnimateKeyguardStatusViewInvisibleEndRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mKeyguardStatusViewAnimating = false;
+ mKeyguardStatusView.setVisibility(View.INVISIBLE);
+ }
+ };
+
+ private final Runnable mAnimateKeyguardStatusViewGoneEndRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mKeyguardStatusViewAnimating = false;
+ mKeyguardStatusView.setVisibility(View.GONE);
+ }
+ };
+
+ private final Runnable mAnimateKeyguardStatusViewVisibleEndRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mKeyguardStatusViewAnimating = false;
+ }
+ };
+
+ private final Runnable mAnimateKeyguardStatusBarInvisibleEndRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mKeyguardStatusBar.setVisibility(View.INVISIBLE);
+ mKeyguardStatusBar.setAlpha(1f);
+ mKeyguardStatusBarAnimateAlpha = 1f;
+ }
+ };
+
+ private void animateKeyguardStatusBarOut() {
+ ValueAnimator anim = ValueAnimator.ofFloat(mKeyguardStatusBar.getAlpha(), 0f);
+ anim.addUpdateListener(mStatusBarAnimateAlphaListener);
+ anim.setStartDelay(mKeyguardStateController.isKeyguardFadingAway()
+ ? mKeyguardStateController.getKeyguardFadingAwayDelay() : 0);
+
+ long duration;
+ if (mKeyguardStateController.isKeyguardFadingAway()) {
+ duration = mKeyguardStateController.getShortenedFadingAwayDuration();
+ } else {
+ duration = StackStateAnimator.ANIMATION_DURATION_STANDARD;
+ }
+ anim.setDuration(duration);
+
+ anim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+ anim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAnimateKeyguardStatusBarInvisibleEndRunnable.run();
+ }
+ });
+ anim.start();
+ }
+
+ private final ValueAnimator.AnimatorUpdateListener
+ mStatusBarAnimateAlphaListener =
+ new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ mKeyguardStatusBarAnimateAlpha = (float) animation.getAnimatedValue();
+ updateHeaderKeyguardAlpha();
+ }
+ };
+
+ private void animateKeyguardStatusBarIn(long duration) {
+ mKeyguardStatusBar.setVisibility(View.VISIBLE);
+ mKeyguardStatusBar.setAlpha(0f);
+ ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
+ anim.addUpdateListener(mStatusBarAnimateAlphaListener);
+ anim.setDuration(duration);
+ anim.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+ anim.start();
+ }
+
+ private final Runnable mAnimateKeyguardBottomAreaInvisibleEndRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mKeyguardBottomArea.setVisibility(View.GONE);
+ }
+ };
+
+ private void setKeyguardBottomAreaVisibility(int statusBarState, boolean goingToFullShade) {
+ mKeyguardBottomArea.animate().cancel();
+ if (goingToFullShade) {
+ mKeyguardBottomArea.animate().alpha(0f).setStartDelay(
+ mKeyguardStateController.getKeyguardFadingAwayDelay()).setDuration(
+ mKeyguardStateController.getShortenedFadingAwayDuration()).setInterpolator(
+ Interpolators.ALPHA_OUT).withEndAction(
+ mAnimateKeyguardBottomAreaInvisibleEndRunnable).start();
+ } else if (statusBarState == StatusBarState.KEYGUARD
+ || statusBarState == StatusBarState.SHADE_LOCKED) {
+ mKeyguardBottomArea.setVisibility(View.VISIBLE);
+ mKeyguardBottomArea.setAlpha(1f);
+ } else {
+ mKeyguardBottomArea.setVisibility(View.GONE);
+ }
+ }
+
+ private void setKeyguardStatusViewVisibility(int statusBarState, boolean keyguardFadingAway,
+ boolean goingToFullShade) {
+ mKeyguardStatusView.animate().cancel();
+ mKeyguardStatusViewAnimating = false;
+ if ((!keyguardFadingAway && mBarState == StatusBarState.KEYGUARD
+ && statusBarState != StatusBarState.KEYGUARD) || goingToFullShade) {
+ mKeyguardStatusViewAnimating = true;
+ mKeyguardStatusView.animate().alpha(0f).setStartDelay(0).setDuration(
+ 160).setInterpolator(Interpolators.ALPHA_OUT).withEndAction(
+ mAnimateKeyguardStatusViewGoneEndRunnable);
+ if (keyguardFadingAway) {
+ mKeyguardStatusView.animate().setStartDelay(
+ mKeyguardStateController.getKeyguardFadingAwayDelay()).setDuration(
+ mKeyguardStateController.getShortenedFadingAwayDuration()).start();
+ }
+ } else if (mBarState == StatusBarState.SHADE_LOCKED
+ && statusBarState == StatusBarState.KEYGUARD) {
+ mKeyguardStatusView.setVisibility(View.VISIBLE);
+ mKeyguardStatusViewAnimating = true;
+ mKeyguardStatusView.setAlpha(0f);
+ mKeyguardStatusView.animate().alpha(1f).setStartDelay(0).setDuration(
+ 320).setInterpolator(Interpolators.ALPHA_IN).withEndAction(
+ mAnimateKeyguardStatusViewVisibleEndRunnable);
+ } else if (statusBarState == StatusBarState.KEYGUARD) {
+ if (keyguardFadingAway) {
+ mKeyguardStatusViewAnimating = true;
+ mKeyguardStatusView.animate().alpha(0).translationYBy(
+ -getHeight() * 0.05f).setInterpolator(
+ Interpolators.FAST_OUT_LINEAR_IN).setDuration(125).setStartDelay(
+ 0).withEndAction(mAnimateKeyguardStatusViewInvisibleEndRunnable).start();
+ } else {
+ mKeyguardStatusView.setVisibility(View.VISIBLE);
+ mKeyguardStatusView.setAlpha(1f);
+ }
+ } else {
+ mKeyguardStatusView.setVisibility(View.GONE);
+ mKeyguardStatusView.setAlpha(1f);
+ }
+ }
+
+ private void updateQsState() {
+ mNotificationStackScroller.setQsExpanded(mQsExpanded);
+ mNotificationStackScroller.setScrollingEnabled(
+ mBarState != StatusBarState.KEYGUARD && (!mQsExpanded
+ || mQsExpansionFromOverscroll));
+ updateEmptyShadeView();
+ if (mNPVPluginManager != null) {
+ mNPVPluginManager.changeVisibility(
+ (mBarState != StatusBarState.KEYGUARD) ? View.VISIBLE : View.INVISIBLE);
+ }
+ mQsNavbarScrim.setVisibility(
+ mBarState == StatusBarState.SHADE && mQsExpanded && !mStackScrollerOverscrolling
+ && mQsScrimEnabled ? View.VISIBLE : View.INVISIBLE);
+ if (mKeyguardUserSwitcher != null && mQsExpanded && !mStackScrollerOverscrolling) {
+ mKeyguardUserSwitcher.hideIfNotSimple(true /* animate */);
+ }
+ if (mQs == null) return;
+ mQs.setExpanded(mQsExpanded);
+ }
+
+ private void setQsExpansion(float height) {
+ height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight);
+ mQsFullyExpanded = height == mQsMaxExpansionHeight && mQsMaxExpansionHeight != 0;
+ if (height > mQsMinExpansionHeight && !mQsExpanded && !mStackScrollerOverscrolling
+ && !mDozing) {
+ setQsExpanded(true);
+ } else if (height <= mQsMinExpansionHeight && mQsExpanded) {
+ setQsExpanded(false);
+ }
+ mQsExpansionHeight = height;
+ updateQsExpansion();
+ requestScrollerTopPaddingUpdate(false /* animate */);
+ updateHeaderKeyguardAlpha();
+ if (mBarState == StatusBarState.SHADE_LOCKED || mBarState == StatusBarState.KEYGUARD) {
+ updateKeyguardBottomAreaAlpha();
+ updateBigClockAlpha();
+ }
+ if (mBarState == StatusBarState.SHADE && mQsExpanded && !mStackScrollerOverscrolling
+ && mQsScrimEnabled) {
+ mQsNavbarScrim.setAlpha(getQsExpansionFraction());
+ }
+
+ if (mAccessibilityManager.isEnabled()) {
+ mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
+ }
+
+ if (!mFalsingManager.isUnlockingDisabled() && mQsFullyExpanded
+ && mFalsingManager.shouldEnforceBouncer()) {
+ mStatusBar.executeRunnableDismissingKeyguard(null, null /* cancelAction */,
+ false /* dismissShade */, true /* afterKeyguardGone */, false /* deferred */);
+ }
+ for (int i = 0; i < mExpansionListeners.size(); i++) {
+ mExpansionListeners.get(i).onQsExpansionChanged(
+ mQsMaxExpansionHeight != 0 ? mQsExpansionHeight / mQsMaxExpansionHeight : 0);
+ }
+ if (DEBUG) {
+ mView.invalidate();
+ }
+ }
+
+ protected void updateQsExpansion() {
+ if (mQs == null) return;
+ float qsExpansionFraction = getQsExpansionFraction();
+ mQs.setQsExpansion(qsExpansionFraction, getHeaderTranslation());
+ int heightDiff = mQs.getDesiredHeight() - mQs.getQsMinExpansionHeight();
+ if (mNPVPluginManager != null) {
+ mNPVPluginManager.setExpansion(qsExpansionFraction, getHeaderTranslation(), heightDiff);
+ }
+ mNotificationStackScroller.setQsExpansionFraction(qsExpansionFraction);
+ }
+
+ private String determineAccessibilityPaneTitle() {
+ if (mQs != null && mQs.isCustomizing()) {
+ return mResources.getString(R.string.accessibility_desc_quick_settings_edit);
+ } else if (mQsExpansionHeight != 0.0f && mQsFullyExpanded) {
+ // Upon initialisation when we are not layouted yet we don't want to announce that we
+ // are fully expanded, hence the != 0.0f check.
+ return mResources.getString(R.string.accessibility_desc_quick_settings);
+ } else if (mBarState == StatusBarState.KEYGUARD) {
+ return mResources.getString(R.string.accessibility_desc_lock_screen);
+ } else {
+ return mResources.getString(R.string.accessibility_desc_notification_shade);
+ }
+ }
+
+ private float calculateQsTopPadding() {
+ if (mKeyguardShowing && (mQsExpandImmediate
+ || mIsExpanding && mQsExpandedWhenExpandingStarted)) {
+
+ // Either QS pushes the notifications down when fully expanded, or QS is fully above the
+ // notifications (mostly on tablets). maxNotificationPadding denotes the normal top
+ // padding on Keyguard, maxQsPadding denotes the top padding from the quick settings
+ // panel. We need to take the maximum and linearly interpolate with the panel expansion
+ // for a nice motion.
+ int maxNotificationPadding = getKeyguardNotificationStaticPadding();
+ int maxQsPadding = mQsMaxExpansionHeight + mQsNotificationTopPadding;
+ int max = mBarState == StatusBarState.KEYGUARD ? Math.max(
+ maxNotificationPadding, maxQsPadding) : maxQsPadding;
+ return (int) MathUtils.lerp((float) mQsMinExpansionHeight, (float) max,
+ getExpandedFraction());
+ } else if (mQsSizeChangeAnimator != null) {
+ return Math.max(
+ (int) mQsSizeChangeAnimator.getAnimatedValue(),
+ getKeyguardNotificationStaticPadding());
+ } else if (mKeyguardShowing) {
+ // We can only do the smoother transition on Keyguard when we also are not collapsing
+ // from a scrolled quick settings.
+ return MathUtils.lerp((float) getKeyguardNotificationStaticPadding(),
+ (float) (mQsMaxExpansionHeight + mQsNotificationTopPadding),
+ getQsExpansionFraction());
+ } else {
+ return mQsExpansionHeight + mQsNotificationTopPadding;
+ }
+ }
+
+ /**
+ * @return the topPadding of notifications when on keyguard not respecting quick settings
+ * expansion
+ */
+ private int getKeyguardNotificationStaticPadding() {
+ if (!mKeyguardShowing) {
+ return 0;
+ }
+ if (!mKeyguardBypassController.getBypassEnabled()) {
+ return mClockPositionResult.stackScrollerPadding;
+ }
+ int collapsedPosition = mHeadsUpInset;
+ if (!mNotificationStackScroller.isPulseExpanding()) {
+ return collapsedPosition;
+ } else {
+ int expandedPosition = mClockPositionResult.stackScrollerPadding;
+ return (int) MathUtils.lerp(collapsedPosition, expandedPosition,
+ mNotificationStackScroller.calculateAppearFractionBypass());
+ }
+ }
+
+
+ protected void requestScrollerTopPaddingUpdate(boolean animate) {
+ mNotificationStackScroller.updateTopPadding(calculateQsTopPadding(), animate);
+ if (mKeyguardShowing && mKeyguardBypassController.getBypassEnabled()) {
+ // update the position of the header
+ updateQsExpansion();
+ }
+ }
+
+
+ private void updateQSPulseExpansion() {
+ if (mQs != null) {
+ mQs.setShowCollapsedOnKeyguard(
+ mKeyguardShowing && mKeyguardBypassController.getBypassEnabled()
+ && mNotificationStackScroller.isPulseExpanding());
+ }
+ }
+
+ private void trackMovement(MotionEvent event) {
+ if (mQsVelocityTracker != null) mQsVelocityTracker.addMovement(event);
+ }
+
+ private void initVelocityTracker() {
+ if (mQsVelocityTracker != null) {
+ mQsVelocityTracker.recycle();
+ }
+ mQsVelocityTracker = VelocityTracker.obtain();
+ }
+
+ private float getCurrentQSVelocity() {
+ if (mQsVelocityTracker == null) {
+ return 0;
+ }
+ mQsVelocityTracker.computeCurrentVelocity(1000);
+ return mQsVelocityTracker.getYVelocity();
+ }
+
+ private void cancelQsAnimation() {
+ if (mQsExpansionAnimator != null) {
+ mQsExpansionAnimator.cancel();
+ }
+ }
+
+ /**
+ * @see #flingSettings(float, int, Runnable, boolean)
+ */
+ public void flingSettings(float vel, int type) {
+ flingSettings(vel, type, null, false /* isClick */);
+ }
+
+ /**
+ * Animates QS or QQS as if the user had swiped up or down.
+ *
+ * @param vel Finger velocity or 0 when not initiated by touch events.
+ * @param type Either {@link #FLING_EXPAND}, {@link #FLING_COLLAPSE} or {@link
+ * #FLING_HIDE}.
+ * @param onFinishRunnable Runnable to be executed at the end of animation.
+ * @param isClick If originated by click (different interpolator and duration.)
+ */
+ protected void flingSettings(float vel, int type, final Runnable onFinishRunnable,
+ boolean isClick) {
+ float target;
+ switch (type) {
+ case FLING_EXPAND:
+ target = mQsMaxExpansionHeight;
+ break;
+ case FLING_COLLAPSE:
+ target = mQsMinExpansionHeight;
+ break;
+ case FLING_HIDE:
+ default:
+ target = 0;
+ }
+ if (target == mQsExpansionHeight) {
+ if (onFinishRunnable != null) {
+ onFinishRunnable.run();
+ }
+ return;
+ }
+
+ // If we move in the opposite direction, reset velocity and use a different duration.
+ boolean oppositeDirection = false;
+ boolean expanding = type == FLING_EXPAND;
+ if (vel > 0 && !expanding || vel < 0 && expanding) {
+ vel = 0;
+ oppositeDirection = true;
+ }
+ ValueAnimator animator = ValueAnimator.ofFloat(mQsExpansionHeight, target);
+ if (isClick) {
+ animator.setInterpolator(Interpolators.TOUCH_RESPONSE);
+ animator.setDuration(368);
+ } else {
+ mFlingAnimationUtils.apply(animator, mQsExpansionHeight, target, vel);
+ }
+ if (oppositeDirection) {
+ animator.setDuration(350);
+ }
+ animator.addUpdateListener(animation -> {
+ setQsExpansion((Float) animation.getAnimatedValue());
+ });
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mNotificationStackScroller.resetCheckSnoozeLeavebehind();
+ mQsExpansionAnimator = null;
+ if (onFinishRunnable != null) {
+ onFinishRunnable.run();
+ }
+ }
+ });
+ animator.start();
+ mQsExpansionAnimator = animator;
+ mQsAnimatorExpand = expanding;
+ }
+
+ /**
+ * @return Whether we should intercept a gesture to open Quick Settings.
+ */
+ private boolean shouldQuickSettingsIntercept(float x, float y, float yDiff) {
+ if (!mQsExpansionEnabled || mCollapsedOnDown || (mKeyguardShowing
+ && mKeyguardBypassController.getBypassEnabled())) {
+ return false;
+ }
+ View header = mKeyguardShowing || mQs == null ? mKeyguardStatusBar : mQs.getHeader();
+ final boolean
+ onHeader =
+ x >= mQsFrame.getX() && x <= mQsFrame.getX() + mQsFrame.getWidth()
+ && y >= header.getTop() && y <= header.getBottom();
+ if (mQsExpanded) {
+ return onHeader || (yDiff < 0 && isInQsArea(x, y));
+ } else {
+ return onHeader;
+ }
+ }
+
+ @Override
+ protected boolean isScrolledToBottom() {
+ if (!isInSettings()) {
+ return mBarState == StatusBarState.KEYGUARD
+ || mNotificationStackScroller.isScrolledToBottom();
+ } else {
+ return true;
+ }
+ }
+
+ @Override
+ protected int getMaxPanelHeight() {
+ if (mKeyguardBypassController.getBypassEnabled() && mBarState == StatusBarState.KEYGUARD) {
+ return getMaxPanelHeightBypass();
+ } else {
+ return getMaxPanelHeightNonBypass();
+ }
+ }
+
+ private int getMaxPanelHeightNonBypass() {
+ int min = mStatusBarMinHeight;
+ if (!(mBarState == StatusBarState.KEYGUARD)
+ && mNotificationStackScroller.getNotGoneChildCount() == 0) {
+ int minHeight = (int) (mQsMinExpansionHeight + getOverExpansionAmount());
+ min = Math.max(min, minHeight);
+ }
+ int maxHeight;
+ if (mQsExpandImmediate || mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted
+ || mPulsing) {
+ maxHeight = calculatePanelHeightQsExpanded();
+ } else {
+ maxHeight = calculatePanelHeightShade();
+ }
+ maxHeight = Math.max(maxHeight, min);
+ return maxHeight;
+ }
+
+ private int getMaxPanelHeightBypass() {
+ int position =
+ mClockPositionAlgorithm.getExpandedClockPosition()
+ + mKeyguardStatusView.getHeight();
+ if (mNotificationStackScroller.getVisibleNotificationCount() != 0) {
+ position += mShelfHeight / 2.0f + mDarkIconSize / 2.0f;
+ }
+ return position;
+ }
+
+ public boolean isInSettings() {
+ return mQsExpanded;
+ }
+
+ public boolean isExpanding() {
+ return mIsExpanding;
+ }
+
+ @Override
+ protected void onHeightUpdated(float expandedHeight) {
+ if (!mQsExpanded || mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted) {
+ // Updating the clock position will set the top padding which might
+ // trigger a new panel height and re-position the clock.
+ // This is a circular dependency and should be avoided, otherwise we'll have
+ // a stack overflow.
+ if (mStackScrollerMeasuringPass > 2) {
+ if (DEBUG) Log.d(TAG, "Unstable notification panel height. Aborting.");
+ } else {
+ positionClockAndNotifications();
+ }
+ }
+ if (mQsExpandImmediate || mQsExpanded && !mQsTracking && mQsExpansionAnimator == null
+ && !mQsExpansionFromOverscroll) {
+ float t;
+ if (mKeyguardShowing) {
+
+ // On Keyguard, interpolate the QS expansion linearly to the panel expansion
+ t = expandedHeight / (getMaxPanelHeight());
+ } else {
+ // In Shade, interpolate linearly such that QS is closed whenever panel height is
+ // minimum QS expansion + minStackHeight
+ float
+ panelHeightQsCollapsed =
+ mNotificationStackScroller.getIntrinsicPadding()
+ + mNotificationStackScroller.getLayoutMinHeight();
+ float panelHeightQsExpanded = calculatePanelHeightQsExpanded();
+ t =
+ (expandedHeight - panelHeightQsCollapsed) / (panelHeightQsExpanded
+ - panelHeightQsCollapsed);
+ }
+ float
+ targetHeight =
+ mQsMinExpansionHeight + t * (mQsMaxExpansionHeight - mQsMinExpansionHeight);
+ setQsExpansion(targetHeight);
+ mHomeControlsLayout.setTranslationY(targetHeight);
+ }
+ updateExpandedHeight(expandedHeight);
+ updateHeader();
+ updateNotificationTranslucency();
+ updatePanelExpanded();
+ updateGestureExclusionRect();
+ if (DEBUG) {
+ mView.invalidate();
+ }
+ }
+
+ private void updatePanelExpanded() {
+ boolean isExpanded = !isFullyCollapsed() || mExpectingSynthesizedDown;
+ if (mPanelExpanded != isExpanded) {
+ mHeadsUpManager.setIsPanelExpanded(isExpanded);
+ mStatusBar.setPanelExpanded(isExpanded);
+ mPanelExpanded = isExpanded;
+ }
+ }
+
+ private int calculatePanelHeightShade() {
+ int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin();
+ int maxHeight = mNotificationStackScroller.getHeight() - emptyBottomMargin;
+ maxHeight += mNotificationStackScroller.getTopPaddingOverflow();
+
+ if (mBarState == StatusBarState.KEYGUARD) {
+ int
+ minKeyguardPanelBottom =
+ mClockPositionAlgorithm.getExpandedClockPosition()
+ + mKeyguardStatusView.getHeight()
+ + mNotificationStackScroller.getIntrinsicContentHeight();
+ return Math.max(maxHeight, minKeyguardPanelBottom);
+ } else {
+ return maxHeight;
+ }
+ }
+
+ private int calculatePanelHeightQsExpanded() {
+ float
+ notificationHeight =
+ mNotificationStackScroller.getHeight()
+ - mNotificationStackScroller.getEmptyBottomMargin()
+ - mNotificationStackScroller.getTopPadding();
+
+ // When only empty shade view is visible in QS collapsed state, simulate that we would have
+ // it in expanded QS state as well so we don't run into troubles when fading the view in/out
+ // and expanding/collapsing the whole panel from/to quick settings.
+ if (mNotificationStackScroller.getNotGoneChildCount() == 0 && mShowEmptyShadeView) {
+ notificationHeight = mNotificationStackScroller.getEmptyShadeViewHeight();
+ }
+ int maxQsHeight = mQsMaxExpansionHeight;
+
+ if (mKeyguardShowing) {
+ maxQsHeight += mQsNotificationTopPadding;
+ }
+
+ // If an animation is changing the size of the QS panel, take the animated value.
+ if (mQsSizeChangeAnimator != null) {
+ maxQsHeight = (int) mQsSizeChangeAnimator.getAnimatedValue();
+ }
+ float totalHeight = Math.max(maxQsHeight,
+ mBarState == StatusBarState.KEYGUARD ? mClockPositionResult.stackScrollerPadding
+ : 0) + notificationHeight
+ + mNotificationStackScroller.getTopPaddingOverflow();
+ if (totalHeight > mNotificationStackScroller.getHeight()) {
+ float
+ fullyCollapsedHeight =
+ maxQsHeight + mNotificationStackScroller.getLayoutMinHeight();
+ totalHeight = Math.max(fullyCollapsedHeight, mNotificationStackScroller.getHeight());
+ }
+ return (int) totalHeight;
+ }
+
+ private void updateNotificationTranslucency() {
+ float alpha = 1f;
+ if (mClosingWithAlphaFadeOut && !mExpandingFromHeadsUp
+ && !mHeadsUpManager.hasPinnedHeadsUp()) {
+ alpha = getFadeoutAlpha();
+ }
+ if (mBarState == StatusBarState.KEYGUARD && !mHintAnimationRunning
+ && !mKeyguardBypassController.getBypassEnabled()) {
+ alpha *= mClockPositionResult.clockAlpha;
+ }
+ mNotificationStackScroller.setAlpha(alpha);
+ }
+
+ private float getFadeoutAlpha() {
+ float alpha;
+ if (mQsMinExpansionHeight == 0) {
+ return 1.0f;
+ }
+ alpha = getExpandedHeight() / mQsMinExpansionHeight;
+ alpha = Math.max(0, Math.min(alpha, 1));
+ alpha = (float) Math.pow(alpha, 0.75);
+ return alpha;
+ }
+
+ @Override
+ protected float getOverExpansionAmount() {
+ return mNotificationStackScroller.getCurrentOverScrollAmount(true /* top */);
+ }
+
+ @Override
+ protected float getOverExpansionPixels() {
+ return mNotificationStackScroller.getCurrentOverScrolledPixels(true /* top */);
+ }
+
+ /**
+ * Hides the header when notifications are colliding with it.
+ */
+ private void updateHeader() {
+ if (mBarState == StatusBarState.KEYGUARD) {
+ updateHeaderKeyguardAlpha();
+ }
+ updateQsExpansion();
+ }
+
+ protected float getHeaderTranslation() {
+ if (mBarState == StatusBarState.KEYGUARD && !mKeyguardBypassController.getBypassEnabled()) {
+ return -mQs.getQsMinExpansionHeight();
+ }
+ float appearAmount = mNotificationStackScroller.calculateAppearFraction(mExpandedHeight);
+ float startHeight = -mQsExpansionHeight;
+ if (mKeyguardBypassController.getBypassEnabled() && isOnKeyguard()
+ && mNotificationStackScroller.isPulseExpanding()) {
+ if (!mPulseExpansionHandler.isExpanding()
+ && !mPulseExpansionHandler.getLeavingLockscreen()) {
+ // If we aborted the expansion we need to make sure the header doesn't reappear
+ // again after the header has animated away
+ appearAmount = 0;
+ } else {
+ appearAmount = mNotificationStackScroller.calculateAppearFractionBypass();
+ }
+ startHeight = -mQs.getQsMinExpansionHeight();
+ if (mNPVPluginManager != null) startHeight -= mNPVPluginManager.getHeight();
+ }
+ float translation = MathUtils.lerp(startHeight, 0, Math.min(1.0f, appearAmount))
+ + mExpandOffset;
+ return Math.min(0, translation);
+ }
+
+ /**
+ * @return the alpha to be used to fade out the contents on Keyguard (status bar, bottom area)
+ * during swiping up
+ */
+ private float getKeyguardContentsAlpha() {
+ float alpha;
+ if (mBarState == StatusBarState.KEYGUARD) {
+
+ // When on Keyguard, we hide the header as soon as we expanded close enough to the
+ // header
+ alpha =
+ getExpandedHeight() / (mKeyguardStatusBar.getHeight()
+ + mNotificationsHeaderCollideDistance);
+ } else {
+
+ // In SHADE_LOCKED, the top card is already really close to the header. Hide it as
+ // soon as we start translating the stack.
+ alpha = getExpandedHeight() / mKeyguardStatusBar.getHeight();
+ }
+ alpha = MathUtils.saturate(alpha);
+ alpha = (float) Math.pow(alpha, 0.75);
+ return alpha;
+ }
+
+ private void updateHeaderKeyguardAlpha() {
+ if (!mKeyguardShowing) {
+ return;
+ }
+ float alphaQsExpansion = 1 - Math.min(1, getQsExpansionFraction() * 2);
+ float newAlpha = Math.min(getKeyguardContentsAlpha(), alphaQsExpansion)
+ * mKeyguardStatusBarAnimateAlpha;
+ newAlpha *= 1.0f - mKeyguardHeadsUpShowingAmount;
+ mKeyguardStatusBar.setAlpha(newAlpha);
+ boolean
+ hideForBypass =
+ mFirstBypassAttempt && mUpdateMonitor.shouldListenForFace()
+ || mDelayShowingKeyguardStatusBar;
+ mKeyguardStatusBar.setVisibility(
+ newAlpha != 0f && !mDozing && !hideForBypass ? View.VISIBLE : View.INVISIBLE);
+ }
+
+ private void updateKeyguardBottomAreaAlpha() {
+ // There are two possible panel expansion behaviors:
+ // • User dragging up to unlock: we want to fade out as quick as possible
+ // (ALPHA_EXPANSION_THRESHOLD) to avoid seeing the bouncer over the bottom area.
+ // • User tapping on lock screen: bouncer won't be visible but panel expansion will
+ // change due to "unlock hint animation." In this case, fading out the bottom area
+ // would also hide the message that says "swipe to unlock," we don't want to do that.
+ float expansionAlpha = MathUtils.map(
+ isUnlockHintRunning() ? 0 : KeyguardBouncer.ALPHA_EXPANSION_THRESHOLD, 1f, 0f, 1f,
+ getExpandedFraction());
+ float alpha = Math.min(expansionAlpha, 1 - getQsExpansionFraction());
+ alpha *= mBottomAreaShadeAlpha;
+ mKeyguardBottomArea.setAffordanceAlpha(alpha);
+ mKeyguardBottomArea.setImportantForAccessibility(
+ alpha == 0f ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ : View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+ View ambientIndicationContainer = mStatusBar.getAmbientIndicationContainer();
+ if (ambientIndicationContainer != null) {
+ ambientIndicationContainer.setAlpha(alpha);
+ }
+ }
+
+ /**
+ * Custom clock fades away when user drags up to unlock or pulls down quick settings.
+ *
+ * Updates alpha of custom clock to match the alpha of the KeyguardBottomArea. See
+ * {@link #updateKeyguardBottomAreaAlpha}.
+ */
+ private void updateBigClockAlpha() {
+ float expansionAlpha = MathUtils.map(
+ isUnlockHintRunning() ? 0 : KeyguardBouncer.ALPHA_EXPANSION_THRESHOLD, 1f, 0f, 1f,
+ getExpandedFraction());
+ float alpha = Math.min(expansionAlpha, 1 - getQsExpansionFraction());
+ mBigClockContainer.setAlpha(alpha);
+ }
+
+ @Override
+ protected void onExpandingStarted() {
+ super.onExpandingStarted();
+ mNotificationStackScroller.onExpansionStarted();
+ mIsExpanding = true;
+ mQsExpandedWhenExpandingStarted = mQsFullyExpanded;
+ if (mQsExpanded) {
+ onQsExpansionStarted();
+ }
+ // Since there are QS tiles in the header now, we need to make sure we start listening
+ // immediately so they can be up to date.
+ if (mQs == null) return;
+ mQs.setHeaderListening(true);
+ }
+
+ @Override
+ protected void onExpandingFinished() {
+ super.onExpandingFinished();
+ mNotificationStackScroller.onExpansionStopped();
+ mHeadsUpManager.onExpandingFinished();
+ mIsExpanding = false;
+ if (isFullyCollapsed()) {
+ DejankUtils.postAfterTraversal(new Runnable() {
+ @Override
+ public void run() {
+ setListening(false);
+ }
+ });
+
+ // Workaround b/22639032: Make sure we invalidate something because else RenderThread
+ // thinks we are actually drawing a frame put in reality we don't, so RT doesn't go
+ // ahead with rendering and we jank.
+ mView.postOnAnimation(new Runnable() {
+ @Override
+ public void run() {
+ mView.getParent().invalidateChild(mView, M_DUMMY_DIRTY_RECT);
+ }
+ });
+ } else {
+ setListening(true);
+ }
+ mQsExpandImmediate = false;
+ mNotificationStackScroller.setShouldShowShelfOnly(false);
+ mTwoFingerQsExpandPossible = false;
+ notifyListenersTrackingHeadsUp(null);
+ mExpandingFromHeadsUp = false;
+ setPanelScrimMinFraction(0.0f);
+ }
+
+ private void notifyListenersTrackingHeadsUp(ExpandableNotificationRow pickedChild) {
+ for (int i = 0; i < mTrackingHeadsUpListeners.size(); i++) {
+ Consumer<ExpandableNotificationRow> listener = mTrackingHeadsUpListeners.get(i);
+ listener.accept(pickedChild);
+ }
+ }
+
+ private void setListening(boolean listening) {
+ mKeyguardStatusBar.setListening(listening);
+ if (mQs == null) return;
+ mQs.setListening(listening);
+ if (mNPVPluginManager != null) mNPVPluginManager.setListening(listening);
+ }
+
+ @Override
+ public void expand(boolean animate) {
+ super.expand(animate);
+ setListening(true);
+ }
+
+ @Override
+ protected void setOverExpansion(float overExpansion, boolean isPixels) {
+ if (mConflictingQsExpansionGesture || mQsExpandImmediate) {
+ return;
+ }
+ if (mBarState != StatusBarState.KEYGUARD) {
+ mNotificationStackScroller.setOnHeightChangedListener(null);
+ if (isPixels) {
+ mNotificationStackScroller.setOverScrolledPixels(overExpansion, true /* onTop */,
+ false /* animate */);
+ } else {
+ mNotificationStackScroller.setOverScrollAmount(overExpansion, true /* onTop */,
+ false /* animate */);
+ }
+ mNotificationStackScroller.setOnHeightChangedListener(mOnHeightChangedListener);
+ }
+ }
+
+ @Override
+ protected void onTrackingStarted() {
+ mFalsingManager.onTrackingStarted(!mKeyguardStateController.canDismissLockScreen());
+ super.onTrackingStarted();
+ if (mQsFullyExpanded) {
+ mQsExpandImmediate = true;
+ mNotificationStackScroller.setShouldShowShelfOnly(true);
+ }
+ if (mBarState == StatusBarState.KEYGUARD || mBarState == StatusBarState.SHADE_LOCKED) {
+ mAffordanceHelper.animateHideLeftRightIcon();
+ }
+ mNotificationStackScroller.onPanelTrackingStarted();
+ }
+
+ @Override
+ protected void onTrackingStopped(boolean expand) {
+ mFalsingManager.onTrackingStopped();
+ super.onTrackingStopped(expand);
+ if (expand) {
+ mNotificationStackScroller.setOverScrolledPixels(0.0f, true /* onTop */,
+ true /* animate */);
+ }
+ mNotificationStackScroller.onPanelTrackingStopped();
+ if (expand && (mBarState == StatusBarState.KEYGUARD
+ || mBarState == StatusBarState.SHADE_LOCKED)) {
+ if (!mHintAnimationRunning) {
+ mAffordanceHelper.reset(true);
+ }
+ }
+ }
+
+ private void updateMaxHeadsUpTranslation() {
+ mNotificationStackScroller.setHeadsUpBoundaries(getHeight(), mNavigationBarBottomHeight);
+ }
+
+ @Override
+ protected void startUnlockHintAnimation() {
+ if (mPowerManager.isPowerSaveMode()) {
+ onUnlockHintStarted();
+ onUnlockHintFinished();
+ return;
+ }
+ super.startUnlockHintAnimation();
+ }
+
+ @Override
+ protected void onUnlockHintFinished() {
+ super.onUnlockHintFinished();
+ mNotificationStackScroller.setUnlockHintRunning(false);
+ }
+
+ @Override
+ protected void onUnlockHintStarted() {
+ super.onUnlockHintStarted();
+ mNotificationStackScroller.setUnlockHintRunning(true);
+ }
+
+ @Override
+ protected float getPeekHeight() {
+ if (mNotificationStackScroller.getNotGoneChildCount() > 0) {
+ return mNotificationStackScroller.getPeekHeight();
+ } else {
+ return mQsMinExpansionHeight;
+ }
+ }
+
+ @Override
+ protected boolean shouldUseDismissingAnimation() {
+ return mBarState != StatusBarState.SHADE && (mKeyguardStateController.canDismissLockScreen()
+ || !isTracking());
+ }
+
+ @Override
+ protected boolean fullyExpandedClearAllVisible() {
+ return mNotificationStackScroller.isFooterViewNotGone()
+ && mNotificationStackScroller.isScrolledToBottom() && !mQsExpandImmediate;
+ }
+
+ @Override
+ protected boolean isClearAllVisible() {
+ return mNotificationStackScroller.isFooterViewContentVisible();
+ }
+
+ @Override
+ protected int getClearAllHeight() {
+ return mNotificationStackScroller.getFooterViewHeight();
+ }
+
+ @Override
+ protected boolean isTrackingBlocked() {
+ return mConflictingQsExpansionGesture && mQsExpanded || mBlockingExpansionForCurrentTouch;
+ }
+
+ public boolean isQsExpanded() {
+ return mQsExpanded;
+ }
+
+ public boolean isQsDetailShowing() {
+ return mQs.isShowingDetail();
+ }
+
+ public void closeQsDetail() {
+ mQs.closeDetail();
+ }
+
+ public boolean isLaunchTransitionFinished() {
+ return mIsLaunchTransitionFinished;
+ }
+
+ public boolean isLaunchTransitionRunning() {
+ return mIsLaunchTransitionRunning;
+ }
+
+ public void setLaunchTransitionEndRunnable(Runnable r) {
+ mLaunchAnimationEndRunnable = r;
+ }
+
+ private void updateDozingVisibilities(boolean animate) {
+ mKeyguardBottomArea.setDozing(mDozing, animate);
+ if (!mDozing && animate) {
+ animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ }
+ }
+
+ @Override
+ public boolean isDozing() {
+ return mDozing;
+ }
+
+ public void showEmptyShadeView(boolean emptyShadeViewVisible) {
+ mShowEmptyShadeView = emptyShadeViewVisible;
+ updateEmptyShadeView();
+ }
+
+ private void updateEmptyShadeView() {
+ // Hide "No notifications" in QS.
+ mNotificationStackScroller.updateEmptyShadeView(mShowEmptyShadeView && !mQsExpanded);
+ }
+
+ public void setQsScrimEnabled(boolean qsScrimEnabled) {
+ boolean changed = mQsScrimEnabled != qsScrimEnabled;
+ mQsScrimEnabled = qsScrimEnabled;
+ if (changed) {
+ updateQsState();
+ }
+ }
+
+ public void setKeyguardUserSwitcher(KeyguardUserSwitcher keyguardUserSwitcher) {
+ mKeyguardUserSwitcher = keyguardUserSwitcher;
+ }
+
+ public void onScreenTurningOn() {
+ mKeyguardStatusView.dozeTimeTick();
+ }
+
+ @Override
+ protected boolean onMiddleClicked() {
+ switch (mBarState) {
+ case StatusBarState.KEYGUARD:
+ if (!mDozingOnDown) {
+ if (mKeyguardBypassController.getBypassEnabled()) {
+ mUpdateMonitor.requestFaceAuth();
+ } else {
+ mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_HINT,
+ 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
+ startUnlockHintAnimation();
+ }
+ }
+ return true;
+ case StatusBarState.SHADE_LOCKED:
+ if (!mQsExpanded) {
+ mStatusBarStateController.setState(StatusBarState.KEYGUARD);
+ }
+ return true;
+ case StatusBarState.SHADE:
+
+ // This gets called in the middle of the touch handling, where the state is still
+ // that we are tracking the panel. Collapse the panel after this is done.
+ mView.post(mPostCollapseRunnable);
+ return false;
+ default:
+ return true;
+ }
+ }
+
+ public void setPanelAlpha(int alpha, boolean animate) {
+ if (mPanelAlpha != alpha) {
+ mPanelAlpha = alpha;
+ PropertyAnimator.setProperty(mView, mPanelAlphaAnimator, alpha, alpha == 255
+ ? mPanelAlphaInPropertiesAnimator : mPanelAlphaOutPropertiesAnimator,
+ animate);
+ }
+ }
+
+ public void setPanelAlphaEndAction(Runnable r) {
+ mPanelAlphaEndAction = r;
+ }
+
+ private void updateKeyguardStatusBarForHeadsUp() {
+ boolean
+ showingKeyguardHeadsUp =
+ mKeyguardShowing && mHeadsUpAppearanceController.shouldBeVisible();
+ if (mShowingKeyguardHeadsUp != showingKeyguardHeadsUp) {
+ mShowingKeyguardHeadsUp = showingKeyguardHeadsUp;
+ if (mKeyguardShowing) {
+ PropertyAnimator.setProperty(mView, KEYGUARD_HEADS_UP_SHOWING_AMOUNT,
+ showingKeyguardHeadsUp ? 1.0f : 0.0f, KEYGUARD_HUN_PROPERTIES,
+ true /* animate */);
+ } else {
+ PropertyAnimator.applyImmediately(mView, KEYGUARD_HEADS_UP_SHOWING_AMOUNT, 0.0f);
+ }
+ }
+ }
+
+ private void setKeyguardHeadsUpShowingAmount(float amount) {
+ mKeyguardHeadsUpShowingAmount = amount;
+ updateHeaderKeyguardAlpha();
+ }
+
+ private float getKeyguardHeadsUpShowingAmount() {
+ return mKeyguardHeadsUpShowingAmount;
+ }
+
+ public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
+ mHeadsUpAnimatingAway = headsUpAnimatingAway;
+ mNotificationStackScroller.setHeadsUpAnimatingAway(headsUpAnimatingAway);
+ updateHeadsUpVisibility();
+ }
+
+ private void updateHeadsUpVisibility() {
+ ((PhoneStatusBarView) mBar).setHeadsUpVisible(mHeadsUpAnimatingAway || mHeadsUpPinnedMode);
+ }
+
+ @Override
+ public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
+ super.setHeadsUpManager(headsUpManager);
+ mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager,
+ mNotificationStackScroller.getHeadsUpCallback(),
+ NotificationPanelViewController.this);
+ }
+
+ public void setTrackedHeadsUp(ExpandableNotificationRow pickedChild) {
+ if (pickedChild != null) {
+ notifyListenersTrackingHeadsUp(pickedChild);
+ mExpandingFromHeadsUp = true;
+ }
+ // otherwise we update the state when the expansion is finished
+ }
+
+ @Override
+ protected void onClosingFinished() {
+ super.onClosingFinished();
+ resetHorizontalPanelPosition();
+ setClosingWithAlphaFadeout(false);
+ }
+
+ private void setClosingWithAlphaFadeout(boolean closing) {
+ mClosingWithAlphaFadeOut = closing;
+ mNotificationStackScroller.forceNoOverlappingRendering(closing);
+ }
+
+ /**
+ * Updates the vertical position of the panel so it is positioned closer to the touch
+ * responsible for opening the panel.
+ *
+ * @param x the x-coordinate the touch event
+ */
+ protected void updateVerticalPanelPosition(float x) {
+ if (mNotificationStackScroller.getWidth() * 1.75f > mView.getWidth()) {
+ resetHorizontalPanelPosition();
+ return;
+ }
+ float leftMost = mPositionMinSideMargin + mNotificationStackScroller.getWidth() / 2;
+ float
+ rightMost =
+ mView.getWidth() - mPositionMinSideMargin
+ - mNotificationStackScroller.getWidth() / 2;
+ if (Math.abs(x - mView.getWidth() / 2) < mNotificationStackScroller.getWidth() / 4) {
+ x = mView.getWidth() / 2;
+ }
+ x = Math.min(rightMost, Math.max(leftMost, x));
+ float
+ center =
+ mNotificationStackScroller.getLeft() + mNotificationStackScroller.getWidth() / 2;
+ setHorizontalPanelTranslation(x - center);
+ }
+
+ private void resetHorizontalPanelPosition() {
+ setHorizontalPanelTranslation(0f);
+ }
+
+ protected void setHorizontalPanelTranslation(float translation) {
+ mNotificationStackScroller.setTranslationX(translation);
+ mQsFrame.setTranslationX(translation);
+ int size = mVerticalTranslationListener.size();
+ for (int i = 0; i < size; i++) {
+ mVerticalTranslationListener.get(i).run();
+ }
+ }
+
+ protected void updateExpandedHeight(float expandedHeight) {
+ if (mTracking) {
+ mNotificationStackScroller.setExpandingVelocity(getCurrentExpandVelocity());
+ }
+ if (mKeyguardBypassController.getBypassEnabled() && isOnKeyguard()) {
+ // The expandedHeight is always the full panel Height when bypassing
+ expandedHeight = getMaxPanelHeightNonBypass();
+ }
+ mNotificationStackScroller.setExpandedHeight(expandedHeight);
+ updateKeyguardBottomAreaAlpha();
+ updateBigClockAlpha();
+ updateStatusBarIcons();
+ }
+
+ /**
+ * @return whether the notifications are displayed full width and don't have any margins on
+ * the side.
+ */
+ public boolean isFullWidth() {
+ return mIsFullWidth;
+ }
+
+ private void updateStatusBarIcons() {
+ boolean
+ showIconsWhenExpanded =
+ (isPanelVisibleBecauseOfHeadsUp() || isFullWidth())
+ && getExpandedHeight() < getOpeningHeight();
+ boolean noVisibleNotifications = true;
+ if (showIconsWhenExpanded && noVisibleNotifications && isOnKeyguard()) {
+ showIconsWhenExpanded = false;
+ }
+ if (showIconsWhenExpanded != mShowIconsWhenExpanded) {
+ mShowIconsWhenExpanded = showIconsWhenExpanded;
+ mCommandQueue.recomputeDisableFlags(mDisplayId, false);
+ }
+ }
+
+ private boolean isOnKeyguard() {
+ return mBarState == StatusBarState.KEYGUARD;
+ }
+
+ public void setPanelScrimMinFraction(float minFraction) {
+ mBar.panelScrimMinFractionChanged(minFraction);
+ }
+
+ public void clearNotificationEffects() {
+ mStatusBar.clearNotificationEffects();
+ }
+
+ @Override
+ protected boolean isPanelVisibleBecauseOfHeadsUp() {
+ return (mHeadsUpManager.hasPinnedHeadsUp() || mHeadsUpAnimatingAway)
+ && mBarState == StatusBarState.SHADE;
+ }
+
+ public void launchCamera(boolean animate, int source) {
+ if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP) {
+ mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP;
+ } else if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE) {
+ mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_WIGGLE;
+ } else if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER) {
+ mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER;
+ } else {
+
+ // Default.
+ mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
+ }
+
+ // If we are launching it when we are occluded already we don't want it to animate,
+ // nor setting these flags, since the occluded state doesn't change anymore, hence it's
+ // never reset.
+ if (!isFullyCollapsed()) {
+ setLaunchingAffordance(true);
+ } else {
+ animate = false;
+ }
+ mAffordanceHasPreview = mKeyguardBottomArea.getRightPreview() != null;
+ mAffordanceHelper.launchAffordance(
+ animate, mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
+ }
+
+ public void onAffordanceLaunchEnded() {
+ setLaunchingAffordance(false);
+ }
+
+ /**
+ * Set whether we are currently launching an affordance. This is currently only set when
+ * launched via a camera gesture.
+ */
+ private void setLaunchingAffordance(boolean launchingAffordance) {
+ mLaunchingAffordance = launchingAffordance;
+ mKeyguardAffordanceHelperCallback.getLeftIcon().setLaunchingAffordance(launchingAffordance);
+ mKeyguardAffordanceHelperCallback.getRightIcon().setLaunchingAffordance(
+ launchingAffordance);
+ mKeyguardBypassController.setLaunchingAffordance(launchingAffordance);
+ if (mAffordanceLaunchListener != null) {
+ mAffordanceLaunchListener.accept(launchingAffordance);
+ }
+ }
+
+ /**
+ * Return true when a bottom affordance is launching an occluded activity with a splash screen.
+ */
+ public boolean isLaunchingAffordanceWithPreview() {
+ return mLaunchingAffordance && mAffordanceHasPreview;
+ }
+
+ /**
+ * Whether the camera application can be launched for the camera launch gesture.
+ */
+ public boolean canCameraGestureBeLaunched() {
+ if (!mStatusBar.isCameraAllowedByAdmin()) {
+ return false;
+ }
+
+ ResolveInfo resolveInfo = mKeyguardBottomArea.resolveCameraIntent();
+ String
+ packageToLaunch =
+ (resolveInfo == null || resolveInfo.activityInfo == null) ? null
+ : resolveInfo.activityInfo.packageName;
+ return packageToLaunch != null && (mBarState != StatusBarState.SHADE || !isForegroundApp(
+ packageToLaunch)) && !mAffordanceHelper.isSwipingInProgress();
+ }
+
+ /**
+ * Return true if the applications with the package name is running in foreground.
+ *
+ * @param pkgName application package name.
+ */
+ private boolean isForegroundApp(String pkgName) {
+ List<ActivityManager.RunningTaskInfo> tasks = mActivityManager.getRunningTasks(1);
+ return !tasks.isEmpty() && pkgName.equals(tasks.get(0).topActivity.getPackageName());
+ }
+
+ private void setGroupManager(NotificationGroupManager groupManager) {
+ mGroupManager = groupManager;
+ }
+
+ public boolean hideStatusBarIconsWhenExpanded() {
+ if (mLaunchingNotification) {
+ return mHideIconsDuringNotificationLaunch;
+ }
+ if (mHeadsUpAppearanceController != null
+ && mHeadsUpAppearanceController.shouldBeVisible()) {
+ return false;
+ }
+ return !isFullWidth() || !mShowIconsWhenExpanded;
+ }
+
+ private final FragmentListener mFragmentListener = new FragmentListener() {
+ @Override
+ public void onFragmentViewCreated(String tag, Fragment fragment) {
+ mQs = (QS) fragment;
+ mQs.setPanelView(mHeightListener);
+ mQs.setExpandClickListener(mOnClickListener);
+ mQs.setHeaderClickable(mQsExpansionEnabled);
+ updateQSPulseExpansion();
+ mQs.setOverscrolling(mStackScrollerOverscrolling);
+
+ // recompute internal state when qspanel height changes
+ mQs.getView().addOnLayoutChangeListener(
+ (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
+ final int height = bottom - top;
+ final int oldHeight = oldBottom - oldTop;
+ if (height != oldHeight) {
+ mHeightListener.onQsHeightChanged();
+ }
+ });
+ mNotificationStackScroller.setQsContainer((ViewGroup) mQs.getView());
+ if (mQs instanceof QSFragment) {
+ mKeyguardStatusBar.setQSPanel(((QSFragment) mQs).getQsPanel());
+ }
+ updateQsExpansion();
+ }
+
+ @Override
+ public void onFragmentViewDestroyed(String tag, Fragment fragment) {
+ // Manual handling of fragment lifecycle is only required because this bridges
+ // non-fragment and fragment code. Once we are using a fragment for the notification
+ // panel, mQs will not need to be null cause it will be tied to the same lifecycle.
+ if (fragment == mQs) {
+ mQs = null;
+ }
+ }
+ };
+
+ @Override
+ public void setTouchAndAnimationDisabled(boolean disabled) {
+ super.setTouchAndAnimationDisabled(disabled);
+ if (disabled && mAffordanceHelper.isSwipingInProgress() && !mIsLaunchTransitionRunning) {
+ mAffordanceHelper.reset(false /* animate */);
+ }
+ mNotificationStackScroller.setAnimationsEnabled(!disabled);
+ }
+
+ /**
+ * Sets the dozing state.
+ *
+ * @param dozing {@code true} when dozing.
+ * @param animate if transition should be animated.
+ * @param wakeUpTouchLocation touch event location - if woken up by SLPI sensor.
+ */
+ public void setDozing(boolean dozing, boolean animate, PointF wakeUpTouchLocation) {
+ if (dozing == mDozing) return;
+ mView.setDozing(dozing);
+ mDozing = dozing;
+ mNotificationStackScroller.setDozing(mDozing, animate, wakeUpTouchLocation);
+ mKeyguardBottomArea.setDozing(mDozing, animate);
+
+ if (dozing) {
+ mBottomAreaShadeAlphaAnimator.cancel();
+ }
+
+ if (mBarState == StatusBarState.KEYGUARD || mBarState == StatusBarState.SHADE_LOCKED) {
+ updateDozingVisibilities(animate);
+ }
+
+ final float dozeAmount = dozing ? 1 : 0;
+ mStatusBarStateController.setDozeAmount(dozeAmount, animate);
+ }
+
+ public void setPulsing(boolean pulsing) {
+ mPulsing = pulsing;
+ final boolean
+ animatePulse =
+ !mDozeParameters.getDisplayNeedsBlanking() && mDozeParameters.getAlwaysOn();
+ if (animatePulse) {
+ mAnimateNextPositionUpdate = true;
+ }
+ // Do not animate the clock when waking up from a pulse.
+ // The height callback will take care of pushing the clock to the right position.
+ if (!mPulsing && !mDozing) {
+ mAnimateNextPositionUpdate = false;
+ }
+ mNotificationStackScroller.setPulsing(pulsing, animatePulse);
+ mKeyguardStatusView.setPulsing(pulsing);
+ }
+
+ public void setAmbientIndicationBottomPadding(int ambientIndicationBottomPadding) {
+ if (mAmbientIndicationBottomPadding != ambientIndicationBottomPadding) {
+ mAmbientIndicationBottomPadding = ambientIndicationBottomPadding;
+ mStatusBar.updateKeyguardMaxNotifications();
+ }
+ }
+
+ public void dozeTimeTick() {
+ mKeyguardBottomArea.dozeTimeTick();
+ mKeyguardStatusView.dozeTimeTick();
+ if (mInterpolatedDarkAmount > 0) {
+ positionClockAndNotifications();
+ }
+ }
+
+ public void setStatusAccessibilityImportance(int mode) {
+ mKeyguardStatusView.setImportantForAccessibility(mode);
+ }
+
+ /**
+ * TODO: this should be removed.
+ * It's not correct to pass this view forward because other classes will end up adding
+ * children to it. Theme will be out of sync.
+ *
+ * @return bottom area view
+ */
+ public KeyguardBottomAreaView getKeyguardBottomAreaView() {
+ return mKeyguardBottomArea;
+ }
+
+ public void setUserSetupComplete(boolean userSetupComplete) {
+ mUserSetupComplete = userSetupComplete;
+ mKeyguardBottomArea.setUserSetupComplete(userSetupComplete);
+ }
+
+ public void applyExpandAnimationParams(ExpandAnimationParameters params) {
+ mExpandOffset = params != null ? params.getTopChange() : 0;
+ updateQsExpansion();
+ if (params != null) {
+ boolean hideIcons = params.getProgress(
+ ActivityLaunchAnimator.ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f;
+ if (hideIcons != mHideIconsDuringNotificationLaunch) {
+ mHideIconsDuringNotificationLaunch = hideIcons;
+ if (!hideIcons) {
+ mCommandQueue.recomputeDisableFlags(mDisplayId, true /* animate */);
+ }
+ }
+ }
+ }
+
+ public void addTrackingHeadsUpListener(Consumer<ExpandableNotificationRow> listener) {
+ mTrackingHeadsUpListeners.add(listener);
+ }
+
+ public void removeTrackingHeadsUpListener(Consumer<ExpandableNotificationRow> listener) {
+ mTrackingHeadsUpListeners.remove(listener);
+ }
+
+ public void addVerticalTranslationListener(Runnable verticalTranslationListener) {
+ mVerticalTranslationListener.add(verticalTranslationListener);
+ }
+
+ public void removeVerticalTranslationListener(Runnable verticalTranslationListener) {
+ mVerticalTranslationListener.remove(verticalTranslationListener);
+ }
+
+ public void setHeadsUpAppearanceController(
+ HeadsUpAppearanceController headsUpAppearanceController) {
+ mHeadsUpAppearanceController = headsUpAppearanceController;
+ }
+
+ /**
+ * Starts the animation before we dismiss Keyguard, i.e. an disappearing animation on the
+ * security view of the bouncer.
+ */
+ public void onBouncerPreHideAnimation() {
+ setKeyguardStatusViewVisibility(mBarState, true /* keyguardFadingAway */,
+ false /* goingToFullShade */);
+ }
+
+ /**
+ * Do not let the user drag the shade up and down for the current touch session.
+ * This is necessary to avoid shade expansion while/after the bouncer is dismissed.
+ */
+ public void blockExpansionForCurrentTouch() {
+ mBlockingExpansionForCurrentTouch = mTracking;
+ }
+
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ super.dump(fd, pw, args);
+ pw.println(" gestureExclusionRect: " + calculateGestureExclusionRect());
+ if (mKeyguardStatusBar != null) {
+ mKeyguardStatusBar.dump(fd, pw, args);
+ }
+ if (mKeyguardStatusView != null) {
+ mKeyguardStatusView.dump(fd, pw, args);
+ }
+ }
+
+ public boolean hasActiveClearableNotifications() {
+ return mNotificationStackScroller.hasActiveClearableNotifications(ROWS_ALL);
+ }
+
+ private void updateShowEmptyShadeView() {
+ boolean
+ showEmptyShadeView =
+ mBarState != StatusBarState.KEYGUARD && !mEntryManager.hasActiveNotifications();
+ showEmptyShadeView(showEmptyShadeView);
+ }
+
+ public RemoteInputController.Delegate createRemoteInputDelegate() {
+ return mNotificationStackScroller.createDelegate();
+ }
+
+ public void updateNotificationViews() {
+ mNotificationStackScroller.updateSectionBoundaries();
+ mNotificationStackScroller.updateSpeedBumpIndex();
+ mNotificationStackScroller.updateFooter();
+ updateShowEmptyShadeView();
+ mNotificationStackScroller.updateIconAreaViews();
+ }
+
+ public void onUpdateRowStates() {
+ mNotificationStackScroller.onUpdateRowStates();
+ }
+
+ public boolean hasPulsingNotifications() {
+ return mNotificationStackScroller.hasPulsingNotifications();
+ }
+
+ public ActivatableNotificationView getActivatedChild() {
+ return mNotificationStackScroller.getActivatedChild();
+ }
+
+ public void setActivatedChild(ActivatableNotificationView o) {
+ mNotificationStackScroller.setActivatedChild(o);
+ }
+
+ public void runAfterAnimationFinished(Runnable r) {
+ mNotificationStackScroller.runAfterAnimationFinished(r);
+ }
+
+ public void setScrollingEnabled(boolean b) {
+ mNotificationStackScroller.setScrollingEnabled(b);
+ }
+
+ public void initDependencies(StatusBar statusBar, NotificationGroupManager groupManager,
+ NotificationShelf notificationShelf,
+ NotificationIconAreaController notificationIconAreaController,
+ ScrimController scrimController) {
+ setStatusBar(statusBar);
+ setGroupManager(mGroupManager);
+ mNotificationStackScroller.setNotificationPanelController(this);
+ mNotificationStackScroller.setIconAreaController(notificationIconAreaController);
+ mNotificationStackScroller.setStatusBar(statusBar);
+ mNotificationStackScroller.setGroupManager(groupManager);
+ mNotificationStackScroller.setShelf(notificationShelf);
+ mNotificationStackScroller.setScrimController(scrimController);
+ updateShowEmptyShadeView();
+ }
+
+ public void showTransientIndication(int id) {
+ mKeyguardIndicationController.showTransientIndication(id);
+ }
+
+ public void setOnReinflationListener(Runnable onReinflationListener) {
+ mOnReinflationListener = onReinflationListener;
+ }
+
+ public static boolean isQsSplitEnabled() {
+ return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+ SystemUiDeviceConfigFlags.QS_SPLIT_ENABLED, false);
+ }
+
+ public void setAlpha(float alpha) {
+ mView.setAlpha(alpha);
+ }
+
+ public ViewPropertyAnimator fadeOut(long startDelayMs, long durationMs, Runnable endAction) {
+ return mView.animate().alpha(0).setStartDelay(startDelayMs).setDuration(
+ durationMs).setInterpolator(Interpolators.ALPHA_OUT).withLayer().withEndAction(
+ endAction);
+ }
+
+ public void resetViewGroupFade() {
+ ViewGroupFadeHelper.reset(mView);
+ }
+
+ public void addOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) {
+ mView.getViewTreeObserver().addOnGlobalLayoutListener(listener);
+ }
+
+ public void removeOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) {
+ mView.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
+ }
+
+ public MyOnHeadsUpChangedListener getOnHeadsUpChangedListener() {
+ return mOnHeadsUpChangedListener;
+ }
+
+ public int getHeight() {
+ return mView.getHeight();
+ }
+
+ public TextView getHeaderDebugInfo() {
+ return mView.findViewById(R.id.header_debug_info);
+ }
+
+ public void onThemeChanged() {
+ mConfigurationListener.onThemeChanged();
+ }
+
+ @Override
+ public OnLayoutChangeListener createLayoutChangeListener() {
+ return new OnLayoutChangeListener();
+ }
+
+ public void setEmptyDragAmount(float amount) {
+ mExpansionCallback.setEmptyDragAmount(amount);
+ }
+
+ @Override
+ protected TouchHandler createTouchHandler() {
+ return new TouchHandler() {
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ if (mBlockTouches || mQsFullyExpanded && mQs.onInterceptTouchEvent(event)) {
+ return false;
+ }
+ initDownStates(event);
+ // Do not let touches go to shade or QS if the bouncer is visible,
+ // but still let user swipe down to expand the panel, dismissing the bouncer.
+ if (mStatusBar.isBouncerShowing()) {
+ return true;
+ }
+ if (mBar.panelEnabled() && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
+ mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
+ mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1);
+ return true;
+ }
+ if (!shouldQuickSettingsIntercept(mDownX, mDownY, 0)
+ && mPulseExpansionHandler.onInterceptTouchEvent(event)) {
+ return true;
+ }
+
+ if (!isFullyCollapsed() && onQsIntercept(event)) {
+ return true;
+ }
+ return super.onInterceptTouchEvent(event);
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (mBlockTouches || (mQs != null && mQs.isCustomizing())) {
+ return false;
+ }
+
+ // Do not allow panel expansion if bouncer is scrimmed, otherwise user would be able
+ // to pull down QS or expand the shade.
+ if (mStatusBar.isBouncerShowingScrimmed()) {
+ return false;
+ }
+
+ // Make sure the next touch won't the blocked after the current ends.
+ if (event.getAction() == MotionEvent.ACTION_UP
+ || event.getAction() == MotionEvent.ACTION_CANCEL) {
+ mBlockingExpansionForCurrentTouch = false;
+ }
+ // When touch focus transfer happens, ACTION_DOWN->ACTION_UP may happen immediately
+ // without any ACTION_MOVE event.
+ // In such case, simply expand the panel instead of being stuck at the bottom bar.
+ if (mLastEventSynthesizedDown && event.getAction() == MotionEvent.ACTION_UP) {
+ expand(true /* animate */);
+ }
+ initDownStates(event);
+ if (!mIsExpanding && !shouldQuickSettingsIntercept(mDownX, mDownY, 0)
+ && mPulseExpansionHandler.onTouchEvent(event)) {
+ // We're expanding all the other ones shouldn't get this anymore
+ return true;
+ }
+ if (mListenForHeadsUp && !mHeadsUpTouchHelper.isTrackingHeadsUp()
+ && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
+ mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1);
+ }
+ boolean handled = false;
+ if ((!mIsExpanding || mHintAnimationRunning) && !mQsExpanded
+ && mBarState != StatusBarState.SHADE && !mDozing) {
+ handled |= mAffordanceHelper.onTouchEvent(event);
+ }
+ if (mOnlyAffordanceInThisMotion) {
+ return true;
+ }
+ handled |= mHeadsUpTouchHelper.onTouchEvent(event);
+
+ if (!mHeadsUpTouchHelper.isTrackingHeadsUp() && handleQsTouch(event)) {
+ return true;
+ }
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN && isFullyCollapsed()) {
+ mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
+ updateVerticalPanelPosition(event.getX());
+ handled = true;
+ }
+ handled |= super.onTouch(v, event);
+ return !mDozing || mPulsing || handled;
+ }
+ };
+ }
+
+ @Override
+ protected PanelViewController.OnConfigurationChangedListener
+ createOnConfigurationChangedListener() {
+ return new OnConfigurationChangedListener();
+ }
+
+ private class OnHeightChangedListener implements ExpandableView.OnHeightChangedListener {
+ @Override
+ public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
+
+ // Block update if we are in quick settings and just the top padding changed
+ // (i.e. view == null).
+ if (view == null && mQsExpanded) {
+ return;
+ }
+ if (needsAnimation && mInterpolatedDarkAmount == 0) {
+ mAnimateNextPositionUpdate = true;
+ }
+ ExpandableView firstChildNotGone = mNotificationStackScroller.getFirstChildNotGone();
+ ExpandableNotificationRow
+ firstRow =
+ firstChildNotGone instanceof ExpandableNotificationRow
+ ? (ExpandableNotificationRow) firstChildNotGone : null;
+ if (firstRow != null && (view == firstRow || (firstRow.getNotificationParent()
+ == firstRow))) {
+ requestScrollerTopPaddingUpdate(false /* animate */);
+ }
+ requestPanelHeightUpdate();
+ }
+
+ @Override
+ public void onReset(ExpandableView view) {
+ }
+ }
+
+ private class OnClickListener implements View.OnClickListener {
+ @Override
+ public void onClick(View v) {
+ onQsExpansionStarted();
+ if (mQsExpanded) {
+ flingSettings(0 /* vel */, FLING_COLLAPSE, null /* onFinishRunnable */,
+ true /* isClick */);
+ } else if (mQsExpansionEnabled) {
+ mLockscreenGestureLogger.write(MetricsEvent.ACTION_SHADE_QS_TAP, 0, 0);
+ flingSettings(0 /* vel */, FLING_EXPAND, null /* onFinishRunnable */,
+ true /* isClick */);
+ }
+ }
+ }
+
+ private class OnOverscrollTopChangedListener implements
+ NotificationStackScrollLayout.OnOverscrollTopChangedListener {
+ @Override
+ public void onOverscrollTopChanged(float amount, boolean isRubberbanded) {
+ cancelQsAnimation();
+ if (!mQsExpansionEnabled) {
+ amount = 0f;
+ }
+ float rounded = amount >= 1f ? amount : 0f;
+ setOverScrolling(rounded != 0f && isRubberbanded);
+ mQsExpansionFromOverscroll = rounded != 0f;
+ mLastOverscroll = rounded;
+ updateQsState();
+ setQsExpansion(mQsMinExpansionHeight + rounded);
+ }
+
+ @Override
+ public void flingTopOverscroll(float velocity, boolean open) {
+ mLastOverscroll = 0f;
+ mQsExpansionFromOverscroll = false;
+ setQsExpansion(mQsExpansionHeight);
+ flingSettings(!mQsExpansionEnabled && open ? 0f : velocity,
+ open && mQsExpansionEnabled ? FLING_EXPAND : FLING_COLLAPSE, () -> {
+ mStackScrollerOverscrolling = false;
+ setOverScrolling(false);
+ updateQsState();
+ }, false /* isClick */);
+ }
+ }
+
+ private class DynamicPrivacyControlListener implements DynamicPrivacyController.Listener {
+ @Override
+ public void onDynamicPrivacyChanged() {
+ // Do not request animation when pulsing or waking up, otherwise the clock wiill be out
+ // of sync with the notification panel.
+ if (mLinearDarkAmount != 0) {
+ return;
+ }
+ mAnimateNextPositionUpdate = true;
+ }
+ }
+
+ private class KeyguardAffordanceHelperCallback implements KeyguardAffordanceHelper.Callback {
+ @Override
+ public void onAnimationToSideStarted(boolean rightPage, float translation, float vel) {
+ boolean
+ start =
+ mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? rightPage
+ : !rightPage;
+ mIsLaunchTransitionRunning = true;
+ mLaunchAnimationEndRunnable = null;
+ float displayDensity = mStatusBar.getDisplayDensity();
+ int lengthDp = Math.abs((int) (translation / displayDensity));
+ int velocityDp = Math.abs((int) (vel / displayDensity));
+ if (start) {
+ mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_DIALER, lengthDp, velocityDp);
+
+ mFalsingManager.onLeftAffordanceOn();
+ if (mFalsingManager.shouldEnforceBouncer()) {
+ mStatusBar.executeRunnableDismissingKeyguard(
+ () -> mKeyguardBottomArea.launchLeftAffordance(), null,
+ true /* dismissShade */, false /* afterKeyguardGone */,
+ true /* deferred */);
+ } else {
+ mKeyguardBottomArea.launchLeftAffordance();
+ }
+ } else {
+ if (KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE.equals(
+ mLastCameraLaunchSource)) {
+ mLockscreenGestureLogger.write(
+ MetricsEvent.ACTION_LS_CAMERA, lengthDp, velocityDp);
+ }
+ mFalsingManager.onCameraOn();
+ if (mFalsingManager.shouldEnforceBouncer()) {
+ mStatusBar.executeRunnableDismissingKeyguard(
+ () -> mKeyguardBottomArea.launchCamera(mLastCameraLaunchSource), null,
+ true /* dismissShade */, false /* afterKeyguardGone */,
+ true /* deferred */);
+ } else {
+ mKeyguardBottomArea.launchCamera(mLastCameraLaunchSource);
+ }
+ }
+ mStatusBar.startLaunchTransitionTimeout();
+ mBlockTouches = true;
+ }
+
+ @Override
+ public void onAnimationToSideEnded() {
+ mIsLaunchTransitionRunning = false;
+ mIsLaunchTransitionFinished = true;
+ if (mLaunchAnimationEndRunnable != null) {
+ mLaunchAnimationEndRunnable.run();
+ mLaunchAnimationEndRunnable = null;
+ }
+ mStatusBar.readyForKeyguardDone();
+ }
+
+ @Override
+ public float getMaxTranslationDistance() {
+ return (float) Math.hypot(mView.getWidth(), getHeight());
+ }
+
+ @Override
+ public void onSwipingStarted(boolean rightIcon) {
+ mFalsingManager.onAffordanceSwipingStarted(rightIcon);
+ boolean
+ camera =
+ mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? !rightIcon
+ : rightIcon;
+ if (camera) {
+ mKeyguardBottomArea.bindCameraPrewarmService();
+ }
+ mView.requestDisallowInterceptTouchEvent(true);
+ mOnlyAffordanceInThisMotion = true;
+ mQsTracking = false;
+ }
+
+ @Override
+ public void onSwipingAborted() {
+ mFalsingManager.onAffordanceSwipingAborted();
+ mKeyguardBottomArea.unbindCameraPrewarmService(false /* launched */);
+ }
+
+ @Override
+ public void onIconClicked(boolean rightIcon) {
+ if (mHintAnimationRunning) {
+ return;
+ }
+ mHintAnimationRunning = true;
+ mAffordanceHelper.startHintAnimation(rightIcon, () -> {
+ mHintAnimationRunning = false;
+ mStatusBar.onHintFinished();
+ });
+ rightIcon =
+ mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? !rightIcon
+ : rightIcon;
+ if (rightIcon) {
+ mStatusBar.onCameraHintStarted();
+ } else {
+ if (mKeyguardBottomArea.isLeftVoiceAssist()) {
+ mStatusBar.onVoiceAssistHintStarted();
+ } else {
+ mStatusBar.onPhoneHintStarted();
+ }
+ }
+ }
+
+ @Override
+ public KeyguardAffordanceView getLeftIcon() {
+ return mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
+ ? mKeyguardBottomArea.getRightView() : mKeyguardBottomArea.getLeftView();
+ }
+
+ @Override
+ public KeyguardAffordanceView getRightIcon() {
+ return mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
+ ? mKeyguardBottomArea.getLeftView() : mKeyguardBottomArea.getRightView();
+ }
+
+ @Override
+ public View getLeftPreview() {
+ return mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
+ ? mKeyguardBottomArea.getRightPreview() : mKeyguardBottomArea.getLeftPreview();
+ }
+
+ @Override
+ public View getRightPreview() {
+ return mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
+ ? mKeyguardBottomArea.getLeftPreview() : mKeyguardBottomArea.getRightPreview();
+ }
+
+ @Override
+ public float getAffordanceFalsingFactor() {
+ return mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
+ }
+
+ @Override
+ public boolean needsAntiFalsing() {
+ return mBarState == StatusBarState.KEYGUARD;
+ }
+ }
+
+ private class OnEmptySpaceClickListener implements
+ NotificationStackScrollLayout.OnEmptySpaceClickListener {
+ @Override
+ public void onEmptySpaceClicked(float x, float y) {
+ onEmptySpaceClick(x);
+ }
+ }
+
+ private class MyOnHeadsUpChangedListener implements OnHeadsUpChangedListener {
+ @Override
+ public void onHeadsUpPinnedModeChanged(final boolean inPinnedMode) {
+ mNotificationStackScroller.setInHeadsUpPinnedMode(inPinnedMode);
+ if (inPinnedMode) {
+ mHeadsUpExistenceChangedRunnable.run();
+ updateNotificationTranslucency();
+ } else {
+ setHeadsUpAnimatingAway(true);
+ mNotificationStackScroller.runAfterAnimationFinished(
+ mHeadsUpExistenceChangedRunnable);
+ }
+ updateGestureExclusionRect();
+ mHeadsUpPinnedMode = inPinnedMode;
+ updateHeadsUpVisibility();
+ updateKeyguardStatusBarForHeadsUp();
+ }
+
+ @Override
+ public void onHeadsUpPinned(NotificationEntry entry) {
+ if (!isOnKeyguard()) {
+ mNotificationStackScroller.generateHeadsUpAnimation(entry.getHeadsUpAnimationView(),
+ true);
+ }
+ }
+
+ @Override
+ public void onHeadsUpUnPinned(NotificationEntry entry) {
+
+ // When we're unpinning the notification via active edge they remain heads-upped,
+ // we need to make sure that an animation happens in this case, otherwise the
+ // notification
+ // will stick to the top without any interaction.
+ if (isFullyCollapsed() && entry.isRowHeadsUp() && !isOnKeyguard()) {
+ mNotificationStackScroller.generateHeadsUpAnimation(
+ entry.getHeadsUpAnimationView(), false);
+ entry.setHeadsUpIsVisible();
+ }
+ }
+
+ @Override
+ public void onHeadsUpStateChanged(NotificationEntry entry, boolean isHeadsUp) {
+ mNotificationStackScroller.generateHeadsUpAnimation(entry, isHeadsUp);
+ }
+ }
+
+ private class HeightListener implements QS.HeightListener {
+ public void onQsHeightChanged() {
+ mQsMaxExpansionHeight = mQs != null ? mQs.getDesiredHeight() : 0;
+ if (mQsExpanded && mQsFullyExpanded) {
+ mQsExpansionHeight = mQsMaxExpansionHeight;
+ requestScrollerTopPaddingUpdate(false /* animate */);
+ requestPanelHeightUpdate();
+ }
+ if (mAccessibilityManager.isEnabled()) {
+ mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
+ }
+ mNotificationStackScroller.setMaxTopPadding(
+ mQsMaxExpansionHeight + mQsNotificationTopPadding);
+ }
+ }
+
+ private class ZenModeControllerCallback implements ZenModeController.Callback {
+ @Override
+ public void onZenChanged(int zen) {
+ updateShowEmptyShadeView();
+ }
+ }
+
+ private class ConfigurationListener implements ConfigurationController.ConfigurationListener {
+ @Override
+ public void onDensityOrFontScaleChanged() {
+ updateShowEmptyShadeView();
+ }
+
+ @Override
+ public void onThemeChanged() {
+ final int themeResId = mView.getContext().getThemeResId();
+ if (mThemeResId == themeResId) {
+ return;
+ }
+ mThemeResId = themeResId;
+
+ reInflateViews();
+ }
+
+ @Override
+ public void onOverlayChanged() {
+ reInflateViews();
+ }
+
+ @Override
+ public void onUiModeChanged() {
+ reinflatePluginContainer();
+ }
+ }
+
+ private class StatusBarStateListener implements StateListener {
+ @Override
+ public void onStateChanged(int statusBarState) {
+ boolean goingToFullShade = mStatusBarStateController.goingToFullShade();
+ boolean keyguardFadingAway = mKeyguardStateController.isKeyguardFadingAway();
+ int oldState = mBarState;
+ boolean keyguardShowing = statusBarState == StatusBarState.KEYGUARD;
+ setKeyguardStatusViewVisibility(statusBarState, keyguardFadingAway, goingToFullShade);
+ setKeyguardBottomAreaVisibility(statusBarState, goingToFullShade);
+
+ mBarState = statusBarState;
+ mKeyguardShowing = keyguardShowing;
+ if (mKeyguardShowing && isQsSplitEnabled()) {
+ mNotificationStackScroller.setVisibility(View.VISIBLE);
+ mQsFrame.setVisibility(View.VISIBLE);
+ mHomeControlsLayout.setVisibility(View.GONE);
+ }
+
+ if (oldState == StatusBarState.KEYGUARD && (goingToFullShade
+ || statusBarState == StatusBarState.SHADE_LOCKED)) {
+ animateKeyguardStatusBarOut();
+ long
+ delay =
+ mBarState == StatusBarState.SHADE_LOCKED ? 0
+ : mKeyguardStateController.calculateGoingToFullShadeDelay();
+ mQs.animateHeaderSlidingIn(delay);
+ } else if (oldState == StatusBarState.SHADE_LOCKED
+ && statusBarState == StatusBarState.KEYGUARD) {
+ animateKeyguardStatusBarIn(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+ mNotificationStackScroller.resetScrollPosition();
+ // Only animate header if the header is visible. If not, it will partially
+ // animate out
+ // the top of QS
+ if (!mQsExpanded) {
+ mQs.animateHeaderSlidingOut();
+ }
+ } else {
+ mKeyguardStatusBar.setAlpha(1f);
+ mKeyguardStatusBar.setVisibility(keyguardShowing ? View.VISIBLE : View.INVISIBLE);
+ ((PhoneStatusBarView) mBar).maybeShowDivider(keyguardShowing);
+ if (keyguardShowing && oldState != mBarState) {
+ if (mQs != null) {
+ mQs.hideImmediately();
+ }
+ }
+ }
+ updateKeyguardStatusBarForHeadsUp();
+ if (keyguardShowing) {
+ updateDozingVisibilities(false /* animate */);
+ }
+ // THe update needs to happen after the headerSlide in above, otherwise the translation
+ // would reset
+ updateQSPulseExpansion();
+ maybeAnimateBottomAreaAlpha();
+ resetHorizontalPanelPosition();
+ updateQsState();
+ }
+
+ @Override
+ public void onDozeAmountChanged(float linearAmount, float amount) {
+ mInterpolatedDarkAmount = amount;
+ mLinearDarkAmount = linearAmount;
+ mKeyguardStatusView.setDarkAmount(mInterpolatedDarkAmount);
+ mKeyguardBottomArea.setDarkAmount(mInterpolatedDarkAmount);
+ positionClockAndNotifications();
+ }
+ }
+
+ private class ExpansionCallback implements PulseExpansionHandler.ExpansionCallback {
+ public void setEmptyDragAmount(float amount) {
+ mEmptyDragAmount = amount * 0.2f;
+ positionClockAndNotifications();
+ }
+ }
+
+ private class OnAttachStateChangeListener implements View.OnAttachStateChangeListener {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ FragmentHostManager.get(mView).addTagListener(QS.TAG, mFragmentListener);
+ mStatusBarStateController.addCallback(mStatusBarStateListener);
+ mZenModeController.addCallback(mZenModeControllerCallback);
+ mConfigurationController.addCallback(mConfigurationListener);
+ mUpdateMonitor.registerCallback(mKeyguardUpdateCallback);
+ // Theme might have changed between inflating this view and attaching it to the
+ // window, so
+ // force a call to onThemeChanged
+ mConfigurationListener.onThemeChanged();
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ FragmentHostManager.get(mView).removeTagListener(QS.TAG, mFragmentListener);
+ mStatusBarStateController.removeCallback(mStatusBarStateListener);
+ mZenModeController.removeCallback(mZenModeControllerCallback);
+ mConfigurationController.removeCallback(mConfigurationListener);
+ mUpdateMonitor.removeCallback(mKeyguardUpdateCallback);
+ }
+ }
+
+ private class OnLayoutChangeListener extends PanelViewController.OnLayoutChangeListener {
+
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
+ int oldTop, int oldRight, int oldBottom) {
+ DejankUtils.startDetectingBlockingIpcs("NVP#onLayout");
+ super.onLayoutChange(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom);
+ setIsFullWidth(mNotificationStackScroller.getWidth() == mView.getWidth());
+
+ // Update Clock Pivot
+ mKeyguardStatusView.setPivotX(mView.getWidth() / 2);
+ mKeyguardStatusView.setPivotY(
+ (FONT_HEIGHT - CAP_HEIGHT) / 2048f * mKeyguardStatusView.getClockTextSize());
+
+ // Calculate quick setting heights.
+ int oldMaxHeight = mQsMaxExpansionHeight;
+ if (mQs != null) {
+ mQsMinExpansionHeight = mKeyguardShowing ? 0 : mQs.getQsMinExpansionHeight();
+ if (mNPVPluginManager != null) {
+ mNPVPluginManager.setYOffset(mQsMinExpansionHeight);
+ mQsMinExpansionHeight += mNPVPluginManager.getHeight();
+ }
+ mQsMaxExpansionHeight = mQs.getDesiredHeight();
+ mNotificationStackScroller.setMaxTopPadding(
+ mQsMaxExpansionHeight + mQsNotificationTopPadding);
+ }
+ positionClockAndNotifications();
+ if (mQsExpanded && mQsFullyExpanded) {
+ mQsExpansionHeight = mQsMaxExpansionHeight;
+ requestScrollerTopPaddingUpdate(false /* animate */);
+ requestPanelHeightUpdate();
+
+ // Size has changed, start an animation.
+ if (mQsMaxExpansionHeight != oldMaxHeight) {
+ startQsSizeChangeAnimation(oldMaxHeight, mQsMaxExpansionHeight);
+ }
+ } else if (!mQsExpanded) {
+ setQsExpansion(mQsMinExpansionHeight + mLastOverscroll);
+ }
+ updateExpandedHeight(getExpandedHeight());
+ updateHeader();
+
+ // If we are running a size change animation, the animation takes care of the height of
+ // the container. However, if we are not animating, we always need to make the QS
+ // container
+ // the desired height so when closing the QS detail, it stays smaller after the size
+ // change
+ // animation is finished but the detail view is still being animated away (this
+ // animation
+ // takes longer than the size change animation).
+ if (mQsSizeChangeAnimator == null && mQs != null) {
+ mQs.setHeightOverride(mQs.getDesiredHeight());
+ }
+ updateMaxHeadsUpTranslation();
+ updateGestureExclusionRect();
+ if (mExpandAfterLayoutRunnable != null) {
+ mExpandAfterLayoutRunnable.run();
+ mExpandAfterLayoutRunnable = null;
+ }
+ DejankUtils.stopDetectingBlockingIpcs("NVP#onLayout");
+ }
+ }
+
+ private class DebugDrawable extends Drawable {
+
+ @Override
+ public void draw(Canvas canvas) {
+ Paint p = new Paint();
+ p.setColor(Color.RED);
+ p.setStrokeWidth(2);
+ p.setStyle(Paint.Style.STROKE);
+ canvas.drawLine(0, getMaxPanelHeight(), mView.getWidth(), getMaxPanelHeight(), p);
+ p.setColor(Color.BLUE);
+ canvas.drawLine(0, getExpandedHeight(), mView.getWidth(), getExpandedHeight(), p);
+ p.setColor(Color.GREEN);
+ canvas.drawLine(0, calculatePanelHeightQsExpanded(), mView.getWidth(),
+ calculatePanelHeightQsExpanded(), p);
+ p.setColor(Color.YELLOW);
+ canvas.drawLine(0, calculatePanelHeightShade(), mView.getWidth(),
+ calculatePanelHeightShade(), p);
+ p.setColor(Color.MAGENTA);
+ canvas.drawLine(
+ 0, calculateQsTopPadding(), mView.getWidth(), calculateQsTopPadding(), p);
+ p.setColor(Color.CYAN);
+ canvas.drawLine(0, mClockPositionResult.stackScrollerPadding, mView.getWidth(),
+ mNotificationStackScroller.getTopPadding(), p);
+ p.setColor(Color.GRAY);
+ canvas.drawLine(0, mClockPositionResult.clockY, mView.getWidth(),
+ mClockPositionResult.clockY, p);
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) {
+
+ }
+
+ @Override
+ public int getOpacity() {
+ return 0;
+ }
+ }
+
+ private class OnConfigurationChangedListener extends
+ PanelViewController.OnConfigurationChangedListener {
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ mAffordanceHelper.onConfigurationChanged();
+ if (newConfig.orientation != mLastOrientation) {
+ resetHorizontalPanelPosition();
+ }
+ mLastOrientation = newConfig.orientation;
+ }
+ }
+
+ private class OnApplyWindowInsetsListener implements View.OnApplyWindowInsetsListener {
+ public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
+ mNavigationBarBottomHeight = insets.getStableInsetBottom();
+ updateMaxHeadsUpTranslation();
+ return insets;
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
index 063d00b..8d8c8da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelBar.java
@@ -43,7 +43,7 @@
public static final int STATE_OPENING = 1;
public static final int STATE_OPEN = 2;
- PanelView mPanel;
+ PanelViewController mPanel;
private int mState = STATE_CLOSED;
private boolean mTracking;
@@ -83,7 +83,8 @@
super.onFinishInflate();
}
- public void setPanel(PanelView pv) {
+ /** Set the PanelViewController */
+ public void setPanel(PanelViewController pv) {
mPanel = pv;
pv.setBar(this);
}
@@ -96,7 +97,7 @@
setImportantForAccessibility(important);
updateVisibility();
- if (mPanel != null) mPanel.setImportantForAccessibility(important);
+ if (mPanel != null) mPanel.getView().setImportantForAccessibility(important);
}
public float getExpansionFraction() {
@@ -108,7 +109,7 @@
}
protected void updateVisibility() {
- mPanel.setVisibility(shouldPanelBeVisible() ? VISIBLE : INVISIBLE);
+ mPanel.getView().setVisibility(shouldPanelBeVisible() ? VISIBLE : INVISIBLE);
}
protected boolean shouldPanelBeVisible() {
@@ -131,7 +132,7 @@
}
if (event.getAction() == MotionEvent.ACTION_DOWN) {
- final PanelView panel = mPanel;
+ final PanelViewController panel = mPanel;
if (panel == null) {
// panel is not there, so we'll eat the gesture
Log.v(TAG, String.format("onTouch: no panel for touch at (%d,%d)",
@@ -149,7 +150,7 @@
return true;
}
}
- return mPanel == null || mPanel.onTouchEvent(event);
+ return mPanel == null || mPanel.getView().dispatchTouchEvent(event);
}
public abstract void panelScrimMinFractionChanged(float minFraction);
@@ -163,7 +164,7 @@
boolean fullyClosed = true;
boolean fullyOpened = false;
if (SPEW) LOG("panelExpansionChanged: start state=%d", mState);
- PanelView pv = mPanel;
+ PanelViewController pv = mPanel;
mExpanded = expanded;
mPanelFraction = frac;
updateVisibility();
@@ -192,7 +193,7 @@
public void collapsePanel(boolean animate, boolean delayed, float speedUpFactor) {
boolean waiting = false;
- PanelView pv = mPanel;
+ PanelViewController pv = mPanel;
if (animate && !pv.isFullyCollapsed()) {
pv.collapse(delayed, speedUpFactor);
waiting = true;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
index cd56d06..2719a32 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
@@ -16,1255 +16,62 @@
package com.android.systemui.statusbar.phone;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ObjectAnimator;
-import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.os.SystemClock;
-import android.os.VibrationEffect;
import android.util.AttributeSet;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.InputDevice;
import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.ViewConfiguration;
-import android.view.ViewTreeObserver;
-import android.view.animation.Interpolator;
import android.widget.FrameLayout;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.util.LatencyTracker;
-import com.android.systemui.DejankUtils;
-import com.android.systemui.Dependency;
-import com.android.systemui.Interpolators;
-import com.android.systemui.R;
-import com.android.systemui.doze.DozeLog;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.statusbar.FlingAnimationUtils;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.VibratorHelper;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
public abstract class PanelView extends FrameLayout {
public static final boolean DEBUG = PanelBar.DEBUG;
public static final String TAG = PanelView.class.getSimpleName();
- private static final int INITIAL_OPENING_PEEK_DURATION = 200;
- private static final int PEEK_ANIMATION_DURATION = 360;
- private static final int NO_FIXED_DURATION = -1;
- protected long mDownTime;
- protected boolean mTouchSlopExceededBeforeDown;
- private float mMinExpandHeight;
- private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
- private boolean mPanelUpdateWhenAnimatorEnds;
- private boolean mVibrateOnOpening;
- protected boolean mLaunchingNotification;
- private int mFixedDuration = NO_FIXED_DURATION;
- protected ArrayList<PanelExpansionListener> mExpansionListeners = new ArrayList<>();
-
- private final void logf(String fmt, Object... args) {
- Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
- }
+ private PanelViewController.TouchHandler mTouchHandler;
protected StatusBar mStatusBar;
protected HeadsUpManagerPhone mHeadsUpManager;
- private float mPeekHeight;
- private float mHintDistance;
- private float mInitialOffsetOnTouch;
- private boolean mCollapsedAndHeadsUpOnDown;
- private float mExpandedFraction = 0;
- protected float mExpandedHeight = 0;
- private boolean mPanelClosedOnDown;
- private boolean mHasLayoutedSinceDown;
- private float mUpdateFlingVelocity;
- private boolean mUpdateFlingOnLayout;
- private boolean mPeekTouching;
- private boolean mJustPeeked;
- private boolean mClosing;
- protected boolean mTracking;
- private boolean mTouchSlopExceeded;
- private int mTrackingPointer;
protected int mTouchSlop;
- protected boolean mHintAnimationRunning;
- private boolean mOverExpandedBeforeFling;
- private boolean mTouchAboveFalsingThreshold;
- private int mUnlockFalsingThreshold;
- private boolean mTouchStartedInEmptyArea;
- private boolean mMotionAborted;
- private boolean mUpwardsWhenTresholdReached;
- private boolean mAnimatingOnDown;
- private ValueAnimator mHeightAnimator;
- private ObjectAnimator mPeekAnimator;
- private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
- private FlingAnimationUtils mFlingAnimationUtils;
- private FlingAnimationUtils mFlingAnimationUtilsClosing;
- private FlingAnimationUtils mFlingAnimationUtilsDismissing;
- private final FalsingManager mFalsingManager;
- private final DozeLog mDozeLog;
- private final VibratorHelper mVibratorHelper;
-
- /**
- * Whether an instant expand request is currently pending and we are just waiting for layout.
- */
- private boolean mInstantExpanding;
- private boolean mAnimateAfterExpanding;
-
- PanelBar mBar;
-
- private String mViewName;
- private float mInitialTouchY;
- private float mInitialTouchX;
- private boolean mTouchDisabled;
-
- /**
- * Whether or not the PanelView can be expanded or collapsed with a drag.
- */
- private boolean mNotificationsDragEnabled;
-
- private Interpolator mBounceInterpolator;
protected KeyguardBottomAreaView mKeyguardBottomArea;
+ private OnConfigurationChangedListener mOnConfigurationChangedListener;
- /**
- * Speed-up factor to be used when {@link #mFlingCollapseRunnable} runs the next time.
- */
- private float mNextCollapseSpeedUpFactor = 1.0f;
-
- protected boolean mExpanding;
- private boolean mGestureWaitForTouchSlop;
- private boolean mIgnoreXTouchSlop;
- private boolean mExpandLatencyTracking;
- protected final KeyguardStateController mKeyguardStateController;
- protected final SysuiStatusBarStateController mStatusBarStateController;
-
- protected void onExpandingFinished() {
- mBar.onExpandingFinished();
+ public PanelView(Context context) {
+ super(context);
}
- protected void onExpandingStarted() {
- }
-
- private void notifyExpandingStarted() {
- if (!mExpanding) {
- mExpanding = true;
- onExpandingStarted();
- }
- }
-
- protected final void notifyExpandingFinished() {
- endClosing();
- if (mExpanding) {
- mExpanding = false;
- onExpandingFinished();
- }
- }
-
- private void runPeekAnimation(long duration, float peekHeight, boolean collapseWhenFinished) {
- mPeekHeight = peekHeight;
- if (DEBUG) logf("peek to height=%.1f", mPeekHeight);
- if (mHeightAnimator != null) {
- return;
- }
- if (mPeekAnimator != null) {
- mPeekAnimator.cancel();
- }
- mPeekAnimator = ObjectAnimator.ofFloat(this, "expandedHeight", mPeekHeight)
- .setDuration(duration);
- mPeekAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
- mPeekAnimator.addListener(new AnimatorListenerAdapter() {
- private boolean mCancelled;
-
- @Override
- public void onAnimationCancel(Animator animation) {
- mCancelled = true;
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- mPeekAnimator = null;
- if (!mCancelled && collapseWhenFinished) {
- postOnAnimation(mPostCollapseRunnable);
- }
-
- }
- });
- notifyExpandingStarted();
- mPeekAnimator.start();
- mJustPeeked = true;
- }
-
- public PanelView(Context context, AttributeSet attrs, FalsingManager falsingManager,
- DozeLog dozeLog, KeyguardStateController keyguardStateController,
- SysuiStatusBarStateController statusBarStateController) {
+ public PanelView(Context context, AttributeSet attrs) {
super(context, attrs);
- mKeyguardStateController = keyguardStateController;
- mStatusBarStateController = statusBarStateController;
- DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
- mFlingAnimationUtils = new FlingAnimationUtils(displayMetrics,
- 0.6f /* maxLengthSeconds */, 0.6f /* speedUpFactor */);
- mFlingAnimationUtilsClosing = new FlingAnimationUtils(displayMetrics,
- 0.5f /* maxLengthSeconds */, 0.6f /* speedUpFactor */);
- mFlingAnimationUtilsDismissing = new FlingAnimationUtils(displayMetrics,
- 0.5f /* maxLengthSeconds */, 0.2f /* speedUpFactor */, 0.6f /* x2 */,
- 0.84f /* y2 */);
- mBounceInterpolator = new BounceInterpolator();
- mFalsingManager = falsingManager;
- mDozeLog = dozeLog;
- mNotificationsDragEnabled =
- getResources().getBoolean(R.bool.config_enableNotificationShadeDrag);
- mVibratorHelper = Dependency.get(VibratorHelper.class);
- mVibrateOnOpening = mContext.getResources().getBoolean(
- R.bool.config_vibrateOnIconAnimation);
}
- protected void loadDimens() {
- final Resources res = getContext().getResources();
- final ViewConfiguration configuration = ViewConfiguration.get(getContext());
- mTouchSlop = configuration.getScaledTouchSlop();
- mHintDistance = res.getDimension(R.dimen.hint_move_distance);
- mUnlockFalsingThreshold = res.getDimensionPixelSize(R.dimen.unlock_falsing_threshold);
+ public PanelView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
}
- private void addMovement(MotionEvent event) {
- // Add movement to velocity tracker using raw screen X and Y coordinates instead
- // of window coordinates because the window frame may be moving at the same time.
- float deltaX = event.getRawX() - event.getX();
- float deltaY = event.getRawY() - event.getY();
- event.offsetLocation(deltaX, deltaY);
- mVelocityTracker.addMovement(event);
- event.offsetLocation(-deltaX, -deltaY);
+ public PanelView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
}
- public void setTouchAndAnimationDisabled(boolean disabled) {
- mTouchDisabled = disabled;
- if (mTouchDisabled) {
- cancelHeightAnimator();
- if (mTracking) {
- onTrackingStopped(true /* expanded */);
- }
- notifyExpandingFinished();
- }
+ public void setOnTouchListener(PanelViewController.TouchHandler touchHandler) {
+ super.setOnTouchListener(touchHandler);
+ mTouchHandler = touchHandler;
}
- public void startExpandLatencyTracking() {
- if (LatencyTracker.isEnabled(mContext)) {
- LatencyTracker.getInstance(mContext).onActionStart(
- LatencyTracker.ACTION_EXPAND_PANEL);
- mExpandLatencyTracking = true;
- }
- }
-
- @Override
- public boolean onTouchEvent(MotionEvent event) {
- if (mInstantExpanding
- || (mTouchDisabled && event.getActionMasked() != MotionEvent.ACTION_CANCEL)
- || (mMotionAborted && event.getActionMasked() != MotionEvent.ACTION_DOWN)) {
- return false;
- }
-
- // If dragging should not expand the notifications shade, then return false.
- if (!mNotificationsDragEnabled) {
- if (mTracking) {
- // Turn off tracking if it's on or the shade can get stuck in the down position.
- onTrackingStopped(true /* expand */);
- }
- return false;
- }
-
- // On expanding, single mouse click expands the panel instead of dragging.
- if (isFullyCollapsed() && event.isFromSource(InputDevice.SOURCE_MOUSE)) {
- if (event.getAction() == MotionEvent.ACTION_UP) {
- expand(true);
- }
- return true;
- }
-
- /*
- * We capture touch events here and update the expand height here in case according to
- * the users fingers. This also handles multi-touch.
- *
- * If the user just clicks shortly, we show a quick peek of the shade.
- *
- * Flinging is also enabled in order to open or close the shade.
- */
-
- int pointerIndex = event.findPointerIndex(mTrackingPointer);
- if (pointerIndex < 0) {
- pointerIndex = 0;
- mTrackingPointer = event.getPointerId(pointerIndex);
- }
- final float x = event.getX(pointerIndex);
- final float y = event.getY(pointerIndex);
-
- if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
- mGestureWaitForTouchSlop = shouldGestureWaitForTouchSlop();
- mIgnoreXTouchSlop = isFullyCollapsed() || shouldGestureIgnoreXTouchSlop(x, y);
- }
-
- switch (event.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
- mJustPeeked = false;
- mMinExpandHeight = 0.0f;
- mPanelClosedOnDown = isFullyCollapsed();
- mHasLayoutedSinceDown = false;
- mUpdateFlingOnLayout = false;
- mMotionAborted = false;
- mPeekTouching = mPanelClosedOnDown;
- mDownTime = SystemClock.uptimeMillis();
- mTouchAboveFalsingThreshold = false;
- mCollapsedAndHeadsUpOnDown = isFullyCollapsed()
- && mHeadsUpManager.hasPinnedHeadsUp();
- addMovement(event);
- if (!mGestureWaitForTouchSlop || (mHeightAnimator != null && !mHintAnimationRunning)
- || mPeekAnimator != null) {
- mTouchSlopExceeded = (mHeightAnimator != null && !mHintAnimationRunning)
- || mPeekAnimator != null || mTouchSlopExceededBeforeDown;
- cancelHeightAnimator();
- cancelPeek();
- onTrackingStarted();
- }
- if (isFullyCollapsed() && !mHeadsUpManager.hasPinnedHeadsUp()
- && !mStatusBar.isBouncerShowing()) {
- startOpening(event);
- }
- break;
-
- case MotionEvent.ACTION_POINTER_UP:
- final int upPointer = event.getPointerId(event.getActionIndex());
- if (mTrackingPointer == upPointer) {
- // gesture is ongoing, find a new pointer to track
- final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
- final float newY = event.getY(newIndex);
- final float newX = event.getX(newIndex);
- mTrackingPointer = event.getPointerId(newIndex);
- startExpandMotion(newX, newY, true /* startTracking */, mExpandedHeight);
- }
- break;
- case MotionEvent.ACTION_POINTER_DOWN:
- if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
- mMotionAborted = true;
- endMotionEvent(event, x, y, true /* forceCancel */);
- return false;
- }
- break;
- case MotionEvent.ACTION_MOVE:
- addMovement(event);
- float h = y - mInitialTouchY;
-
- // If the panel was collapsed when touching, we only need to check for the
- // y-component of the gesture, as we have no conflicting horizontal gesture.
- if (Math.abs(h) > mTouchSlop
- && (Math.abs(h) > Math.abs(x - mInitialTouchX)
- || mIgnoreXTouchSlop)) {
- mTouchSlopExceeded = true;
- if (mGestureWaitForTouchSlop && !mTracking && !mCollapsedAndHeadsUpOnDown) {
- if (!mJustPeeked && mInitialOffsetOnTouch != 0f) {
- startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
- h = 0;
- }
- cancelHeightAnimator();
- onTrackingStarted();
- }
- }
- float newHeight = Math.max(0, h + mInitialOffsetOnTouch);
- if (newHeight > mPeekHeight) {
- if (mPeekAnimator != null) {
- mPeekAnimator.cancel();
- }
- mJustPeeked = false;
- } else if (mPeekAnimator == null && mJustPeeked) {
- // The initial peek has finished, but we haven't dragged as far yet, lets
- // speed it up by starting at the peek height.
- mInitialOffsetOnTouch = mExpandedHeight;
- mInitialTouchY = y;
- mMinExpandHeight = mExpandedHeight;
- mJustPeeked = false;
- }
- newHeight = Math.max(newHeight, mMinExpandHeight);
- if (-h >= getFalsingThreshold()) {
- mTouchAboveFalsingThreshold = true;
- mUpwardsWhenTresholdReached = isDirectionUpwards(x, y);
- }
- if (!mJustPeeked && (!mGestureWaitForTouchSlop || mTracking) &&
- !isTrackingBlocked()) {
- setExpandedHeightInternal(newHeight);
- }
- break;
-
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- addMovement(event);
- endMotionEvent(event, x, y, false /* forceCancel */);
- break;
- }
- return !mGestureWaitForTouchSlop || mTracking;
- }
-
- private void startOpening(MotionEvent event) {
- runPeekAnimation(INITIAL_OPENING_PEEK_DURATION, getOpeningHeight(),
- false /* collapseWhenFinished */);
- notifyBarPanelExpansionChanged();
- maybeVibrateOnOpening();
-
- //TODO: keyguard opens QS a different way; log that too?
-
- // Log the position of the swipe that opened the panel
- float width = mStatusBar.getDisplayWidth();
- float height = mStatusBar.getDisplayHeight();
- int rot = mStatusBar.getRotation();
-
- mLockscreenGestureLogger.writeAtFractionalPosition(MetricsEvent.ACTION_PANEL_VIEW_EXPAND,
- (int) (event.getX() / width * 100),
- (int) (event.getY() / height * 100),
- rot);
- }
-
- protected void maybeVibrateOnOpening() {
- if (mVibrateOnOpening) {
- mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
- }
- }
-
- protected abstract float getOpeningHeight();
-
- /**
- * @return whether the swiping direction is upwards and above a 45 degree angle compared to the
- * horizontal direction
- */
- private boolean isDirectionUpwards(float x, float y) {
- float xDiff = x - mInitialTouchX;
- float yDiff = y - mInitialTouchY;
- if (yDiff >= 0) {
- return false;
- }
- return Math.abs(yDiff) >= Math.abs(xDiff);
- }
-
- protected void startExpandingFromPeek() {
- mStatusBar.handlePeekToExpandTransistion();
- }
-
- protected void startExpandMotion(float newX, float newY, boolean startTracking,
- float expandedHeight) {
- mInitialOffsetOnTouch = expandedHeight;
- mInitialTouchY = newY;
- mInitialTouchX = newX;
- if (startTracking) {
- mTouchSlopExceeded = true;
- setExpandedHeight(mInitialOffsetOnTouch);
- onTrackingStarted();
- }
- }
-
- private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) {
- mTrackingPointer = -1;
- if ((mTracking && mTouchSlopExceeded)
- || Math.abs(x - mInitialTouchX) > mTouchSlop
- || Math.abs(y - mInitialTouchY) > mTouchSlop
- || event.getActionMasked() == MotionEvent.ACTION_CANCEL
- || forceCancel) {
- mVelocityTracker.computeCurrentVelocity(1000);
- float vel = mVelocityTracker.getYVelocity();
- float vectorVel = (float) Math.hypot(
- mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
-
- boolean expand = flingExpands(vel, vectorVel, x, y)
- || event.getActionMasked() == MotionEvent.ACTION_CANCEL
- || forceCancel;
- mDozeLog.traceFling(expand, mTouchAboveFalsingThreshold,
- mStatusBar.isFalsingThresholdNeeded(),
- mStatusBar.isWakeUpComingFromTouch());
- // Log collapse gesture if on lock screen.
- if (!expand && mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
- float displayDensity = mStatusBar.getDisplayDensity();
- int heightDp = (int) Math.abs((y - mInitialTouchY) / displayDensity);
- int velocityDp = (int) Math.abs(vel / displayDensity);
- mLockscreenGestureLogger.write(
- MetricsEvent.ACTION_LS_UNLOCK,
- heightDp, velocityDp);
- }
- fling(vel, expand, isFalseTouch(x, y));
- onTrackingStopped(expand);
- mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown;
- if (mUpdateFlingOnLayout) {
- mUpdateFlingVelocity = vel;
- }
- } else if (mPanelClosedOnDown && !mHeadsUpManager.hasPinnedHeadsUp() && !mTracking
- && !mStatusBar.isBouncerShowing()
- && !mKeyguardStateController.isKeyguardFadingAway()) {
- long timePassed = SystemClock.uptimeMillis() - mDownTime;
- if (timePassed < ViewConfiguration.getLongPressTimeout()) {
- // Lets show the user that he can actually expand the panel
- runPeekAnimation(PEEK_ANIMATION_DURATION, getPeekHeight(), true /* collapseWhenFinished */);
- } else {
- // We need to collapse the panel since we peeked to the small height.
- postOnAnimation(mPostCollapseRunnable);
- }
- } else if (!mStatusBar.isBouncerShowing()) {
- boolean expands = onEmptySpaceClick(mInitialTouchX);
- onTrackingStopped(expands);
- }
-
- mVelocityTracker.clear();
- mPeekTouching = false;
- }
-
- protected float getCurrentExpandVelocity() {
- mVelocityTracker.computeCurrentVelocity(1000);
- return mVelocityTracker.getYVelocity();
- }
-
- private int getFalsingThreshold() {
- float factor = mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
- return (int) (mUnlockFalsingThreshold * factor);
- }
-
- protected abstract boolean shouldGestureWaitForTouchSlop();
-
- protected abstract boolean shouldGestureIgnoreXTouchSlop(float x, float y);
-
- protected void onTrackingStopped(boolean expand) {
- mTracking = false;
- mBar.onTrackingStopped(expand);
- notifyBarPanelExpansionChanged();
- }
-
- protected void onTrackingStarted() {
- endClosing();
- mTracking = true;
- mBar.onTrackingStarted();
- notifyExpandingStarted();
- notifyBarPanelExpansionChanged();
+ public void setOnConfigurationChangedListener(OnConfigurationChangedListener listener) {
+ mOnConfigurationChangedListener = listener;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
- if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled
- || (mMotionAborted && event.getActionMasked() != MotionEvent.ACTION_DOWN)) {
- return false;
- }
-
- /*
- * If the user drags anywhere inside the panel we intercept it if the movement is
- * upwards. This allows closing the shade from anywhere inside the panel.
- *
- * We only do this if the current content is scrolled to the bottom,
- * i.e isScrolledToBottom() is true and therefore there is no conflicting scrolling gesture
- * possible.
- */
- int pointerIndex = event.findPointerIndex(mTrackingPointer);
- if (pointerIndex < 0) {
- pointerIndex = 0;
- mTrackingPointer = event.getPointerId(pointerIndex);
- }
- final float x = event.getX(pointerIndex);
- final float y = event.getY(pointerIndex);
- boolean scrolledToBottom = isScrolledToBottom();
-
- switch (event.getActionMasked()) {
- case MotionEvent.ACTION_DOWN:
- mStatusBar.userActivity();
- mAnimatingOnDown = mHeightAnimator != null;
- mMinExpandHeight = 0.0f;
- mDownTime = SystemClock.uptimeMillis();
- if (mAnimatingOnDown && mClosing && !mHintAnimationRunning
- || mPeekAnimator != null) {
- cancelHeightAnimator();
- cancelPeek();
- mTouchSlopExceeded = true;
- return true;
- }
- mInitialTouchY = y;
- mInitialTouchX = x;
- mTouchStartedInEmptyArea = !isInContentBounds(x, y);
- mTouchSlopExceeded = mTouchSlopExceededBeforeDown;
- mJustPeeked = false;
- mMotionAborted = false;
- mPanelClosedOnDown = isFullyCollapsed();
- mCollapsedAndHeadsUpOnDown = false;
- mHasLayoutedSinceDown = false;
- mUpdateFlingOnLayout = false;
- mTouchAboveFalsingThreshold = false;
- addMovement(event);
- break;
- case MotionEvent.ACTION_POINTER_UP:
- final int upPointer = event.getPointerId(event.getActionIndex());
- if (mTrackingPointer == upPointer) {
- // gesture is ongoing, find a new pointer to track
- final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
- mTrackingPointer = event.getPointerId(newIndex);
- mInitialTouchX = event.getX(newIndex);
- mInitialTouchY = event.getY(newIndex);
- }
- break;
- case MotionEvent.ACTION_POINTER_DOWN:
- if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
- mMotionAborted = true;
- mVelocityTracker.clear();
- }
- break;
- case MotionEvent.ACTION_MOVE:
- final float h = y - mInitialTouchY;
- addMovement(event);
- if (scrolledToBottom || mTouchStartedInEmptyArea || mAnimatingOnDown) {
- float hAbs = Math.abs(h);
- if ((h < -mTouchSlop || (mAnimatingOnDown && hAbs > mTouchSlop))
- && hAbs > Math.abs(x - mInitialTouchX)) {
- cancelHeightAnimator();
- startExpandMotion(x, y, true /* startTracking */, mExpandedHeight);
- return true;
- }
- }
- break;
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
- mVelocityTracker.clear();
- break;
- }
- return false;
- }
-
- /**
- * @return Whether a pair of coordinates are inside the visible view content bounds.
- */
- protected abstract boolean isInContentBounds(float x, float y);
-
- protected void cancelHeightAnimator() {
- if (mHeightAnimator != null) {
- if (mHeightAnimator.isRunning()) {
- mPanelUpdateWhenAnimatorEnds = false;
- }
- mHeightAnimator.cancel();
- }
- endClosing();
- }
-
- private void endClosing() {
- if (mClosing) {
- mClosing = false;
- onClosingFinished();
- }
- }
-
- protected boolean isScrolledToBottom() {
- return true;
- }
-
- protected float getContentHeight() {
- return mExpandedHeight;
+ return mTouchHandler.onInterceptTouchEvent(event);
}
@Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- loadDimens();
+ public void dispatchConfigurationChanged(Configuration newConfig) {
+ super.dispatchConfigurationChanged(newConfig);
+ mOnConfigurationChangedListener.onConfigurationChanged(newConfig);
}
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- loadDimens();
- }
-
- /**
- * @param vel the current vertical velocity of the motion
- * @param vectorVel the length of the vectorial velocity
- * @return whether a fling should expands the panel; contracts otherwise
- */
- protected boolean flingExpands(float vel, float vectorVel, float x, float y) {
- if (mFalsingManager.isUnlockingDisabled()) {
- return true;
- }
-
- if (isFalseTouch(x, y)) {
- return true;
- }
- if (Math.abs(vectorVel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
- return shouldExpandWhenNotFlinging();
- } else {
- return vel > 0;
- }
- }
-
- protected boolean shouldExpandWhenNotFlinging() {
- return getExpandedFraction() > 0.5f;
- }
-
- /**
- * @param x the final x-coordinate when the finger was lifted
- * @param y the final y-coordinate when the finger was lifted
- * @return whether this motion should be regarded as a false touch
- */
- private boolean isFalseTouch(float x, float y) {
- if (!mStatusBar.isFalsingThresholdNeeded()) {
- return false;
- }
- if (mFalsingManager.isClassifierEnabled()) {
- return mFalsingManager.isFalseTouch();
- }
- if (!mTouchAboveFalsingThreshold) {
- return true;
- }
- if (mUpwardsWhenTresholdReached) {
- return false;
- }
- return !isDirectionUpwards(x, y);
- }
-
- protected void fling(float vel, boolean expand) {
- fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, false);
- }
-
- protected void fling(float vel, boolean expand, boolean expandBecauseOfFalsing) {
- fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, expandBecauseOfFalsing);
- }
-
- protected void fling(float vel, boolean expand, float collapseSpeedUpFactor,
- boolean expandBecauseOfFalsing) {
- cancelPeek();
- float target = expand ? getMaxPanelHeight() : 0;
- if (!expand) {
- mClosing = true;
- }
- flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
- }
-
- protected void flingToHeight(float vel, boolean expand, float target,
- float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) {
- // Hack to make the expand transition look nice when clear all button is visible - we make
- // the animation only to the last notification, and then jump to the maximum panel height so
- // clear all just fades in and the decelerating motion is towards the last notification.
- final boolean clearAllExpandHack = expand && fullyExpandedClearAllVisible()
- && mExpandedHeight < getMaxPanelHeight() - getClearAllHeight()
- && !isClearAllVisible();
- if (clearAllExpandHack) {
- target = getMaxPanelHeight() - getClearAllHeight();
- }
- if (target == mExpandedHeight || getOverExpansionAmount() > 0f && expand) {
- notifyExpandingFinished();
- return;
- }
- mOverExpandedBeforeFling = getOverExpansionAmount() > 0f;
- ValueAnimator animator = createHeightAnimator(target);
- if (expand) {
- if (expandBecauseOfFalsing && vel < 0) {
- vel = 0;
- }
- mFlingAnimationUtils.apply(animator, mExpandedHeight, target, vel, getHeight());
- if (vel == 0) {
- animator.setDuration(350);
- }
- } else {
- if (shouldUseDismissingAnimation()) {
- if (vel == 0) {
- animator.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED);
- long duration = (long) (200 + mExpandedHeight / getHeight() * 100);
- animator.setDuration(duration);
- } else {
- mFlingAnimationUtilsDismissing.apply(animator, mExpandedHeight, target, vel,
- getHeight());
- }
- } else {
- mFlingAnimationUtilsClosing
- .apply(animator, mExpandedHeight, target, vel, getHeight());
- }
-
- // Make it shorter if we run a canned animation
- if (vel == 0) {
- animator.setDuration((long) (animator.getDuration() / collapseSpeedUpFactor));
- }
- if (mFixedDuration != NO_FIXED_DURATION) {
- animator.setDuration(mFixedDuration);
- }
- }
- animator.addListener(new AnimatorListenerAdapter() {
- private boolean mCancelled;
-
- @Override
- public void onAnimationCancel(Animator animation) {
- mCancelled = true;
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- if (clearAllExpandHack && !mCancelled) {
- setExpandedHeightInternal(getMaxPanelHeight());
- }
- setAnimator(null);
- if (!mCancelled) {
- notifyExpandingFinished();
- }
- notifyBarPanelExpansionChanged();
- }
- });
- setAnimator(animator);
- animator.start();
- }
-
- protected abstract boolean shouldUseDismissingAnimation();
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mViewName = getResources().getResourceName(getId());
- }
-
- public String getName() {
- return mViewName;
- }
-
- public void setExpandedHeight(float height) {
- if (DEBUG) logf("setExpandedHeight(%.1f)", height);
- setExpandedHeightInternal(height + getOverExpansionPixels());
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- mStatusBar.onPanelLaidOut();
- requestPanelHeightUpdate();
- mHasLayoutedSinceDown = true;
- if (mUpdateFlingOnLayout) {
- abortAnimations();
- fling(mUpdateFlingVelocity, true /* expands */);
- mUpdateFlingOnLayout = false;
- }
- }
-
- protected void requestPanelHeightUpdate() {
- float currentMaxPanelHeight = getMaxPanelHeight();
-
- if (isFullyCollapsed()) {
- return;
- }
-
- if (currentMaxPanelHeight == mExpandedHeight) {
- return;
- }
-
- if (mPeekAnimator != null || mPeekTouching) {
- return;
- }
-
- if (mTracking && !isTrackingBlocked()) {
- return;
- }
-
- if (mHeightAnimator != null) {
- mPanelUpdateWhenAnimatorEnds = true;
- return;
- }
-
- setExpandedHeight(currentMaxPanelHeight);
- }
-
- public void setExpandedHeightInternal(float h) {
- if (mExpandLatencyTracking && h != 0f) {
- DejankUtils.postAfterTraversal(() -> LatencyTracker.getInstance(mContext).onActionEnd(
- LatencyTracker.ACTION_EXPAND_PANEL));
- mExpandLatencyTracking = false;
- }
- float fhWithoutOverExpansion = getMaxPanelHeight() - getOverExpansionAmount();
- if (mHeightAnimator == null) {
- float overExpansionPixels = Math.max(0, h - fhWithoutOverExpansion);
- if (getOverExpansionPixels() != overExpansionPixels && mTracking) {
- setOverExpansion(overExpansionPixels, true /* isPixels */);
- }
- mExpandedHeight = Math.min(h, fhWithoutOverExpansion) + getOverExpansionAmount();
- } else {
- mExpandedHeight = h;
- if (mOverExpandedBeforeFling) {
- setOverExpansion(Math.max(0, h - fhWithoutOverExpansion), false /* isPixels */);
- }
- }
-
- // If we are closing the panel and we are almost there due to a slow decelerating
- // interpolator, abort the animation.
- if (mExpandedHeight < 1f && mExpandedHeight != 0f && mClosing) {
- mExpandedHeight = 0f;
- if (mHeightAnimator != null) {
- mHeightAnimator.end();
- }
- }
- mExpandedFraction = Math.min(1f,
- fhWithoutOverExpansion == 0 ? 0 : mExpandedHeight / fhWithoutOverExpansion);
- onHeightUpdated(mExpandedHeight);
- notifyBarPanelExpansionChanged();
- }
-
- /**
- * @return true if the panel tracking should be temporarily blocked; this is used when a
- * conflicting gesture (opening QS) is happening
- */
- protected abstract boolean isTrackingBlocked();
-
- protected abstract void setOverExpansion(float overExpansion, boolean isPixels);
-
- protected abstract void onHeightUpdated(float expandedHeight);
-
- protected abstract float getOverExpansionAmount();
-
- protected abstract float getOverExpansionPixels();
-
- /**
- * This returns the maximum height of the panel. Children should override this if their
- * desired height is not the full height.
- *
- * @return the default implementation simply returns the maximum height.
- */
- protected abstract int getMaxPanelHeight();
-
- public void setExpandedFraction(float frac) {
- setExpandedHeight(getMaxPanelHeight() * frac);
- }
-
- public float getExpandedHeight() {
- return mExpandedHeight;
- }
-
- public float getExpandedFraction() {
- return mExpandedFraction;
- }
-
- public boolean isFullyExpanded() {
- return mExpandedHeight >= getMaxPanelHeight();
- }
-
- public boolean isFullyCollapsed() {
- return mExpandedFraction <= 0.0f;
- }
-
- public boolean isCollapsing() {
- return mClosing || mLaunchingNotification;
- }
-
- public boolean isTracking() {
- return mTracking;
- }
-
- public void setBar(PanelBar panelBar) {
- mBar = panelBar;
- }
-
- public void collapse(boolean delayed, float speedUpFactor) {
- if (DEBUG) logf("collapse: " + this);
- if (canPanelBeCollapsed()) {
- cancelHeightAnimator();
- notifyExpandingStarted();
-
- // Set after notifyExpandingStarted, as notifyExpandingStarted resets the closing state.
- mClosing = true;
- if (delayed) {
- mNextCollapseSpeedUpFactor = speedUpFactor;
- postDelayed(mFlingCollapseRunnable, 120);
- } else {
- fling(0, false /* expand */, speedUpFactor, false /* expandBecauseOfFalsing */);
- }
- }
- }
-
- public boolean canPanelBeCollapsed() {
- return !isFullyCollapsed() && !mTracking && !mClosing;
- }
-
- private final Runnable mFlingCollapseRunnable = new Runnable() {
- @Override
- public void run() {
- fling(0, false /* expand */, mNextCollapseSpeedUpFactor,
- false /* expandBecauseOfFalsing */);
- }
- };
-
- public void cancelPeek() {
- boolean cancelled = false;
- if (mPeekAnimator != null) {
- cancelled = true;
- mPeekAnimator.cancel();
- }
-
- if (cancelled) {
- // When peeking, we already tell mBar that we expanded ourselves. Make sure that we also
- // notify mBar that we might have closed ourselves.
- notifyBarPanelExpansionChanged();
- }
- }
-
- public void expand(final boolean animate) {
- if (!isFullyCollapsed() && !isCollapsing()) {
- return;
- }
-
- mInstantExpanding = true;
- mAnimateAfterExpanding = animate;
- mUpdateFlingOnLayout = false;
- abortAnimations();
- cancelPeek();
- if (mTracking) {
- onTrackingStopped(true /* expands */); // The panel is expanded after this call.
- }
- if (mExpanding) {
- notifyExpandingFinished();
- }
- notifyBarPanelExpansionChanged();
-
- // Wait for window manager to pickup the change, so we know the maximum height of the panel
- // then.
- getViewTreeObserver().addOnGlobalLayoutListener(
- new ViewTreeObserver.OnGlobalLayoutListener() {
- @Override
- public void onGlobalLayout() {
- if (!mInstantExpanding) {
- getViewTreeObserver().removeOnGlobalLayoutListener(this);
- return;
- }
- if (mStatusBar.getStatusBarWindow().getHeight()
- != mStatusBar.getStatusBarHeight()) {
- getViewTreeObserver().removeOnGlobalLayoutListener(this);
- if (mAnimateAfterExpanding) {
- notifyExpandingStarted();
- fling(0, true /* expand */);
- } else {
- setExpandedFraction(1f);
- }
- mInstantExpanding = false;
- }
- }
- });
-
- // Make sure a layout really happens.
- requestLayout();
- }
-
- public void instantCollapse() {
- abortAnimations();
- setExpandedFraction(0f);
- if (mExpanding) {
- notifyExpandingFinished();
- }
- if (mInstantExpanding) {
- mInstantExpanding = false;
- notifyBarPanelExpansionChanged();
- }
- }
-
- private void abortAnimations() {
- cancelPeek();
- cancelHeightAnimator();
- removeCallbacks(mPostCollapseRunnable);
- removeCallbacks(mFlingCollapseRunnable);
- }
-
- protected void onClosingFinished() {
- mBar.onClosingFinished();
- }
-
-
- protected void startUnlockHintAnimation() {
-
- // We don't need to hint the user if an animation is already running or the user is changing
- // the expansion.
- if (mHeightAnimator != null || mTracking) {
- return;
- }
- cancelPeek();
- notifyExpandingStarted();
- startUnlockHintAnimationPhase1(() -> {
- notifyExpandingFinished();
- onUnlockHintFinished();
- mHintAnimationRunning = false;
- });
- onUnlockHintStarted();
- mHintAnimationRunning = true;
- }
-
- protected void onUnlockHintFinished() {
- mStatusBar.onHintFinished();
- }
-
- protected void onUnlockHintStarted() {
- mStatusBar.onUnlockHintStarted();
- }
-
- public boolean isUnlockHintRunning() {
- return mHintAnimationRunning;
- }
-
- /**
- * Phase 1: Move everything upwards.
- */
- private void startUnlockHintAnimationPhase1(final Runnable onAnimationFinished) {
- float target = Math.max(0, getMaxPanelHeight() - mHintDistance);
- ValueAnimator animator = createHeightAnimator(target);
- animator.setDuration(250);
- animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- animator.addListener(new AnimatorListenerAdapter() {
- private boolean mCancelled;
-
- @Override
- public void onAnimationCancel(Animator animation) {
- mCancelled = true;
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- if (mCancelled) {
- setAnimator(null);
- onAnimationFinished.run();
- } else {
- startUnlockHintAnimationPhase2(onAnimationFinished);
- }
- }
- });
- animator.start();
- setAnimator(animator);
-
- View[] viewsToAnimate = {
- mKeyguardBottomArea.getIndicationArea(),
- mStatusBar.getAmbientIndicationContainer()};
- for (View v : viewsToAnimate) {
- if (v == null) {
- continue;
- }
- v.animate()
- .translationY(-mHintDistance)
- .setDuration(250)
- .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
- .withEndAction(() -> v.animate()
- .translationY(0)
- .setDuration(450)
- .setInterpolator(mBounceInterpolator)
- .start())
- .start();
- }
- }
-
- private void setAnimator(ValueAnimator animator) {
- mHeightAnimator = animator;
- if (animator == null && mPanelUpdateWhenAnimatorEnds) {
- mPanelUpdateWhenAnimatorEnds = false;
- requestPanelHeightUpdate();
- }
- }
-
- /**
- * Phase 2: Bounce down.
- */
- private void startUnlockHintAnimationPhase2(final Runnable onAnimationFinished) {
- ValueAnimator animator = createHeightAnimator(getMaxPanelHeight());
- animator.setDuration(450);
- animator.setInterpolator(mBounceInterpolator);
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- setAnimator(null);
- onAnimationFinished.run();
- notifyBarPanelExpansionChanged();
- }
- });
- animator.start();
- setAnimator(animator);
- }
-
- private ValueAnimator createHeightAnimator(float targetHeight) {
- ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, targetHeight);
- animator.addUpdateListener(
- animation -> setExpandedHeightInternal((float) animation.getAnimatedValue()));
- return animator;
- }
-
- protected void notifyBarPanelExpansionChanged() {
- if (mBar != null) {
- mBar.panelExpansionChanged(mExpandedFraction, mExpandedFraction > 0f
- || mPeekAnimator != null || mInstantExpanding
- || isPanelVisibleBecauseOfHeadsUp() || mTracking || mHeightAnimator != null);
- }
- for (int i = 0; i < mExpansionListeners.size(); i++) {
- mExpansionListeners.get(i).onPanelExpansionChanged(mExpandedFraction, mTracking);
- }
- }
-
- public void addExpansionListener(PanelExpansionListener panelExpansionListener) {
- mExpansionListeners.add(panelExpansionListener);
- }
-
- protected abstract boolean isPanelVisibleBecauseOfHeadsUp();
-
- /**
- * Gets called when the user performs a click anywhere in the empty area of the panel.
- *
- * @return whether the panel will be expanded after the action performed by this method
- */
- protected boolean onEmptySpaceClick(float x) {
- if (mHintAnimationRunning) {
- return true;
- }
- return onMiddleClicked();
- }
-
- protected final Runnable mPostCollapseRunnable = new Runnable() {
- @Override
- public void run() {
- collapse(false /* delayed */, 1.0f /* speedUpFactor */);
- }
- };
-
- protected abstract boolean onMiddleClicked();
-
- protected abstract boolean isDozing();
-
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println(String.format("[PanelView(%s): expandedHeight=%f maxPanelHeight=%d closing=%s"
- + " tracking=%s justPeeked=%s peekAnim=%s%s timeAnim=%s%s touchDisabled=%s"
- + "]",
- this.getClass().getSimpleName(),
- getExpandedHeight(),
- getMaxPanelHeight(),
- mClosing?"T":"f",
- mTracking?"T":"f",
- mJustPeeked?"T":"f",
- mPeekAnimator, ((mPeekAnimator!=null && mPeekAnimator.isStarted())?" (started)":""),
- mHeightAnimator, ((mHeightAnimator !=null && mHeightAnimator.isStarted())?" (started)":""),
- mTouchDisabled?"T":"f"
- ));
- }
-
- public abstract void resetViews(boolean animate);
-
- protected abstract float getPeekHeight();
- /**
- * @return whether "Clear all" button will be visible when the panel is fully expanded
- */
- protected abstract boolean fullyExpandedClearAllVisible();
-
- protected abstract boolean isClearAllVisible();
-
- /**
- * @return the height of the clear all button, in pixels
- */
- protected abstract int getClearAllHeight();
-
- public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
- mHeadsUpManager = headsUpManager;
- }
-
- public void setLaunchingNotification(boolean launchingNotification) {
- mLaunchingNotification = launchingNotification;
- }
-
- public void collapseWithDuration(int animationDuration) {
- mFixedDuration = animationDuration;
- collapse(false /* delayed */, 1.0f /* speedUpFactor */);
- mFixedDuration = NO_FIXED_DURATION;
+ interface OnConfigurationChangedListener {
+ void onConfigurationChanged(Configuration newConfig);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
new file mode 100644
index 0000000..3d8e09a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -0,0 +1,1297 @@
+/*
+ * 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 com.android.systemui.statusbar.phone;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.SystemClock;
+import android.os.VibrationEffect;
+import android.util.Log;
+import android.view.InputDevice;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver;
+import android.view.animation.Interpolator;
+
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.LatencyTracker;
+import com.android.systemui.DejankUtils;
+import com.android.systemui.Interpolators;
+import com.android.systemui.R;
+import com.android.systemui.doze.DozeLog;
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.statusbar.FlingAnimationUtils;
+import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.VibratorHelper;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+public abstract class PanelViewController {
+ public static final boolean DEBUG = PanelBar.DEBUG;
+ public static final String TAG = PanelView.class.getSimpleName();
+ private static final int INITIAL_OPENING_PEEK_DURATION = 200;
+ private static final int PEEK_ANIMATION_DURATION = 360;
+ private static final int NO_FIXED_DURATION = -1;
+ protected long mDownTime;
+ protected boolean mTouchSlopExceededBeforeDown;
+ private float mMinExpandHeight;
+ private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
+ private boolean mPanelUpdateWhenAnimatorEnds;
+ private boolean mVibrateOnOpening;
+ protected boolean mLaunchingNotification;
+ private int mFixedDuration = NO_FIXED_DURATION;
+ protected ArrayList<PanelExpansionListener> mExpansionListeners = new ArrayList<>();
+
+ private void logf(String fmt, Object... args) {
+ Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
+ }
+
+ protected StatusBar mStatusBar;
+ protected HeadsUpManagerPhone mHeadsUpManager;
+
+ private float mPeekHeight;
+ private float mHintDistance;
+ private float mInitialOffsetOnTouch;
+ private boolean mCollapsedAndHeadsUpOnDown;
+ private float mExpandedFraction = 0;
+ protected float mExpandedHeight = 0;
+ private boolean mPanelClosedOnDown;
+ private boolean mHasLayoutedSinceDown;
+ private float mUpdateFlingVelocity;
+ private boolean mUpdateFlingOnLayout;
+ private boolean mPeekTouching;
+ private boolean mJustPeeked;
+ private boolean mClosing;
+ protected boolean mTracking;
+ private boolean mTouchSlopExceeded;
+ private int mTrackingPointer;
+ protected int mTouchSlop;
+ protected boolean mHintAnimationRunning;
+ private boolean mOverExpandedBeforeFling;
+ private boolean mTouchAboveFalsingThreshold;
+ private int mUnlockFalsingThreshold;
+ private boolean mTouchStartedInEmptyArea;
+ private boolean mMotionAborted;
+ private boolean mUpwardsWhenThresholdReached;
+ private boolean mAnimatingOnDown;
+
+ private ValueAnimator mHeightAnimator;
+ private ObjectAnimator mPeekAnimator;
+ private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
+ private FlingAnimationUtils mFlingAnimationUtils;
+ private FlingAnimationUtils mFlingAnimationUtilsClosing;
+ private FlingAnimationUtils mFlingAnimationUtilsDismissing;
+ private final LatencyTracker mLatencyTracker;
+ private final FalsingManager mFalsingManager;
+ private final DozeLog mDozeLog;
+ private final VibratorHelper mVibratorHelper;
+
+ /**
+ * Whether an instant expand request is currently pending and we are just waiting for layout.
+ */
+ private boolean mInstantExpanding;
+ private boolean mAnimateAfterExpanding;
+
+ PanelBar mBar;
+
+ private String mViewName;
+ private float mInitialTouchY;
+ private float mInitialTouchX;
+ private boolean mTouchDisabled;
+
+ /**
+ * Whether or not the PanelView can be expanded or collapsed with a drag.
+ */
+ private boolean mNotificationsDragEnabled;
+
+ private Interpolator mBounceInterpolator;
+ protected KeyguardBottomAreaView mKeyguardBottomArea;
+
+ /**
+ * Speed-up factor to be used when {@link #mFlingCollapseRunnable} runs the next time.
+ */
+ private float mNextCollapseSpeedUpFactor = 1.0f;
+
+ protected boolean mExpanding;
+ private boolean mGestureWaitForTouchSlop;
+ private boolean mIgnoreXTouchSlop;
+ private boolean mExpandLatencyTracking;
+ private final PanelView mView;
+ protected final Resources mResources;
+ protected final KeyguardStateController mKeyguardStateController;
+ protected final SysuiStatusBarStateController mStatusBarStateController;
+
+ protected void onExpandingFinished() {
+ mBar.onExpandingFinished();
+ }
+
+ protected void onExpandingStarted() {
+ }
+
+ private void notifyExpandingStarted() {
+ if (!mExpanding) {
+ mExpanding = true;
+ onExpandingStarted();
+ }
+ }
+
+ protected final void notifyExpandingFinished() {
+ endClosing();
+ if (mExpanding) {
+ mExpanding = false;
+ onExpandingFinished();
+ }
+ }
+
+ private void runPeekAnimation(long duration, float peekHeight, boolean collapseWhenFinished) {
+ mPeekHeight = peekHeight;
+ if (DEBUG) logf("peek to height=%.1f", mPeekHeight);
+ if (mHeightAnimator != null) {
+ return;
+ }
+ if (mPeekAnimator != null) {
+ mPeekAnimator.cancel();
+ }
+ mPeekAnimator = ObjectAnimator.ofFloat(this, "expandedHeight", mPeekHeight).setDuration(
+ duration);
+ mPeekAnimator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
+ mPeekAnimator.addListener(new AnimatorListenerAdapter() {
+ private boolean mCancelled;
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCancelled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mPeekAnimator = null;
+ if (!mCancelled && collapseWhenFinished) {
+ mView.postOnAnimation(mPostCollapseRunnable);
+ }
+
+ }
+ });
+ notifyExpandingStarted();
+ mPeekAnimator.start();
+ mJustPeeked = true;
+ }
+
+ public PanelViewController(PanelView view,
+ FalsingManager falsingManager, DozeLog dozeLog,
+ KeyguardStateController keyguardStateController,
+ SysuiStatusBarStateController statusBarStateController, VibratorHelper vibratorHelper,
+ LatencyTracker latencyTracker, FlingAnimationUtils.Builder flingAnimationUtilsBuilder) {
+ mView = view;
+ mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
+ @Override
+ public void onViewAttachedToWindow(View v) {
+ mViewName = mResources.getResourceName(mView.getId());
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View v) {
+ }
+ });
+
+ mView.addOnLayoutChangeListener(createLayoutChangeListener());
+ mView.setOnTouchListener(createTouchHandler());
+ mView.setOnConfigurationChangedListener(createOnConfigurationChangedListener());
+
+ mResources = mView.getResources();
+ mKeyguardStateController = keyguardStateController;
+ mStatusBarStateController = statusBarStateController;
+ mFlingAnimationUtils = flingAnimationUtilsBuilder
+ .reset()
+ .setMaxLengthSeconds(0.6f)
+ .setSpeedUpFactor(0.6f)
+ .build();
+ mFlingAnimationUtilsClosing = flingAnimationUtilsBuilder
+ .reset()
+ .setMaxLengthSeconds(0.5f)
+ .setSpeedUpFactor(0.6f)
+ .build();
+ mFlingAnimationUtilsDismissing = flingAnimationUtilsBuilder
+ .reset()
+ .setMaxLengthSeconds(0.5f)
+ .setSpeedUpFactor(0.6f)
+ .setX2(0.6f)
+ .setY2(0.84f)
+ .build();
+ mLatencyTracker = latencyTracker;
+ mBounceInterpolator = new BounceInterpolator();
+ mFalsingManager = falsingManager;
+ mDozeLog = dozeLog;
+ mNotificationsDragEnabled = mResources.getBoolean(
+ R.bool.config_enableNotificationShadeDrag);
+ mVibratorHelper = vibratorHelper;
+ mVibrateOnOpening = mResources.getBoolean(R.bool.config_vibrateOnIconAnimation);
+ }
+
+ protected void loadDimens() {
+ final ViewConfiguration configuration = ViewConfiguration.get(mView.getContext());
+ mTouchSlop = configuration.getScaledTouchSlop();
+ mHintDistance = mResources.getDimension(R.dimen.hint_move_distance);
+ mUnlockFalsingThreshold = mResources.getDimensionPixelSize(
+ R.dimen.unlock_falsing_threshold);
+ }
+
+ private void addMovement(MotionEvent event) {
+ // Add movement to velocity tracker using raw screen X and Y coordinates instead
+ // of window coordinates because the window frame may be moving at the same time.
+ float deltaX = event.getRawX() - event.getX();
+ float deltaY = event.getRawY() - event.getY();
+ event.offsetLocation(deltaX, deltaY);
+ mVelocityTracker.addMovement(event);
+ event.offsetLocation(-deltaX, -deltaY);
+ }
+
+ public void setTouchAndAnimationDisabled(boolean disabled) {
+ mTouchDisabled = disabled;
+ if (mTouchDisabled) {
+ cancelHeightAnimator();
+ if (mTracking) {
+ onTrackingStopped(true /* expanded */);
+ }
+ notifyExpandingFinished();
+ }
+ }
+
+ public void startExpandLatencyTracking() {
+ if (mLatencyTracker.isEnabled()) {
+ mLatencyTracker.onActionStart(LatencyTracker.ACTION_EXPAND_PANEL);
+ mExpandLatencyTracking = true;
+ }
+ }
+
+ private void startOpening(MotionEvent event) {
+ runPeekAnimation(INITIAL_OPENING_PEEK_DURATION, getOpeningHeight(),
+ false /* collapseWhenFinished */);
+ notifyBarPanelExpansionChanged();
+ maybeVibrateOnOpening();
+
+ //TODO: keyguard opens QS a different way; log that too?
+
+ // Log the position of the swipe that opened the panel
+ float width = mStatusBar.getDisplayWidth();
+ float height = mStatusBar.getDisplayHeight();
+ int rot = mStatusBar.getRotation();
+
+ mLockscreenGestureLogger.writeAtFractionalPosition(MetricsEvent.ACTION_PANEL_VIEW_EXPAND,
+ (int) (event.getX() / width * 100), (int) (event.getY() / height * 100), rot);
+ }
+
+ protected void maybeVibrateOnOpening() {
+ if (mVibrateOnOpening) {
+ mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
+ }
+ }
+
+ protected abstract float getOpeningHeight();
+
+ /**
+ * @return whether the swiping direction is upwards and above a 45 degree angle compared to the
+ * horizontal direction
+ */
+ private boolean isDirectionUpwards(float x, float y) {
+ float xDiff = x - mInitialTouchX;
+ float yDiff = y - mInitialTouchY;
+ if (yDiff >= 0) {
+ return false;
+ }
+ return Math.abs(yDiff) >= Math.abs(xDiff);
+ }
+
+ protected void startExpandingFromPeek() {
+ mStatusBar.handlePeekToExpandTransistion();
+ }
+
+ protected void startExpandMotion(float newX, float newY, boolean startTracking,
+ float expandedHeight) {
+ mInitialOffsetOnTouch = expandedHeight;
+ mInitialTouchY = newY;
+ mInitialTouchX = newX;
+ if (startTracking) {
+ mTouchSlopExceeded = true;
+ setExpandedHeight(mInitialOffsetOnTouch);
+ onTrackingStarted();
+ }
+ }
+
+ private void endMotionEvent(MotionEvent event, float x, float y, boolean forceCancel) {
+ mTrackingPointer = -1;
+ if ((mTracking && mTouchSlopExceeded) || Math.abs(x - mInitialTouchX) > mTouchSlop
+ || Math.abs(y - mInitialTouchY) > mTouchSlop
+ || event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel) {
+ mVelocityTracker.computeCurrentVelocity(1000);
+ float vel = mVelocityTracker.getYVelocity();
+ float vectorVel = (float) Math.hypot(
+ mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
+
+ boolean expand = flingExpands(vel, vectorVel, x, y)
+ || event.getActionMasked() == MotionEvent.ACTION_CANCEL || forceCancel;
+ mDozeLog.traceFling(expand, mTouchAboveFalsingThreshold,
+ mStatusBar.isFalsingThresholdNeeded(), mStatusBar.isWakeUpComingFromTouch());
+ // Log collapse gesture if on lock screen.
+ if (!expand && mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
+ float displayDensity = mStatusBar.getDisplayDensity();
+ int heightDp = (int) Math.abs((y - mInitialTouchY) / displayDensity);
+ int velocityDp = (int) Math.abs(vel / displayDensity);
+ mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_UNLOCK, heightDp, velocityDp);
+ }
+ fling(vel, expand, isFalseTouch(x, y));
+ onTrackingStopped(expand);
+ mUpdateFlingOnLayout = expand && mPanelClosedOnDown && !mHasLayoutedSinceDown;
+ if (mUpdateFlingOnLayout) {
+ mUpdateFlingVelocity = vel;
+ }
+ } else if (mPanelClosedOnDown && !mHeadsUpManager.hasPinnedHeadsUp() && !mTracking
+ && !mStatusBar.isBouncerShowing()
+ && !mKeyguardStateController.isKeyguardFadingAway()) {
+ long timePassed = SystemClock.uptimeMillis() - mDownTime;
+ if (timePassed < ViewConfiguration.getLongPressTimeout()) {
+ // Lets show the user that he can actually expand the panel
+ runPeekAnimation(
+ PEEK_ANIMATION_DURATION, getPeekHeight(), true /* collapseWhenFinished */);
+ } else {
+ // We need to collapse the panel since we peeked to the small height.
+ mView.postOnAnimation(mPostCollapseRunnable);
+ }
+ } else if (!mStatusBar.isBouncerShowing()) {
+ boolean expands = onEmptySpaceClick(mInitialTouchX);
+ onTrackingStopped(expands);
+ }
+
+ mVelocityTracker.clear();
+ mPeekTouching = false;
+ }
+
+ protected float getCurrentExpandVelocity() {
+ mVelocityTracker.computeCurrentVelocity(1000);
+ return mVelocityTracker.getYVelocity();
+ }
+
+ private int getFalsingThreshold() {
+ float factor = mStatusBar.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
+ return (int) (mUnlockFalsingThreshold * factor);
+ }
+
+ protected abstract boolean shouldGestureWaitForTouchSlop();
+
+ protected abstract boolean shouldGestureIgnoreXTouchSlop(float x, float y);
+
+ protected void onTrackingStopped(boolean expand) {
+ mTracking = false;
+ mBar.onTrackingStopped(expand);
+ notifyBarPanelExpansionChanged();
+ }
+
+ protected void onTrackingStarted() {
+ endClosing();
+ mTracking = true;
+ mBar.onTrackingStarted();
+ notifyExpandingStarted();
+ notifyBarPanelExpansionChanged();
+ }
+
+ /**
+ * @return Whether a pair of coordinates are inside the visible view content bounds.
+ */
+ protected abstract boolean isInContentBounds(float x, float y);
+
+ protected void cancelHeightAnimator() {
+ if (mHeightAnimator != null) {
+ if (mHeightAnimator.isRunning()) {
+ mPanelUpdateWhenAnimatorEnds = false;
+ }
+ mHeightAnimator.cancel();
+ }
+ endClosing();
+ }
+
+ private void endClosing() {
+ if (mClosing) {
+ mClosing = false;
+ onClosingFinished();
+ }
+ }
+
+ protected boolean isScrolledToBottom() {
+ return true;
+ }
+
+ protected float getContentHeight() {
+ return mExpandedHeight;
+ }
+
+ /**
+ * @param vel the current vertical velocity of the motion
+ * @param vectorVel the length of the vectorial velocity
+ * @return whether a fling should expands the panel; contracts otherwise
+ */
+ protected boolean flingExpands(float vel, float vectorVel, float x, float y) {
+ if (mFalsingManager.isUnlockingDisabled()) {
+ return true;
+ }
+
+ if (isFalseTouch(x, y)) {
+ return true;
+ }
+ if (Math.abs(vectorVel) < mFlingAnimationUtils.getMinVelocityPxPerSecond()) {
+ return shouldExpandWhenNotFlinging();
+ } else {
+ return vel > 0;
+ }
+ }
+
+ protected boolean shouldExpandWhenNotFlinging() {
+ return getExpandedFraction() > 0.5f;
+ }
+
+ /**
+ * @param x the final x-coordinate when the finger was lifted
+ * @param y the final y-coordinate when the finger was lifted
+ * @return whether this motion should be regarded as a false touch
+ */
+ private boolean isFalseTouch(float x, float y) {
+ if (!mStatusBar.isFalsingThresholdNeeded()) {
+ return false;
+ }
+ if (mFalsingManager.isClassifierEnabled()) {
+ return mFalsingManager.isFalseTouch();
+ }
+ if (!mTouchAboveFalsingThreshold) {
+ return true;
+ }
+ if (mUpwardsWhenThresholdReached) {
+ return false;
+ }
+ return !isDirectionUpwards(x, y);
+ }
+
+ protected void fling(float vel, boolean expand) {
+ fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, false);
+ }
+
+ protected void fling(float vel, boolean expand, boolean expandBecauseOfFalsing) {
+ fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, expandBecauseOfFalsing);
+ }
+
+ protected void fling(float vel, boolean expand, float collapseSpeedUpFactor,
+ boolean expandBecauseOfFalsing) {
+ cancelPeek();
+ float target = expand ? getMaxPanelHeight() : 0;
+ if (!expand) {
+ mClosing = true;
+ }
+ flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
+ }
+
+ protected void flingToHeight(float vel, boolean expand, float target,
+ float collapseSpeedUpFactor, boolean expandBecauseOfFalsing) {
+ // Hack to make the expand transition look nice when clear all button is visible - we make
+ // the animation only to the last notification, and then jump to the maximum panel height so
+ // clear all just fades in and the decelerating motion is towards the last notification.
+ final boolean
+ clearAllExpandHack =
+ expand && fullyExpandedClearAllVisible()
+ && mExpandedHeight < getMaxPanelHeight() - getClearAllHeight()
+ && !isClearAllVisible();
+ if (clearAllExpandHack) {
+ target = getMaxPanelHeight() - getClearAllHeight();
+ }
+ if (target == mExpandedHeight || getOverExpansionAmount() > 0f && expand) {
+ notifyExpandingFinished();
+ return;
+ }
+ mOverExpandedBeforeFling = getOverExpansionAmount() > 0f;
+ ValueAnimator animator = createHeightAnimator(target);
+ if (expand) {
+ if (expandBecauseOfFalsing && vel < 0) {
+ vel = 0;
+ }
+ mFlingAnimationUtils.apply(animator, mExpandedHeight, target, vel, mView.getHeight());
+ if (vel == 0) {
+ animator.setDuration(350);
+ }
+ } else {
+ if (shouldUseDismissingAnimation()) {
+ if (vel == 0) {
+ animator.setInterpolator(Interpolators.PANEL_CLOSE_ACCELERATED);
+ long duration = (long) (200 + mExpandedHeight / mView.getHeight() * 100);
+ animator.setDuration(duration);
+ } else {
+ mFlingAnimationUtilsDismissing.apply(animator, mExpandedHeight, target, vel,
+ mView.getHeight());
+ }
+ } else {
+ mFlingAnimationUtilsClosing.apply(
+ animator, mExpandedHeight, target, vel, mView.getHeight());
+ }
+
+ // Make it shorter if we run a canned animation
+ if (vel == 0) {
+ animator.setDuration((long) (animator.getDuration() / collapseSpeedUpFactor));
+ }
+ if (mFixedDuration != NO_FIXED_DURATION) {
+ animator.setDuration(mFixedDuration);
+ }
+ }
+ animator.addListener(new AnimatorListenerAdapter() {
+ private boolean mCancelled;
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCancelled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (clearAllExpandHack && !mCancelled) {
+ setExpandedHeightInternal(getMaxPanelHeight());
+ }
+ setAnimator(null);
+ if (!mCancelled) {
+ notifyExpandingFinished();
+ }
+ notifyBarPanelExpansionChanged();
+ }
+ });
+ setAnimator(animator);
+ animator.start();
+ }
+
+ protected abstract boolean shouldUseDismissingAnimation();
+
+ public String getName() {
+ return mViewName;
+ }
+
+ public void setExpandedHeight(float height) {
+ if (DEBUG) logf("setExpandedHeight(%.1f)", height);
+ setExpandedHeightInternal(height + getOverExpansionPixels());
+ }
+
+ protected void requestPanelHeightUpdate() {
+ float currentMaxPanelHeight = getMaxPanelHeight();
+
+ if (isFullyCollapsed()) {
+ return;
+ }
+
+ if (currentMaxPanelHeight == mExpandedHeight) {
+ return;
+ }
+
+ if (mPeekAnimator != null || mPeekTouching) {
+ return;
+ }
+
+ if (mTracking && !isTrackingBlocked()) {
+ return;
+ }
+
+ if (mHeightAnimator != null) {
+ mPanelUpdateWhenAnimatorEnds = true;
+ return;
+ }
+
+ setExpandedHeight(currentMaxPanelHeight);
+ }
+
+ public void setExpandedHeightInternal(float h) {
+ if (mExpandLatencyTracking && h != 0f) {
+ DejankUtils.postAfterTraversal(
+ () -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL));
+ mExpandLatencyTracking = false;
+ }
+ float fhWithoutOverExpansion = getMaxPanelHeight() - getOverExpansionAmount();
+ if (mHeightAnimator == null) {
+ float overExpansionPixels = Math.max(0, h - fhWithoutOverExpansion);
+ if (getOverExpansionPixels() != overExpansionPixels && mTracking) {
+ setOverExpansion(overExpansionPixels, true /* isPixels */);
+ }
+ mExpandedHeight = Math.min(h, fhWithoutOverExpansion) + getOverExpansionAmount();
+ } else {
+ mExpandedHeight = h;
+ if (mOverExpandedBeforeFling) {
+ setOverExpansion(Math.max(0, h - fhWithoutOverExpansion), false /* isPixels */);
+ }
+ }
+
+ // If we are closing the panel and we are almost there due to a slow decelerating
+ // interpolator, abort the animation.
+ if (mExpandedHeight < 1f && mExpandedHeight != 0f && mClosing) {
+ mExpandedHeight = 0f;
+ if (mHeightAnimator != null) {
+ mHeightAnimator.end();
+ }
+ }
+ mExpandedFraction = Math.min(1f,
+ fhWithoutOverExpansion == 0 ? 0 : mExpandedHeight / fhWithoutOverExpansion);
+ onHeightUpdated(mExpandedHeight);
+ notifyBarPanelExpansionChanged();
+ }
+
+ /**
+ * @return true if the panel tracking should be temporarily blocked; this is used when a
+ * conflicting gesture (opening QS) is happening
+ */
+ protected abstract boolean isTrackingBlocked();
+
+ protected abstract void setOverExpansion(float overExpansion, boolean isPixels);
+
+ protected abstract void onHeightUpdated(float expandedHeight);
+
+ protected abstract float getOverExpansionAmount();
+
+ protected abstract float getOverExpansionPixels();
+
+ /**
+ * This returns the maximum height of the panel. Children should override this if their
+ * desired height is not the full height.
+ *
+ * @return the default implementation simply returns the maximum height.
+ */
+ protected abstract int getMaxPanelHeight();
+
+ public void setExpandedFraction(float frac) {
+ setExpandedHeight(getMaxPanelHeight() * frac);
+ }
+
+ public float getExpandedHeight() {
+ return mExpandedHeight;
+ }
+
+ public float getExpandedFraction() {
+ return mExpandedFraction;
+ }
+
+ public boolean isFullyExpanded() {
+ return mExpandedHeight >= getMaxPanelHeight();
+ }
+
+ public boolean isFullyCollapsed() {
+ return mExpandedFraction <= 0.0f;
+ }
+
+ public boolean isCollapsing() {
+ return mClosing || mLaunchingNotification;
+ }
+
+ public boolean isTracking() {
+ return mTracking;
+ }
+
+ public void setBar(PanelBar panelBar) {
+ mBar = panelBar;
+ }
+
+ public void collapse(boolean delayed, float speedUpFactor) {
+ if (DEBUG) logf("collapse: " + this);
+ if (canPanelBeCollapsed()) {
+ cancelHeightAnimator();
+ notifyExpandingStarted();
+
+ // Set after notifyExpandingStarted, as notifyExpandingStarted resets the closing state.
+ mClosing = true;
+ if (delayed) {
+ mNextCollapseSpeedUpFactor = speedUpFactor;
+ mView.postDelayed(mFlingCollapseRunnable, 120);
+ } else {
+ fling(0, false /* expand */, speedUpFactor, false /* expandBecauseOfFalsing */);
+ }
+ }
+ }
+
+ public boolean canPanelBeCollapsed() {
+ return !isFullyCollapsed() && !mTracking && !mClosing;
+ }
+
+ private final Runnable mFlingCollapseRunnable = new Runnable() {
+ @Override
+ public void run() {
+ fling(0, false /* expand */, mNextCollapseSpeedUpFactor,
+ false /* expandBecauseOfFalsing */);
+ }
+ };
+
+ public void cancelPeek() {
+ boolean cancelled = false;
+ if (mPeekAnimator != null) {
+ cancelled = true;
+ mPeekAnimator.cancel();
+ }
+
+ if (cancelled) {
+ // When peeking, we already tell mBar that we expanded ourselves. Make sure that we also
+ // notify mBar that we might have closed ourselves.
+ notifyBarPanelExpansionChanged();
+ }
+ }
+
+ public void expand(final boolean animate) {
+ if (!isFullyCollapsed() && !isCollapsing()) {
+ return;
+ }
+
+ mInstantExpanding = true;
+ mAnimateAfterExpanding = animate;
+ mUpdateFlingOnLayout = false;
+ abortAnimations();
+ cancelPeek();
+ if (mTracking) {
+ onTrackingStopped(true /* expands */); // The panel is expanded after this call.
+ }
+ if (mExpanding) {
+ notifyExpandingFinished();
+ }
+ notifyBarPanelExpansionChanged();
+
+ // Wait for window manager to pickup the change, so we know the maximum height of the panel
+ // then.
+ mView.getViewTreeObserver().addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ if (!mInstantExpanding) {
+ mView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ return;
+ }
+ if (mStatusBar.getStatusBarWindow().getHeight()
+ != mStatusBar.getStatusBarHeight()) {
+ mView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+ if (mAnimateAfterExpanding) {
+ notifyExpandingStarted();
+ fling(0, true /* expand */);
+ } else {
+ setExpandedFraction(1f);
+ }
+ mInstantExpanding = false;
+ }
+ }
+ });
+
+ // Make sure a layout really happens.
+ mView.requestLayout();
+ }
+
+ public void instantCollapse() {
+ abortAnimations();
+ setExpandedFraction(0f);
+ if (mExpanding) {
+ notifyExpandingFinished();
+ }
+ if (mInstantExpanding) {
+ mInstantExpanding = false;
+ notifyBarPanelExpansionChanged();
+ }
+ }
+
+ private void abortAnimations() {
+ cancelPeek();
+ cancelHeightAnimator();
+ mView.removeCallbacks(mPostCollapseRunnable);
+ mView.removeCallbacks(mFlingCollapseRunnable);
+ }
+
+ protected void onClosingFinished() {
+ mBar.onClosingFinished();
+ }
+
+
+ protected void startUnlockHintAnimation() {
+
+ // We don't need to hint the user if an animation is already running or the user is changing
+ // the expansion.
+ if (mHeightAnimator != null || mTracking) {
+ return;
+ }
+ cancelPeek();
+ notifyExpandingStarted();
+ startUnlockHintAnimationPhase1(() -> {
+ notifyExpandingFinished();
+ onUnlockHintFinished();
+ mHintAnimationRunning = false;
+ });
+ onUnlockHintStarted();
+ mHintAnimationRunning = true;
+ }
+
+ protected void onUnlockHintFinished() {
+ mStatusBar.onHintFinished();
+ }
+
+ protected void onUnlockHintStarted() {
+ mStatusBar.onUnlockHintStarted();
+ }
+
+ public boolean isUnlockHintRunning() {
+ return mHintAnimationRunning;
+ }
+
+ /**
+ * Phase 1: Move everything upwards.
+ */
+ private void startUnlockHintAnimationPhase1(final Runnable onAnimationFinished) {
+ float target = Math.max(0, getMaxPanelHeight() - mHintDistance);
+ ValueAnimator animator = createHeightAnimator(target);
+ animator.setDuration(250);
+ animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ animator.addListener(new AnimatorListenerAdapter() {
+ private boolean mCancelled;
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCancelled = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (mCancelled) {
+ setAnimator(null);
+ onAnimationFinished.run();
+ } else {
+ startUnlockHintAnimationPhase2(onAnimationFinished);
+ }
+ }
+ });
+ animator.start();
+ setAnimator(animator);
+
+ View[] viewsToAnimate = {
+ mKeyguardBottomArea.getIndicationArea(),
+ mStatusBar.getAmbientIndicationContainer()};
+ for (View v : viewsToAnimate) {
+ if (v == null) {
+ continue;
+ }
+ v.animate().translationY(-mHintDistance).setDuration(250).setInterpolator(
+ Interpolators.FAST_OUT_SLOW_IN).withEndAction(() -> v.animate().translationY(
+ 0).setDuration(450).setInterpolator(mBounceInterpolator).start()).start();
+ }
+ }
+
+ private void setAnimator(ValueAnimator animator) {
+ mHeightAnimator = animator;
+ if (animator == null && mPanelUpdateWhenAnimatorEnds) {
+ mPanelUpdateWhenAnimatorEnds = false;
+ requestPanelHeightUpdate();
+ }
+ }
+
+ /**
+ * Phase 2: Bounce down.
+ */
+ private void startUnlockHintAnimationPhase2(final Runnable onAnimationFinished) {
+ ValueAnimator animator = createHeightAnimator(getMaxPanelHeight());
+ animator.setDuration(450);
+ animator.setInterpolator(mBounceInterpolator);
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ setAnimator(null);
+ onAnimationFinished.run();
+ notifyBarPanelExpansionChanged();
+ }
+ });
+ animator.start();
+ setAnimator(animator);
+ }
+
+ private ValueAnimator createHeightAnimator(float targetHeight) {
+ ValueAnimator animator = ValueAnimator.ofFloat(mExpandedHeight, targetHeight);
+ animator.addUpdateListener(
+ animation -> setExpandedHeightInternal((float) animation.getAnimatedValue()));
+ return animator;
+ }
+
+ protected void notifyBarPanelExpansionChanged() {
+ if (mBar != null) {
+ mBar.panelExpansionChanged(
+ mExpandedFraction,
+ mExpandedFraction > 0f || mPeekAnimator != null || mInstantExpanding
+ || isPanelVisibleBecauseOfHeadsUp() || mTracking
+ || mHeightAnimator != null);
+ }
+ for (int i = 0; i < mExpansionListeners.size(); i++) {
+ mExpansionListeners.get(i).onPanelExpansionChanged(mExpandedFraction, mTracking);
+ }
+ }
+
+ public void addExpansionListener(PanelExpansionListener panelExpansionListener) {
+ mExpansionListeners.add(panelExpansionListener);
+ }
+
+ protected abstract boolean isPanelVisibleBecauseOfHeadsUp();
+
+ /**
+ * Gets called when the user performs a click anywhere in the empty area of the panel.
+ *
+ * @return whether the panel will be expanded after the action performed by this method
+ */
+ protected boolean onEmptySpaceClick(float x) {
+ if (mHintAnimationRunning) {
+ return true;
+ }
+ return onMiddleClicked();
+ }
+
+ protected final Runnable mPostCollapseRunnable = new Runnable() {
+ @Override
+ public void run() {
+ collapse(false /* delayed */, 1.0f /* speedUpFactor */);
+ }
+ };
+
+ protected abstract boolean onMiddleClicked();
+
+ protected abstract boolean isDozing();
+
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println(String.format("[PanelView(%s): expandedHeight=%f maxPanelHeight=%d closing=%s"
+ + " tracking=%s justPeeked=%s peekAnim=%s%s timeAnim=%s%s "
+ + "touchDisabled=%s" + "]",
+ this.getClass().getSimpleName(), getExpandedHeight(), getMaxPanelHeight(),
+ mClosing ? "T" : "f", mTracking ? "T" : "f", mJustPeeked ? "T" : "f", mPeekAnimator,
+ ((mPeekAnimator != null && mPeekAnimator.isStarted()) ? " (started)" : ""),
+ mHeightAnimator,
+ ((mHeightAnimator != null && mHeightAnimator.isStarted()) ? " (started)" : ""),
+ mTouchDisabled ? "T" : "f"));
+ }
+
+ public abstract void resetViews(boolean animate);
+
+ protected abstract float getPeekHeight();
+
+ /**
+ * @return whether "Clear all" button will be visible when the panel is fully expanded
+ */
+ protected abstract boolean fullyExpandedClearAllVisible();
+
+ protected abstract boolean isClearAllVisible();
+
+ /**
+ * @return the height of the clear all button, in pixels
+ */
+ protected abstract int getClearAllHeight();
+
+ public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
+ mHeadsUpManager = headsUpManager;
+ }
+
+ public void setLaunchingNotification(boolean launchingNotification) {
+ mLaunchingNotification = launchingNotification;
+ }
+
+ public void collapseWithDuration(int animationDuration) {
+ mFixedDuration = animationDuration;
+ collapse(false /* delayed */, 1.0f /* speedUpFactor */);
+ mFixedDuration = NO_FIXED_DURATION;
+ }
+
+ public ViewGroup getView() {
+ // TODO: remove this method, or at least reduce references to it.
+ return mView;
+ }
+
+ public boolean isEnabled() {
+ return mView.isEnabled();
+ }
+
+ public OnLayoutChangeListener createLayoutChangeListener() {
+ return new OnLayoutChangeListener();
+ }
+
+ protected TouchHandler createTouchHandler() {
+ return new TouchHandler();
+ }
+
+ protected OnConfigurationChangedListener createOnConfigurationChangedListener() {
+ return new OnConfigurationChangedListener();
+ }
+
+ public class TouchHandler implements View.OnTouchListener {
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled || (mMotionAborted
+ && event.getActionMasked() != MotionEvent.ACTION_DOWN)) {
+ return false;
+ }
+
+ /*
+ * If the user drags anywhere inside the panel we intercept it if the movement is
+ * upwards. This allows closing the shade from anywhere inside the panel.
+ *
+ * We only do this if the current content is scrolled to the bottom,
+ * i.e isScrolledToBottom() is true and therefore there is no conflicting scrolling
+ * gesture
+ * possible.
+ */
+ int pointerIndex = event.findPointerIndex(mTrackingPointer);
+ if (pointerIndex < 0) {
+ pointerIndex = 0;
+ mTrackingPointer = event.getPointerId(pointerIndex);
+ }
+ final float x = event.getX(pointerIndex);
+ final float y = event.getY(pointerIndex);
+ boolean scrolledToBottom = isScrolledToBottom();
+
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ mStatusBar.userActivity();
+ mAnimatingOnDown = mHeightAnimator != null;
+ mMinExpandHeight = 0.0f;
+ mDownTime = SystemClock.uptimeMillis();
+ if (mAnimatingOnDown && mClosing && !mHintAnimationRunning
+ || mPeekAnimator != null) {
+ cancelHeightAnimator();
+ cancelPeek();
+ mTouchSlopExceeded = true;
+ return true;
+ }
+ mInitialTouchY = y;
+ mInitialTouchX = x;
+ mTouchStartedInEmptyArea = !isInContentBounds(x, y);
+ mTouchSlopExceeded = mTouchSlopExceededBeforeDown;
+ mJustPeeked = false;
+ mMotionAborted = false;
+ mPanelClosedOnDown = isFullyCollapsed();
+ mCollapsedAndHeadsUpOnDown = false;
+ mHasLayoutedSinceDown = false;
+ mUpdateFlingOnLayout = false;
+ mTouchAboveFalsingThreshold = false;
+ addMovement(event);
+ break;
+ case MotionEvent.ACTION_POINTER_UP:
+ final int upPointer = event.getPointerId(event.getActionIndex());
+ if (mTrackingPointer == upPointer) {
+ // gesture is ongoing, find a new pointer to track
+ final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
+ mTrackingPointer = event.getPointerId(newIndex);
+ mInitialTouchX = event.getX(newIndex);
+ mInitialTouchY = event.getY(newIndex);
+ }
+ break;
+ case MotionEvent.ACTION_POINTER_DOWN:
+ if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
+ mMotionAborted = true;
+ mVelocityTracker.clear();
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ final float h = y - mInitialTouchY;
+ addMovement(event);
+ if (scrolledToBottom || mTouchStartedInEmptyArea || mAnimatingOnDown) {
+ float hAbs = Math.abs(h);
+ if ((h < -mTouchSlop || (mAnimatingOnDown && hAbs > mTouchSlop))
+ && hAbs > Math.abs(x - mInitialTouchX)) {
+ cancelHeightAnimator();
+ startExpandMotion(x, y, true /* startTracking */, mExpandedHeight);
+ return true;
+ }
+ }
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ mVelocityTracker.clear();
+ break;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ if (mInstantExpanding || (mTouchDisabled
+ && event.getActionMasked() != MotionEvent.ACTION_CANCEL) || (mMotionAborted
+ && event.getActionMasked() != MotionEvent.ACTION_DOWN)) {
+ return false;
+ }
+
+ // If dragging should not expand the notifications shade, then return false.
+ if (!mNotificationsDragEnabled) {
+ if (mTracking) {
+ // Turn off tracking if it's on or the shade can get stuck in the down position.
+ onTrackingStopped(true /* expand */);
+ }
+ return false;
+ }
+
+ // On expanding, single mouse click expands the panel instead of dragging.
+ if (isFullyCollapsed() && event.isFromSource(InputDevice.SOURCE_MOUSE)) {
+ if (event.getAction() == MotionEvent.ACTION_UP) {
+ expand(true);
+ }
+ return true;
+ }
+
+ /*
+ * We capture touch events here and update the expand height here in case according to
+ * the users fingers. This also handles multi-touch.
+ *
+ * If the user just clicks shortly, we show a quick peek of the shade.
+ *
+ * Flinging is also enabled in order to open or close the shade.
+ */
+
+ int pointerIndex = event.findPointerIndex(mTrackingPointer);
+ if (pointerIndex < 0) {
+ pointerIndex = 0;
+ mTrackingPointer = event.getPointerId(pointerIndex);
+ }
+ final float x = event.getX(pointerIndex);
+ final float y = event.getY(pointerIndex);
+
+ if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
+ mGestureWaitForTouchSlop = shouldGestureWaitForTouchSlop();
+ mIgnoreXTouchSlop = isFullyCollapsed() || shouldGestureIgnoreXTouchSlop(x, y);
+ }
+
+ switch (event.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
+ mJustPeeked = false;
+ mMinExpandHeight = 0.0f;
+ mPanelClosedOnDown = isFullyCollapsed();
+ mHasLayoutedSinceDown = false;
+ mUpdateFlingOnLayout = false;
+ mMotionAborted = false;
+ mPeekTouching = mPanelClosedOnDown;
+ mDownTime = SystemClock.uptimeMillis();
+ mTouchAboveFalsingThreshold = false;
+ mCollapsedAndHeadsUpOnDown =
+ isFullyCollapsed() && mHeadsUpManager.hasPinnedHeadsUp();
+ addMovement(event);
+ if (!mGestureWaitForTouchSlop || (mHeightAnimator != null
+ && !mHintAnimationRunning) || mPeekAnimator != null) {
+ mTouchSlopExceeded =
+ (mHeightAnimator != null && !mHintAnimationRunning)
+ || mPeekAnimator != null || mTouchSlopExceededBeforeDown;
+ cancelHeightAnimator();
+ cancelPeek();
+ onTrackingStarted();
+ }
+ if (isFullyCollapsed() && !mHeadsUpManager.hasPinnedHeadsUp()
+ && !mStatusBar.isBouncerShowing()) {
+ startOpening(event);
+ }
+ break;
+
+ case MotionEvent.ACTION_POINTER_UP:
+ final int upPointer = event.getPointerId(event.getActionIndex());
+ if (mTrackingPointer == upPointer) {
+ // gesture is ongoing, find a new pointer to track
+ final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
+ final float newY = event.getY(newIndex);
+ final float newX = event.getX(newIndex);
+ mTrackingPointer = event.getPointerId(newIndex);
+ startExpandMotion(newX, newY, true /* startTracking */, mExpandedHeight);
+ }
+ break;
+ case MotionEvent.ACTION_POINTER_DOWN:
+ if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
+ mMotionAborted = true;
+ endMotionEvent(event, x, y, true /* forceCancel */);
+ return false;
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ addMovement(event);
+ float h = y - mInitialTouchY;
+
+ // If the panel was collapsed when touching, we only need to check for the
+ // y-component of the gesture, as we have no conflicting horizontal gesture.
+ if (Math.abs(h) > mTouchSlop && (Math.abs(h) > Math.abs(x - mInitialTouchX)
+ || mIgnoreXTouchSlop)) {
+ mTouchSlopExceeded = true;
+ if (mGestureWaitForTouchSlop && !mTracking && !mCollapsedAndHeadsUpOnDown) {
+ if (!mJustPeeked && mInitialOffsetOnTouch != 0f) {
+ startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
+ h = 0;
+ }
+ cancelHeightAnimator();
+ onTrackingStarted();
+ }
+ }
+ float newHeight = Math.max(0, h + mInitialOffsetOnTouch);
+ if (newHeight > mPeekHeight) {
+ if (mPeekAnimator != null) {
+ mPeekAnimator.cancel();
+ }
+ mJustPeeked = false;
+ } else if (mPeekAnimator == null && mJustPeeked) {
+ // The initial peek has finished, but we haven't dragged as far yet, lets
+ // speed it up by starting at the peek height.
+ mInitialOffsetOnTouch = mExpandedHeight;
+ mInitialTouchY = y;
+ mMinExpandHeight = mExpandedHeight;
+ mJustPeeked = false;
+ }
+ newHeight = Math.max(newHeight, mMinExpandHeight);
+ if (-h >= getFalsingThreshold()) {
+ mTouchAboveFalsingThreshold = true;
+ mUpwardsWhenThresholdReached = isDirectionUpwards(x, y);
+ }
+ if (!mJustPeeked && (!mGestureWaitForTouchSlop || mTracking)
+ && !isTrackingBlocked()) {
+ setExpandedHeightInternal(newHeight);
+ }
+ break;
+
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL:
+ addMovement(event);
+ endMotionEvent(event, x, y, false /* forceCancel */);
+ break;
+ }
+ return !mGestureWaitForTouchSlop || mTracking;
+ }
+ }
+
+ public class OnLayoutChangeListener implements View.OnLayoutChangeListener {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
+ int oldTop, int oldRight, int oldBottom) {
+ mStatusBar.onPanelLaidOut();
+ requestPanelHeightUpdate();
+ mHasLayoutedSinceDown = true;
+ if (mUpdateFlingOnLayout) {
+ abortAnimations();
+ fling(mUpdateFlingVelocity, true /* expands */);
+ mUpdateFlingOnLayout = false;
+ }
+ }
+ }
+
+ public class OnConfigurationChangedListener implements
+ PanelView.OnConfigurationChangedListener {
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ loadDimens();
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 5b34aa7..00e38f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -179,7 +179,7 @@
filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
filter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
- broadcastDispatcher.registerReceiver(mIntentReceiver, filter, mHandler);
+ broadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter, mHandler);
// listen for user / profile change.
try {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 312ca26..45f3bf9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -236,7 +236,7 @@
public void onPanelFullyOpened() {
super.onPanelFullyOpened();
if (!mIsFullyOpenedPanel) {
- mPanel.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ mPanel.getView().sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
}
mIsFullyOpenedPanel = true;
maybeShowDivider(!mBar.mPanelExpanded);
@@ -420,7 +420,8 @@
void maybeShowDivider(boolean showDivider) {
int state =
- showDivider && NotificationPanelView.isQsSplitEnabled() ? View.VISIBLE : View.GONE;
+ showDivider && NotificationPanelViewController.isQsSplitEnabled()
+ ? View.VISIBLE : View.GONE;
mDividerContainer.setVisibility(state);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
index 57e7014..866dc2d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
@@ -79,7 +79,7 @@
public void instantExpandNotificationsPanel() {
// Make our window larger and the panel expanded.
getStatusBar().makeExpandedVisible(true /* force */);
- getNotificationPanelView().expand(false /* animate */);
+ getNotificationPanelViewController().expand(false /* animate */);
mCommandQueue.recomputeDisableFlags(mDisplayId, false /* animate */);
}
@@ -123,8 +123,9 @@
// TODO(b/62444020): remove when this bug is fixed
Log.v(TAG, "mStatusBarWindow: " + getStatusBarWindowView() + " canPanelBeCollapsed(): "
- + getNotificationPanelView().canPanelBeCollapsed());
- if (getStatusBarWindowView() != null && getNotificationPanelView().canPanelBeCollapsed()) {
+ + getNotificationPanelViewController().canPanelBeCollapsed());
+ if (getStatusBarWindowView() != null
+ && getNotificationPanelViewController().canPanelBeCollapsed()) {
// release focus immediately to kick off focus change transition
mStatusBarWindowController.setStatusBarFocusable(false);
@@ -138,7 +139,7 @@
@Override
public boolean closeShadeIfOpen() {
- if (!getNotificationPanelView().isFullyCollapsed()) {
+ if (!getNotificationPanelViewController().isFullyCollapsed()) {
mCommandQueue.animateCollapsePanels(
CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
getStatusBar().visibilityChanged(false);
@@ -149,15 +150,14 @@
@Override
public void postOnShadeExpanded(Runnable executable) {
- getNotificationPanelView().getViewTreeObserver().addOnGlobalLayoutListener(
+ getNotificationPanelViewController().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (getStatusBar().getStatusBarWindow().getHeight()
!= getStatusBar().getStatusBarHeight()) {
- getNotificationPanelView().getViewTreeObserver()
- .removeOnGlobalLayoutListener(this);
- getNotificationPanelView().post(executable);
+ getNotificationPanelViewController().removeOnGlobalLayoutListener(this);
+ getNotificationPanelViewController().getView().post(executable);
}
}
});
@@ -187,7 +187,7 @@
@Override
public boolean collapsePanel() {
- if (!getNotificationPanelView().isFullyCollapsed()) {
+ if (!getNotificationPanelViewController().isFullyCollapsed()) {
// close the shade if it was open
animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
true /* force */, true /* delayed */);
@@ -230,7 +230,7 @@
return (PhoneStatusBarView) getStatusBar().getStatusBarView();
}
- private NotificationPanelView getNotificationPanelView() {
- return getStatusBar().getPanel();
+ private NotificationPanelViewController getNotificationPanelViewController() {
+ return getStatusBar().getPanelController();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index a6a734a..dc9cf77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -29,7 +29,6 @@
import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS;
-import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
@@ -131,7 +130,6 @@
import com.android.systemui.Dumpable;
import com.android.systemui.EventLogTags;
import com.android.systemui.InitController;
-import com.android.systemui.Interpolators;
import com.android.systemui.Prefs;
import com.android.systemui.R;
import com.android.systemui.SystemUI;
@@ -199,15 +197,15 @@
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
import com.android.systemui.statusbar.notification.NotificationListController;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
-import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
+import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
@@ -366,8 +364,7 @@
private final HeadsUpManagerPhone mHeadsUpManager;
private final DynamicPrivacyController mDynamicPrivacyController;
private final BypassHeadsUpNotifier mBypassHeadsUpNotifier;
- private final boolean mAllowNotificationLongPress;
- private final Lazy<NewNotifPipeline> mNewNotifPipeline;
+ private final Lazy<NotifPipelineInitializer> mNewNotifPipeline;
private final FalsingManager mFalsingManager;
private final BroadcastDispatcher mBroadcastDispatcher;
private final ConfigurationController mConfigurationController;
@@ -389,10 +386,12 @@
private final KeyguardDismissUtil mKeyguardDismissUtil;
private final ExtensionController mExtensionController;
private final UserInfoControllerImpl mUserInfoControllerImpl;
+ private final NotificationRowBinderImpl mNotificationRowBinder;
private final DismissCallbackRegistry mDismissCallbackRegistry;
// expanded notifications
- protected NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window
+ // the sliding/resizing panel within the notification window
+ protected NotificationPanelViewController mNotificationPanelViewController;
// settings
private QSPanel mQSPanel;
@@ -461,8 +460,8 @@
mUserSetup = userSetup;
if (!mUserSetup && mStatusBarView != null)
animateCollapseQuickSettings();
- if (mNotificationPanel != null) {
- mNotificationPanel.setUserSetupComplete(mUserSetup);
+ if (mNotificationPanelViewController != null) {
+ mNotificationPanelViewController.setUserSetupComplete(mUserSetup);
}
updateQsExpansionEnabled();
}
@@ -626,8 +625,7 @@
HeadsUpManagerPhone headsUpManagerPhone,
DynamicPrivacyController dynamicPrivacyController,
BypassHeadsUpNotifier bypassHeadsUpNotifier,
- @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowNotificationLongPress,
- Lazy<NewNotifPipeline> newNotifPipeline,
+ Lazy<NotifPipelineInitializer> newNotifPipeline,
FalsingManager falsingManager,
BroadcastDispatcher broadcastDispatcher,
RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler,
@@ -693,6 +691,7 @@
KeyguardDismissUtil keyguardDismissUtil,
ExtensionController extensionController,
UserInfoControllerImpl userInfoControllerImpl,
+ NotificationRowBinderImpl notificationRowBinder,
DismissCallbackRegistry dismissCallbackRegistry) {
super(context);
mFeatureFlags = featureFlags;
@@ -707,7 +706,6 @@
mHeadsUpManager = headsUpManagerPhone;
mDynamicPrivacyController = dynamicPrivacyController;
mBypassHeadsUpNotifier = bypassHeadsUpNotifier;
- mAllowNotificationLongPress = allowNotificationLongPress;
mNewNotifPipeline = newNotifPipeline;
mFalsingManager = falsingManager;
mBroadcastDispatcher = broadcastDispatcher;
@@ -772,6 +770,7 @@
mKeyguardDismissUtil = keyguardDismissUtil;
mExtensionController = extensionController;
mUserInfoControllerImpl = userInfoControllerImpl;
+ mNotificationRowBinder = notificationRowBinder;
mDismissCallbackRegistry = dismissCallbackRegistry;
mBubbleExpandListener =
@@ -913,9 +912,8 @@
mKeyguardUpdateMonitor.registerCallback(mUpdateCallback);
mDozeServiceHost.initialize(this, mNotificationIconAreaController,
- mStatusBarKeyguardViewManager,
- mStatusBarWindowViewController,
- mNotificationPanel, mAmbientIndicationContainer);
+ mStatusBarKeyguardViewManager, mStatusBarWindowViewController,
+ mNotificationPanelViewController, mAmbientIndicationContainer);
mConfigurationController.addCallback(this);
@@ -985,8 +983,6 @@
// TODO: Deal with the ugliness that comes from having some of the statusbar broken out
// into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
- mNotificationPanel = mSuperStatusBarViewFactory.getNotificationPanelView();
-
mStackScroller = mStatusBarWindow.findViewById(R.id.notification_stack_scroller);
NotificationListContainer notifListContainer = (NotificationListContainer) mStackScroller;
mNotificationLogger.setUpWithContainer(notifListContainer);
@@ -1000,8 +996,9 @@
mWakeUpCoordinator.setIconAreaController(mNotificationIconAreaController);
inflateShelf();
mNotificationIconAreaController.setupShelf(mNotificationShelf);
- mNotificationPanel.setOnReinflationListener(mNotificationIconAreaController::initAodIcons);
- mNotificationPanel.addExpansionListener(mWakeUpCoordinator);
+ mNotificationPanelViewController.setOnReinflationListener(
+ mNotificationIconAreaController::initAodIcons);
+ mNotificationPanelViewController.addExpansionListener(mWakeUpCoordinator);
mDarkIconDispatcher.addDarkReceiver(mNotificationIconAreaController);
// Allow plugins to reference DarkIconDispatcher and StatusBarStateController
@@ -1015,7 +1012,7 @@
PhoneStatusBarView oldStatusBarView = mStatusBarView;
mStatusBarView = (PhoneStatusBarView) fragment.getView();
mStatusBarView.setBar(this);
- mStatusBarView.setPanel(mNotificationPanel);
+ mStatusBarView.setPanel(mNotificationPanelViewController);
mStatusBarView.setScrimController(mScrimController);
// CollapsedStatusBarFragment re-inflated PhoneStatusBarView and both of
@@ -1026,7 +1023,7 @@
// it needs to notify PhoneStatusBarView's new instance to update the correct
// status by calling mNotificationPanel.notifyBarPanelExpansionChanged().
if (mHeadsUpManager.hasPinnedHeadsUp()) {
- mNotificationPanel.notifyBarPanelExpansionChanged();
+ mNotificationPanelViewController.notifyBarPanelExpansionChanged();
}
mStatusBarView.setBouncerShowing(mBouncerShowing);
if (oldStatusBarView != null) {
@@ -1040,10 +1037,12 @@
// This view is being recreated, let's destroy the old one
mHeadsUpAppearanceController.destroy();
}
+ // TODO: this should probably be scoped to the StatusBarComponent
mHeadsUpAppearanceController = new HeadsUpAppearanceController(
mNotificationIconAreaController, mHeadsUpManager, mStatusBarWindow,
mStatusBarStateController, mKeyguardBypassController,
- mKeyguardStateController, mWakeUpCoordinator, mCommandQueue);
+ mKeyguardStateController, mWakeUpCoordinator, mCommandQueue,
+ mNotificationPanelViewController);
mHeadsUpAppearanceController.readFrom(oldController);
mLightsOutNotifController.setLightsOutNotifView(
@@ -1059,11 +1058,11 @@
mHeadsUpManager.setUp(mStatusBarWindow, mGroupManager, this, mVisualStabilityManager);
mConfigurationController.addCallback(mHeadsUpManager);
mHeadsUpManager.addListener(this);
- mHeadsUpManager.addListener(mNotificationPanel);
+ mHeadsUpManager.addListener(mNotificationPanelViewController.getOnHeadsUpChangedListener());
mHeadsUpManager.addListener(mGroupManager);
mHeadsUpManager.addListener(mGroupAlertTransferHelper);
mHeadsUpManager.addListener(mVisualStabilityManager);
- mNotificationPanel.setHeadsUpManager(mHeadsUpManager);
+ mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
mGroupManager.setHeadsUpManager(mHeadsUpManager);
mGroupAlertTransferHelper.setHeadsUpManager(mHeadsUpManager);
mNotificationLogger.setHeadsUpManager(mHeadsUpManager);
@@ -1078,7 +1077,8 @@
SystemUIFactory.getInstance().createKeyguardIndicationController(mContext,
mStatusBarWindow.findViewById(R.id.keyguard_indication_area),
mStatusBarWindow.findViewById(R.id.lock_icon));
- mNotificationPanel.setKeyguardIndicationController(mKeyguardIndicationController);
+ mNotificationPanelViewController.setKeyguardIndicationController(
+ mKeyguardIndicationController);
mAmbientIndicationContainer = mStatusBarWindow.findViewById(
R.id.ambient_indication_container);
@@ -1113,19 +1113,19 @@
});
mScrimController.attachViews(scrimBehind, scrimInFront, scrimForBubble);
- mNotificationPanel.initDependencies(this, mGroupManager, mNotificationShelf,
- mHeadsUpManager, mNotificationIconAreaController, mScrimController);
+ mNotificationPanelViewController.initDependencies(this, mGroupManager, mNotificationShelf,
+ mNotificationIconAreaController, mScrimController);
BackDropView backdrop = mStatusBarWindow.findViewById(R.id.backdrop);
mMediaManager.setup(backdrop, backdrop.findViewById(R.id.backdrop_front),
backdrop.findViewById(R.id.backdrop_back), mScrimController, mLockscreenWallpaper);
- mNotificationPanel.setUserSetupComplete(mUserSetup);
+ mNotificationPanelViewController.setUserSetupComplete(mUserSetup);
if (UserManager.get(mContext).isUserSwitcherEnabled()) {
createUserSwitcher();
}
- mNotificationPanel.setLaunchAffordanceListener(
+ mNotificationPanelViewController.setLaunchAffordanceListener(
mLockscreenLockIconController::onShowingLaunchAffordanceChanged);
// Set up the quick settings tile panel
@@ -1139,6 +1139,7 @@
.withDefault(this::createDefaultQSFragment)
.build());
mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow,
+ mNotificationPanelViewController,
(visible) -> {
mBrightnessMirrorVisible = visible;
updateScrimController();
@@ -1232,22 +1233,14 @@
private void setUpPresenter() {
// Set up the initial notification state.
mActivityLaunchAnimator = new ActivityLaunchAnimator(
- mStatusBarWindowViewController, this, mNotificationPanel,
+ mStatusBarWindowViewController, this, mNotificationPanelViewController,
(NotificationListContainer) mStackScroller);
- final NotificationRowBinderImpl rowBinder =
- new NotificationRowBinderImpl(
- mContext,
- mAllowNotificationLongPress,
- mKeyguardBypassController,
- mStatusBarStateController,
- mNotificationLogger);
-
// TODO: inject this.
- mPresenter = new StatusBarNotificationPresenter(mContext, mNotificationPanel,
+ mPresenter = new StatusBarNotificationPresenter(mContext, mNotificationPanelViewController,
mHeadsUpManager, mStatusBarWindow, mStackScroller, mDozeScrimController,
mScrimController, mActivityLaunchAnimator, mDynamicPrivacyController,
- mNotificationAlertingManager, rowBinder, mKeyguardStateController,
+ mNotificationAlertingManager, mNotificationRowBinder, mKeyguardStateController,
mKeyguardIndicationController,
this /* statusBar */, mShadeController, mCommandQueue, mInitController);
@@ -1265,21 +1258,25 @@
.setStatusBar(this)
.setActivityLaunchAnimator(mActivityLaunchAnimator)
.setNotificationPresenter(mPresenter)
+ .setNotificationPanelViewController(mNotificationPanelViewController)
.build();
mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
- mEntryManager.setRowBinder(rowBinder);
+ if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
+ mNotificationRowBinder.setInflationCallback(mEntryManager);
+ }
+
mRemoteInputUriController.attach(mEntryManager);
- rowBinder.setNotificationClicker(new NotificationClicker(
+ mNotificationRowBinder.setNotificationClicker(new NotificationClicker(
Optional.of(this), mBubbleController, mNotificationActivityStarter));
mGroupAlertTransferHelper.bind(mEntryManager, mGroupManager);
mNotificationListController.bind();
if (mFeatureFlags.isNewNotifPipelineEnabled()) {
- mNewNotifPipeline.get().initialize(mNotificationListener);
+ mNewNotifPipeline.get().initialize(mNotificationListener, mNotificationRowBinder);
}
mEntryManager.attach(mNotificationListener);
}
@@ -1374,7 +1371,7 @@
}
// We need the new R.id.keyguard_indication_area before recreating
// mKeyguardIndicationController
- mNotificationPanel.onThemeChanged();
+ mNotificationPanelViewController.onThemeChanged();
onThemeChanged();
}
@@ -1389,7 +1386,7 @@
mKeyguardUserSwitcher = new KeyguardUserSwitcher(mContext,
mStatusBarWindow.findViewById(R.id.keyguard_user_switcher),
mStatusBarWindow.findViewById(R.id.keyguard_header),
- mNotificationPanel);
+ mNotificationPanelViewController);
}
private void inflateStatusBarWindow() {
@@ -1398,16 +1395,17 @@
.statusBarWindowView(mStatusBarWindow).build();
mStatusBarWindowViewController = statusBarComponent.getStatusBarWindowViewController();
mStatusBarWindowViewController.setupExpandedStatusBar();
+ mNotificationPanelViewController = statusBarComponent.getNotificationPanelViewController();
}
protected void startKeyguard() {
Trace.beginSection("StatusBar#startKeyguard");
mBiometricUnlockController = mBiometricUnlockControllerLazy.get();
mStatusBarKeyguardViewManager.registerStatusBar(
- /* statusBar= */ this, getBouncerContainer(), mNotificationPanel,
- mBiometricUnlockController, mDismissCallbackRegistry,
- mStatusBarWindow.findViewById(R.id.lock_icon_container), mStackScroller,
- mKeyguardBypassController, mFalsingManager);
+ /* statusBar= */ this, getBouncerContainer(),
+ mNotificationPanelViewController, mBiometricUnlockController,
+ mDismissCallbackRegistry, mStatusBarWindow.findViewById(R.id.lock_icon_container),
+ mStackScroller, mKeyguardBypassController, mFalsingManager);
mKeyguardIndicationController
.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
mBiometricUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
@@ -1484,7 +1482,7 @@
&& ((mDisabled2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) == 0)
&& !mDozing
&& !ONLY_CORE_APPS;
- mNotificationPanel.setQsExpansionEnabled(expandEnabled);
+ mNotificationPanelViewController.setQsExpansionEnabled(expandEnabled);
Log.d(TAG, "updateQsExpansionEnabled - QS Expand enabled: " + expandEnabled);
}
@@ -1644,7 +1642,7 @@
public void setQsExpanded(boolean expanded) {
mStatusBarWindowController.setQsExpanded(expanded);
- mNotificationPanel.setStatusAccessibilityImportance(expanded
+ mNotificationPanelViewController.setStatusAccessibilityImportance(expanded
? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
: View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
if (getNavigationBarView() != null) {
@@ -1678,22 +1676,22 @@
if (inPinnedMode) {
mStatusBarWindowController.setHeadsUpShowing(true);
mStatusBarWindowController.setForceStatusBarVisible(true);
- if (mNotificationPanel.isFullyCollapsed()) {
+ if (mNotificationPanelViewController.isFullyCollapsed()) {
// We need to ensure that the touchable region is updated before the window will be
// resized, in order to not catch any touches. A layout will ensure that
// onComputeInternalInsets will be called and after that we can resize the layout. Let's
// make sure that the window stays small for one frame until the touchableRegion is set.
- mNotificationPanel.requestLayout();
+ mNotificationPanelViewController.getView().requestLayout();
mStatusBarWindowController.setForceWindowCollapsed(true);
- mNotificationPanel.post(() -> {
+ mNotificationPanelViewController.getView().post(() -> {
mStatusBarWindowController.setForceWindowCollapsed(false);
});
}
} else {
boolean bypassKeyguard = mKeyguardBypassController.getBypassEnabled()
&& mState == StatusBarState.KEYGUARD;
- if (!mNotificationPanel.isFullyCollapsed() || mNotificationPanel.isTracking()
- || bypassKeyguard) {
+ if (!mNotificationPanelViewController.isFullyCollapsed()
+ || mNotificationPanelViewController.isTracking() || bypassKeyguard) {
// We are currently tracking or is open and the shade doesn't need to be kept
// open artificially.
mStatusBarWindowController.setHeadsUpShowing(false);
@@ -1704,7 +1702,7 @@
// we need to keep the panel open artificially, let's wait until the animation
// is finished.
mHeadsUpManager.setHeadsUpGoingAway(true);
- mNotificationPanel.runAfterAnimationFinished(() -> {
+ mNotificationPanelViewController.runAfterAnimationFinished(() -> {
if (!mHeadsUpManager.hasPinnedHeadsUp()) {
mStatusBarWindowController.setHeadsUpShowing(false);
mHeadsUpManager.setHeadsUpGoingAway(false);
@@ -1757,7 +1755,7 @@
}
public boolean hideStatusBarIconsWhenExpanded() {
- return mNotificationPanel.hideStatusBarIconsWhenExpanded();
+ return mNotificationPanelViewController.hideStatusBarIconsWhenExpanded();
}
@Override
@@ -1947,19 +1945,21 @@
if (KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP == key) {
mMetricsLogger.action(MetricsEvent.ACTION_SYSTEM_NAVIGATION_KEY_UP);
- mNotificationPanel.collapse(false /* delayed */, 1.0f /* speedUpFactor */);
+ mNotificationPanelViewController.collapse(
+ false /* delayed */, 1.0f /* speedUpFactor */);
} else if (KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN == key) {
mMetricsLogger.action(MetricsEvent.ACTION_SYSTEM_NAVIGATION_KEY_DOWN);
- if (mNotificationPanel.isFullyCollapsed()) {
+ if (mNotificationPanelViewController.isFullyCollapsed()) {
if (mVibrateOnOpening) {
mVibratorHelper.vibrate(VibrationEffect.EFFECT_TICK);
}
- mNotificationPanel.expand(true /* animate */);
+ mNotificationPanelViewController.expand(true /* animate */);
((NotificationListContainer) mStackScroller).setWillExpand(true);
mHeadsUpManager.unpinAll(true /* userUnpinned */);
mMetricsLogger.count(NotificationPanelView.COUNTER_PANEL_OPEN, 1);
- } else if (!mNotificationPanel.isInSettings() && !mNotificationPanel.isExpanding()){
- mNotificationPanel.flingSettings(0 /* velocity */,
+ } else if (!mNotificationPanelViewController.isInSettings()
+ && !mNotificationPanelViewController.isExpanding()) {
+ mNotificationPanelViewController.flingSettings(0 /* velocity */,
NotificationPanelView.FLING_EXPAND);
mMetricsLogger.count(NotificationPanelView.COUNTER_PANEL_OPEN_QS, 1);
}
@@ -2054,9 +2054,9 @@
}
if (start) {
- mNotificationPanel.startWaitingForOpenPanelGesture();
+ mNotificationPanelViewController.startWaitingForOpenPanelGesture();
} else {
- mNotificationPanel.stopWaitingForOpenPanelGesture(velocity);
+ mNotificationPanelViewController.stopWaitingForOpenPanelGesture(velocity);
}
}
@@ -2067,7 +2067,7 @@
return ;
}
- mNotificationPanel.expandWithoutQs();
+ mNotificationPanelViewController.expandWithoutQs();
if (false) postStartTracing();
}
@@ -2085,7 +2085,7 @@
if (subPanel != null) {
mQSPanel.openDetails(subPanel);
}
- mNotificationPanel.expandWithQs();
+ mNotificationPanelViewController.expandWithQs();
if (false) postStartTracing();
}
@@ -2108,7 +2108,7 @@
mStatusBarView.collapsePanel(/*animate=*/ false, false /* delayed*/,
1.0f /* speedUpFactor */);
- mNotificationPanel.closeQs();
+ mNotificationPanelViewController.closeQs();
mExpandedVisible = false;
visibilityChanged(false);
@@ -2129,7 +2129,8 @@
Log.d(TAG, "Not showing bouncer due to activity showing over lockscreen");
}
mCommandQueue.recomputeDisableFlags(
- mDisplayId, mNotificationPanel.hideStatusBarIconsWhenExpanded() /* animate */);
+ mDisplayId,
+ mNotificationPanelViewController.hideStatusBarIconsWhenExpanded() /* animate */);
// Trimming will happen later if Keyguard is showing - doing it here might cause a jank in
// the bouncer appear animation.
@@ -2310,12 +2311,12 @@
batteryLevel, new WirelessChargingAnimation.Callback() {
@Override
public void onAnimationStarting() {
- CrossFadeHelper.fadeOut(mNotificationPanel, 1);
+ CrossFadeHelper.fadeOut(mNotificationPanelViewController.getView(), 1);
}
@Override
public void onAnimationEnded() {
- CrossFadeHelper.fadeIn(mNotificationPanel);
+ CrossFadeHelper.fadeIn(mNotificationPanelViewController.getView());
}
}, mDozing).show();
} else {
@@ -2344,7 +2345,7 @@
// Called by NavigationBarFragment
void setQsScrimEnabled(boolean scrimEnabled) {
- mNotificationPanel.setQsScrimEnabled(scrimEnabled);
+ mNotificationPanelViewController.setQsScrimEnabled(scrimEnabled);
}
void checkBarMode(@TransitionMode int mode, @WindowVisibleState int windowState,
@@ -2436,11 +2437,12 @@
}
pw.println(" Panels: ");
- if (mNotificationPanel != null) {
- pw.println(" mNotificationPanel=" +
- mNotificationPanel + " params=" + mNotificationPanel.getLayoutParams().debug(""));
+ if (mNotificationPanelViewController != null) {
+ pw.println(" mNotificationPanel="
+ + mNotificationPanelViewController.getView() + " params="
+ + mNotificationPanelViewController.getView().getLayoutParams().debug(""));
pw.print (" ");
- mNotificationPanel.dump(fd, pw, args);
+ mNotificationPanelViewController.dump(fd, pw, args);
}
pw.println(" mStackScroller: ");
if (mStackScroller instanceof Dumpable) {
@@ -2653,7 +2655,8 @@
// Do it after DismissAction has been processed to conserve the needed ordering.
mHandler.post(mShadeController::runPostCollapseRunnables);
}
- } else if (isInLaunchTransition() && mNotificationPanel.isLaunchTransitionFinished()) {
+ } else if (isInLaunchTransition()
+ && mNotificationPanelViewController.isLaunchTransitionFinished()) {
// We are not dismissing the shade, but the launch transition is already finished,
// so nobody will call readyForKeyguardDone anymore. Post it such that
@@ -2810,8 +2813,8 @@
if (mStatusBarView != null) {
mStatusBarView.updateResources();
}
- if (mNotificationPanel != null) {
- mNotificationPanel.updateResources();
+ if (mNotificationPanelViewController != null) {
+ mNotificationPanelViewController.updateResources();
}
if (mBrightnessMirrorController != null) {
mBrightnessMirrorController.updateResources();
@@ -3103,7 +3106,7 @@
public void showKeyguardImpl() {
mIsKeyguard = true;
if (mKeyguardStateController.isLaunchTransitionFadingAway()) {
- mNotificationPanel.animate().cancel();
+ mNotificationPanelViewController.cancelAnimation();
onLaunchTransitionFadingEnded();
}
mHandler.removeMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
@@ -3130,8 +3133,8 @@
}
private void onLaunchTransitionFadingEnded() {
- mNotificationPanel.setAlpha(1.0f);
- mNotificationPanel.onAffordanceLaunchEnded();
+ mNotificationPanelViewController.setAlpha(1.0f);
+ mNotificationPanelViewController.onAffordanceLaunchEnded();
releaseGestureWakeLock();
runLaunchTransitionEndRunnable();
mKeyguardStateController.setLaunchTransitionFadingAway(false);
@@ -3139,8 +3142,8 @@
}
public boolean isInLaunchTransition() {
- return mNotificationPanel.isLaunchTransitionRunning()
- || mNotificationPanel.isLaunchTransitionFinished();
+ return mNotificationPanelViewController.isLaunchTransitionRunning()
+ || mNotificationPanelViewController.isLaunchTransitionFinished();
}
/**
@@ -3161,18 +3164,15 @@
}
updateScrimController();
mPresenter.updateMediaMetaData(false, true);
- mNotificationPanel.setAlpha(1);
- mNotificationPanel.animate()
- .alpha(0)
- .setStartDelay(FADE_KEYGUARD_START_DELAY)
- .setDuration(FADE_KEYGUARD_DURATION)
- .withLayer()
- .withEndAction(this::onLaunchTransitionFadingEnded);
+ mNotificationPanelViewController.setAlpha(1);
+ mNotificationPanelViewController.fadeOut(
+ FADE_KEYGUARD_START_DELAY, FADE_KEYGUARD_DURATION,
+ this::onLaunchTransitionFadingEnded);
mCommandQueue.appTransitionStarting(mDisplayId, SystemClock.uptimeMillis(),
LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true);
};
- if (mNotificationPanel.isLaunchTransitionRunning()) {
- mNotificationPanel.setLaunchTransitionEndRunnable(hideRunnable);
+ if (mNotificationPanelViewController.isLaunchTransitionRunning()) {
+ mNotificationPanelViewController.setLaunchTransitionEndRunnable(hideRunnable);
} else {
hideRunnable.run();
}
@@ -3183,22 +3183,18 @@
* fading.
*/
public void fadeKeyguardWhilePulsing() {
- mNotificationPanel.animate()
- .alpha(0f)
- .setStartDelay(0)
- .setDuration(FADE_KEYGUARD_DURATION_PULSING)
- .setInterpolator(Interpolators.ALPHA_OUT)
- .withEndAction(()-> {
- hideKeyguard();
- mStatusBarKeyguardViewManager.onKeyguardFadedAway();
- }).start();
+ mNotificationPanelViewController.fadeOut(0, FADE_KEYGUARD_DURATION_PULSING,
+ ()-> {
+ hideKeyguard();
+ mStatusBarKeyguardViewManager.onKeyguardFadedAway();
+ }).start();
}
/**
* Plays the animation when an activity that was occluding Keyguard goes away.
*/
public void animateKeyguardUnoccluding() {
- mNotificationPanel.setExpandedFraction(0f);
+ mNotificationPanelViewController.setExpandedFraction(0f);
animateExpandNotificationsPanel();
}
@@ -3214,9 +3210,9 @@
private void onLaunchTransitionTimeout() {
Log.w(TAG, "Launch transition: Timeout!");
- mNotificationPanel.onAffordanceLaunchEnded();
+ mNotificationPanelViewController.onAffordanceLaunchEnded();
releaseGestureWakeLock();
- mNotificationPanel.resetViews(false /* animate */);
+ mNotificationPanelViewController.resetViews(false /* animate */);
}
private void runLaunchTransitionEndRunnable() {
@@ -3249,7 +3245,7 @@
mStatusBarStateController.setLeaveOpenOnKeyguardHide(false);
}
long delay = mKeyguardStateController.calculateGoingToFullShadeDelay();
- mNotificationPanel.animateToFullShade(delay);
+ mNotificationPanelViewController.animateToFullShade(delay);
if (mDraggedDownEntry != null) {
mDraggedDownEntry.setUserLocked(false);
mDraggedDownEntry = null;
@@ -3258,7 +3254,7 @@
// Disable layout transitions in navbar for this transition because the load is just
// too heavy for the CPU and GPU on any device.
mNavigationBarController.disableAnimationsDuringHide(mDisplayId, delay);
- } else if (!mNotificationPanel.isCollapsing()) {
+ } else if (!mNotificationPanelViewController.isCollapsing()) {
instantCollapseNotificationPanel();
}
@@ -3269,10 +3265,10 @@
}
mHandler.removeMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
releaseGestureWakeLock();
- mNotificationPanel.onAffordanceLaunchEnded();
- mNotificationPanel.animate().cancel();
- mNotificationPanel.setAlpha(1f);
- ViewGroupFadeHelper.reset(mNotificationPanel);
+ mNotificationPanelViewController.onAffordanceLaunchEnded();
+ mNotificationPanelViewController.cancelAnimation();
+ mNotificationPanelViewController.setAlpha(1f);
+ mNotificationPanelViewController.resetViewGroupFade();
updateScrimController();
Trace.endSection();
return staying;
@@ -3347,7 +3343,7 @@
boolean animate = (!mDozing && mDozeServiceHost.shouldAnimateWakeup() && !wakeAndUnlock)
|| (mDozing && mDozeServiceHost.shouldAnimateScreenOff() && sleepingFromKeyguard);
- mNotificationPanel.setDozing(mDozing, animate, mWakeUpTouchLocation);
+ mNotificationPanelViewController.setDozing(mDozing, animate, mWakeUpTouchLocation);
updateQsExpansionEnabled();
Trace.endSection();
}
@@ -3379,27 +3375,27 @@
public void endAffordanceLaunch() {
releaseGestureWakeLock();
- mNotificationPanel.onAffordanceLaunchEnded();
+ mNotificationPanelViewController.onAffordanceLaunchEnded();
}
public boolean onBackPressed() {
boolean isScrimmedBouncer = mScrimController.getState() == ScrimState.BOUNCER_SCRIMMED;
if (mStatusBarKeyguardViewManager.onBackPressed(isScrimmedBouncer /* hideImmediately */)) {
if (!isScrimmedBouncer) {
- mNotificationPanel.expandWithoutQs();
+ mNotificationPanelViewController.expandWithoutQs();
}
return true;
}
- if (mNotificationPanel.isQsExpanded()) {
- if (mNotificationPanel.isQsDetailShowing()) {
- mNotificationPanel.closeQsDetail();
+ if (mNotificationPanelViewController.isQsExpanded()) {
+ if (mNotificationPanelViewController.isQsDetailShowing()) {
+ mNotificationPanelViewController.closeQsDetail();
} else {
- mNotificationPanel.animateCloseQs(false /* animateAway */);
+ mNotificationPanelViewController.animateCloseQs(false /* animateAway */);
}
return true;
}
if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED) {
- if (mNotificationPanel.canPanelBeCollapsed()) {
+ if (mNotificationPanelViewController.canPanelBeCollapsed()) {
mShadeController.animateCollapsePanels();
} else {
mBubbleController.performBackPressIfNeeded();
@@ -3429,7 +3425,7 @@
}
void instantCollapseNotificationPanel() {
- mNotificationPanel.instantCollapse();
+ mNotificationPanelViewController.instantCollapse();
mShadeController.runPostCollapseRunnables();
}
@@ -3491,12 +3487,11 @@
public void onDozingChanged(boolean isDozing) {
Trace.beginSection("StatusBar#updateDozing");
mDozing = isDozing;
- mDozeServiceHost.setDozing(mDozing);
// Collapse the notification panel if open
boolean dozingAnimated = mDozeServiceHost.getDozingRequested()
&& mDozeParameters.shouldControlScreenOff();
- mNotificationPanel.resetViews(dozingAnimated);
+ mNotificationPanelViewController.resetViews(dozingAnimated);
updateQsExpansionEnabled();
mKeyguardViewMediator.setDozing(mDozing);
@@ -3570,7 +3565,7 @@
* @return bottom area view
*/
public KeyguardBottomAreaView getKeyguardBottomAreaView() {
- return mNotificationPanel.getKeyguardBottomAreaView();
+ return mNotificationPanelViewController.getKeyguardBottomAreaView();
}
/**
@@ -3609,7 +3604,7 @@
mDraggedDownEntry = entry;
mPendingRemoteInputView = null;
} else {
- mNotificationPanel.animateToFullShade(0 /* delay */);
+ mNotificationPanelViewController.animateToFullShade(0 /* delay */);
mStatusBarStateController.setState(StatusBarState.SHADE_LOCKED);
}
}
@@ -3635,7 +3630,7 @@
* Collapses the notification shade if it is tracking or expanded.
*/
public void collapseShade() {
- if (mNotificationPanel.isTracking()) {
+ if (mNotificationPanelViewController.isTracking()) {
mStatusBarWindowViewController.cancelCurrentTouch();
}
if (mPanelExpanded && mState == StatusBarState.SHADE) {
@@ -3647,7 +3642,7 @@
final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() {
@Override
public void onFinishedGoingToSleep() {
- mNotificationPanel.onAffordanceLaunchEnded();
+ mNotificationPanelViewController.onAffordanceLaunchEnded();
releaseGestureWakeLock();
mLaunchCameraWhenFinishedWaking = false;
mDeviceInteractive = false;
@@ -3708,7 +3703,8 @@
mBypassHeadsUpNotifier.setFullyAwake(true);
mWakeUpCoordinator.setWakingUp(false);
if (mLaunchCameraWhenFinishedWaking) {
- mNotificationPanel.launchCamera(false /* animate */, mLastCameraLaunchSource);
+ mNotificationPanelViewController.launchCamera(
+ false /* animate */, mLastCameraLaunchSource);
mLaunchCameraWhenFinishedWaking = false;
}
updateScrimController();
@@ -3725,7 +3721,7 @@
&& !mDozeParameters.shouldControlScreenOff();
boolean disabled = (!mDeviceInteractive && !mDozeServiceHost.isPulsing())
|| goingToSleepWithoutAnimation;
- mNotificationPanel.setTouchAndAnimationDisabled(disabled);
+ mNotificationPanelViewController.setTouchAndAnimationDisabled(disabled);
mNotificationIconAreaController.setAnimationsEnabled(!disabled);
}
@@ -3733,7 +3729,7 @@
@Override
public void onScreenTurningOn() {
mFalsingManager.onScreenTurningOn();
- mNotificationPanel.onScreenTurningOn();
+ mNotificationPanelViewController.onScreenTurningOn();
}
@Override
@@ -3802,7 +3798,7 @@
mLaunchCameraOnFinishedGoingToSleep = true;
return;
}
- if (!mNotificationPanel.canCameraGestureBeLaunched()) {
+ if (!mNotificationPanelViewController.canCameraGestureBeLaunched()) {
if (DEBUG_CAMERA_LIFT) Slog.d(TAG, "Can't launch camera right now");
return;
}
@@ -3832,7 +3828,8 @@
if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
mStatusBarKeyguardViewManager.reset(true /* hide */);
}
- mNotificationPanel.launchCamera(mDeviceInteractive /* animate */, source);
+ mNotificationPanelViewController.launchCamera(
+ mDeviceInteractive /* animate */, source);
updateScrimController();
} else {
// We need to defer the camera launch until the screen comes on, since otherwise
@@ -3891,7 +3888,7 @@
!mBiometricUnlockController.isBiometricUnlock());
boolean launchingAffordanceWithPreview =
- mNotificationPanel.isLaunchingAffordanceWithPreview();
+ mNotificationPanelViewController.isLaunchingAffordanceWithPreview();
mScrimController.setLaunchingAffordanceWithPreview(launchingAffordanceWithPreview);
if (mBouncerShowing) {
@@ -4029,6 +4026,11 @@
}
}
+ public void setNotificationSnoozed(StatusBarNotification sbn, int hoursToSnooze) {
+ mNotificationListener.snoozeNotification(sbn.getKey(),
+ hoursToSnooze * 60 * 60 * 1000);
+ }
+
@Override
public void toggleSplitScreen() {
toggleSplitScreenMode(-1 /* metricsDockAction */, -1 /* metricsUndockAction */);
@@ -4240,7 +4242,7 @@
* When {@link KeyguardBouncer} starts to be dismissed, playing its animation.
*/
public void onBouncerPreHideAnimation() {
- mNotificationPanel.onBouncerPreHideAnimation();
+ mNotificationPanelViewController.onBouncerPreHideAnimation();
mLockscreenLockIconController.onBouncerPreHideAnimation();
}
@@ -4283,8 +4285,8 @@
mAssistManagerLazy.get().showDisclosure();
}
- public NotificationPanelView getPanel() {
- return mNotificationPanel;
+ public NotificationPanelViewController getPanelController() {
+ return mNotificationPanelViewController;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index f51174b..407d256 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -133,7 +133,7 @@
protected LockPatternUtils mLockPatternUtils;
protected ViewMediatorCallback mViewMediatorCallback;
protected StatusBar mStatusBar;
- private NotificationPanelView mNotificationPanelView;
+ private NotificationPanelViewController mNotificationPanelViewController;
private BiometricUnlockController mBiometricUnlockController;
private ViewGroup mContainer;
@@ -224,7 +224,7 @@
public void registerStatusBar(StatusBar statusBar,
ViewGroup container,
- NotificationPanelView notificationPanelView,
+ NotificationPanelViewController notificationPanelViewController,
BiometricUnlockController biometricUnlockController,
DismissCallbackRegistry dismissCallbackRegistry,
ViewGroup lockIconContainer, View notificationContainer,
@@ -239,8 +239,8 @@
mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext,
mViewMediatorCallback, mLockPatternUtils, container, dismissCallbackRegistry,
mExpansionCallback, mKeyguardStateController, falsingManager, bypassController);
- mNotificationPanelView = notificationPanelView;
- notificationPanelView.addExpansionListener(this);
+ mNotificationPanelViewController = notificationPanelViewController;
+ notificationPanelViewController.addExpansionListener(this);
mBypassController = bypassController;
mNotificationContainer = notificationContainer;
}
@@ -253,7 +253,7 @@
// • The user quickly taps on the display and we show "swipe up to unlock."
// • Keyguard will be dismissed by an action. a.k.a: FLAG_DISMISS_KEYGUARD_ACTIVITY
// • Full-screen user switcher is displayed.
- if (mNotificationPanelView.isUnlockHintRunning()) {
+ if (mNotificationPanelViewController.isUnlockHintRunning()) {
mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
} else if (bouncerNeedsScrimming()) {
mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
@@ -284,7 +284,7 @@
return;
}
boolean keyguardWithoutQs = mStatusBarStateController.getState() == StatusBarState.KEYGUARD
- && !mNotificationPanelView.isQsExpanded();
+ && !mNotificationPanelViewController.isQsExpanded();
boolean lockVisible = (mBouncer.isShowing() || keyguardWithoutQs)
&& !mBouncer.isAnimatingAway() && !mKeyguardStateController.isKeyguardFadingAway();
@@ -555,7 +555,7 @@
} else if (finishRunnable != null) {
finishRunnable.run();
}
- mNotificationPanelView.blockExpansionForCurrentTouch();
+ mNotificationPanelViewController.blockExpansionForCurrentTouch();
updateLockIcon();
}
@@ -609,7 +609,8 @@
hideBouncer(true /* destroyView */);
if (wakeUnlockPulsing) {
if (needsFading) {
- ViewGroupFadeHelper.fadeOutAllChildrenExcept(mNotificationPanelView,
+ ViewGroupFadeHelper.fadeOutAllChildrenExcept(
+ mNotificationPanelViewController.getView(),
mNotificationContainer,
fadeoutDuration,
() -> {
@@ -625,7 +626,8 @@
if (!staying) {
mStatusBarWindowController.setKeyguardFadingAway(true);
if (needsFading) {
- ViewGroupFadeHelper.fadeOutAllChildrenExcept(mNotificationPanelView,
+ ViewGroupFadeHelper.fadeOutAllChildrenExcept(
+ mNotificationPanelViewController.getView(),
mNotificationContainer,
fadeoutDuration,
() -> {
@@ -684,7 +686,7 @@
public void onKeyguardFadedAway() {
mContainer.postDelayed(() -> mStatusBarWindowController.setKeyguardFadingAway(false),
100);
- ViewGroupFadeHelper.reset(mNotificationPanelView);
+ ViewGroupFadeHelper.reset(mNotificationPanelViewController.getView());
mStatusBar.finishKeyguardFadingAway();
mBiometricUnlockController.finishKeyguardFadingAway();
WindowManagerGlobal.getInstance().trimMemory(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java
index 12033de..b4d5dad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarModule.java
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.phone;
-import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
import android.content.Context;
@@ -66,9 +65,11 @@
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
-import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
+import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -116,8 +117,7 @@
HeadsUpManagerPhone headsUpManagerPhone,
DynamicPrivacyController dynamicPrivacyController,
BypassHeadsUpNotifier bypassHeadsUpNotifier,
- @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowNotificationLongPress,
- Lazy<NewNotifPipeline> newNotifPipeline,
+ Lazy<NotifPipelineInitializer> newNotifPipeline,
FalsingManager falsingManager,
BroadcastDispatcher broadcastDispatcher,
RemoteInputQuickSettingsDisabler remoteInputQuickSettingsDisabler,
@@ -183,6 +183,7 @@
KeyguardDismissUtil keyguardDismissUtil,
ExtensionController extensionController,
UserInfoControllerImpl userInfoControllerImpl,
+ NotificationRowBinderImpl notificationRowBinder,
DismissCallbackRegistry dismissCallbackRegistry) {
return new StatusBar(
context,
@@ -198,7 +199,6 @@
headsUpManagerPhone,
dynamicPrivacyController,
bypassHeadsUpNotifier,
- allowNotificationLongPress,
newNotifPipeline,
falsingManager,
broadcastDispatcher,
@@ -264,6 +264,7 @@
keyguardDismissUtil,
extensionController,
userInfoControllerImpl,
+ notificationRowBinder,
dismissCallbackRegistry);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 661a7b1..0f3b5db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -61,7 +61,6 @@
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.SuperStatusBarViewFactory;
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
@@ -102,7 +101,7 @@
private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
private final MetricsLogger mMetricsLogger;
private final Context mContext;
- private final NotificationPanelView mNotificationPanel;
+ private final NotificationPanelViewController mNotificationPanel;
private final NotificationPresenter mPresenter;
private final LockPatternUtils mLockPatternUtils;
private final HeadsUpManagerPhone mHeadsUpManager;
@@ -121,7 +120,7 @@
private boolean mIsCollapsingToShowActivityOverLockscreen;
private StatusBarNotificationActivityStarter(Context context, CommandQueue commandQueue,
- Lazy<AssistManager> assistManagerLazy, NotificationPanelView panel,
+ Lazy<AssistManager> assistManagerLazy, NotificationPanelViewController panel,
NotificationPresenter presenter, NotificationEntryManager entryManager,
HeadsUpManagerPhone headsUpManager, ActivityStarter activityStarter,
ActivityLaunchAnimator activityLaunchAnimator, IStatusBarService statusBarService,
@@ -519,7 +518,6 @@
private final NotificationGroupManager mGroupManager;
private final NotificationLockscreenUserManager mLockscreenUserManager;
private final KeyguardStateController mKeyguardStateController;
- private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
private final MetricsLogger mMetricsLogger;
private final LockPatternUtils mLockPatternUtils;
private final Handler mMainThreadHandler;
@@ -527,7 +525,8 @@
private final Executor mUiBgExecutor;
private final ActivityIntentHelper mActivityIntentHelper;
private final BubbleController mBubbleController;
- private final SuperStatusBarViewFactory mSuperStatusBarViewFactory;
+ private NotificationPanelViewController mNotificationPanelViewController;
+ private NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
private final ShadeController mShadeController;
private NotificationPresenter mNotificationPresenter;
private ActivityLaunchAnimator mActivityLaunchAnimator;
@@ -558,8 +557,7 @@
@UiBackground Executor uiBgExecutor,
ActivityIntentHelper activityIntentHelper,
BubbleController bubbleController,
- ShadeController shadeController,
- SuperStatusBarViewFactory superStatusBarViewFactory) {
+ ShadeController shadeController) {
mContext = context;
mCommandQueue = commandQueue;
mAssistManagerLazy = assistManagerLazy;
@@ -585,7 +583,6 @@
mActivityIntentHelper = activityIntentHelper;
mBubbleController = bubbleController;
mShadeController = shadeController;
- mSuperStatusBarViewFactory = superStatusBarViewFactory;
}
/** Sets the status bar to use as {@link StatusBar}. */
@@ -604,10 +601,19 @@
return this;
}
+ /** Set the NotificationPanelViewController */
+ public Builder setNotificationPanelViewController(
+ NotificationPanelViewController notificationPanelViewController) {
+ mNotificationPanelViewController = notificationPanelViewController;
+ return this;
+ }
+
+
+
public StatusBarNotificationActivityStarter build() {
return new StatusBarNotificationActivityStarter(mContext,
mCommandQueue, mAssistManagerLazy,
- mSuperStatusBarViewFactory.getNotificationPanelView(),
+ mNotificationPanelViewController,
mNotificationPresenter,
mEntryManager,
mHeadsUpManager,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index beb4579..720f229 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -66,7 +66,7 @@
import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
@@ -107,7 +107,7 @@
private final NotificationGutsManager mGutsManager =
Dependency.get(NotificationGutsManager.class);
- private final NotificationPanelView mNotificationPanel;
+ private final NotificationPanelViewController mNotificationPanel;
private final HeadsUpManagerPhone mHeadsUpManager;
private final AboveShelfObserver mAboveShelfObserver;
private final DozeScrimController mDozeScrimController;
@@ -132,7 +132,7 @@
private int mMaxKeyguardNotifications;
public StatusBarNotificationPresenter(Context context,
- NotificationPanelView panel,
+ NotificationPanelViewController panel,
HeadsUpManagerPhone headsUp,
StatusBarWindowView statusBarWindow,
ViewGroup stackScroller,
@@ -172,7 +172,7 @@
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
if (MULTIUSER_DEBUG) {
- mNotificationPanelDebugText = mNotificationPanel.findViewById(R.id.header_debug_info);
+ mNotificationPanelDebugText = mNotificationPanel.getHeaderDebugInfo();
mNotificationPanelDebugText.setVisibility(View.VISIBLE);
}
@@ -217,7 +217,7 @@
mEntryManager.addNotificationLifetimeExtenders(
remoteInputManager.getLifetimeExtenders());
notificationRowBinder.setUpWithPresenter(this, notifListContainer, mHeadsUpManager,
- mEntryManager, this);
+ this);
mNotificationInterruptionStateProvider.setUpWithPresenter(
this, mHeadsUpManager, this::canHeadsUp);
mLockscreenUserManager.setUpWithPresenter(this);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
index 6b51391..1e3c5d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java
@@ -75,6 +75,10 @@
setMotionEventSplittingEnabled(false);
}
+ public NotificationPanelView getNotificationPanelView() {
+ return findViewById(R.id.notification_panel);
+ }
+
@Override
public WindowInsets onApplyWindowInsets(WindowInsets windowInsets) {
final Insets insets = windowInsets.getMaxInsets(WindowInsets.Type.systemBars());
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowViewController.java
index eb86bcc..4935f0e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowViewController.java
@@ -26,11 +26,9 @@
import android.view.GestureDetector;
import android.view.InputDevice;
import android.view.KeyEvent;
-import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewStub;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.ExpandHelper;
@@ -93,6 +91,7 @@
private boolean mSingleTapEnabled;
private boolean mExpandingBelowNotch;
private final DockManager mDockManager;
+ private final NotificationPanelViewController mNotificationPanelViewController;
@Inject
public StatusBarWindowViewController(
@@ -113,7 +112,8 @@
CommandQueue commandQueue,
ShadeController shadeController,
DockManager dockManager,
- StatusBarWindowView statusBarWindowView) {
+ StatusBarWindowView statusBarWindowView,
+ NotificationPanelViewController notificationPanelViewController) {
mInjectionInflationController = injectionInflationController;
mCoordinator = coordinator;
mPulseExpansionHandler = pulseExpansionHandler;
@@ -132,6 +132,7 @@
mView = statusBarWindowView;
mShadeController = shadeController;
mDockManager = dockManager;
+ mNotificationPanelViewController = notificationPanelViewController;
// This view is not part of the newly inflated expanded status bar.
mBrightnessMirror = mView.findViewById(R.id.brightness_mirror);
@@ -139,39 +140,6 @@
/** Inflates the {@link R.layout#status_bar_expanded} layout and sets it up. */
public void setupExpandedStatusBar() {
- // TODO: create controller for NotificationPanelView
- NotificationPanelView notificationPanelView = new NotificationPanelView(
- mView.getContext(),
- null,
- mInjectionInflationController,
- mCoordinator,
- mPulseExpansionHandler,
- mDynamicPrivacyController,
- mBypassController,
- mFalsingManager,
- mPluginManager,
- mShadeController,
- mNotificationLockscreenUserManager,
- mNotificationEntryManager,
- mKeyguardStateController,
- mStatusBarStateController,
- mDozeLog,
- mDozeParameters,
- mCommandQueue);
- ViewGroup.LayoutParams lp = new ViewGroup.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
- notificationPanelView.setVisibility(View.INVISIBLE);
- notificationPanelView.setId(R.id.notification_panel);
- LayoutInflater li = mInjectionInflationController.injectable(
- LayoutInflater.from(mView.getContext()));
-
- li.inflate(R.layout.status_bar_expanded, notificationPanelView);
- notificationPanelView.onChildrenAttached();
-
- ViewStub statusBarExpanded = mView.findViewById(R.id.status_bar_expanded);
- mView.addView(notificationPanelView, mView.indexOfChild(statusBarExpanded), lp);
- mView.removeView(statusBarExpanded);
-
mStackScrollLayout = mView.findViewById(R.id.notification_stack_scroller);
TunerService.Tunable tunable = (key, newValue) -> {
@@ -233,8 +201,8 @@
if (!isCancel && mService.shouldIgnoreTouch()) {
return false;
}
- if (isDown && notificationPanelView.isFullyCollapsed()) {
- notificationPanelView.startExpandLatencyTracking();
+ if (isDown && mNotificationPanelViewController.isFullyCollapsed()) {
+ mNotificationPanelViewController.startExpandLatencyTracking();
}
if (isDown) {
setTouchActive(true);
@@ -287,7 +255,7 @@
return true;
}
boolean intercept = false;
- if (notificationPanelView.isFullyExpanded()
+ if (mNotificationPanelViewController.isFullyExpanded()
&& mDragDownHelper.isDragDownEnabled()
&& !mService.isBouncerShowing()
&& !mStatusBarStateController.isDozing()) {
@@ -303,7 +271,7 @@
MotionEvent cancellation = MotionEvent.obtain(ev);
cancellation.setAction(MotionEvent.ACTION_CANCEL);
mStackScrollLayout.onInterceptTouchEvent(cancellation);
- notificationPanelView.onInterceptTouchEvent(cancellation);
+ mNotificationPanelViewController.getView().onInterceptTouchEvent(cancellation);
cancellation.recycle();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java
similarity index 74%
rename from packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarComponent.java
rename to packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java
index f3c843c..21d0bb8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarComponent.java
@@ -14,10 +14,14 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.statusbar.phone.dagger;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
+import com.android.systemui.statusbar.phone.NotificationPanelViewController;
+import com.android.systemui.statusbar.phone.StatusBarWindowView;
+import com.android.systemui.statusbar.phone.StatusBarWindowViewController;
+
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
@@ -29,7 +33,8 @@
/**
* Dagger subcomponent tied to the lifecycle of StatusBar views.
*/
-@Subcomponent
+@Subcomponent(modules = {StatusBarViewModule.class})
+@StatusBarComponent.StatusBarScope
public interface StatusBarComponent {
/**
* Builder for {@link StatusBarComponent}.
@@ -54,4 +59,10 @@
@StatusBarScope
StatusBarWindowViewController getStatusBarWindowViewController();
+ /**
+ * Creates a NotificationPanelViewController.
+ */
+ @StatusBarScope
+ NotificationPanelViewController getNotificationPanelViewController();
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
new file mode 100644
index 0000000..20bd51d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -0,0 +1,35 @@
+/*
+ * 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 com.android.systemui.statusbar.phone.dagger;
+
+import com.android.systemui.statusbar.phone.NotificationPanelView;
+import com.android.systemui.statusbar.phone.StatusBarWindowView;
+
+import dagger.Module;
+import dagger.Provides;
+
+@Module
+public abstract class StatusBarViewModule {
+ /** */
+ @Provides
+ @StatusBarComponent.StatusBarScope
+ public static NotificationPanelView getNotificationPanelView(
+ StatusBarWindowView statusBarWindowView) {
+ return statusBarWindowView.getNotificationPanelView();
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
index 88edf8e..625d884 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
@@ -24,7 +24,7 @@
import android.widget.FrameLayout;
import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.NotificationPanelView;
+import com.android.systemui.statusbar.phone.NotificationPanelViewController;
import com.android.systemui.statusbar.phone.StatusBarWindowView;
import java.util.Objects;
@@ -38,16 +38,17 @@
private final StatusBarWindowView mStatusBarWindow;
private final Consumer<Boolean> mVisibilityCallback;
- private final NotificationPanelView mNotificationPanel;
+ private final NotificationPanelViewController mNotificationPanel;
private final ArraySet<BrightnessMirrorListener> mBrightnessMirrorListeners = new ArraySet<>();
private final int[] mInt2Cache = new int[2];
private View mBrightnessMirror;
public BrightnessMirrorController(StatusBarWindowView statusBarWindow,
+ NotificationPanelViewController notificationPanelViewController,
@NonNull Consumer<Boolean> visibilityCallback) {
mStatusBarWindow = statusBarWindow;
mBrightnessMirror = statusBarWindow.findViewById(R.id.brightness_mirror);
- mNotificationPanel = statusBarWindow.findViewById(R.id.notification_panel);
+ mNotificationPanel = notificationPanelViewController;
mNotificationPanel.setPanelAlphaEndAction(() -> {
mBrightnessMirror.setVisibility(View.INVISIBLE);
});
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
index ddacc3a..4f0af9e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/Clock.java
@@ -188,7 +188,7 @@
// NOTE: This receiver could run before this method returns, as it's not dispatching
// on the main thread and BroadcastDispatcher may not need to register with Context.
// The receiver will return immediately if the view does not have a Handler yet.
- mBroadcastDispatcher.registerReceiver(mIntentReceiver, filter,
+ mBroadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter,
Dependency.get(Dependency.TIME_TICK_HANDLER), UserHandle.ALL);
Dependency.get(TunerService.class).addTunable(this, CLOCK_SECONDS,
StatusBarIconController.ICON_BLACKLIST);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java
index 2e26711..b4c154a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DateView.java
@@ -96,7 +96,7 @@
filter.addAction(Intent.ACTION_TIME_CHANGED);
filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
filter.addAction(Intent.ACTION_LOCALE_CHANGED);
- mBroadcastDispatcher.registerReceiver(mIntentReceiver, filter,
+ mBroadcastDispatcher.registerReceiverWithHandler(mIntentReceiver, filter,
Dependency.get(Dependency.TIME_TICK_HANDLER));
updateClock();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
index 4b283fed..d28a6699 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java
@@ -35,7 +35,7 @@
import com.android.systemui.R;
import com.android.systemui.qs.tiles.UserDetailItemView;
import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
-import com.android.systemui.statusbar.phone.NotificationPanelView;
+import com.android.systemui.statusbar.phone.NotificationPanelViewController;
/**
* Manages the user switcher on the Keyguard.
@@ -57,7 +57,8 @@
private boolean mAnimating;
public KeyguardUserSwitcher(Context context, ViewStub userSwitcher,
- KeyguardStatusBarView statusBarView, NotificationPanelView panelView) {
+ KeyguardStatusBarView statusBarView,
+ NotificationPanelViewController panelViewController) {
boolean keyguardUserSwitcherEnabled =
context.getResources().getBoolean(R.bool.config_keyguardUserSwitcher) || ALWAYS_ON;
UserSwitcherController userSwitcherController = Dependency.get(UserSwitcherController.class);
@@ -67,7 +68,7 @@
reinflateViews();
mStatusBarView = statusBarView;
mStatusBarView.setKeyguardUserSwitcher(this);
- panelView.setKeyguardUserSwitcher(this);
+ panelViewController.setKeyguardUserSwitcher(this);
mAdapter = new Adapter(context, userSwitcherController, this);
mAdapter.registerDataSetObserver(mDataSetObserver);
mUserSwitcherController = userSwitcherController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
index 570f153..cb40d77 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java
@@ -79,7 +79,8 @@
IntentFilter filter = new IntentFilter();
filter.addAction(LocationManager.HIGH_POWER_REQUEST_CHANGE_ACTION);
filter.addAction(LocationManager.MODE_CHANGED_ACTION);
- mBroadcastDispatcher.registerReceiver(this, filter, new Handler(bgLooper), UserHandle.ALL);
+ mBroadcastDispatcher.registerReceiverWithHandler(this, filter,
+ new Handler(bgLooper), UserHandle.ALL);
mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
mStatusBarManager
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
index 6759020..cca100f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/MobileSignalController.java
@@ -15,6 +15,9 @@
*/
package com.android.systemui.statusbar.policy;
+import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
+import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
+
import android.content.Context;
import android.content.Intent;
import android.database.ContentObserver;
@@ -23,6 +26,7 @@
import android.os.Looper;
import android.os.Message;
import android.provider.Settings.Global;
+import android.telephony.Annotation;
import android.telephony.CellSignalStrength;
import android.telephony.CellSignalStrengthCdma;
import android.telephony.NetworkRegistrationInfo;
@@ -34,7 +38,6 @@
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
-import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.TelephonyIntents;
@@ -50,7 +53,9 @@
import java.io.PrintWriter;
import java.util.BitSet;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.regex.Matcher;
@@ -74,12 +79,14 @@
final SubscriptionInfo mSubscriptionInfo;
// @VisibleForDemoMode
- final SparseArray<MobileIconGroup> mNetworkToIconLookup;
+ final Map<String, MobileIconGroup> mNetworkToIconLookup;
// Since some pieces of the phone state are interdependent we store it locally,
// this could potentially become part of MobileState for simplification/complication
// of code.
private int mDataNetType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
+ private boolean mCA = false;
+ private boolean mCAPlus = false;
private int mDataState = TelephonyManager.DATA_DISCONNECTED;
private ServiceState mServiceState;
private SignalStrength mSignalStrength;
@@ -90,9 +97,6 @@
boolean mInflateSignalStrengths = false;
@VisibleForTesting
boolean mIsShowingIconGracefully = false;
- // Some specific carriers have 5GE network which is special LTE CA network.
- private static final int NETWORK_TYPE_LTE_CA_5GE =
- TelephonyManager.getAllNetworkTypes().length + 1;
// TODO: Reduce number of vars passed in, if we have the NetworkController, probably don't
// need listener lists anymore.
@@ -103,7 +107,7 @@
super("MobileSignalController(" + info.getSubscriptionId() + ")", context,
NetworkCapabilities.TRANSPORT_CELLULAR, callbackHandler,
networkController);
- mNetworkToIconLookup = new SparseArray<>();
+ mNetworkToIconLookup = new HashMap<>();
mConfig = config;
mPhone = phone;
mDefaults = defaults;
@@ -210,29 +214,38 @@
private void mapIconSets() {
mNetworkToIconLookup.clear();
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_0, TelephonyIcons.THREE_G);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_A, TelephonyIcons.THREE_G);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EVDO_B, TelephonyIcons.THREE_G);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EHRPD, TelephonyIcons.THREE_G);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UMTS, TelephonyIcons.THREE_G);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_TD_SCDMA, TelephonyIcons.THREE_G);
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_EVDO_0),
+ TelephonyIcons.THREE_G);
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_EVDO_A),
+ TelephonyIcons.THREE_G);
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_EVDO_B),
+ TelephonyIcons.THREE_G);
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_EHRPD),
+ TelephonyIcons.THREE_G);
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_UMTS),
+ TelephonyIcons.THREE_G);
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_TD_SCDMA),
+ TelephonyIcons.THREE_G);
if (!mConfig.showAtLeast3G) {
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_UNKNOWN),
TelephonyIcons.UNKNOWN);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EDGE, TelephonyIcons.E);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_CDMA, TelephonyIcons.ONE_X);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_1xRTT, TelephonyIcons.ONE_X);
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_EDGE),
+ TelephonyIcons.E);
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_CDMA),
+ TelephonyIcons.ONE_X);
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_1xRTT),
+ TelephonyIcons.ONE_X);
mDefaultIcons = TelephonyIcons.G;
} else {
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_UNKNOWN,
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_UNKNOWN),
TelephonyIcons.THREE_G);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_EDGE,
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_EDGE),
TelephonyIcons.THREE_G);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_CDMA,
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_CDMA),
TelephonyIcons.THREE_G);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_1xRTT,
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_1xRTT),
TelephonyIcons.THREE_G);
mDefaultIcons = TelephonyIcons.THREE_G;
}
@@ -247,33 +260,59 @@
hPlusGroup = TelephonyIcons.H_PLUS;
}
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSDPA, hGroup);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSUPA, hGroup);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSPA, hGroup);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_HSPAP, hPlusGroup);
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_HSDPA), hGroup);
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_HSUPA), hGroup);
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_HSPA), hGroup);
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_HSPAP), hPlusGroup);
if (mConfig.show4gForLte) {
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE, TelephonyIcons.FOUR_G);
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_LTE),
+ TelephonyIcons.FOUR_G);
if (mConfig.hideLtePlus) {
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE_CA,
+ mNetworkToIconLookup.put(toIconKeyCA(TelephonyManager.NETWORK_TYPE_LTE),
TelephonyIcons.FOUR_G);
} else {
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE_CA,
+ mNetworkToIconLookup.put(toIconKeyCA(TelephonyManager.NETWORK_TYPE_LTE),
TelephonyIcons.FOUR_G_PLUS);
}
} else {
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE, TelephonyIcons.LTE);
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_LTE),
+ TelephonyIcons.LTE);
if (mConfig.hideLtePlus) {
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE_CA,
+ mNetworkToIconLookup.put(toIconKeyCA(TelephonyManager.NETWORK_TYPE_LTE),
TelephonyIcons.LTE);
} else {
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_LTE_CA,
+ mNetworkToIconLookup.put(toIconKeyCA(TelephonyManager.NETWORK_TYPE_LTE),
TelephonyIcons.LTE_PLUS);
}
}
- mNetworkToIconLookup.put(NETWORK_TYPE_LTE_CA_5GE,
+ mNetworkToIconLookup.put(toIconKeyCAPlus(TelephonyManager.NETWORK_TYPE_LTE),
TelephonyIcons.LTE_CA_5G_E);
- mNetworkToIconLookup.put(TelephonyManager.NETWORK_TYPE_IWLAN, TelephonyIcons.WFC);
+ mNetworkToIconLookup.put(toIconKey(TelephonyManager.NETWORK_TYPE_IWLAN),
+ TelephonyIcons.WFC);
+ }
+
+ private String getIconKey() {
+ if (mCA) {
+ return toIconKeyCA(mDataNetType);
+ } else if (mCAPlus) {
+ return toIconKeyCAPlus(mDataNetType);
+ } else {
+ return toIconKey(mDataNetType);
+ }
+ }
+
+ // Some specific carriers have 5GE network which is special CA network.
+ private String toIconKeyCAPlus(@Annotation.NetworkType int networkType) {
+ return toIconKeyCA(networkType) + "_Plus";
+ }
+
+ private String toIconKeyCA(@Annotation.NetworkType int networkType) {
+ return toIconKey(networkType) + "_CA";
+ }
+
+ private String toIconKey(@Annotation.NetworkType int networkType) {
+ return Integer.toString(networkType);
}
private void updateInflateSignalStrength() {
@@ -398,7 +437,7 @@
intent.getBooleanExtra(TelephonyIntents.EXTRA_SHOW_PLMN, false),
intent.getStringExtra(TelephonyIntents.EXTRA_PLMN));
notifyListenersIfNecessary();
- } else if (action.equals(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)) {
+ } else if (action.equals(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)) {
updateDataSim();
notifyListenersIfNecessary();
}
@@ -520,10 +559,11 @@
nr5GIconGroup = adjustNr5GIconGroupByDisplayGraceTime(nr5GIconGroup);
}
+ String iconKey = getIconKey();
if (nr5GIconGroup != null) {
mCurrentState.iconGroup = nr5GIconGroup;
- } else if (mNetworkToIconLookup.indexOfKey(mDataNetType) >= 0) {
- mCurrentState.iconGroup = mNetworkToIconLookup.get(mDataNetType);
+ } else if (mNetworkToIconLookup.get(iconKey) != null) {
+ mCurrentState.iconGroup = mNetworkToIconLookup.get(iconKey);
} else {
mCurrentState.iconGroup = mDefaultIcons;
}
@@ -654,7 +694,7 @@
}
boolean isDataDisabled() {
- return !mPhone.isDataCapable();
+ return !mPhone.isDataConnectionEnabled();
}
@VisibleForTesting
@@ -676,6 +716,8 @@
pw.println(" mSignalStrength=" + mSignalStrength + ",");
pw.println(" mDataState=" + mDataState + ",");
pw.println(" mDataNetType=" + mDataNetType + ",");
+ pw.println(" mCA=" + mCA + ",");
+ pw.println(" mCAPlus=" + mCAPlus + ",");
pw.println(" mInflateSignalStrengths=" + mInflateSignalStrengths + ",");
pw.println(" isDataDisabled=" + isDataDisabled() + ",");
pw.println(" mIsShowingIconGracefully=" + mIsShowingIconGracefully + ",");
@@ -704,7 +746,11 @@
}
mServiceState = state;
if (mServiceState != null) {
- updateDataNetType(mServiceState.getDataNetworkType());
+ NetworkRegistrationInfo regInfo = mServiceState.getNetworkRegistrationInfo(
+ DOMAIN_PS, TRANSPORT_TYPE_WWAN);
+ if (regInfo != null) {
+ updateDataNetType(regInfo.getAccessNetworkTechnology());
+ }
}
updateTelephony();
}
@@ -722,11 +768,13 @@
private void updateDataNetType(int networkType) {
mDataNetType = networkType;
+ mCA = false;
+ mCAPlus = false;
if (mDataNetType == TelephonyManager.NETWORK_TYPE_LTE) {
if (isCarrierSpecificDataIcon()) {
- mDataNetType = NETWORK_TYPE_LTE_CA_5GE;
+ mCAPlus = true;
} else if (mServiceState != null && mServiceState.isUsingCarrierAggregation()) {
- mDataNetType = TelephonyManager.NETWORK_TYPE_LTE_CA;
+ mCA = true;
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index f20a47b..6b3c5dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -22,8 +22,7 @@
import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_NONE;
import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_OUT;
import static android.telephony.PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE;
-
-import static com.android.internal.telephony.PhoneConstants.MAX_PHONE_COUNT_DUAL_SIM;
+import static android.telephony.TelephonyManager.MODEM_COUNT_DUAL_MODEM;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -57,7 +56,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.TelephonyIntents;
import com.android.settingslib.net.DataUsageController;
import com.android.systemui.DemoMode;
@@ -318,15 +316,15 @@
filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
- filter.addAction(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
- filter.addAction(TelephonyIntents.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED);
- filter.addAction(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
+ filter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
+ filter.addAction(TelephonyManager.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED);
+ filter.addAction(Intent.ACTION_SERVICE_STATE);
filter.addAction(TelephonyIntents.SPN_STRINGS_UPDATED_ACTION);
filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
filter.addAction(ConnectivityManager.INET_CONDITION_ACTION);
filter.addAction(Intent.ACTION_AIRPLANE_MODE_CHANGED);
filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
- mBroadcastDispatcher.registerReceiver(this, filter, mReceiverHandler);
+ mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mReceiverHandler);
mListening = true;
updateMobileControllers();
@@ -512,11 +510,11 @@
refreshLocale();
updateAirplaneMode(false);
break;
- case TelephonyIntents.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED:
+ case TelephonyManager.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED:
// We are using different subs now, we might be able to make calls.
recalculateEmergency();
break;
- case TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED:
+ case TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED:
// Notify every MobileSignalController so they can know whether they are the
// data sim or not.
for (int i = 0; i < mMobileSignalControllers.size(); i++) {
@@ -534,7 +532,7 @@
// Might have different subscriptions now.
updateMobileControllers();
break;
- case TelephonyIntents.ACTION_SERVICE_STATE_CHANGED:
+ case Intent.ACTION_SERVICE_STATE:
mLastServiceState = ServiceState.newFromBundle(intent.getExtras());
if (mMobileSignalControllers.size() == 0) {
// If none of the subscriptions are active, we might need to recalculate
@@ -547,7 +545,7 @@
mReceiverHandler.post(this::handleConfigurationChanged);
break;
default:
- int subId = intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY,
+ int subId = intent.getIntExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX,
SubscriptionManager.INVALID_SUBSCRIPTION_ID);
if (SubscriptionManager.isValidSubscriptionId(subId)) {
if (mMobileSignalControllers.indexOfKey(subId) >= 0) {
@@ -582,7 +580,7 @@
}
private void filterMobileSubscriptionInSameGroup(List<SubscriptionInfo> subscriptions) {
- if (subscriptions.size() == MAX_PHONE_COUNT_DUAL_SIM) {
+ if (subscriptions.size() == MODEM_COUNT_DUAL_MODEM) {
SubscriptionInfo info1 = subscriptions.get(0);
SubscriptionInfo info2 = subscriptions.get(1);
if (info1.getGroupUuid() != null && info1.getGroupUuid().equals(info2.getGroupUuid())) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
index 019ef3b..312c4ac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SecurityControllerImpl.java
@@ -126,7 +126,8 @@
IntentFilter filter = new IntentFilter();
filter.addAction(KeyChain.ACTION_TRUST_STORE_CHANGED);
filter.addAction(Intent.ACTION_USER_UNLOCKED);
- broadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, bgHandler, UserHandle.ALL);
+ broadcastDispatcher.registerReceiverWithHandler(mBroadcastReceiver, filter, bgHandler,
+ UserHandle.ALL);
// TODO: re-register network callback on user change.
mConnectivityManager.registerNetworkCallback(REQUEST, mNetworkCallback);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
index e7d1c95..718522c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/WifiSignalController.java
@@ -85,7 +85,8 @@
boolean visibleWhenEnabled = mContext.getResources().getBoolean(
R.bool.config_showWifiIndicatorWhenEnabled);
boolean wifiVisible = mCurrentState.enabled
- && (mCurrentState.connected || !mHasMobileData || visibleWhenEnabled);
+ && ((mCurrentState.connected && mCurrentState.inetCondition == 1)
+ || !mHasMobileData || visibleWhenEnabled);
String wifiDesc = wifiVisible ? mCurrentState.ssid : null;
boolean ssidPresent = wifiVisible && mCurrentState.ssid != null;
String contentDescription = getStringIfExists(getContentDescription());
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 7758aba..31b9952 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -88,7 +88,7 @@
final IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_USER_SWITCHED);
filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
- mBroadcastDispatcher.registerReceiver(new BroadcastReceiver() {
+ mBroadcastDispatcher.registerReceiverWithHandler(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (DEBUG) Log.d(TAG, "Updating overlays for user switch / profile added.");
diff --git a/packages/SystemUI/src/com/android/systemui/util/Assert.java b/packages/SystemUI/src/com/android/systemui/util/Assert.java
index 096ac3f..f6e921e 100644
--- a/packages/SystemUI/src/com/android/systemui/util/Assert.java
+++ b/packages/SystemUI/src/com/android/systemui/util/Assert.java
@@ -18,7 +18,7 @@
import android.os.Looper;
-import com.android.internal.annotations.VisibleForTesting;
+import androidx.annotation.VisibleForTesting;
/**
* Helper providing common assertions.
@@ -30,7 +30,9 @@
public static void isMainThread() {
if (!sMainLooper.isCurrentThread()) {
- throw new IllegalStateException("should be called from the main thread.");
+ throw new IllegalStateException("should be called from the main thread."
+ + " sMainLooper.threadName=" + sMainLooper.getThread().getName()
+ + " Thread.currentThread()=" + Thread.currentThread().getName());
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
index 6976649..56aae17 100644
--- a/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
+++ b/packages/SystemUI/src/com/android/systemui/util/InjectionInflationController.java
@@ -35,7 +35,6 @@
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.phone.LockIcon;
-import com.android.systemui.statusbar.phone.NotificationPanelView;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -129,11 +128,6 @@
NotificationStackScrollLayout createNotificationStackScrollLayout();
/**
- * Creates the NotificationPanelView.
- */
- NotificationPanelView createPanelView();
-
- /**
* Creates the Shelf.
*/
NotificationShelf creatNotificationShelf();
diff --git a/packages/SystemUI/src/com/android/systemui/util/LifecycleActivity.kt b/packages/SystemUI/src/com/android/systemui/util/LifecycleActivity.kt
new file mode 100644
index 0000000..e4b7a20
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/LifecycleActivity.kt
@@ -0,0 +1,72 @@
+/*
+ * 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 com.android.systemui.util
+
+import android.app.Activity
+import android.os.Bundle
+import android.os.PersistableBundle
+import androidx.lifecycle.LifecycleOwner
+import com.android.settingslib.core.lifecycle.Lifecycle
+
+open class LifecycleActivity : Activity(), LifecycleOwner {
+
+ private val lifecycle = Lifecycle(this)
+
+ override fun getLifecycle() = lifecycle
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ lifecycle.onAttach(this)
+ lifecycle.onCreate(savedInstanceState)
+ lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_CREATE)
+ super.onCreate(savedInstanceState)
+ }
+
+ override fun onCreate(
+ savedInstanceState: Bundle?,
+ persistentState: PersistableBundle?
+ ) {
+ lifecycle.onAttach(this)
+ lifecycle.onCreate(savedInstanceState)
+ lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_CREATE)
+ super.onCreate(savedInstanceState, persistentState)
+ }
+
+ override fun onStart() {
+ lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_START)
+ super.onStart()
+ }
+
+ override fun onResume() {
+ lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_RESUME)
+ super.onResume()
+ }
+
+ override fun onPause() {
+ lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_PAUSE)
+ super.onPause()
+ }
+
+ override fun onStop() {
+ lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_STOP)
+ super.onStop()
+ }
+
+ override fun onDestroy() {
+ lifecycle.handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event.ON_DESTROY)
+ super.onDestroy()
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
index cca76bd..8a1759d 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
@@ -27,6 +27,9 @@
import androidx.dynamicanimation.animation.SpringForce
import com.android.systemui.util.animation.PhysicsAnimator.Companion.getInstance
import java.util.WeakHashMap
+import kotlin.math.abs
+import kotlin.math.max
+import kotlin.math.min
/**
* Extension function for all objects which will return a PhysicsAnimator instance for that object.
@@ -35,6 +38,15 @@
private const val TAG = "PhysicsAnimator"
+private val UNSET = -Float.MAX_VALUE
+
+/**
+ * [FlingAnimation] multiplies the friction set via [FlingAnimation.setFriction] by 4.2f, which is
+ * where this number comes from. We use it in [PhysicsAnimator.flingThenSpring] to calculate the
+ * minimum velocity for a fling to reach a certain value, given the fling's friction.
+ */
+private const val FLING_FRICTION_SCALAR_MULTIPLIER = 4.2f
+
typealias EndAction = () -> Unit
/** A map of Property -> AnimationUpdate, which is provided to update listeners on each frame. */
@@ -236,6 +248,71 @@
}
/**
+ * Flings a property using the given start velocity. If the fling animation reaches the min/max
+ * bounds (from the [flingConfig]) with velocity remaining, it'll overshoot it and spring back.
+ *
+ * If the object is already out of the fling bounds, it will immediately spring back within
+ * bounds.
+ *
+ * This is useful for animating objects that are bounded by constraints such as screen edges,
+ * since otherwise the fling animation would end abruptly upon reaching the min/max bounds.
+ *
+ * @param property The property to animate.
+ * @param startVelocity The velocity, in pixels/second, with which to start the fling. If the
+ * object is already outside the fling bounds, this velocity will be used as the start velocity
+ * of the spring that will spring it back within bounds.
+ * @param flingMustReachMinOrMax If true, the fling animation is guaranteed to reach either its
+ * minimum bound (if [startVelocity] is negative) or maximum bound (if it's positive). The
+ * animator will use startVelocity if it's sufficient, or add more velocity if necessary. This
+ * is useful when fling's deceleration-based physics are preferable to the acceleration-based
+ * forces used by springs - typically, when you're allowing the user to move an object somewhere
+ * on the screen, but it needs to be along an edge.
+ * @param flingConfig The configuration to use for the fling portion of the animation.
+ * @param springConfig The configuration to use for the spring portion of the animation.
+ */
+ @JvmOverloads
+ fun flingThenSpring(
+ property: FloatPropertyCompat<in T>,
+ startVelocity: Float,
+ flingConfig: FlingConfig,
+ springConfig: SpringConfig,
+ flingMustReachMinOrMax: Boolean = false
+ ): PhysicsAnimator<T> {
+ val flingConfigCopy = flingConfig.copy()
+ val springConfigCopy = springConfig.copy()
+ val toAtLeast = if (startVelocity < 0) flingConfig.min else flingConfig.max
+
+ // If the fling needs to reach min/max, calculate the velocity required to do so and use
+ // that if the provided start velocity is not sufficient.
+ if (flingMustReachMinOrMax &&
+ toAtLeast != -Float.MAX_VALUE && toAtLeast != Float.MAX_VALUE) {
+ val distanceToDestination = toAtLeast - property.getValue(target)
+
+ // The minimum velocity required for the fling to end up at the given destination,
+ // taking the provided fling friction value.
+ val velocityToReachDestination = distanceToDestination *
+ (flingConfig.friction * FLING_FRICTION_SCALAR_MULTIPLIER)
+
+ // Try to use the provided start velocity, but use the required velocity to reach the
+ // destination if the provided velocity is insufficient.
+ val sufficientVelocity =
+ if (distanceToDestination < 0)
+ min(velocityToReachDestination, startVelocity)
+ else
+ max(velocityToReachDestination, startVelocity)
+
+ flingConfigCopy.startVelocity = sufficientVelocity
+ springConfigCopy.finalPosition = toAtLeast
+ } else {
+ flingConfigCopy.startVelocity = startVelocity
+ }
+
+ flingConfigs[property] = flingConfigCopy
+ springConfigs[property] = springConfigCopy
+ return this
+ }
+
+ /**
* Adds a listener that will be called whenever any property on the animated object is updated.
* This will be called on every animation frame, with the current value of the animated object
* and the new property values.
@@ -246,7 +323,7 @@
}
/**
- * Adds a listener that will be called whenever a property's animation ends. This is useful if
+ * Adds a listener that will be called when a property stops animating. This is useful if
* you care about a specific property ending, or want to use the end value/end velocity from a
* particular property's animation. If you just want to run an action when all property
* animations have ended, use [withEndActions].
@@ -311,6 +388,114 @@
"your test setup.")
}
+ // Functions that will actually start the animations. These are run after we build and add
+ // the InternalListener, since some animations might update/end immediately and we don't
+ // want to miss those updates.
+ val animationStartActions = ArrayList<() -> Unit>()
+
+ for (animatedProperty in getAnimatedProperties()) {
+ val flingConfig = flingConfigs[animatedProperty]
+ val springConfig = springConfigs[animatedProperty]
+
+ // The property's current value on the object.
+ val currentValue = animatedProperty.getValue(target)
+
+ // Start by checking for a fling configuration. If one is present, we're either flinging
+ // or flinging-then-springing. Either way, we'll want to start the fling first.
+ if (flingConfig != null) {
+ animationStartActions.add {
+ // When the animation is starting, adjust the min/max bounds to include the
+ // current value of the property, if necessary. This is required to allow a
+ // fling to bring an out-of-bounds object back into bounds. For example, if an
+ // object was dragged halfway off the left side of the screen, but then flung to
+ // the right, we don't want the animation to end instantly just because the
+ // object started out of bounds. If the fling is in the direction that would
+ // take it farther out of bounds, it will end instantly as expected.
+ flingConfig.apply {
+ min = min(currentValue, this.min)
+ max = max(currentValue, this.max)
+ }
+
+ // Apply the configuration and start the animation.
+ getFlingAnimation(animatedProperty)
+ .also { flingConfig.applyToAnimation(it) }
+ .start()
+ }
+ }
+
+ // Check for a spring configuration. If one is present, we're either springing, or
+ // flinging-then-springing.
+ if (springConfig != null) {
+
+ // If there is no corresponding fling config, we're only springing.
+ if (flingConfig == null) {
+ // Apply the configuration and start the animation.
+ val springAnim = getSpringAnimation(animatedProperty)
+ springConfig.applyToAnimation(springAnim)
+ animationStartActions.add(springAnim::start)
+ } else {
+ // If there's a corresponding fling config, we're flinging-then-springing. Save
+ // the fling's original bounds so we can spring to them when the fling ends.
+ val flingMin = flingConfig.min
+ val flingMax = flingConfig.max
+
+ // Add an end listener that will start the spring when the fling ends.
+ endListeners.add(0, object : EndListener<T> {
+ override fun onAnimationEnd(
+ target: T,
+ property: FloatPropertyCompat<in T>,
+ wasFling: Boolean,
+ canceled: Boolean,
+ finalValue: Float,
+ finalVelocity: Float,
+ allRelevantPropertyAnimsEnded: Boolean
+ ) {
+ // If this isn't the relevant property, it wasn't a fling, or the fling
+ // was explicitly cancelled, don't spring.
+ if (property != animatedProperty || !wasFling || canceled) {
+ return
+ }
+
+ val endedWithVelocity = abs(finalVelocity) > 0
+
+ // If the object was out of bounds when the fling animation started, it
+ // will immediately end. In that case, we'll spring it back in bounds.
+ val endedOutOfBounds = finalValue !in flingMin..flingMax
+
+ // If the fling ended either out of bounds or with remaining velocity,
+ // it's time to spring.
+ if (endedWithVelocity || endedOutOfBounds) {
+ springConfig.startVelocity = finalVelocity
+
+ // If the spring's final position isn't set, this is a
+ // flingThenSpring where flingMustReachMinOrMax was false. We'll
+ // need to set the spring's final position here.
+ if (springConfig.finalPosition == UNSET) {
+ if (endedWithVelocity) {
+ // If the fling ended with negative velocity, that means it
+ // hit the min bound, so spring to that bound (and vice
+ // versa).
+ springConfig.finalPosition =
+ if (finalVelocity < 0) flingMin else flingMax
+ } else if (endedOutOfBounds) {
+ // If the fling ended out of bounds, spring it to the
+ // nearest bound.
+ springConfig.finalPosition =
+ if (finalValue < flingMin) flingMin else flingMax
+ }
+ }
+
+ // Apply the configuration and start the spring animation.
+ getSpringAnimation(animatedProperty)
+ .also { springConfig.applyToAnimation(it) }
+ .start()
+ }
+ }
+ })
+ }
+ }
+ }
+
// Add an internal listener that will dispatch animation events to the provided listeners.
internalListeners.add(InternalListener(
getAnimatedProperties(),
@@ -318,24 +503,10 @@
ArrayList(endListeners),
ArrayList(endActions)))
- for ((property, config) in flingConfigs) {
- val currentValue = property.getValue(target)
-
- // If the fling is already out of bounds, don't start it.
- if (currentValue <= config.min || currentValue >= config.max) {
- continue
- }
-
- val flingAnim = getFlingAnimation(property)
- config.applyToAnimation(flingAnim)
- flingAnim.start()
- }
-
- for ((property, config) in springConfigs) {
- val springAnim = getSpringAnimation(property)
- config.applyToAnimation(springAnim)
- springAnim.start()
- }
+ // Actually start the DynamicAnimations. This is delayed until after the InternalListener is
+ // constructed and added so that we don't miss the end listener firing for any animations
+ // that immediately end.
+ animationStartActions.forEach { it.invoke() }
clearAnimator()
}
@@ -381,7 +552,10 @@
}
anim.addEndListener { _, canceled, value, velocity ->
internalListeners.removeAll {
- it.onInternalAnimationEnd(property, canceled, value, velocity) } }
+ it.onInternalAnimationEnd(
+ property, canceled, value, velocity, anim is FlingAnimation)
+ }
+ }
return anim
}
@@ -434,7 +608,8 @@
property: FloatPropertyCompat<in T>,
canceled: Boolean,
finalValue: Float,
- finalVelocity: Float
+ finalVelocity: Float,
+ isFling: Boolean
): Boolean {
// If this property animation isn't relevant to this listener, ignore it.
@@ -461,7 +636,15 @@
val allEnded = !arePropertiesAnimating(properties)
endListeners.forEach {
- it.onAnimationEnd(target, property, canceled, finalValue, finalVelocity, allEnded) }
+ it.onAnimationEnd(
+ target, property, isFling, canceled, finalValue, finalVelocity,
+ allEnded)
+
+ // Check that the end listener didn't restart this property's animation.
+ if (isPropertyAnimating(property)) {
+ return false
+ }
+ }
// If all of the animations that this listener cares about have ended, run the end
// actions unless the animation was canceled.
@@ -495,7 +678,8 @@
/** Returns whether the given property is animating. */
fun isPropertyAnimating(property: FloatPropertyCompat<in T>): Boolean {
- return springAnimations[property]?.isRunning ?: false
+ return springAnimations[property]?.isRunning ?: false ||
+ flingAnimations[property]?.isRunning ?: false
}
/** Returns whether any of the given properties are animating. */
@@ -523,15 +707,15 @@
data class SpringConfig internal constructor(
internal var stiffness: Float,
internal var dampingRatio: Float,
- internal var startVel: Float = 0f,
- internal var finalPosition: Float = -Float.MAX_VALUE
+ internal var startVelocity: Float = 0f,
+ internal var finalPosition: Float = UNSET
) {
constructor() :
this(defaultSpring.stiffness, defaultSpring.dampingRatio)
constructor(stiffness: Float, dampingRatio: Float) :
- this(stiffness = stiffness, dampingRatio = dampingRatio, startVel = 0f)
+ this(stiffness = stiffness, dampingRatio = dampingRatio, startVelocity = 0f)
/** Apply these configuration settings to the given SpringAnimation. */
internal fun applyToAnimation(anim: SpringAnimation) {
@@ -542,7 +726,7 @@
finalPosition = this@SpringConfig.finalPosition
}
- if (startVel != 0f) anim.setStartVelocity(startVel)
+ if (startVelocity != 0f) anim.setStartVelocity(startVelocity)
}
}
@@ -555,7 +739,7 @@
internal var friction: Float,
internal var min: Float,
internal var max: Float,
- internal var startVel: Float
+ internal var startVelocity: Float
) {
constructor() : this(defaultFling.friction)
@@ -564,7 +748,7 @@
this(friction, defaultFling.min, defaultFling.max)
constructor(friction: Float, min: Float, max: Float) :
- this(friction, min, max, startVel = 0f)
+ this(friction, min, max, startVelocity = 0f)
/** Apply these configuration settings to the given FlingAnimation. */
internal fun applyToAnimation(anim: FlingAnimation) {
@@ -572,7 +756,7 @@
friction = this@FlingConfig.friction
setMinValue(min)
setMaxValue(max)
- setStartVelocity(startVel)
+ setStartVelocity(startVelocity)
}
}
}
@@ -625,6 +809,10 @@
*
* @param target The animated object itself.
* @param property The property whose animation has just ended.
+ * @param wasFling Whether this property ended after a fling animation (as opposed to a
+ * spring animation). If this property was animated via [flingThenSpring], this will be true
+ * if the fling animation did not reach the min/max bounds, decelerating to a stop
+ * naturally. It will be false if it hit the bounds and was sprung back.
* @param canceled Whether the animation was explicitly canceled before it naturally ended.
* @param finalValue The final value of the animated property.
* @param finalVelocity The final velocity (in pixels per second) of the ended animation.
@@ -662,6 +850,7 @@
fun onAnimationEnd(
target: T,
property: FloatPropertyCompat<in T>,
+ wasFling: Boolean,
canceled: Boolean,
finalValue: Float,
finalVelocity: Float,
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt
index e86970c..965decd 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimatorTestUtils.kt
@@ -19,6 +19,7 @@
import android.os.Looper
import android.util.ArrayMap
import androidx.dynamicanimation.animation.FloatPropertyCompat
+import com.android.systemui.util.animation.PhysicsAnimatorTestUtils.prepareForTest
import java.util.ArrayDeque
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
@@ -119,6 +120,7 @@
override fun onAnimationEnd(
target: T,
property: FloatPropertyCompat<in T>,
+ wasFling: Boolean,
canceled: Boolean,
finalValue: Float,
finalVelocity: Float,
@@ -389,8 +391,6 @@
val unblockLatch = CountDownLatch(if (startBlocksUntilAnimationsEnd) 2 else 1)
animationThreadHandler.post {
- val animatedProperties = animator.getAnimatedProperties()
-
// Add an update listener that dispatches to any test update listeners added by
// tests.
animator.addUpdateListener(object : PhysicsAnimator.UpdateListener<T> {
@@ -398,6 +398,10 @@
target: T,
values: ArrayMap<FloatPropertyCompat<in T>, PhysicsAnimator.AnimationUpdate>
) {
+ values.forEach { (property, value) ->
+ allUpdates.getOrPut(property, { ArrayList() }).add(value)
+ }
+
for (listener in testUpdateListeners) {
listener.onAnimationUpdateForProperty(target, values)
}
@@ -410,6 +414,7 @@
override fun onAnimationEnd(
target: T,
property: FloatPropertyCompat<in T>,
+ wasFling: Boolean,
canceled: Boolean,
finalValue: Float,
finalVelocity: Float,
@@ -417,7 +422,7 @@
) {
for (listener in testEndListeners) {
listener.onAnimationEnd(
- target, property, canceled, finalValue, finalVelocity,
+ target, property, wasFling, canceled, finalValue, finalVelocity,
allRelevantPropertyAnimsEnded)
}
@@ -432,31 +437,6 @@
}
})
- val updateListeners = ArrayList<PhysicsAnimator.UpdateListener<T>>().also {
- it.add(object : PhysicsAnimator.UpdateListener<T> {
- override fun onAnimationUpdateForProperty(
- target: T,
- values: ArrayMap<FloatPropertyCompat<in T>,
- PhysicsAnimator.AnimationUpdate>
- ) {
- values.forEach { (property, value) ->
- allUpdates.getOrPut(property, { ArrayList() }).add(value)
- }
- }
- })
- }
-
- /**
- * Add an internal listener at the head of the list that captures update values
- * directly from DynamicAnimation. We use this to build a list of all updates so we
- * can verify that InternalListener dispatches to the real listeners properly.
- */
- animator.internalListeners.add(0, animator.InternalListener(
- animatedProperties,
- updateListeners,
- ArrayList(),
- ArrayList()))
-
animator.startInternal()
unblockLatch.countDown()
}
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
index 7cdba86..cc6d607 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
@@ -83,17 +83,19 @@
* Provide a Background-Thread Executor by default.
*/
@Provides
+ @Singleton
public static Executor provideExecutor(@Background Looper looper) {
- return new ExecutorImpl(new Handler(looper));
+ return new ExecutorImpl(looper);
}
/**
* Provide a Background-Thread Executor.
*/
@Provides
+ @Singleton
@Background
public static Executor provideBackgroundExecutor(@Background Looper looper) {
- return new ExecutorImpl(new Handler(looper));
+ return new ExecutorImpl(looper);
}
/**
@@ -109,26 +111,29 @@
* Provide a Background-Thread Executor by default.
*/
@Provides
+ @Singleton
public static DelayableExecutor provideDelayableExecutor(@Background Looper looper) {
- return new ExecutorImpl(new Handler(looper));
+ return new ExecutorImpl(looper);
}
/**
* Provide a Background-Thread Executor.
*/
@Provides
+ @Singleton
@Background
public static DelayableExecutor provideBackgroundDelayableExecutor(@Background Looper looper) {
- return new ExecutorImpl(new Handler(looper));
+ return new ExecutorImpl(looper);
}
/**
* Provide a Main-Thread Executor.
*/
@Provides
+ @Singleton
@Main
public static DelayableExecutor provideMainDelayableExecutor(@Main Looper looper) {
- return new ExecutorImpl(new Handler(looper));
+ return new ExecutorImpl(looper);
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/ExecutorImpl.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/ExecutorImpl.java
index 7e77321..2bbf950 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/ExecutorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ExecutorImpl.java
@@ -17,37 +17,69 @@
package com.android.systemui.util.concurrency;
import android.os.Handler;
-import android.os.HandlerExecutor;
+import android.os.Looper;
import android.os.Message;
+import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
/**
* Implementations of {@link DelayableExecutor} for SystemUI.
*/
-public class ExecutorImpl extends HandlerExecutor implements DelayableExecutor {
+public class ExecutorImpl implements DelayableExecutor {
private final Handler mHandler;
- public ExecutorImpl(Handler handler) {
- super(handler);
- mHandler = handler;
+ ExecutorImpl(Looper looper) {
+ mHandler = new Handler(looper, this::onHandleMessage);
+ }
+
+ @Override
+ public void execute(Runnable command) {
+ if (!mHandler.post(command)) {
+ throw new RejectedExecutionException(mHandler + " is shutting down");
+ }
}
@Override
public Runnable executeDelayed(Runnable r, long delay, TimeUnit unit) {
- Object token = new Object();
- Message m = mHandler.obtainMessage(0, token);
+ ExecutionToken token = new ExecutionToken(r);
+ Message m = mHandler.obtainMessage(MSG_EXECUTE_RUNNABLE, token);
mHandler.sendMessageDelayed(m, unit.toMillis(delay));
- return () -> mHandler.removeCallbacksAndMessages(token);
+ return token;
}
@Override
public Runnable executeAtTime(Runnable r, long uptimeMillis, TimeUnit unit) {
- Object token = new Object();
- Message m = mHandler.obtainMessage(0, token);
+ ExecutionToken token = new ExecutionToken(r);
+ Message m = mHandler.obtainMessage(MSG_EXECUTE_RUNNABLE, token);
mHandler.sendMessageAtTime(m, unit.toMillis(uptimeMillis));
- return () -> mHandler.removeCallbacksAndMessages(token);
+ return token;
}
+
+ private boolean onHandleMessage(Message msg) {
+ if (msg.what == MSG_EXECUTE_RUNNABLE) {
+ ExecutionToken token = (ExecutionToken) msg.obj;
+ token.runnable.run();
+ } else {
+ throw new IllegalStateException("Unrecognized message: " + msg.what);
+ }
+ return true;
+ }
+
+ private class ExecutionToken implements Runnable {
+ public final Runnable runnable;
+
+ private ExecutionToken(Runnable runnable) {
+ this.runnable = runnable;
+ }
+
+ @Override
+ public void run() {
+ mHandler.removeCallbacksAndMessages(this);
+ }
+ }
+
+ private static final int MSG_EXECUTE_RUNNABLE = 0;
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index a4ed31d..112ae6f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -1008,7 +1008,7 @@
filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
filter.addAction(Intent.ACTION_SCREEN_OFF);
filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
- mBroadcastDispatcher.registerReceiver(this, filter, mWorker);
+ mBroadcastDispatcher.registerReceiverWithHandler(this, filter, mWorker);
}
public void destroy() {
diff --git a/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
new file mode 100644
index 0000000..d413308
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/wm/DisplayImeController.java
@@ -0,0 +1,333 @@
+/*
+ * 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 com.android.systemui.wm;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.content.res.Configuration;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.IDisplayWindowInsetsController;
+import android.view.InsetsSource;
+import android.view.InsetsSourceControl;
+import android.view.InsetsState;
+import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.WindowInsets;
+import android.view.animation.Interpolator;
+import android.view.animation.PathInterpolator;
+
+import com.android.systemui.dagger.qualifiers.Main;
+
+import java.util.ArrayList;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * Manages IME control at the display-level. This occurs when IME comes up in multi-window mode.
+ */
+@Singleton
+public class DisplayImeController implements DisplayWindowController.DisplayWindowListener {
+ private static final String TAG = "DisplayImeController";
+
+ static final int ANIMATION_DURATION_SHOW_MS = 275;
+ static final int ANIMATION_DURATION_HIDE_MS = 340;
+ static final Interpolator INTERPOLATOR = new PathInterpolator(0.4f, 0f, 0.2f, 1f);
+ private static final int DIRECTION_NONE = 0;
+ private static final int DIRECTION_SHOW = 1;
+ private static final int DIRECTION_HIDE = 2;
+
+ SystemWindows mSystemWindows;
+ final Handler mHandler;
+
+ final SparseArray<PerDisplay> mImePerDisplay = new SparseArray<>();
+
+ final ArrayList<ImePositionProcessor> mPositionProcessors = new ArrayList<>();
+
+ @Inject
+ DisplayImeController(SystemWindows syswin, DisplayWindowController displayController,
+ @Main Handler mainHandler) {
+ mHandler = mainHandler;
+ mSystemWindows = syswin;
+ displayController.addDisplayWindowListener(this);
+ }
+
+ @Override
+ public void onDisplayAdded(int displayId) {
+ // Add's a system-ui window-manager specifically for ime. This type is special because
+ // WM will defer IME inset handling to it in multi-window scenarious.
+ PerDisplay pd = new PerDisplay(displayId,
+ mSystemWindows.mDisplayController.getDisplayLayout(displayId).rotation());
+ try {
+ mSystemWindows.mWmService.setDisplayWindowInsetsController(displayId, pd);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Unable to set insets controller on display " + displayId);
+ }
+ mImePerDisplay.put(displayId, pd);
+ }
+
+ @Override
+ public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+ PerDisplay pd = mImePerDisplay.get(displayId);
+ if (pd == null) {
+ return;
+ }
+ if (mSystemWindows.mDisplayController.getDisplayLayout(displayId).rotation()
+ != pd.mRotation && isImeShowing(displayId)) {
+ pd.startAnimation(true);
+ }
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ try {
+ mSystemWindows.mWmService.setDisplayWindowInsetsController(displayId, null);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Unable to remove insets controller on display " + displayId);
+ }
+ mImePerDisplay.remove(displayId);
+ }
+
+ private boolean isImeShowing(int displayId) {
+ PerDisplay pd = mImePerDisplay.get(displayId);
+ if (pd == null) {
+ return false;
+ }
+ final InsetsSource imeSource = pd.mInsetsState.getSource(InsetsState.ITYPE_IME);
+ return imeSource != null && pd.mImeSourceControl != null && imeSource.isVisible();
+ }
+
+ private void dispatchPositionChanged(int displayId, int imeTop,
+ SurfaceControl.Transaction t) {
+ synchronized (mPositionProcessors) {
+ for (ImePositionProcessor pp : mPositionProcessors) {
+ pp.onImePositionChanged(displayId, imeTop, t);
+ }
+ }
+ }
+
+ private void dispatchStartPositioning(int displayId, int imeTop, int finalImeTop,
+ boolean show, SurfaceControl.Transaction t) {
+ synchronized (mPositionProcessors) {
+ for (ImePositionProcessor pp : mPositionProcessors) {
+ pp.onImeStartPositioning(displayId, imeTop, finalImeTop, show, t);
+ }
+ }
+ }
+
+ private void dispatchEndPositioning(int displayId, int imeTop, boolean show,
+ SurfaceControl.Transaction t) {
+ synchronized (mPositionProcessors) {
+ for (ImePositionProcessor pp : mPositionProcessors) {
+ pp.onImeEndPositioning(displayId, imeTop, show, t);
+ }
+ }
+ }
+
+ /**
+ * Adds an {@link ImePositionProcessor} to be called during ime position updates.
+ */
+ public void addPositionProcessor(ImePositionProcessor processor) {
+ synchronized (mPositionProcessors) {
+ if (mPositionProcessors.contains(processor)) {
+ return;
+ }
+ mPositionProcessors.add(processor);
+ }
+ }
+
+ /**
+ * Removes an {@link ImePositionProcessor} to be called during ime position updates.
+ */
+ public void removePositionProcessor(ImePositionProcessor processor) {
+ synchronized (mPositionProcessors) {
+ mPositionProcessors.remove(processor);
+ }
+ }
+
+ class PerDisplay extends IDisplayWindowInsetsController.Stub {
+ final int mDisplayId;
+ final InsetsState mInsetsState = new InsetsState();
+ InsetsSourceControl mImeSourceControl = null;
+ int mAnimationDirection = DIRECTION_NONE;
+ ValueAnimator mAnimation = null;
+ int mRotation = Surface.ROTATION_0;
+
+ PerDisplay(int displayId, int initialRotation) {
+ mDisplayId = displayId;
+ mRotation = initialRotation;
+ }
+
+ @Override
+ public void insetsChanged(InsetsState insetsState) {
+ if (mInsetsState.equals(insetsState)) {
+ return;
+ }
+ mInsetsState.set(insetsState, true /* copySources */);
+ }
+
+ @Override
+ public void insetsControlChanged(InsetsState insetsState,
+ InsetsSourceControl[] activeControls) {
+ insetsChanged(insetsState);
+ if (activeControls != null) {
+ for (InsetsSourceControl activeControl : activeControls) {
+ if (activeControl == null) {
+ continue;
+ }
+ if (activeControl.getType() == InsetsState.ITYPE_IME) {
+ mImeSourceControl = activeControl;
+ }
+ }
+ }
+ }
+
+ @Override
+ public void showInsets(int types, boolean fromIme) {
+ if ((types & WindowInsets.Type.ime()) == 0) {
+ return;
+ }
+ startAnimation(true /* show */);
+ }
+
+ @Override
+ public void hideInsets(int types, boolean fromIme) {
+ if ((types & WindowInsets.Type.ime()) == 0) {
+ return;
+ }
+ startAnimation(false /* show */);
+ }
+
+ /**
+ * Sends the local visibility state back to window manager. Needed for legacy adjustForIme.
+ */
+ private void setVisibleDirectly(boolean visible) {
+ mInsetsState.getSource(InsetsState.ITYPE_IME).setVisible(visible);
+ try {
+ mSystemWindows.mWmService.modifyDisplayWindowInsets(mDisplayId, mInsetsState);
+ } catch (RemoteException e) {
+ }
+ }
+
+ private int imeTop(InsetsSource imeSource, float surfaceOffset) {
+ return imeSource.getFrame().top + (int) surfaceOffset;
+ }
+
+ private void startAnimation(final boolean show) {
+ final InsetsSource imeSource = mInsetsState.getSource(InsetsState.ITYPE_IME);
+ if (imeSource == null || mImeSourceControl == null) {
+ return;
+ }
+ if ((mAnimationDirection == DIRECTION_SHOW && show)
+ || (mAnimationDirection == DIRECTION_HIDE && !show)) {
+ return;
+ }
+ if (mAnimationDirection != DIRECTION_NONE) {
+ mAnimation.end();
+ mAnimationDirection = DIRECTION_NONE;
+ }
+ mAnimationDirection = show ? DIRECTION_SHOW : DIRECTION_HIDE;
+ mHandler.post(() -> {
+ final float defaultY = mImeSourceControl.getSurfacePosition().y;
+ final float x = mImeSourceControl.getSurfacePosition().x;
+ final float startY = show ? defaultY + imeSource.getFrame().height() : defaultY;
+ final float endY = show ? defaultY : defaultY + imeSource.getFrame().height();
+ mAnimation = ValueAnimator.ofFloat(startY, endY);
+ mAnimation.setDuration(
+ show ? ANIMATION_DURATION_SHOW_MS : ANIMATION_DURATION_HIDE_MS);
+
+ mAnimation.addUpdateListener(animation -> {
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ float value = (float) animation.getAnimatedValue();
+ t.setPosition(mImeSourceControl.getLeash(), x, value);
+ dispatchPositionChanged(mDisplayId, imeTop(imeSource, value), t);
+ t.apply();
+ t.close();
+ });
+ mAnimation.setInterpolator(INTERPOLATOR);
+ mAnimation.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.setPosition(mImeSourceControl.getLeash(), x, startY);
+ dispatchStartPositioning(mDisplayId, imeTop(imeSource, startY),
+ imeTop(imeSource, endY), mAnimationDirection == DIRECTION_SHOW,
+ t);
+ if (mAnimationDirection == DIRECTION_SHOW) {
+ t.show(mImeSourceControl.getLeash());
+ }
+ t.apply();
+ t.close();
+ }
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ t.setPosition(mImeSourceControl.getLeash(), x, endY);
+ dispatchEndPositioning(mDisplayId, imeTop(imeSource, endY),
+ mAnimationDirection == DIRECTION_SHOW, t);
+ if (mAnimationDirection == DIRECTION_HIDE) {
+ t.hide(mImeSourceControl.getLeash());
+ }
+ t.apply();
+ t.close();
+
+ mAnimationDirection = DIRECTION_NONE;
+ mAnimation = null;
+ }
+ });
+ if (!show) {
+ // When going away, queue up insets change first, otherwise any bounds changes
+ // can have a "flicker" of ime-provided insets.
+ setVisibleDirectly(false /* visible */);
+ }
+ mAnimation.start();
+ if (show) {
+ // When showing away, queue up insets change last, otherwise any bounds changes
+ // can have a "flicker" of ime-provided insets.
+ setVisibleDirectly(true /* visible */);
+ }
+ });
+ }
+ }
+
+ /**
+ * Allows other things to synchronize with the ime position
+ */
+ public interface ImePositionProcessor {
+ /**
+ * Called when the IME position is starting to animate.
+ */
+ void onImeStartPositioning(int displayId, int imeTop, int finalImeTop, boolean showing,
+ SurfaceControl.Transaction t);
+
+ /**
+ * Called when the ime position changed. This is expected to be a synchronous call on the
+ * animation thread. Operations can be added to the transaction to be applied in sync.
+ */
+ void onImePositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t);
+
+ /**
+ * Called when the IME position is done animating.
+ */
+ void onImeEndPositioning(int displayId, int imeTop, boolean showing,
+ SurfaceControl.Transaction t);
+ }
+}
diff --git a/packages/SystemUI/tests/AndroidManifest.xml b/packages/SystemUI/tests/AndroidManifest.xml
index bfb0e15..c51624b 100644
--- a/packages/SystemUI/tests/AndroidManifest.xml
+++ b/packages/SystemUI/tests/AndroidManifest.xml
@@ -35,7 +35,6 @@
<uses-permission android:name="android.permission.STATUS_BAR_SERVICE" />
<uses-permission android:name="android.permission.ACCESS_VR_MANAGER" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
- <uses-permission android:name="android.permission.CONNECTIVITY_INTERNAL" />
<uses-permission android:name="android.permission.MANAGE_NETWORK_POLICY" />
<uses-permission android:name="android.permission.REQUEST_NETWORK_SCORES" />
<uses-permission android:name="android.permission.CONTROL_VPN" />
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
index b70fdbd..eccf096 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextControllerTest.java
@@ -19,7 +19,7 @@
import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE;
import static android.telephony.SubscriptionManager.DATA_ROAMING_ENABLE;
-import static android.telephony.SubscriptionManager.NAME_SOURCE_DEFAULT_SOURCE;
+import static android.telephony.SubscriptionManager.NAME_SOURCE_DEFAULT;
import static junit.framework.Assert.assertTrue;
import static junit.framework.TestCase.assertFalse;
@@ -83,14 +83,14 @@
private static final String TEST_CARRIER_2 = "TEST_CARRIER_2";
private static final int TEST_CARRIER_ID = 1;
private static final SubscriptionInfo TEST_SUBSCRIPTION = new SubscriptionInfo(0, "", 0,
- TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "",
+ TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT, 0xFFFFFF, "",
DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", false, null,
TEST_CARRIER_ID, 0);
private static final SubscriptionInfo TEST_SUBSCRIPTION_NULL = new SubscriptionInfo(0, "", 0,
- TEST_CARRIER, null, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "", DATA_ROAMING_DISABLE,
+ TEST_CARRIER, null, NAME_SOURCE_DEFAULT, 0xFFFFFF, "", DATA_ROAMING_DISABLE,
null, null, null, null, false, null, "");
private static final SubscriptionInfo TEST_SUBSCRIPTION_ROAMING = new SubscriptionInfo(0, "", 0,
- TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "",
+ TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT, 0xFFFFFF, "",
DATA_ROAMING_ENABLE, null, null, null, null, false, null, "");
@Mock
private WifiManager mWifiManager;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 4bf1e1c..12da006 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -17,7 +17,7 @@
package com.android.keyguard;
import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE;
-import static android.telephony.SubscriptionManager.NAME_SOURCE_DEFAULT_SOURCE;
+import static android.telephony.SubscriptionManager.NAME_SOURCE_DEFAULT;
import static com.google.common.truth.Truth.assertThat;
@@ -57,7 +57,6 @@
import android.testing.TestableContext;
import android.testing.TestableLooper;
-import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.TelephonyIntents;
import com.android.systemui.DumpController;
import com.android.systemui.SysuiTestCase;
@@ -84,11 +83,11 @@
private static final int TEST_CARRIER_ID = 1;
private static final String TEST_GROUP_UUID = "59b5c870-fc4c-47a4-a99e-9db826b48b24";
private static final SubscriptionInfo TEST_SUBSCRIPTION = new SubscriptionInfo(1, "", 0,
- TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "",
+ TEST_CARRIER, TEST_CARRIER, NAME_SOURCE_DEFAULT, 0xFFFFFF, "",
DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", false, TEST_GROUP_UUID,
TEST_CARRIER_ID, 0);
private static final SubscriptionInfo TEST_SUBSCRIPTION_2 = new SubscriptionInfo(2, "", 0,
- TEST_CARRIER, TEST_CARRIER_2, NAME_SOURCE_DEFAULT_SOURCE, 0xFFFFFF, "",
+ TEST_CARRIER, TEST_CARRIER_2, NAME_SOURCE_DEFAULT, 0xFFFFFF, "",
DATA_ROAMING_DISABLE, null, null, null, null, false, null, "", true, TEST_GROUP_UUID,
TEST_CARRIER_ID, 0);
@Mock
@@ -150,10 +149,10 @@
@Test
public void testReceiversRegistered() {
- verify(mBroadcastDispatcher, atLeastOnce()).registerReceiver(
+ verify(mBroadcastDispatcher, atLeastOnce()).registerReceiverWithHandler(
eq(mKeyguardUpdateMonitor.mBroadcastReceiver),
any(IntentFilter.class), any(Handler.class));
- verify(mBroadcastDispatcher, atLeastOnce()).registerReceiver(
+ verify(mBroadcastDispatcher, atLeastOnce()).registerReceiverWithHandler(
eq(mKeyguardUpdateMonitor.mBroadcastAllReceiver),
any(IntentFilter.class), any(Handler.class), eq(UserHandle.ALL));
}
@@ -204,7 +203,7 @@
@Test
public void testTelephonyCapable_SimInvalid_ServiceState_InService() {
// SERVICE_STATE - IN_SERVICE, but SIM_STATE is invalid TelephonyCapable should be False
- Intent intent = new Intent(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
+ Intent intent = new Intent(Intent.ACTION_SERVICE_STATE);
Bundle data = new Bundle();
ServiceState state = new ServiceState();
state.setState(ServiceState.STATE_IN_SERVICE);
@@ -219,7 +218,7 @@
public void testTelephonyCapable_SimValid_ServiceState_PowerOff() {
// Simulate AirplaneMode case, SERVICE_STATE - POWER_OFF, check TelephonyCapable False
// Only receive ServiceState callback IN_SERVICE -> OUT_OF_SERVICE -> POWER_OFF
- Intent intent = new Intent(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
+ Intent intent = new Intent(Intent.ACTION_SERVICE_STATE);
intent.putExtra(Intent.EXTRA_SIM_STATE
, Intent.SIM_STATE_LOADED);
Bundle data = new Bundle();
@@ -241,7 +240,7 @@
*/
@Test
public void testTelephonyCapable_BootInitState_ServiceState_OutOfService() {
- Intent intent = new Intent(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
+ Intent intent = new Intent(Intent.ACTION_SERVICE_STATE);
Bundle data = new Bundle();
ServiceState state = new ServiceState();
state.setState(ServiceState.STATE_OUT_OF_SERVICE);
@@ -285,7 +284,7 @@
@Test
public void testTelephonyCapable_BootInitState_ServiceState_PowerOff() {
- Intent intent = new Intent(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
+ Intent intent = new Intent(Intent.ACTION_SERVICE_STATE);
Bundle data = new Bundle();
ServiceState state = new ServiceState();
state.setState(ServiceState.STATE_POWER_OFF);
@@ -298,7 +297,7 @@
@Test
public void testTelephonyCapable_SimValid_ServiceState_InService() {
- Intent intent = new Intent(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
+ Intent intent = new Intent(Intent.ACTION_SERVICE_STATE);
Bundle data = new Bundle();
ServiceState state = new ServiceState();
state.setState(ServiceState.STATE_IN_SERVICE);
@@ -321,10 +320,10 @@
mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext()
, putPhoneInfo(intentSimState, data, true));
mTestableLooper.processAllMessages();
- // Even SimState Loaded, still need ACTION_SERVICE_STATE_CHANGED turn on mTelephonyCapable
+ // Even SimState Loaded, still need ACTION_SERVICE_STATE turn on mTelephonyCapable
assertThat(mKeyguardUpdateMonitor.mTelephonyCapable).isFalse();
- Intent intentServiceState = new Intent(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
+ Intent intentServiceState = new Intent(Intent.ACTION_SERVICE_STATE);
intentSimState.putExtra(Intent.EXTRA_SIM_STATE
, Intent.SIM_STATE_LOADED);
mKeyguardUpdateMonitor.mBroadcastReceiver.onReceive(getContext()
@@ -524,9 +523,9 @@
int subscription = simInited
? 1/* mock subid=1 */ : SubscriptionManager.DUMMY_SUBSCRIPTION_ID_BASE;
if (data != null) intent.putExtras(data);
- intent.putExtra(PhoneConstants.PHONE_NAME_KEY, "Phone");
- intent.putExtra("subscription", subscription);
- intent.putExtra("slot", 0/* SLOT 1 */);
+
+ intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subscription);
+ intent.putExtra(SubscriptionManager.EXTRA_SLOT_INDEX, 0);
return intent;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
index 9c9a627..8d11b54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ForegroundServiceControllerTest.java
@@ -48,7 +48,7 @@
import com.android.systemui.appops.AppOpsController;
import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.NotifCollection;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -71,7 +71,7 @@
@Mock private NotificationEntryManager mEntryManager;
@Mock private AppOpsController mAppOpsController;
@Mock private Handler mMainHandler;
- @Mock private NotifCollection mNotifCollection;
+ @Mock private NotifPipeline mNotifPipeline;
@Before
public void setUp() throws Exception {
@@ -81,7 +81,7 @@
MockitoAnnotations.initMocks(this);
mFsc = new ForegroundServiceController(mEntryManager, mAppOpsController, mMainHandler);
mListener = new ForegroundServiceNotificationListener(
- mContext, mFsc, mEntryManager, mNotifCollection);
+ mContext, mFsc, mEntryManager, mNotifPipeline);
ArgumentCaptor<NotificationEntryListener> entryListenerCaptor =
ArgumentCaptor.forClass(NotificationEntryListener.class);
verify(mEntryManager).addNotificationEntryListener(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java b/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
index 0c53b03..2ecc8ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/TestableDependency.java
@@ -52,6 +52,7 @@
@Override
protected <T> T createDependency(Object key) {
if (mObjs.containsKey(key)) return (T) mObjs.get(key);
+
mInstantiatedObjects.add(key);
return super.createDependency(key);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
index af0ef82..e5ae1aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/appops/AppOpsControllerTest.java
@@ -25,11 +25,11 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+
+import static java.lang.Thread.sleep;
import android.app.AppOpsManager;
import android.content.pm.PackageManager;
@@ -57,7 +57,6 @@
private static final String TEST_PACKAGE_NAME = "test";
private static final int TEST_UID = UserHandle.getUid(0, 0);
private static final int TEST_UID_OTHER = UserHandle.getUid(1, 0);
- private static final int TEST_UID_NON_USER_SENSITIVE = UserHandle.getUid(2, 0);
@Mock
private AppOpsManager mAppOpsManager;
@@ -68,8 +67,6 @@
@Mock
private AppOpsControllerImpl.H mMockHandler;
@Mock
- private PermissionFlagsCache mFlagsCache;
- @Mock
private DumpController mDumpController;
private AppOpsControllerImpl mController;
@@ -85,17 +82,9 @@
// All permissions of TEST_UID and TEST_UID_OTHER are user sensitive. None of
// TEST_UID_NON_USER_SENSITIVE are user sensitive.
getContext().setMockPackageManager(mPackageManager);
- when(mFlagsCache.getPermissionFlags(anyString(), anyString(),
- eq(UserHandle.getUserHandleForUid(TEST_UID)))).thenReturn(
- PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED);
- when(mFlagsCache.getPermissionFlags(anyString(), anyString(),
- eq(UserHandle.getUserHandleForUid(TEST_UID_OTHER)))).thenReturn(
- PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED);
- when(mFlagsCache.getPermissionFlags(anyString(), anyString(),
- eq(UserHandle.getUserHandleForUid(TEST_UID_NON_USER_SENSITIVE)))).thenReturn(0);
- mController = new AppOpsControllerImpl(mContext, mTestableLooper.getLooper(), mFlagsCache,
- mDumpController);
+ mController =
+ new AppOpsControllerImpl(mContext, mTestableLooper.getLooper(), mDumpController);
}
@Test
@@ -191,14 +180,6 @@
}
@Test
- public void nonUserSensitiveOpsAreIgnored() {
- mController.onOpActiveChanged(AppOpsManager.OP_RECORD_AUDIO,
- TEST_UID_NON_USER_SENSITIVE, TEST_PACKAGE_NAME, true);
- assertEquals(0, mController.getActiveAppOpsForUser(
- UserHandle.getUserId(TEST_UID_NON_USER_SENSITIVE)).size());
- }
-
- @Test
public void opNotedScheduledForRemoval() {
mController.setBGHandler(mMockHandler);
mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
@@ -251,4 +232,100 @@
// Only one post to notify subscribers
verify(mMockHandler, times(2)).scheduleRemoval(any(), anyLong());
}
+
+ @Test
+ public void testActiveOpNotRemovedAfterNoted() throws InterruptedException {
+ // Replaces the timeout delay with 5 ms
+ AppOpsControllerImpl.H testHandler = mController.new H(mTestableLooper.getLooper()) {
+ @Override
+ public void scheduleRemoval(AppOpItem item, long timeToRemoval) {
+ super.scheduleRemoval(item, 5L);
+ }
+ };
+
+ mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
+ mController.setBGHandler(testHandler);
+
+ mController.onOpActiveChanged(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+
+ mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
+ AppOpsManager.MODE_ALLOWED);
+
+ mTestableLooper.processAllMessages();
+ List<AppOpItem> list = mController.getActiveAppOps();
+ verify(mCallback).onActiveStateChanged(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+
+ // Duplicates are not removed between active and noted
+ assertEquals(2, list.size());
+
+ sleep(10L);
+
+ mTestableLooper.processAllMessages();
+
+ verify(mCallback, never()).onActiveStateChanged(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, false);
+ list = mController.getActiveAppOps();
+ assertEquals(1, list.size());
+ }
+
+ @Test
+ public void testNotedNotRemovedAfterActive() {
+ mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
+
+ mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
+ AppOpsManager.MODE_ALLOWED);
+
+ mController.onOpActiveChanged(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+
+ mTestableLooper.processAllMessages();
+ List<AppOpItem> list = mController.getActiveAppOps();
+ verify(mCallback).onActiveStateChanged(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+
+ // Duplicates are not removed between active and noted
+ assertEquals(2, list.size());
+
+ mController.onOpActiveChanged(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, false);
+
+ mTestableLooper.processAllMessages();
+
+ verify(mCallback, never()).onActiveStateChanged(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, false);
+ list = mController.getActiveAppOps();
+ assertEquals(1, list.size());
+ }
+
+ @Test
+ public void testNotedAndActiveOnlyOneCall() {
+ mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
+
+ mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
+ AppOpsManager.MODE_ALLOWED);
+
+ mController.onOpActiveChanged(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+
+ mTestableLooper.processAllMessages();
+ verify(mCallback).onActiveStateChanged(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+ }
+
+ @Test
+ public void testActiveAndNotedOnlyOneCall() {
+ mController.addCallback(new int[]{AppOpsManager.OP_FINE_LOCATION}, mCallback);
+
+ mController.onOpActiveChanged(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+
+ mController.onOpNoted(AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME,
+ AppOpsManager.MODE_ALLOWED);
+
+ mTestableLooper.processAllMessages();
+ verify(mCallback).onActiveStateChanged(
+ AppOpsManager.OP_FINE_LOCATION, TEST_UID, TEST_PACKAGE_NAME, true);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/appops/PermissionFlagsCacheTest.kt b/packages/SystemUI/tests/src/com/android/systemui/appops/PermissionFlagsCacheTest.kt
deleted file mode 100644
index dc070de..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/appops/PermissionFlagsCacheTest.kt
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * 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 com.android.systemui.appops
-
-import android.content.Context
-import android.content.pm.PackageManager
-import android.os.UserHandle
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.any
-import org.mockito.ArgumentMatchers.anyString
-import org.mockito.Mock
-import org.mockito.Mockito.times
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(AndroidTestingRunner::class)
-class PermissionFlagsCacheTest : SysuiTestCase() {
-
- companion object {
- const val TEST_PERMISSION = "test_permission"
- const val TEST_PACKAGE = "test_package"
- }
-
- @Mock
- private lateinit var mPackageManager: PackageManager
- @Mock
- private lateinit var mUserHandle: UserHandle
- private lateinit var flagsCache: TestPermissionFlagsCache
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- mContext.setMockPackageManager(mPackageManager)
- flagsCache = TestPermissionFlagsCache(mContext)
- }
-
- @Test
- fun testCallsPackageManager_exactlyOnce() {
- flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle)
- flagsCache.time = CACHE_EXPIRATION - 1
- verify(mPackageManager).getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle)
- }
-
- @Test
- fun testCallsPackageManager_cacheExpired() {
- flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle)
- flagsCache.time = CACHE_EXPIRATION + 1
- flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle)
- verify(mPackageManager, times(2))
- .getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle)
- }
-
- @Test
- fun testCallsPackageMaanger_multipleKeys() {
- flagsCache.getPermissionFlags(TEST_PERMISSION, TEST_PACKAGE, mUserHandle)
- flagsCache.getPermissionFlags(TEST_PERMISSION, "", mUserHandle)
- verify(mPackageManager, times(2))
- .getPermissionFlags(anyString(), anyString(), any())
- }
-
- private class TestPermissionFlagsCache(context: Context) : PermissionFlagsCache(context) {
- var time = 0L
-
- override fun getCurrentTime(): Long {
- return time
- }
- }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
index 42fbf59..22b1837 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt
@@ -27,6 +27,8 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
import junit.framework.Assert.assertSame
import org.junit.Before
import org.junit.Test
@@ -39,6 +41,7 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import java.util.concurrent.Executor
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
@@ -73,6 +76,8 @@
@Mock
private lateinit var mockHandler: Handler
+ private lateinit var executor: Executor
+
@Captor
private lateinit var argumentCaptor: ArgumentCaptor<ReceiverData>
@@ -83,6 +88,7 @@
fun setUp() {
MockitoAnnotations.initMocks(this)
testableLooper = TestableLooper.get(this)
+ executor = FakeExecutor(FakeSystemClock())
broadcastDispatcher = TestBroadcastDispatcher(
mockContext,
@@ -98,8 +104,9 @@
@Test
fun testAddingReceiverToCorrectUBR() {
- broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user0)
- broadcastDispatcher.registerReceiver(
+ broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter,
+ mockHandler, user0)
+ broadcastDispatcher.registerReceiverWithHandler(
broadcastReceiverOther, intentFilterOther, mockHandler, user1)
testableLooper.processAllMessages()
@@ -115,9 +122,29 @@
}
@Test
+ fun testAddingReceiverToCorrectUBR_executor() {
+ broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, executor, user0)
+ broadcastDispatcher.registerReceiver(
+ broadcastReceiverOther, intentFilterOther, executor, user1)
+
+ testableLooper.processAllMessages()
+
+ verify(mockUBRUser0).registerReceiver(capture(argumentCaptor))
+
+ assertSame(broadcastReceiver, argumentCaptor.value.receiver)
+ assertSame(intentFilter, argumentCaptor.value.filter)
+
+ verify(mockUBRUser1).registerReceiver(capture(argumentCaptor))
+ assertSame(broadcastReceiverOther, argumentCaptor.value.receiver)
+ assertSame(intentFilterOther, argumentCaptor.value.filter)
+ }
+
+ @Test
fun testRemovingReceiversRemovesFromAllUBR() {
- broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user0)
- broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user1)
+ broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter,
+ mockHandler, user0)
+ broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter,
+ mockHandler, user1)
broadcastDispatcher.unregisterReceiver(broadcastReceiver)
@@ -129,8 +156,10 @@
@Test
fun testRemoveReceiverFromUser() {
- broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user0)
- broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler, user1)
+ broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter,
+ mockHandler, user0)
+ broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter,
+ mockHandler, user1)
broadcastDispatcher.unregisterReceiverForUser(broadcastReceiver, user0)
@@ -143,8 +172,8 @@
@Test
fun testRegisterCurrentAsActualUser() {
setUserMock(mockContext, user1)
- broadcastDispatcher.registerReceiver(broadcastReceiver, intentFilter, mockHandler,
- UserHandle.CURRENT)
+ broadcastDispatcher.registerReceiverWithHandler(broadcastReceiver, intentFilter,
+ mockHandler, UserHandle.CURRENT)
testableLooper.processAllMessages()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
index 21ed155..7821ae2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt
@@ -26,6 +26,8 @@
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
@@ -69,8 +71,6 @@
@Mock
private lateinit var mockContext: Context
@Mock
- private lateinit var mockHandler: Handler
- @Mock
private lateinit var mPendingResult: BroadcastReceiver.PendingResult
@Captor
@@ -81,12 +81,14 @@
private lateinit var intentFilter: IntentFilter
private lateinit var intentFilterOther: IntentFilter
private lateinit var handler: Handler
+ private lateinit var fakeExecutor: FakeExecutor
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
testableLooper = TestableLooper.get(this)
handler = Handler(testableLooper.looper)
+ fakeExecutor = FakeExecutor(FakeSystemClock())
userBroadcastDispatcher = UserBroadcastDispatcher(
mockContext, USER_ID, handler, testableLooper.looper)
@@ -108,7 +110,7 @@
intentFilter = IntentFilter(ACTION_1)
userBroadcastDispatcher.registerReceiver(
- ReceiverData(broadcastReceiver, intentFilter, mockHandler, USER_HANDLE))
+ ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
testableLooper.processAllMessages()
assertTrue(userBroadcastDispatcher.isRegistered())
@@ -128,7 +130,7 @@
intentFilter = IntentFilter(ACTION_1)
userBroadcastDispatcher.registerReceiver(
- ReceiverData(broadcastReceiver, intentFilter, mockHandler, USER_HANDLE))
+ ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
testableLooper.processAllMessages()
reset(mockContext)
@@ -151,9 +153,9 @@
}
userBroadcastDispatcher.registerReceiver(
- ReceiverData(broadcastReceiver, intentFilter, mockHandler, USER_HANDLE))
+ ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
userBroadcastDispatcher.registerReceiver(
- ReceiverData(broadcastReceiverOther, intentFilterOther, mockHandler, USER_HANDLE))
+ ReceiverData(broadcastReceiverOther, intentFilterOther, fakeExecutor, USER_HANDLE))
testableLooper.processAllMessages()
assertTrue(userBroadcastDispatcher.isRegistered())
@@ -179,14 +181,15 @@
intentFilterOther = IntentFilter(ACTION_2)
userBroadcastDispatcher.registerReceiver(
- ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE))
+ ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
userBroadcastDispatcher.registerReceiver(
- ReceiverData(broadcastReceiverOther, intentFilterOther, handler, USER_HANDLE))
+ ReceiverData(broadcastReceiverOther, intentFilterOther, fakeExecutor, USER_HANDLE))
val intent = Intent(ACTION_2)
userBroadcastDispatcher.onReceive(mockContext, intent)
testableLooper.processAllMessages()
+ fakeExecutor.runAllReady()
verify(broadcastReceiver, never()).onReceive(any(), any())
verify(broadcastReceiverOther).onReceive(mockContext, intent)
@@ -198,14 +201,15 @@
intentFilterOther = IntentFilter(ACTION_2)
userBroadcastDispatcher.registerReceiver(
- ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE))
+ ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
userBroadcastDispatcher.registerReceiver(
- ReceiverData(broadcastReceiver, intentFilterOther, handler, USER_HANDLE))
+ ReceiverData(broadcastReceiver, intentFilterOther, fakeExecutor, USER_HANDLE))
val intent = Intent(ACTION_2)
userBroadcastDispatcher.onReceive(mockContext, intent)
testableLooper.processAllMessages()
+ fakeExecutor.runAllReady()
verify(broadcastReceiver).onReceive(mockContext, intent)
}
@@ -218,14 +222,15 @@
intentFilterOther.addCategory(CATEGORY_2)
userBroadcastDispatcher.registerReceiver(
- ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE))
+ ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
userBroadcastDispatcher.registerReceiver(
- ReceiverData(broadcastReceiverOther, intentFilterOther, handler, USER_HANDLE))
+ ReceiverData(broadcastReceiverOther, intentFilterOther, fakeExecutor, USER_HANDLE))
val intent = Intent(ACTION_1)
userBroadcastDispatcher.onReceive(mockContext, intent)
testableLooper.processAllMessages()
+ fakeExecutor.runAllReady()
verify(broadcastReceiver).onReceive(mockContext, intent)
verify(broadcastReceiverOther).onReceive(mockContext, intent)
@@ -235,12 +240,13 @@
fun testPendingResult() {
intentFilter = IntentFilter(ACTION_1)
userBroadcastDispatcher.registerReceiver(
- ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE))
+ ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
val intent = Intent(ACTION_1)
userBroadcastDispatcher.onReceive(mockContext, intent)
testableLooper.processAllMessages()
+ fakeExecutor.runAllReady()
verify(broadcastReceiver).onReceive(mockContext, intent)
verify(broadcastReceiver).pendingResult = mPendingResult
@@ -250,15 +256,16 @@
fun testRemoveReceiverReferences() {
intentFilter = IntentFilter(ACTION_1)
userBroadcastDispatcher.registerReceiver(
- ReceiverData(broadcastReceiver, intentFilter, handler, USER_HANDLE))
+ ReceiverData(broadcastReceiver, intentFilter, fakeExecutor, USER_HANDLE))
intentFilterOther = IntentFilter(ACTION_1)
intentFilterOther.addAction(ACTION_2)
userBroadcastDispatcher.registerReceiver(
- ReceiverData(broadcastReceiverOther, intentFilterOther, handler, USER_HANDLE))
+ ReceiverData(broadcastReceiverOther, intentFilterOther, fakeExecutor, USER_HANDLE))
userBroadcastDispatcher.unregisterReceiver(broadcastReceiver)
testableLooper.processAllMessages()
+ fakeExecutor.runAllReady()
assertFalse(userBroadcastDispatcher.isReceiverReferenceHeld(broadcastReceiver))
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index e0b4b81..c3df3f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -356,8 +356,7 @@
// Switch which bubble is expanded
mBubbleController.selectBubble(mRow.getEntry().getKey());
- stackView.setExpandedBubble(mRow.getEntry().getKey());
- assertEquals(mRow.getEntry(), stackView.getExpandedBubble().getEntry());
+ mBubbleController.expandStack();
assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
mRow.getEntry().getKey()));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
new file mode 100644
index 0000000..7c8c7c8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt
@@ -0,0 +1,169 @@
+/*
+ * 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.systemui.controls.controller
+
+import android.content.ComponentName
+import android.content.Context
+import android.os.Binder
+import android.service.controls.Control
+import android.service.controls.DeviceTypes
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import dagger.Lazy
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsBindingControllerTest : SysuiTestCase() {
+
+ companion object {
+ fun <T> any(): T = Mockito.any<T>()
+ private val TEST_COMPONENT_NAME_1 = ComponentName("TEST_PKG", "TEST_CLS_1")
+ private val TEST_COMPONENT_NAME_2 = ComponentName("TEST_PKG", "TEST_CLS_2")
+ private val TEST_COMPONENT_NAME_3 = ComponentName("TEST_PKG", "TEST_CLS_3")
+ }
+
+ @Mock
+ private lateinit var mockControlsController: ControlsController
+
+ private val executor = FakeExecutor(FakeSystemClock())
+ private lateinit var controller: ControlsBindingController
+ private val providers = TestableControlsBindingControllerImpl.providers
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ controller = TestableControlsBindingControllerImpl(
+ mContext, executor, Lazy { mockControlsController })
+ }
+
+ @After
+ fun tearDown() {
+ executor.advanceClockToLast()
+ executor.runAllReady()
+ providers.clear()
+ }
+
+ @Test
+ fun testBindAndLoad() {
+ val callback: (List<Control>) -> Unit = {}
+ controller.bindAndLoad(TEST_COMPONENT_NAME_1, callback)
+
+ assertEquals(1, providers.size)
+ val provider = providers.first()
+ verify(provider).maybeBindAndLoad(callback)
+ }
+
+ @Test
+ fun testBindServices() {
+ controller.bindServices(listOf(TEST_COMPONENT_NAME_1, TEST_COMPONENT_NAME_2))
+ executor.runAllReady()
+
+ assertEquals(2, providers.size)
+ assertEquals(setOf(TEST_COMPONENT_NAME_1, TEST_COMPONENT_NAME_2),
+ providers.map { it.componentName }.toSet())
+ providers.forEach {
+ verify(it).bindPermanently()
+ }
+ }
+
+ @Test
+ fun testSubscribe() {
+ val controlInfo1 = ControlInfo(TEST_COMPONENT_NAME_1, "id_1", "", DeviceTypes.TYPE_UNKNOWN)
+ val controlInfo2 = ControlInfo(TEST_COMPONENT_NAME_2, "id_2", "", DeviceTypes.TYPE_UNKNOWN)
+ controller.bindServices(listOf(TEST_COMPONENT_NAME_3))
+
+ controller.subscribe(listOf(controlInfo1, controlInfo2))
+
+ executor.runAllReady()
+
+ assertEquals(3, providers.size)
+ val provider1 = providers.first { it.componentName == TEST_COMPONENT_NAME_1 }
+ val provider2 = providers.first { it.componentName == TEST_COMPONENT_NAME_2 }
+ val provider3 = providers.first { it.componentName == TEST_COMPONENT_NAME_3 }
+
+ verify(provider1).maybeBindAndSubscribe(listOf(controlInfo1.controlId))
+ verify(provider2).maybeBindAndSubscribe(listOf(controlInfo2.controlId))
+ verify(provider3, never()).maybeBindAndSubscribe(any())
+ verify(provider3).unbindService() // Not needed services will be unbound
+ }
+
+ @Test
+ fun testUnsubscribe_notRefreshing() {
+ controller.bindServices(listOf(TEST_COMPONENT_NAME_1, TEST_COMPONENT_NAME_2))
+ controller.unsubscribe()
+
+ executor.runAllReady()
+
+ providers.forEach {
+ verify(it, never()).unsubscribe()
+ }
+ }
+
+ @Test
+ fun testUnsubscribe_refreshing() {
+ val controlInfo1 = ControlInfo(TEST_COMPONENT_NAME_1, "id_1", "", DeviceTypes.TYPE_UNKNOWN)
+ val controlInfo2 = ControlInfo(TEST_COMPONENT_NAME_2, "id_2", "", DeviceTypes.TYPE_UNKNOWN)
+
+ controller.subscribe(listOf(controlInfo1, controlInfo2))
+
+ controller.unsubscribe()
+
+ executor.runAllReady()
+
+ providers.forEach {
+ verify(it).unsubscribe()
+ }
+ }
+}
+
+class TestableControlsBindingControllerImpl(
+ context: Context,
+ executor: DelayableExecutor,
+ lazyController: Lazy<ControlsController>
+) : ControlsBindingControllerImpl(context, executor, lazyController) {
+
+ companion object {
+ val providers = mutableSetOf<ControlsProviderLifecycleManager>()
+ }
+
+ override fun createProviderManager(component: ComponentName):
+ ControlsProviderLifecycleManager {
+ val provider = mock(ControlsProviderLifecycleManager::class.java)
+ val token = Binder()
+ `when`(provider.componentName).thenReturn(component)
+ `when`(provider.token).thenReturn(token)
+ providers.add(provider)
+ return provider
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
new file mode 100644
index 0000000..a19c299
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
@@ -0,0 +1,360 @@
+/*
+ * 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.systemui.controls.controller
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.provider.Settings
+import android.service.controls.Control
+import android.service.controls.DeviceTypes
+import android.service.controls.actions.ControlAction
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.DumpController
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.ControlStatus
+import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+import java.util.Optional
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsControllerImplTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var uiController: ControlsUiController
+ @Mock
+ private lateinit var bindingController: ControlsBindingController
+ @Mock
+ private lateinit var dumpController: DumpController
+ @Mock
+ private lateinit var pendingIntent: PendingIntent
+ @Mock
+ private lateinit var persistenceWrapper: ControlsFavoritePersistenceWrapper
+
+ @Captor
+ private lateinit var controlInfoListCaptor: ArgumentCaptor<List<ControlInfo>>
+ @Captor
+ private lateinit var controlLoadCallbackCaptor: ArgumentCaptor<(List<Control>) -> Unit>
+
+ private lateinit var delayableExecutor: FakeExecutor
+ private lateinit var controller: ControlsController
+
+ companion object {
+ fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
+ fun <T : Any> safeEq(value: T): T = eq(value) ?: value
+
+ private val TEST_COMPONENT = ComponentName("test.pkg", "test.class")
+ private const val TEST_CONTROL_ID = "control1"
+ private const val TEST_CONTROL_TITLE = "Test"
+ private const val TEST_DEVICE_TYPE = DeviceTypes.TYPE_AC_HEATER
+ private val TEST_CONTROL_INFO = ControlInfo(
+ TEST_COMPONENT, TEST_CONTROL_ID, TEST_CONTROL_TITLE, TEST_DEVICE_TYPE)
+
+ private val TEST_COMPONENT_2 = ComponentName("test.pkg", "test.class.2")
+ private const val TEST_CONTROL_ID_2 = "control2"
+ private const val TEST_CONTROL_TITLE_2 = "Test 2"
+ private const val TEST_DEVICE_TYPE_2 = DeviceTypes.TYPE_CAMERA
+ private val TEST_CONTROL_INFO_2 = ControlInfo(
+ TEST_COMPONENT_2, TEST_CONTROL_ID_2, TEST_CONTROL_TITLE_2, TEST_DEVICE_TYPE_2)
+ }
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ Settings.Secure.putInt(mContext.contentResolver,
+ ControlsControllerImpl.CONTROLS_AVAILABLE, 1)
+
+ delayableExecutor = FakeExecutor(FakeSystemClock())
+
+ controller = ControlsControllerImpl(
+ mContext,
+ delayableExecutor,
+ uiController,
+ bindingController,
+ Optional.of(persistenceWrapper),
+ dumpController
+ )
+ assertTrue(controller.available)
+ }
+
+ private fun builderFromInfo(controlInfo: ControlInfo): Control.StatelessBuilder {
+ return Control.StatelessBuilder(controlInfo.controlId, pendingIntent)
+ .setDeviceType(controlInfo.deviceType).setTitle(controlInfo.controlTitle)
+ }
+
+ @Test
+ fun testStartWithoutFavorites() {
+ assertTrue(controller.getFavoriteControls().isEmpty())
+ }
+
+ @Test
+ fun testStartWithSavedFavorites() {
+ `when`(persistenceWrapper.readFavorites()).thenReturn(listOf(TEST_CONTROL_INFO))
+ val controller_other = ControlsControllerImpl(
+ mContext,
+ delayableExecutor,
+ uiController,
+ bindingController,
+ Optional.of(persistenceWrapper),
+ dumpController
+ )
+ assertEquals(listOf(TEST_CONTROL_INFO), controller_other.getFavoriteControls())
+ }
+
+ @Test
+ fun testAddFavorite() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+
+ val favorites = controller.getFavoriteControls()
+ assertTrue(TEST_CONTROL_INFO in favorites)
+ assertEquals(1, favorites.size)
+ }
+
+ @Test
+ fun testAddMultipleFavorites() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO_2, true)
+
+ val favorites = controller.getFavoriteControls()
+ assertTrue(TEST_CONTROL_INFO in favorites)
+ assertTrue(TEST_CONTROL_INFO_2 in favorites)
+ assertEquals(2, favorites.size)
+ }
+
+ @Test
+ fun testAddAndRemoveFavorite() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO_2, true)
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, false)
+
+ val favorites = controller.getFavoriteControls()
+ assertTrue(TEST_CONTROL_INFO !in favorites)
+ assertTrue(TEST_CONTROL_INFO_2 in favorites)
+ assertEquals(1, favorites.size)
+ }
+
+ @Test
+ fun testFavoritesSavedOnAdd() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+
+ verify(persistenceWrapper).storeFavorites(listOf(TEST_CONTROL_INFO))
+ }
+
+ @Test
+ fun testFavoritesSavedOnRemove() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ reset(persistenceWrapper)
+
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, false)
+ verify(persistenceWrapper).storeFavorites(emptyList())
+ }
+
+ @Test
+ fun testFavoritesSavedOnChange() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2)
+ val control = builderFromInfo(newControlInfo).build()
+
+ controller.loadForComponent(TEST_COMPONENT) {}
+
+ reset(persistenceWrapper)
+ verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
+ capture(controlLoadCallbackCaptor))
+
+ controlLoadCallbackCaptor.value.invoke(listOf(control))
+
+ verify(persistenceWrapper).storeFavorites(listOf(newControlInfo))
+ }
+
+ @Test
+ fun testFavoritesNotSavedOnRedundantAdd() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+
+ reset(persistenceWrapper)
+
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ verify(persistenceWrapper, never()).storeFavorites(ArgumentMatchers.anyList())
+ }
+
+ @Test
+ fun testFavoritesNotSavedOnNotRemove() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, false)
+ verify(persistenceWrapper, never()).storeFavorites(ArgumentMatchers.anyList())
+ }
+
+ @Test
+ fun testOnActionResponse() {
+ controller.onActionResponse(TEST_COMPONENT, TEST_CONTROL_ID, ControlAction.RESPONSE_OK)
+
+ verify(uiController).onActionResponse(TEST_COMPONENT, TEST_CONTROL_ID,
+ ControlAction.RESPONSE_OK)
+ }
+
+ @Test
+ fun testRefreshStatus() {
+ val list = listOf(Control.StatefulBuilder(TEST_CONTROL_ID, pendingIntent).build())
+ controller.refreshStatus(TEST_COMPONENT, list)
+
+ verify(uiController).onRefreshState(TEST_COMPONENT, list)
+ }
+
+ @Test
+ fun testUnsubscribe() {
+ controller.unsubscribe()
+ verify(bindingController).unsubscribe()
+ }
+
+ @Test
+ fun testSubscribeFavorites() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO_2, true)
+
+ controller.subscribeToFavorites()
+
+ verify(bindingController).subscribe(capture(controlInfoListCaptor))
+
+ assertTrue(TEST_CONTROL_INFO in controlInfoListCaptor.value)
+ assertTrue(TEST_CONTROL_INFO_2 in controlInfoListCaptor.value)
+ }
+
+ @Test
+ fun testLoadForComponent_noFavorites() {
+ var loaded = false
+ val control = builderFromInfo(TEST_CONTROL_INFO).build()
+
+ controller.loadForComponent(TEST_COMPONENT) {
+ loaded = true
+ assertEquals(1, it.size)
+ val controlStatus = it[0]
+ assertEquals(ControlStatus(control, false), controlStatus)
+ }
+
+ verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
+ capture(controlLoadCallbackCaptor))
+
+ controlLoadCallbackCaptor.value.invoke(listOf(control))
+
+ assertTrue(loaded)
+ }
+
+ @Test
+ fun testLoadForComponent_favorites() {
+ var loaded = false
+ val control = builderFromInfo(TEST_CONTROL_INFO).build()
+ val control2 = builderFromInfo(TEST_CONTROL_INFO_2).build()
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+
+ controller.loadForComponent(TEST_COMPONENT) {
+ loaded = true
+ assertEquals(2, it.size)
+ val controlStatus = it.first { it.control.controlId == TEST_CONTROL_ID }
+ assertEquals(ControlStatus(control, true), controlStatus)
+
+ val controlStatus2 = it.first { it.control.controlId == TEST_CONTROL_ID_2 }
+ assertEquals(ControlStatus(control2, false), controlStatus2)
+ }
+
+ verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
+ capture(controlLoadCallbackCaptor))
+
+ controlLoadCallbackCaptor.value.invoke(listOf(control, control2))
+
+ assertTrue(loaded)
+ }
+
+ @Test
+ fun testLoadForComponent_removed() {
+ var loaded = false
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+
+ controller.loadForComponent(TEST_COMPONENT) {
+ loaded = true
+ assertEquals(1, it.size)
+ val controlStatus = it[0]
+ assertEquals(TEST_CONTROL_ID, controlStatus.control.controlId)
+ assertTrue(controlStatus.favorite)
+ assertTrue(controlStatus.removed)
+ }
+
+ verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
+ capture(controlLoadCallbackCaptor))
+
+ controlLoadCallbackCaptor.value.invoke(emptyList())
+
+ assertTrue(loaded)
+ }
+
+ @Test
+ fun testFavoriteInformationModifiedOnLoad() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2)
+ val control = builderFromInfo(newControlInfo).build()
+
+ controller.loadForComponent(TEST_COMPONENT) {}
+
+ verify(bindingController).bindAndLoad(safeEq(TEST_COMPONENT),
+ capture(controlLoadCallbackCaptor))
+
+ controlLoadCallbackCaptor.value.invoke(listOf(control))
+
+ val favorites = controller.getFavoriteControls()
+ assertEquals(1, favorites.size)
+ assertEquals(newControlInfo, favorites[0])
+ }
+
+ @Test
+ fun testFavoriteInformationModifiedOnRefresh() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2)
+ val control = builderFromInfo(newControlInfo).build()
+
+ controller.refreshStatus(TEST_COMPONENT, listOf(control))
+
+ delayableExecutor.runAllReady()
+
+ val favorites = controller.getFavoriteControls()
+ assertEquals(1, favorites.size)
+ assertEquals(newControlInfo, favorites[0])
+ }
+
+ @Test
+ fun testClearFavorites() {
+ controller.changeFavoriteStatus(TEST_CONTROL_INFO, true)
+ assertEquals(1, controller.getFavoriteControls().size)
+
+ controller.clearFavorites()
+ assertTrue(controller.getFavoriteControls().isEmpty())
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt
new file mode 100644
index 0000000..c145c1f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.systemui.controls.controller
+
+import android.content.ComponentName
+import android.service.controls.DeviceTypes
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.io.File
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsFavoritePersistenceWrapperTest : SysuiTestCase() {
+
+ private lateinit var file: File
+
+ private val executor = FakeExecutor(FakeSystemClock())
+
+ private lateinit var wrapper: ControlsFavoritePersistenceWrapper
+
+ @Before
+ fun setUp() {
+ file = File.createTempFile("controls_favorites", ".temp")
+ wrapper = ControlsFavoritePersistenceWrapper(file, executor)
+ }
+
+ @After
+ fun tearDown() {
+ if (file.exists() ?: false) {
+ file.delete()
+ }
+ }
+
+ @Test
+ fun testSaveAndRestore() {
+ val controlInfo1 = ControlInfo(
+ ComponentName.unflattenFromString("TEST_PKG/.TEST_CLS_1")!!,
+ "id1", "name_1", DeviceTypes.TYPE_UNKNOWN)
+ val controlInfo2 = ControlInfo(
+ ComponentName.unflattenFromString("TEST_PKG/.TEST_CLS_2")!!,
+ "id2", "name_2", DeviceTypes.TYPE_GENERIC_ON_OFF)
+ val list = listOf(controlInfo1, controlInfo2)
+
+ wrapper.storeFavorites(list)
+
+ executor.runAllReady()
+
+ assertEquals(list, wrapper.readFavorites())
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
new file mode 100644
index 0000000..556bb40
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt
@@ -0,0 +1,138 @@
+/*
+ * 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.systemui.controls.controller
+
+import android.content.ComponentName
+import android.service.controls.Control
+import android.service.controls.IControlsProvider
+import android.service.controls.IControlsProviderCallback
+import android.service.controls.actions.ControlAction
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.DelayableExecutor
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsProviderLifecycleManagerTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var serviceCallback: IControlsProviderCallback.Stub
+ @Mock
+ private lateinit var service: IControlsProvider.Stub
+
+ private val componentName = ComponentName("test.pkg", "test.cls")
+ private lateinit var manager: ControlsProviderLifecycleManager
+ private lateinit var executor: DelayableExecutor
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ mContext.addMockService(componentName, service)
+ executor = FakeExecutor(FakeSystemClock())
+ `when`(service.asBinder()).thenCallRealMethod()
+ `when`(service.queryLocalInterface(ArgumentMatchers.anyString())).thenReturn(service)
+
+ manager = ControlsProviderLifecycleManager(
+ context,
+ executor,
+ serviceCallback,
+ componentName
+ )
+ }
+
+ @After
+ fun tearDown() {
+ manager.unbindService()
+ }
+
+ @Test
+ fun testBindService() {
+ manager.bindPermanently()
+ assertTrue(mContext.isBound(componentName))
+ }
+
+ @Test
+ fun testUnbindService() {
+ manager.bindPermanently()
+ manager.unbindService()
+ assertFalse(mContext.isBound(componentName))
+ }
+
+ @Test
+ fun testMaybeBindAndLoad() {
+ val callback: (List<Control>) -> Unit = {}
+ manager.maybeBindAndLoad(callback)
+
+ verify(service).load()
+
+ assertTrue(mContext.isBound(componentName))
+ assertEquals(callback, manager.lastLoadCallback)
+ }
+
+ @Test
+ fun testMaybeUnbind_bindingAndCallback() {
+ manager.maybeBindAndLoad {}
+
+ manager.maybeUnbindAndRemoveCallback()
+ assertFalse(mContext.isBound(componentName))
+ assertNull(manager.lastLoadCallback)
+ }
+
+ @Test
+ fun testUnsubscribe() {
+ manager.bindPermanently()
+ manager.unsubscribe()
+
+ verify(service).unsubscribe()
+ }
+
+ @Test
+ fun testMaybeBindAndSubscribe() {
+ val list = listOf("TEST_ID")
+ manager.maybeBindAndSubscribe(list)
+
+ assertTrue(mContext.isBound(componentName))
+ verify(service).subscribe(list)
+ }
+
+ @Test
+ fun testMaybeBindAndAction() {
+ val controlId = "TEST_ID"
+ val action = ControlAction.UNKNOWN_ACTION
+ manager.maybeBindAndSendAction(controlId, action)
+
+ assertTrue(mContext.isBound(componentName))
+ verify(service).onAction(controlId, action)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapperTest.kt
new file mode 100644
index 0000000..d6993c0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderServiceWrapperTest.kt
@@ -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 com.android.systemui.controls.controller
+
+import android.os.RemoteException
+import android.service.controls.IControlsProvider
+import android.service.controls.actions.ControlAction
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.any
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsProviderServiceWrapperTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var service: IControlsProvider
+
+ private val exception = RemoteException()
+
+ private lateinit var wrapper: ControlsProviderServiceWrapper
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ wrapper = ControlsProviderServiceWrapper(service)
+ }
+
+ @Test
+ fun testLoad_happyPath() {
+ val result = wrapper.load()
+
+ assertTrue(result)
+ verify(service).load()
+ }
+
+ @Test
+ fun testLoad_error() {
+ `when`(service.load()).thenThrow(exception)
+ val result = wrapper.load()
+
+ assertFalse(result)
+ }
+
+ @Test
+ fun testSubscribe_happyPath() {
+ val list = listOf("TEST_ID")
+ val result = wrapper.subscribe(list)
+
+ assertTrue(result)
+ verify(service).subscribe(list)
+ }
+
+ @Test
+ fun testSubscribe_error() {
+ `when`(service.subscribe(any())).thenThrow(exception)
+
+ val list = listOf("TEST_ID")
+ val result = wrapper.subscribe(list)
+
+ assertFalse(result)
+ }
+
+ @Test
+ fun testUnsubscribe_happyPath() {
+ val result = wrapper.unsubscribe()
+
+ assertTrue(result)
+ verify(service).unsubscribe()
+ }
+
+ @Test
+ fun testUnsubscribe_error() {
+ `when`(service.unsubscribe()).thenThrow(exception)
+ val result = wrapper.unsubscribe()
+
+ assertFalse(result)
+ }
+
+ @Test
+ fun testOnAction_happyPath() {
+ val id = "TEST_ID"
+ val action = ControlAction.UNKNOWN_ACTION
+
+ val result = wrapper.onAction(id, action)
+
+ assertTrue(result)
+ verify(service).onAction(id, action)
+ }
+
+ @Test
+ fun testOnAction_error() {
+ `when`(service.onAction(any(), any())).thenThrow(exception)
+
+ val id = "TEST_ID"
+ val action = ControlAction.UNKNOWN_ACTION
+
+ val result = wrapper.onAction(id, action)
+
+ assertFalse(result)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
new file mode 100644
index 0000000..f09aab9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
@@ -0,0 +1,188 @@
+/*
+ * 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.systemui.controls.management
+
+import android.content.ComponentName
+import android.content.pm.ServiceInfo
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.settingslib.applications.ServiceListing
+import com.android.settingslib.widget.CandidateInfo
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.`when`
+import org.mockito.Mockito.never
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ControlsListingControllerImplTest : SysuiTestCase() {
+
+ companion object {
+ private const val TEST_LABEL = "TEST_LABEL"
+ private const val TEST_PERMISSION = "permission"
+ fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
+ fun <T> any(): T = Mockito.any<T>()
+ }
+
+ @Mock
+ private lateinit var mockSL: ServiceListing
+ @Mock
+ private lateinit var mockCallback: ControlsListingController.ControlsListingCallback
+ @Mock
+ private lateinit var mockCallbackOther: ControlsListingController.ControlsListingCallback
+ @Mock
+ private lateinit var serviceInfo: ServiceInfo
+ @Mock
+ private lateinit var componentName: ComponentName
+
+ private val executor = FakeExecutor(FakeSystemClock())
+
+ private lateinit var controller: ControlsListingControllerImpl
+
+ private var serviceListingCallbackCaptor =
+ ArgumentCaptor.forClass(ServiceListing.Callback::class.java)
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ `when`(serviceInfo.componentName).thenReturn(componentName)
+
+ controller = ControlsListingControllerImpl(mContext, executor, mockSL)
+ verify(mockSL).addCallback(capture(serviceListingCallbackCaptor))
+ }
+
+ @After
+ fun tearDown() {
+ executor.advanceClockToLast()
+ executor.runAllReady()
+ }
+
+ @Test
+ fun testNoServices_notListening() {
+ assertTrue(controller.getCurrentServices().isEmpty())
+ }
+
+ @Test
+ fun testStartListening_onFirstCallback() {
+ controller.addCallback(mockCallback)
+ executor.runAllReady()
+
+ verify(mockSL).setListening(true)
+ }
+
+ @Test
+ fun testStartListening_onlyOnce() {
+ controller.addCallback(mockCallback)
+ controller.addCallback(mockCallbackOther)
+
+ executor.runAllReady()
+
+ verify(mockSL).setListening(true)
+ }
+
+ @Test
+ fun testStopListening_callbackRemoved() {
+ controller.addCallback(mockCallback)
+
+ executor.runAllReady()
+
+ controller.removeCallback(mockCallback)
+
+ executor.runAllReady()
+
+ verify(mockSL).setListening(false)
+ }
+
+ @Test
+ fun testStopListening_notWhileRemainingCallbacks() {
+ controller.addCallback(mockCallback)
+ controller.addCallback(mockCallbackOther)
+
+ executor.runAllReady()
+
+ controller.removeCallback(mockCallback)
+
+ executor.runAllReady()
+
+ verify(mockSL, never()).setListening(false)
+ }
+
+ @Test
+ fun testReloadOnFirstCallbackAdded() {
+ controller.addCallback(mockCallback)
+ executor.runAllReady()
+
+ verify(mockSL).reload()
+ }
+
+ @Test
+ fun testCallbackCalledWhenAdded() {
+ `when`(mockSL.reload()).then {
+ serviceListingCallbackCaptor.value.onServicesReloaded(emptyList())
+ }
+
+ controller.addCallback(mockCallback)
+ executor.runAllReady()
+ verify(mockCallback).onServicesUpdated(any())
+ reset(mockCallback)
+
+ controller.addCallback(mockCallbackOther)
+ executor.runAllReady()
+ verify(mockCallbackOther).onServicesUpdated(any())
+ verify(mockCallback, never()).onServicesUpdated(any())
+ }
+
+ @Test
+ fun testCallbackGetsList() {
+ val list = listOf(serviceInfo)
+ controller.addCallback(mockCallback)
+ controller.addCallback(mockCallbackOther)
+
+ @Suppress("unchecked_cast")
+ val captor: ArgumentCaptor<List<CandidateInfo>> =
+ ArgumentCaptor.forClass(List::class.java) as ArgumentCaptor<List<CandidateInfo>>
+
+ executor.runAllReady()
+ reset(mockCallback)
+ reset(mockCallbackOther)
+
+ serviceListingCallbackCaptor.value.onServicesReloaded(list)
+
+ executor.runAllReady()
+ verify(mockCallback).onServicesUpdated(capture(captor))
+ assertEquals(1, captor.value.size)
+ assertEquals(componentName.flattenToString(), captor.value[0].key)
+
+ verify(mockCallbackOther).onServicesUpdated(capture(captor))
+ assertEquals(1, captor.value.size)
+ assertEquals(componentName.flattenToString(), captor.value[0].key)
+ }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
index 167f361..548da8e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java
@@ -110,7 +110,7 @@
@Test
public void testReceiverIsRegisteredToDispatcherOnStart() {
mPowerUI.start();
- verify(mBroadcastDispatcher).registerReceiver(
+ verify(mBroadcastDispatcher).registerReceiverWithHandler(
any(BroadcastReceiver.class),
any(IntentFilter.class),
any(Handler.class)); //PowerUI does not call with User
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
index 40b0ba9..3fdbd3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
@@ -47,6 +47,9 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpansionLogger;
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener;
+import com.android.systemui.statusbar.notification.row.NotifRemoteViewCache;
import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
@@ -72,6 +75,7 @@
public static final UserHandle USER_HANDLE = UserHandle.of(ActivityManager.getCurrentUser());
private static final String GROUP_KEY = "gruKey";
+ private static final String APP_NAME = "appName";
private final Context mContext;
private int mId;
@@ -303,9 +307,6 @@
null /* root */,
false /* attachToRoot */);
ExpandableNotificationRow row = mRow;
- row.setGroupManager(mGroupManager);
- row.setHeadsUpManager(mHeadsUpManager);
- row.setAboveShelfChangedListener(aboveShelf -> {});
final NotificationChannel channel =
new NotificationChannel(
@@ -329,6 +330,23 @@
entry.setRow(row);
entry.createIcons(mContext, entry.getSbn());
row.setEntry(entry);
+
+ NotificationContentInflater contentBinder = new NotificationContentInflater(
+ mock(NotifRemoteViewCache.class),
+ mock(NotificationRemoteInputManager.class));
+ contentBinder.setInflateSynchronously(true);
+
+ row.initialize(
+ APP_NAME,
+ entry.getKey(),
+ mock(ExpansionLogger.class),
+ mock(KeyguardBypassController.class),
+ mGroupManager,
+ mHeadsUpManager,
+ contentBinder,
+ mock(OnExpandClickListener.class));
+ row.setAboveShelfChangedListener(aboveShelf -> { });
+
row.setInflationFlags(extraInflationFlags);
inflateAndWait(row);
@@ -341,11 +359,10 @@
private static void inflateAndWait(ExpandableNotificationRow row) throws Exception {
CountDownLatch countDownLatch = new CountDownLatch(1);
- row.getNotificationInflater().setInflateSynchronously(true);
NotificationContentInflater.InflationCallback callback =
new NotificationContentInflater.InflationCallback() {
@Override
- public void handleInflationException(StatusBarNotification notification,
+ public void handleInflationException(NotificationEntry entry,
Exception e) {
countDownLatch.countDown();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java
index 145a25c..a54f733 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java
@@ -33,7 +33,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.statusbar.phone.NotificationPanelView;
+import com.android.systemui.statusbar.phone.NotificationPanelViewController;
import com.android.systemui.statusbar.phone.StatusBarWindowView;
import com.android.systemui.statusbar.phone.StatusBarWindowViewController;
@@ -64,7 +64,7 @@
mLaunchAnimator = new ActivityLaunchAnimator(
mStatusBarWindowViewController,
mCallback,
- mock(NotificationPanelView.class),
+ mock(NotificationPanelViewController.class),
mNotificationContainer);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index f55ea4f..d852fa1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -58,15 +58,13 @@
import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
-import com.android.internal.logging.MetricsLogger;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dependency;
-import com.android.systemui.ForegroundServiceController;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.NotificationLifetimeExtender;
-import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationPresenter;
@@ -80,21 +78,24 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
-import com.android.systemui.statusbar.notification.collection.NotificationRowBinder;
-import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.logging.NotifLog;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotifRemoteViewCache;
+import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder;
import com.android.systemui.statusbar.notification.row.RowInflaterTask;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
-import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.util.Assert;
+import com.android.systemui.util.leak.LeakDetector;
import org.junit.Before;
import org.junit.Test;
@@ -128,21 +129,16 @@
@Mock private HeadsUpManager mHeadsUpManager;
@Mock private RankingMap mRankingMap;
@Mock private RemoteInputController mRemoteInputController;
-
- // Dependency mocks:
- @Mock private ForegroundServiceController mForegroundServiceController;
+ @Mock private NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
@Mock private NotificationLockscreenUserManager mLockscreenUserManager;
@Mock private NotificationGroupManager mGroupManager;
@Mock private NotificationGutsManager mGutsManager;
@Mock private NotificationRemoteInputManager mRemoteInputManager;
- @Mock private NotificationListener mNotificationListener;
@Mock private DeviceProvisionedController mDeviceProvisionedController;
- @Mock private VisualStabilityManager mVisualStabilityManager;
- @Mock private MetricsLogger mMetricsLogger;
- @Mock private SmartReplyController mSmartReplyController;
@Mock private RowInflaterTask mAsyncInflationTask;
- @Mock private NotificationRowBinder mMockedRowBinder;
@Mock private NotifLog mNotifLog;
+ @Mock private FeatureFlags mFeatureFlags;
+ @Mock private LeakDetector mLeakDetector;
private int mId;
private NotificationEntry mEntry;
@@ -189,21 +185,9 @@
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mDependency.injectMockDependency(ShadeController.class);
- mDependency.injectTestDependency(ForegroundServiceController.class,
- mForegroundServiceController);
- mDependency.injectTestDependency(NotificationLockscreenUserManager.class,
- mLockscreenUserManager);
- mDependency.injectTestDependency(NotificationGroupManager.class, mGroupManager);
- mDependency.injectTestDependency(NotificationGutsManager.class, mGutsManager);
- mDependency.injectTestDependency(NotificationRemoteInputManager.class, mRemoteInputManager);
- mDependency.injectTestDependency(NotificationListener.class, mNotificationListener);
- mDependency.injectTestDependency(DeviceProvisionedController.class,
- mDeviceProvisionedController);
- mDependency.injectTestDependency(VisualStabilityManager.class, mVisualStabilityManager);
- mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
- mDependency.injectTestDependency(SmartReplyController.class, mSmartReplyController);
- mDependency.injectTestDependency(KeyguardEnvironment.class, mEnvironment);
+ if (!mDependency.hasInstantiatedDependency(SmartReplyController.class)) {
+ mDependency.injectMockDependency(SmartReplyController.class);
+ }
mDependency.injectMockDependency(NotificationMediaManager.class);
mCountDownLatch = new CountDownLatch(1);
@@ -219,6 +203,25 @@
mEntry.expandedIcon = mock(StatusBarIconView.class);
+ NotificationRowContentBinder contentBinder = new NotificationContentInflater(
+ mock(NotifRemoteViewCache.class),
+ mRemoteInputManager);
+
+ NotificationRowBinderImpl notificationRowBinder =
+ new NotificationRowBinderImpl(mContext,
+ mRemoteInputManager,
+ mLockscreenUserManager,
+ contentBinder,
+ true, /* allowLongPress */
+ mock(KeyguardBypassController.class),
+ mock(StatusBarStateController.class),
+ mGroupManager,
+ mGutsManager,
+ mNotificationInterruptionStateProvider,
+ mock(NotificationLogger.class));
+
+ when(mFeatureFlags.isNewNotifPipelineEnabled()).thenReturn(false);
+ when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false);
mEntryManager = new TestableNotificationEntryManager(
mNotifLog,
mGroupManager,
@@ -229,22 +232,22 @@
mock(NotificationFilter.class),
mNotifLog,
mock(NotificationSectionsFeatureManager.class),
- mock(PeopleNotificationIdentifier.class)),
- mEnvironment
+ mock(PeopleNotificationIdentifier.class),
+ mock(HighPriorityProvider.class)),
+ mEnvironment,
+ mFeatureFlags,
+ () -> notificationRowBinder,
+ () -> mRemoteInputManager,
+ mLeakDetector
);
mEntryManager.setUpWithPresenter(mPresenter, mListContainer, mHeadsUpManager);
mEntryManager.addNotificationEntryListener(mEntryListener);
mEntryManager.setNotificationRemoveInterceptor(mRemoveInterceptor);
- NotificationRowBinderImpl notificationRowBinder =
- new NotificationRowBinderImpl(mContext, true, /* allowLongPress */
- mock(KeyguardBypassController.class),
- mock(StatusBarStateController.class),
- mock(NotificationLogger.class));
notificationRowBinder.setUpWithPresenter(
- mPresenter, mListContainer, mHeadsUpManager, mEntryManager, mBindCallback);
+ mPresenter, mListContainer, mHeadsUpManager, mBindCallback);
+ notificationRowBinder.setInflationCallback(mEntryManager);
notificationRowBinder.setNotificationClicker(mock(NotificationClicker.class));
- mEntryManager.setRowBinder(notificationRowBinder);
setUserSentiment(
mEntry.getKey(), Ranking.USER_SENTIMENT_NEUTRAL);
@@ -364,9 +367,6 @@
@Test
public void testRemoveNotification_whilePending() {
-
- mEntryManager.setRowBinder(mMockedRowBinder);
-
mEntryManager.addNotification(mSbn, mRankingMap);
mEntryManager.removeNotification(mSbn.getKey(), mRankingMap, UNDEFINED_DISMISS_REASON);
@@ -439,7 +439,6 @@
@Test
public void testLifetimeExtenders_ifNotificationIsRetainedItIsntRemoved() {
// GIVEN an entry manager with a notification
- mEntryManager.setRowBinder(mMockedRowBinder);
mEntryManager.addActiveNotificationForTest(mEntry);
// GIVEN a lifetime extender that always tries to extend lifetime
@@ -463,7 +462,6 @@
Assert.sMainLooper = TestableLooper.get(this).getLooper();
// GIVEN an entry manager with a notification whose life has been extended
- mEntryManager.setRowBinder(mMockedRowBinder);
mEntryManager.addActiveNotificationForTest(mEntry);
final FakeNotificationLifetimeExtender extender = new FakeNotificationLifetimeExtender();
mEntryManager.addNotificationLifetimeExtender(extender);
@@ -482,7 +480,6 @@
@Test
public void testLifetimeExtenders_whenNotificationUpdatedRetainersAreCanceled() {
// GIVEN an entry manager with a notification whose life has been extended
- mEntryManager.setRowBinder(mMockedRowBinder);
mEntryManager.addActiveNotificationForTest(mEntry);
NotificationLifetimeExtender extender = mock(NotificationLifetimeExtender.class);
when(extender.shouldExtendLifetime(mEntry)).thenReturn(true);
@@ -499,7 +496,6 @@
@Test
public void testLifetimeExtenders_whenNewExtenderTakesPrecedenceOldExtenderIsCanceled() {
// GIVEN an entry manager with a notification
- mEntryManager.setRowBinder(mMockedRowBinder);
mEntryManager.addActiveNotificationForTest(mEntry);
// GIVEN two lifetime extenders, the first which never extends and the second which
@@ -538,7 +534,6 @@
@Test
public void testRemoveInterceptor_interceptsDontGetRemoved() throws InterruptedException {
// GIVEN an entry manager with a notification
- mEntryManager.setRowBinder(mMockedRowBinder);
mEntryManager.addActiveNotificationForTest(mEntry);
// GIVEN interceptor that intercepts that entry
@@ -560,7 +555,6 @@
Assert.sMainLooper = TestableLooper.get(this).getLooper();
// GIVEN an entry manager with a notification
- mEntryManager.setRowBinder(mMockedRowBinder);
mEntryManager.addActiveNotificationForTest(mEntry);
// GIVEN interceptor that doesn't intercept
@@ -577,7 +571,7 @@
}
private NotificationEntry createNotification() {
- Notification.Builder n = new Notification.Builder(mContext, "")
+ Notification.Builder n = new Notification.Builder(mContext, "id")
.setSmallIcon(R.drawable.ic_person)
.setContentTitle("Title")
.setContentText("Text");
@@ -588,6 +582,7 @@
.setUid(TEST_UID)
.setId(mId++)
.setNotification(n.build())
+ .setChannel(new NotificationChannel("id", "", IMPORTANCE_DEFAULT))
.setUser(new UserHandle(ActivityManager.getCurrentUser()))
.build();
}
@@ -622,7 +617,7 @@
@Test
public void testGetNotificationsForCurrentUser_shouldFilterNonCurrentUserNotifications() {
Assert.sMainLooper = TestableLooper.get(this).getLooper();
- Notification.Builder n = new Notification.Builder(mContext, "")
+ Notification.Builder n = new Notification.Builder(mContext, "di")
.setSmallIcon(R.drawable.ic_person)
.setContentTitle("Title")
.setContentText("Text");
@@ -634,6 +629,7 @@
.setId(mId++)
.setNotification(n.build())
.setUser(new UserHandle(ActivityManager.getCurrentUser()))
+ .setChannel(new NotificationChannel("id", "", IMPORTANCE_DEFAULT))
.build();
mEntryManager.addActiveNotificationForTest(mEntry);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt
index 34beefe..29ce920 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt
@@ -16,13 +16,17 @@
package com.android.systemui.statusbar.notification
+import com.android.systemui.statusbar.FeatureFlags
import com.android.systemui.statusbar.NotificationPresenter
+import com.android.systemui.statusbar.NotificationRemoteInputManager
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationRankingManager
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder
import com.android.systemui.statusbar.notification.logging.NotifLog
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
import com.android.systemui.statusbar.phone.NotificationGroupManager
+import com.android.systemui.util.leak.LeakDetector
import java.util.concurrent.CountDownLatch
@@ -33,8 +37,13 @@
log: NotifLog,
gm: NotificationGroupManager,
rm: NotificationRankingManager,
- ke: KeyguardEnvironment
-) : NotificationEntryManager(log, gm, rm, ke) {
+ ke: KeyguardEnvironment,
+ ff: FeatureFlags,
+ rb: dagger.Lazy<NotificationRowBinder>,
+ notificationRemoteInputManagerLazy: dagger.Lazy<NotificationRemoteInputManager>,
+ leakDetector: LeakDetector
+) : NotificationEntryManager(log, gm, rm, ke, ff, rb,
+ notificationRemoteInputManagerLazy, leakDetector) {
public var countDownLatch: CountDownLatch = CountDownLatch(1)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryTest.java
deleted file mode 100644
index a06d6c1..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryTest.java
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * 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 com.android.systemui.statusbar.notification.collection;
-
-import static android.app.NotificationManager.IMPORTANCE_HIGH;
-import static android.app.NotificationManager.IMPORTANCE_MIN;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import android.testing.AndroidTestingRunner;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.RankingBuilder;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class GroupEntryTest extends SysuiTestCase {
- @Test
- public void testIsHighPriority_addChild() {
- // GIVEN a GroupEntry with a lowPrioritySummary and no children
- final GroupEntry parentEntry = new GroupEntry("test_group_key");
- final NotificationEntry lowPrioritySummary = createNotifEntry(false);
- setSummary(parentEntry, lowPrioritySummary);
- assertFalse(parentEntry.isHighPriority());
-
- // WHEN we add a high priority child and invalidate derived members
- addChild(parentEntry, createNotifEntry(true));
- parentEntry.invalidateDerivedMembers();
-
- // THEN the GroupEntry's priority is updated to high even though the summary is still low
- // priority
- assertTrue(parentEntry.isHighPriority());
- assertFalse(lowPrioritySummary.isHighPriority());
- }
-
- @Test
- public void testIsHighPriority_clearChildren() {
- // GIVEN a GroupEntry with a lowPrioritySummary and high priority children
- final GroupEntry parentEntry = new GroupEntry("test_group_key");
- setSummary(parentEntry, createNotifEntry(false));
- addChild(parentEntry, createNotifEntry(true));
- addChild(parentEntry, createNotifEntry(true));
- addChild(parentEntry, createNotifEntry(true));
- assertTrue(parentEntry.isHighPriority());
-
- // WHEN we clear the children and invalidate derived members
- parentEntry.clearChildren();
- parentEntry.invalidateDerivedMembers();
-
- // THEN the parentEntry isn't high priority anymore
- assertFalse(parentEntry.isHighPriority());
- }
-
- @Test
- public void testIsHighPriority_summaryUpdated() {
- // GIVEN a GroupEntry with a lowPrioritySummary and no children
- final GroupEntry parentEntry = new GroupEntry("test_group_key");
- final NotificationEntry lowPrioritySummary = createNotifEntry(false);
- setSummary(parentEntry, lowPrioritySummary);
- assertFalse(parentEntry.isHighPriority());
-
- // WHEN the summary changes to high priority and invalidates its derived members
- lowPrioritySummary.setRanking(
- new RankingBuilder()
- .setKey(lowPrioritySummary.getKey())
- .setImportance(IMPORTANCE_HIGH)
- .build());
- lowPrioritySummary.invalidateDerivedMembers();
- assertTrue(lowPrioritySummary.isHighPriority());
-
- // THEN the GroupEntry's priority is updated to high
- assertTrue(parentEntry.isHighPriority());
- }
-
- @Test
- public void testIsHighPriority_checkChildrenToCalculatePriority() {
- // GIVEN:
- // GroupEntry = parentEntry, summary = lowPrioritySummary
- // NotificationEntry = lowPriorityChild
- // NotificationEntry = highPriorityChild
- final GroupEntry parentEntry = new GroupEntry("test_group_key");
- setSummary(parentEntry, createNotifEntry(false));
- addChild(parentEntry, createNotifEntry(false));
- addChild(parentEntry, createNotifEntry(true));
-
- // THEN the GroupEntry parentEntry is high priority since it has a high priority child
- assertTrue(parentEntry.isHighPriority());
- }
-
- @Test
- public void testIsHighPriority_childEntryRankingUpdated() {
- // GIVEN:
- // GroupEntry = parentEntry, summary = lowPrioritySummary
- // NotificationEntry = lowPriorityChild
- final GroupEntry parentEntry = new GroupEntry("test_group_key");
- final NotificationEntry lowPriorityChild = createNotifEntry(false);
- setSummary(parentEntry, createNotifEntry(false));
- addChild(parentEntry, lowPriorityChild);
-
- // WHEN the child entry ranking changes to high priority and invalidates its derived members
- lowPriorityChild.setRanking(
- new RankingBuilder()
- .setKey(lowPriorityChild.getKey())
- .setImportance(IMPORTANCE_HIGH)
- .build());
- lowPriorityChild.invalidateDerivedMembers();
-
- // THEN the parent entry's high priority value is updated - but not the parent's summary
- assertTrue(parentEntry.isHighPriority());
- assertFalse(parentEntry.getSummary().isHighPriority());
- }
-
- private NotificationEntry createNotifEntry(boolean highPriority) {
- return new NotificationEntryBuilder()
- .setImportance(highPriority ? IMPORTANCE_HIGH : IMPORTANCE_MIN)
- .build();
- }
-
- private void setSummary(GroupEntry parent, NotificationEntry summary) {
- parent.setSummary(summary);
- summary.setParent(parent);
- }
-
- private void addChild(GroupEntry parent, NotificationEntry child) {
- parent.addChild(child);
- child.setParent(parent);
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
new file mode 100644
index 0000000..7c3665b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/HighPriorityProviderTest.java
@@ -0,0 +1,231 @@
+/*
+ * 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.systemui.statusbar.notification.collection;
+
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.IMPORTANCE_MIN;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.RankingBuilder;
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
+import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class HighPriorityProviderTest extends SysuiTestCase {
+ @Mock private PeopleNotificationIdentifier mPeopleNotificationIdentifier;
+ private HighPriorityProvider mHighPriorityProvider;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mHighPriorityProvider = new HighPriorityProvider(mPeopleNotificationIdentifier);
+ }
+
+ @Test
+ public void highImportance() {
+ // GIVEN notification has high importance
+ final NotificationEntry entry = new NotificationEntryBuilder()
+ .setImportance(IMPORTANCE_HIGH)
+ .build();
+ when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getChannel()))
+ .thenReturn(false);
+
+ // THEN it has high priority
+ assertTrue(mHighPriorityProvider.isHighPriority(entry));
+ }
+
+ @Test
+ public void peopleNotification() {
+ // GIVEN notification is low importance and is a people notification
+ final Notification notification = new Notification.Builder(mContext, "test")
+ .build();
+ final NotificationEntry entry = new NotificationEntryBuilder()
+ .setNotification(notification)
+ .setImportance(IMPORTANCE_LOW)
+ .build();
+ when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getChannel()))
+ .thenReturn(true);
+
+ // THEN it has high priority
+ assertTrue(mHighPriorityProvider.isHighPriority(entry));
+ }
+
+ @Test
+ public void messagingStyle() {
+ // GIVEN notification is low importance but has messaging style
+ final Notification notification = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.MessagingStyle(""))
+ .build();
+ final NotificationEntry entry = new NotificationEntryBuilder()
+ .setNotification(notification)
+ .build();
+ when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getChannel()))
+ .thenReturn(false);
+
+ // THEN it has high priority
+ assertTrue(mHighPriorityProvider.isHighPriority(entry));
+ }
+
+ @Test
+ public void lowImportanceForeground() {
+ // GIVEN notification is low importance and is associated with a foreground service
+ final Notification notification = mock(Notification.class);
+ when(notification.isForegroundService()).thenReturn(true);
+
+ final NotificationEntry entry = new NotificationEntryBuilder()
+ .setNotification(notification)
+ .setImportance(IMPORTANCE_LOW)
+ .build();
+ when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getChannel()))
+ .thenReturn(false);
+
+ // THEN it has high priority
+ assertTrue(mHighPriorityProvider.isHighPriority(entry));
+ }
+
+ @Test
+ public void minImportanceForeground() {
+ // GIVEN notification is low importance and is associated with a foreground service
+ final Notification notification = mock(Notification.class);
+ when(notification.isForegroundService()).thenReturn(true);
+
+ final NotificationEntry entry = new NotificationEntryBuilder()
+ .setNotification(notification)
+ .setImportance(IMPORTANCE_MIN)
+ .build();
+ when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getChannel()))
+ .thenReturn(false);
+
+ // THEN it does NOT have high priority
+ assertFalse(mHighPriorityProvider.isHighPriority(entry));
+ }
+
+ @Test
+ public void userChangeTrumpsHighPriorityCharacteristics() {
+ // GIVEN notification has high priority characteristics but the user changed the importance
+ // to less than IMPORTANCE_DEFAULT (ie: IMPORTANCE_LOW or IMPORTANCE_MIN)
+ final Notification notification = new Notification.Builder(mContext, "test")
+ .setStyle(new Notification.MessagingStyle(""))
+ .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
+ .build();
+ final NotificationChannel channel = new NotificationChannel("a", "a",
+ IMPORTANCE_LOW);
+ channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
+
+ final NotificationEntry entry = new NotificationEntryBuilder()
+ .setNotification(notification)
+ .setChannel(channel)
+ .build();
+ when(mPeopleNotificationIdentifier.isPeopleNotification(entry.getSbn(), entry.getChannel()))
+ .thenReturn(true);
+
+ // THEN it does NOT have high priority
+ assertFalse(mHighPriorityProvider.isHighPriority(entry));
+ }
+
+ @Test
+ public void testIsHighPriority_summaryUpdated() {
+ // GIVEN a GroupEntry with a lowPrioritySummary and no children
+ final GroupEntry parentEntry = new GroupEntry("test_group_key");
+ final NotificationEntry lowPrioritySummary = createNotifEntry(false);
+ setSummary(parentEntry, lowPrioritySummary);
+ assertFalse(mHighPriorityProvider.isHighPriority(parentEntry));
+
+ // WHEN the summary changes to high priority
+ lowPrioritySummary.setRanking(
+ new RankingBuilder()
+ .setKey(lowPrioritySummary.getKey())
+ .setImportance(IMPORTANCE_HIGH)
+ .build());
+ assertTrue(mHighPriorityProvider.isHighPriority(lowPrioritySummary));
+
+ // THEN the GroupEntry's priority is updated to high
+ assertTrue(mHighPriorityProvider.isHighPriority(parentEntry));
+ }
+
+ @Test
+ public void testIsHighPriority_checkChildrenToCalculatePriority() {
+ // GIVEN:
+ // GroupEntry = parentEntry, summary = lowPrioritySummary
+ // NotificationEntry = lowPriorityChild
+ // NotificationEntry = highPriorityChild
+ final GroupEntry parentEntry = new GroupEntry("test_group_key");
+ setSummary(parentEntry, createNotifEntry(false));
+ addChild(parentEntry, createNotifEntry(false));
+ addChild(parentEntry, createNotifEntry(true));
+
+ // THEN the GroupEntry parentEntry is high priority since it has a high priority child
+ assertTrue(mHighPriorityProvider.isHighPriority(parentEntry));
+ }
+
+ @Test
+ public void testIsHighPriority_childEntryRankingUpdated() {
+ // GIVEN:
+ // GroupEntry = parentEntry, summary = lowPrioritySummary
+ // NotificationEntry = lowPriorityChild
+ final GroupEntry parentEntry = new GroupEntry("test_group_key");
+ final NotificationEntry lowPriorityChild = createNotifEntry(false);
+ setSummary(parentEntry, createNotifEntry(false));
+ addChild(parentEntry, lowPriorityChild);
+
+ // WHEN the child entry ranking changes to high priority
+ lowPriorityChild.setRanking(
+ new RankingBuilder()
+ .setKey(lowPriorityChild.getKey())
+ .setImportance(IMPORTANCE_HIGH)
+ .build());
+
+ // THEN the parent entry's high priority value is updated - but not the parent's summary
+ assertTrue(mHighPriorityProvider.isHighPriority(parentEntry));
+ assertFalse(mHighPriorityProvider.isHighPriority(parentEntry.getSummary()));
+ }
+
+ private NotificationEntry createNotifEntry(boolean highPriority) {
+ return new NotificationEntryBuilder()
+ .setImportance(highPriority ? IMPORTANCE_HIGH : IMPORTANCE_MIN)
+ .build();
+ }
+
+ private void setSummary(GroupEntry parent, NotificationEntry summary) {
+ parent.setSummary(summary);
+ summary.setParent(parent);
+ }
+
+ private void addChild(GroupEntry parent, NotificationEntry child) {
+ parent.addChild(child);
+ child.setParent(parent);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index 0837a42..fe8d769 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -24,10 +24,12 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -47,13 +49,18 @@
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.DumpController;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.notification.collection.NoManSimulator.NotifEvent;
import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason;
-import com.android.systemui.statusbar.notification.collection.notifcollection.CoalescedEvent;
-import com.android.systemui.statusbar.notification.collection.notifcollection.GroupCoalescer;
-import com.android.systemui.statusbar.notification.collection.notifcollection.GroupCoalescer.BatchableNotificationHandler;
+import com.android.systemui.statusbar.notification.collection.coalescer.CoalescedEvent;
+import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer;
+import com.android.systemui.statusbar.notification.collection.coalescer.GroupCoalescer.BatchableNotificationHandler;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
+import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
import com.android.systemui.util.Assert;
import org.junit.Before;
@@ -98,7 +105,7 @@
MockitoAnnotations.initMocks(this);
Assert.sMainLooper = TestableLooper.get(this).getLooper();
- mCollection = new NotifCollection(mStatusBarService);
+ mCollection = new NotifCollection(mStatusBarService, mock(DumpController.class));
mCollection.attach(mGroupCoalescer);
mCollection.addCollectionListener(mCollectionListener);
mCollection.setBuildListener(mBuildListener);
@@ -261,9 +268,13 @@
.setRank(5)
.setExplanation("baz buzz")
.build();
+
+ // WHEN entry3's ranking update includes an update to its overrideGroupKey
+ final String newOverrideGroupKey = "newOverrideGroupKey";
Ranking newRanking3 = new RankingBuilder(notif3.ranking)
.setRank(6)
.setExplanation("Penguin pizza")
+ .setOverrideGroupKey(newOverrideGroupKey)
.build();
mNoMan.setRanking(notif1.sbn.getKey(), newRanking1);
@@ -275,6 +286,10 @@
assertEquals(newRanking1, entry1.getRanking());
assertEquals(newRanking2, entry2.getRanking());
assertEquals(newRanking3, entry3.getRanking());
+
+ // THEN the entry3's overrideGroupKey is updated along with its groupKey
+ assertEquals(newOverrideGroupKey, entry3.getSbn().getOverrideGroupKey());
+ assertNotNull(entry3.getSbn().getGroupKey());
}
@Test
@@ -368,7 +383,7 @@
verify(mExtender3).shouldExtendLifetime(entry2, REASON_UNKNOWN);
// THEN the entry is not removed
- assertTrue(mCollection.getNotifs().contains(entry2));
+ assertTrue(mCollection.getActiveNotifs().contains(entry2));
// THEN the entry properly records all extenders that returned true
assertEquals(Arrays.asList(mExtender1, mExtender2), entry2.mLifetimeExtenders);
@@ -389,7 +404,7 @@
// GIVEN a notification gets lifetime-extended by one of them
mNoMan.retractNotif(notif2.sbn, REASON_APP_CANCEL);
- assertTrue(mCollection.getNotifs().contains(entry2));
+ assertTrue(mCollection.getActiveNotifs().contains(entry2));
clearInvocations(mExtender1, mExtender2, mExtender3);
// WHEN the last active extender expires (but new ones become active)
@@ -404,7 +419,7 @@
verify(mExtender3).shouldExtendLifetime(entry2, REASON_UNKNOWN);
// THEN the entry is not removed
- assertTrue(mCollection.getNotifs().contains(entry2));
+ assertTrue(mCollection.getActiveNotifs().contains(entry2));
// THEN the entry properly records all extenders that returned true
assertEquals(Arrays.asList(mExtender1, mExtender3), entry2.mLifetimeExtenders);
@@ -426,7 +441,7 @@
// GIVEN a notification gets lifetime-extended by a couple of them
mNoMan.retractNotif(notif2.sbn, REASON_APP_CANCEL);
- assertTrue(mCollection.getNotifs().contains(entry2));
+ assertTrue(mCollection.getActiveNotifs().contains(entry2));
clearInvocations(mExtender1, mExtender2, mExtender3);
// WHEN one (but not all) of the extenders expires
@@ -434,7 +449,7 @@
mExtender2.callback.onEndLifetimeExtension(mExtender2, entry2);
// THEN the entry is not removed
- assertTrue(mCollection.getNotifs().contains(entry2));
+ assertTrue(mCollection.getActiveNotifs().contains(entry2));
// THEN we don't re-query the extenders
verify(mExtender1, never()).shouldExtendLifetime(eq(entry2), anyInt());
@@ -461,7 +476,7 @@
// GIVEN a notification gets lifetime-extended by a couple of them
mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
- assertTrue(mCollection.getNotifs().contains(entry2));
+ assertTrue(mCollection.getActiveNotifs().contains(entry2));
clearInvocations(mExtender1, mExtender2, mExtender3);
// WHEN all of the active extenders expire
@@ -471,7 +486,7 @@
mExtender1.callback.onEndLifetimeExtension(mExtender1, entry2);
// THEN the entry removed
- assertFalse(mCollection.getNotifs().contains(entry2));
+ assertFalse(mCollection.getActiveNotifs().contains(entry2));
verify(mCollectionListener).onEntryRemoved(entry2, REASON_UNKNOWN, false);
}
@@ -491,7 +506,7 @@
// GIVEN a notification gets lifetime-extended by a couple of them
mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
- assertTrue(mCollection.getNotifs().contains(entry2));
+ assertTrue(mCollection.getActiveNotifs().contains(entry2));
clearInvocations(mExtender1, mExtender2, mExtender3);
// WHEN the notification is reposted
@@ -502,7 +517,7 @@
verify(mExtender2).cancelLifetimeExtension(entry2);
// THEN the notification is still present
- assertTrue(mCollection.getNotifs().contains(entry2));
+ assertTrue(mCollection.getActiveNotifs().contains(entry2));
}
@Test(expected = IllegalStateException.class)
@@ -521,7 +536,7 @@
// GIVEN a notification gets lifetime-extended by a couple of them
mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
- assertTrue(mCollection.getNotifs().contains(entry2));
+ assertTrue(mCollection.getActiveNotifs().contains(entry2));
clearInvocations(mExtender1, mExtender2, mExtender3);
// WHEN a lifetime extender makes a reentrant call during cancelLifetimeExtension()
@@ -550,7 +565,7 @@
// GIVEN a notification gets lifetime-extended by a couple of them
mNoMan.retractNotif(notif2.sbn, REASON_UNKNOWN);
- assertTrue(mCollection.getNotifs().contains(entry2));
+ assertTrue(mCollection.getActiveNotifs().contains(entry2));
clearInvocations(mExtender1, mExtender2, mExtender3);
// WHEN the notification is reposted
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
index 39ae68a..5b0b668 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
@@ -21,8 +21,6 @@
import static android.app.Notification.CATEGORY_EVENT;
import static android.app.Notification.CATEGORY_MESSAGE;
import static android.app.Notification.CATEGORY_REMINDER;
-import static android.app.NotificationManager.IMPORTANCE_HIGH;
-import static android.app.NotificationManager.IMPORTANCE_MIN;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
@@ -92,44 +90,6 @@
}
@Test
- public void testIsHighPriority_notificationUpdates() {
- // GIVEN a notification with high importance
- final NotificationEntry entryHigh = new NotificationEntryBuilder()
- .setImportance(IMPORTANCE_HIGH)
- .build();
-
- // WHEN we get the value for the high priority entry, we're caching the high priority value
- assertTrue(entryHigh.isHighPriority());
-
- // WHEN we change the ranking and derived members (high priority) are invalidated
- entryHigh.setRanking(
- new RankingBuilder()
- .setKey(entryHigh.getKey())
- .setImportance(IMPORTANCE_MIN)
- .build());
- entryHigh.invalidateDerivedMembers();
-
- // THEN the priority is recalculated and is now low
- assertFalse(entryHigh.isHighPriority());
-
- // WHEN the sbn is updated to have messaging style (high priority characteristic)
- // AND the entry invalidates its derived members
- final Notification notification =
- new Notification.Builder(mContext, "test")
- .setStyle(new Notification.MessagingStyle(""))
- .build();
- final StatusBarNotification sbn = entryHigh.getSbn();
- entryHigh.setSbn(new StatusBarNotification(
- sbn.getPackageName(), sbn.getPackageName(),
- sbn.getId(), sbn.getTag(), sbn.getUid(), sbn.getInitialPid(),
- notification, sbn.getUser(), sbn.getOverrideGroupKey(), 0));
- entryHigh.invalidateDerivedMembers();
-
- // THEN the priority is recalculated and is now high
- assertTrue(entryHigh.isHighPriority());
- }
-
- @Test
public void testIsExemptFromDndVisualSuppression_foreground() {
mEntry.getSbn().getNotification().flags = Notification.FLAG_FOREGROUND_SERVICE;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
index 10450fa..7ab4846 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationRankingManagerTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.collection
import android.app.Notification
+import android.app.NotificationChannel
import android.app.NotificationManager.IMPORTANCE_DEFAULT
import android.app.NotificationManager.IMPORTANCE_HIGH
import android.app.NotificationManager.IMPORTANCE_LOW
@@ -28,6 +29,7 @@
import com.android.systemui.statusbar.NotificationMediaManager
import com.android.systemui.statusbar.notification.NotificationFilter
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider
import com.android.systemui.statusbar.notification.logging.NotifLog
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING
@@ -62,7 +64,8 @@
mock(NotificationFilter::class.java),
mock(NotifLog::class.java),
mock(NotificationSectionsFeatureManager::class.java),
- personNotificationIdentifier
+ personNotificationIdentifier,
+ HighPriorityProvider(personNotificationIdentifier)
)
}
@@ -79,6 +82,7 @@
.setNotification(
Notification.Builder(mContext, "test")
.build())
+ .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
.setUser(mContext.getUser())
.setOverrideGroupKey("")
.build()
@@ -92,6 +96,7 @@
.setNotification(
Notification.Builder(mContext, "test")
.build())
+ .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
.setUser(mContext.getUser())
.setOverrideGroupKey("")
.build()
@@ -114,6 +119,7 @@
.setOpPkg("pkg")
.setTag("tag")
.setNotification(aN)
+ .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
.setUser(mContext.getUser())
.setOverrideGroupKey("")
.build()
@@ -128,6 +134,7 @@
.setOpPkg("pkg2")
.setTag("tag")
.setNotification(bN)
+ .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
.setUser(mContext.getUser())
.setOverrideGroupKey("")
.build()
@@ -147,6 +154,7 @@
.setTag("tag")
.setNotification(notif)
.setUser(mContext.user)
+ .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
.setOverrideGroupKey("")
.build()
@@ -166,6 +174,7 @@
.setTag("tag")
.setNotification(notif)
.setUser(mContext.user)
+ .setChannel(NotificationChannel("test", "", IMPORTANCE_DEFAULT))
.setOverrideGroupKey("")
.build()
@@ -182,7 +191,8 @@
filter: NotificationFilter,
notifLog: NotifLog,
sectionsFeatureManager: NotificationSectionsFeatureManager,
- peopleNotificationIdentifier: PeopleNotificationIdentifier
+ peopleNotificationIdentifier: PeopleNotificationIdentifier,
+ highPriorityProvider: HighPriorityProvider
) : NotificationRankingManager(
mediaManager,
groupManager,
@@ -190,7 +200,8 @@
filter,
notifLog,
sectionsFeatureManager,
- peopleNotificationIdentifier
+ peopleNotificationIdentifier,
+ highPriorityProvider
) {
fun applyTestRankingMap(r: RankingMap) {
rankingMap = r
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
similarity index 84%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImplTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index 3e4068b..e915be3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifListBuilderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -16,9 +16,10 @@
package com.android.systemui.statusbar.notification.collection;
-import static com.android.systemui.statusbar.notification.collection.ListDumper.dumpList;
+import static com.android.systemui.statusbar.notification.collection.ListDumper.dumpTree;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyList;
@@ -27,6 +28,7 @@
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
@@ -37,15 +39,17 @@
import androidx.test.filters.SmallTest;
+import com.android.systemui.DumpController;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl.OnRenderListListener;
+import com.android.systemui.statusbar.notification.collection.ShadeListBuilder.OnRenderListListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeSortListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeTransformGroupsListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.SectionsProvider;
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
import com.android.systemui.statusbar.notification.logging.NotifLog;
import com.android.systemui.util.Assert;
import com.android.systemui.util.time.FakeSystemClock;
@@ -72,9 +76,9 @@
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
-public class NotifListBuilderImplTest extends SysuiTestCase {
+public class ShadeListBuilderTest extends SysuiTestCase {
- private NotifListBuilderImpl mListBuilder;
+ private ShadeListBuilder mListBuilder;
private FakeSystemClock mSystemClock = new FakeSystemClock();
@Mock private NotifLog mNotifLog;
@@ -99,7 +103,7 @@
MockitoAnnotations.initMocks(this);
Assert.sMainLooper = TestableLooper.get(this).getLooper();
- mListBuilder = new NotifListBuilderImpl(mSystemClock, mNotifLog);
+ mListBuilder = new ShadeListBuilder(mSystemClock, mNotifLog, mock(DumpController.class));
mListBuilder.setOnRenderListListener(mOnRenderListListener);
mListBuilder.attach(mNotifCollection);
@@ -447,6 +451,29 @@
}
@Test
+ public void testPreRenderNotifsFilteredBreakupGroups() {
+ final String filterTag = "FILTER_ME";
+ // GIVEN a NotifFilter that filters out notifications with a tag
+ NotifFilter filter1 = spy(new NotifFilterWithTag(filterTag));
+ mListBuilder.addPreRenderFilter(filter1);
+
+ // WHEN the pipeline is kicked off on a list of notifs
+ addGroupChildWithTag(0, PACKAGE_2, GROUP_1, filterTag);
+ addGroupChild(1, PACKAGE_2, GROUP_1);
+ addGroupSummary(2, PACKAGE_2, GROUP_1);
+ dispatchBuild();
+
+ // THEN the final list doesn't contain any filtered-out notifs
+ // and groups that are too small are broken up
+ verifyBuiltList(
+ notif(1)
+ );
+
+ // THEN each filtered notif records the filter that did it
+ assertEquals(filter1, mEntrySet.get(0).mExcludingFilter);
+ }
+
+ @Test
public void testNotifFiltersCanBePreempted() {
// GIVEN two notif filters
NotifFilter filter1 = spy(new PackageFilter(PACKAGE_2));
@@ -549,12 +576,17 @@
}
@Test
- public void testNotifsAreSectioned() {
- // GIVEN a filter that removes all PACKAGE_4 notifs and a SectionsProvider that divides
+ public void testNotifSections() {
+ // GIVEN a filter that removes all PACKAGE_4 notifs and sections that divide
// notifs based on package name
mListBuilder.addPreGroupFilter(new PackageFilter(PACKAGE_4));
- final SectionsProvider sectionsProvider = spy(new PackageSectioner());
- mListBuilder.setSectionsProvider(sectionsProvider);
+ final NotifSection pkg1Section = spy(new PackageSection(PACKAGE_1));
+ final NotifSection pkg2Section = spy(new PackageSection(PACKAGE_2));
+ // NOTE: no package 3 section explicitly added, so notifs with package 3 will get set by
+ // ShadeListBuilder's sDefaultSection which will demote it to the last section
+ final NotifSection pkg4Section = spy(new PackageSection(PACKAGE_4));
+ final NotifSection pkg5Section = spy(new PackageSection(PACKAGE_5));
+ mListBuilder.setSections(Arrays.asList(pkg1Section, pkg2Section, pkg4Section, pkg5Section));
// WHEN we build a list with different packages
addNotif(0, PACKAGE_4);
@@ -581,19 +613,93 @@
child(6)
),
notif(8),
- notif(3),
- notif(9)
+ notif(9),
+ notif(3)
);
- // THEN the sections provider is called on all top level elements (but no children and no
- // entries that were filtered out)
- verify(sectionsProvider).getSection(mEntrySet.get(1));
- verify(sectionsProvider).getSection(mEntrySet.get(2));
- verify(sectionsProvider).getSection(mEntrySet.get(3));
- verify(sectionsProvider).getSection(mEntrySet.get(7));
- verify(sectionsProvider).getSection(mEntrySet.get(8));
- verify(sectionsProvider).getSection(mEntrySet.get(9));
- verify(sectionsProvider).getSection(mBuiltList.get(3));
+ // THEN the first section (pkg1Section) is called on all top level elements (but
+ // no children and no entries that were filtered out)
+ verify(pkg1Section).isInSection(mEntrySet.get(1));
+ verify(pkg1Section).isInSection(mEntrySet.get(2));
+ verify(pkg1Section).isInSection(mEntrySet.get(3));
+ verify(pkg1Section).isInSection(mEntrySet.get(7));
+ verify(pkg1Section).isInSection(mEntrySet.get(8));
+ verify(pkg1Section).isInSection(mEntrySet.get(9));
+ verify(pkg1Section).isInSection(mBuiltList.get(3));
+
+ verify(pkg1Section, never()).isInSection(mEntrySet.get(0));
+ verify(pkg1Section, never()).isInSection(mEntrySet.get(4));
+ verify(pkg1Section, never()).isInSection(mEntrySet.get(5));
+ verify(pkg1Section, never()).isInSection(mEntrySet.get(6));
+ verify(pkg1Section, never()).isInSection(mEntrySet.get(10));
+
+ // THEN the last section (pkg5Section) is not called on any of the entries that were
+ // filtered or already in a section
+ verify(pkg5Section, never()).isInSection(mEntrySet.get(0));
+ verify(pkg5Section, never()).isInSection(mEntrySet.get(1));
+ verify(pkg5Section, never()).isInSection(mEntrySet.get(2));
+ verify(pkg5Section, never()).isInSection(mEntrySet.get(4));
+ verify(pkg5Section, never()).isInSection(mEntrySet.get(5));
+ verify(pkg5Section, never()).isInSection(mEntrySet.get(6));
+ verify(pkg5Section, never()).isInSection(mEntrySet.get(7));
+ verify(pkg5Section, never()).isInSection(mEntrySet.get(8));
+ verify(pkg5Section, never()).isInSection(mEntrySet.get(10));
+
+ verify(pkg5Section).isInSection(mEntrySet.get(3));
+ verify(pkg5Section).isInSection(mEntrySet.get(9));
+
+ // THEN the correct section is assigned for entries in pkg1Section
+ assertEquals(pkg1Section, mEntrySet.get(2).mNotifSection);
+ assertEquals(0, mEntrySet.get(2).getSection());
+ assertEquals(pkg1Section, mEntrySet.get(7).mNotifSection);
+ assertEquals(0, mEntrySet.get(7).getSection());
+
+ // THEN the correct section is assigned for entries in pkg2Section
+ assertEquals(pkg2Section, mEntrySet.get(1).mNotifSection);
+ assertEquals(1, mEntrySet.get(1).getSection());
+ assertEquals(pkg2Section, mEntrySet.get(8).mNotifSection);
+ assertEquals(1, mEntrySet.get(8).getSection());
+ assertEquals(pkg2Section, mBuiltList.get(3).mNotifSection);
+ assertEquals(1, mBuiltList.get(3).getSection());
+
+ // THEN no section was assigned to entries in pkg4Section (since they were filtered)
+ assertEquals(null, mEntrySet.get(0).mNotifSection);
+ assertEquals(-1, mEntrySet.get(0).getSection());
+ assertEquals(null, mEntrySet.get(10).mNotifSection);
+ assertEquals(-1, mEntrySet.get(10).getSection());
+
+
+ // THEN the correct section is assigned for entries in pkg5Section
+ assertEquals(pkg5Section, mEntrySet.get(9).mNotifSection);
+ assertEquals(3, mEntrySet.get(9).getSection());
+
+ // THEN the children entries are assigned the same section as its parent
+ assertEquals(mBuiltList.get(3).mNotifSection, child(5).entry.mNotifSection);
+ assertEquals(mBuiltList.get(3).getSection(), child(5).entry.getSection());
+ assertEquals(mBuiltList.get(3).mNotifSection, child(6).entry.mNotifSection);
+ assertEquals(mBuiltList.get(3).getSection(), child(6).entry.getSection());
+ }
+
+ @Test
+ public void testNotifUsesDefaultSection() {
+ // GIVEN a Section for Package2
+ final NotifSection pkg2Section = spy(new PackageSection(PACKAGE_2));
+ mListBuilder.setSections(Arrays.asList(pkg2Section));
+
+ // WHEN we build a list with pkg1 and pkg2 packages
+ addNotif(0, PACKAGE_1);
+ addNotif(1, PACKAGE_2);
+ dispatchBuild();
+
+ // THEN the list is sorted according to section
+ verifyBuiltList(
+ notif(1),
+ notif(0)
+ );
+
+ // THEN the entry that didn't have an explicit section gets assigned the DefaultSection
+ assertEquals(1, notif(0).entry.getSection());
+ assertNotNull(notif(0).entry.mNotifSection);
}
@Test
@@ -628,7 +734,7 @@
// GIVEN a bunch of registered listeners and pluggables
NotifFilter preGroupFilter = spy(new PackageFilter(PACKAGE_1));
NotifPromoter promoter = spy(new IdPromoter(3));
- PackageSectioner sectioner = spy(new PackageSectioner());
+ NotifSection section = spy(new PackageSection(PACKAGE_1));
NotifComparator comparator = spy(new HypeComparator(PACKAGE_4));
NotifFilter preRenderFilter = spy(new PackageFilter(PACKAGE_5));
mListBuilder.addPreGroupFilter(preGroupFilter);
@@ -636,7 +742,7 @@
mListBuilder.addPromoter(promoter);
mListBuilder.addOnBeforeSortListener(mOnBeforeSortListener);
mListBuilder.setComparators(Collections.singletonList(comparator));
- mListBuilder.setSectionsProvider(sectioner);
+ mListBuilder.setSections(Arrays.asList(section));
mListBuilder.addOnBeforeRenderListListener(mOnBeforeRenderListListener);
mListBuilder.addPreRenderFilter(preRenderFilter);
@@ -656,7 +762,7 @@
mOnBeforeTransformGroupsListener,
promoter,
mOnBeforeSortListener,
- sectioner,
+ section,
comparator,
preRenderFilter,
mOnBeforeRenderListListener,
@@ -669,7 +775,7 @@
inOrder.verify(promoter, atLeastOnce())
.shouldPromoteToTopLevel(any(NotificationEntry.class));
inOrder.verify(mOnBeforeSortListener).onBeforeSort(anyList());
- inOrder.verify(sectioner, atLeastOnce()).getSection(any(ListEntry.class));
+ inOrder.verify(section, atLeastOnce()).isInSection(any(ListEntry.class));
inOrder.verify(comparator, atLeastOnce())
.compare(any(ListEntry.class), any(ListEntry.class));
inOrder.verify(preRenderFilter, atLeastOnce())
@@ -683,12 +789,12 @@
// GIVEN a variety of pluggables
NotifFilter packageFilter = new PackageFilter(PACKAGE_1);
NotifPromoter idPromoter = new IdPromoter(4);
- SectionsProvider sectionsProvider = new PackageSectioner();
+ NotifSection section = new PackageSection(PACKAGE_1);
NotifComparator hypeComparator = new HypeComparator(PACKAGE_2);
mListBuilder.addPreGroupFilter(packageFilter);
mListBuilder.addPromoter(idPromoter);
- mListBuilder.setSectionsProvider(sectionsProvider);
+ mListBuilder.setSections(Arrays.asList(section));
mListBuilder.setComparators(Collections.singletonList(hypeComparator));
// GIVEN a set of random notifs
@@ -708,7 +814,7 @@
verify(mOnRenderListListener).onRenderList(anyList());
clearInvocations(mOnRenderListListener);
- sectionsProvider.invalidateList();
+ section.invalidateList();
verify(mOnRenderListListener).onRenderList(anyList());
clearInvocations(mOnRenderListListener);
@@ -981,9 +1087,10 @@
return builder;
}
- /** Same behavior as {@link #addNotif(int, String)}. */
- private NotificationEntryBuilder addGroupChild(int index, String packageId, String groupId) {
+ private NotificationEntryBuilder addGroupChildWithTag(int index, String packageId,
+ String groupId, String tag) {
final NotificationEntryBuilder builder = new NotificationEntryBuilder()
+ .setTag(tag)
.setPkg(packageId)
.setId(nextId(packageId))
.setRank(nextRank());
@@ -998,6 +1105,11 @@
return builder;
}
+ /** Same behavior as {@link #addNotif(int, String)}. */
+ private NotificationEntryBuilder addGroupChild(int index, String packageId, String groupId) {
+ return addGroupChildWithTag(index, packageId, groupId, null);
+ }
+
private int nextId(String packageName) {
Integer nextId = mNextIdMap.get(packageName);
if (nextId == null) {
@@ -1080,7 +1192,8 @@
}
} catch (AssertionError err) {
throw new AssertionError(
- "List under test failed verification:\n" + dumpList(mBuiltList), err);
+ "List under test failed verification:\n" + dumpTree(mBuiltList,
+ true, ""), err);
}
}
@@ -1165,6 +1278,21 @@
}
}
+ /** Filters out notifications with a particular tag */
+ private static class NotifFilterWithTag extends NotifFilter {
+ private final String mTag;
+
+ NotifFilterWithTag(String tag) {
+ super("NotifFilterWithTag_" + tag);
+ mTag = tag;
+ }
+
+ @Override
+ public boolean shouldFilterOut(NotificationEntry entry, long now) {
+ return Objects.equals(entry.getSbn().getTag(), mTag);
+ }
+ }
+
/** Promotes notifs with particular IDs */
private static class IdPromoter extends NotifPromoter {
private final List<Integer> mIds;
@@ -1201,25 +1329,18 @@
}
}
- /** Sorts notifs into sections based on their package name */
- private static class PackageSectioner extends SectionsProvider {
+ /** Represents a section for the passed pkg */
+ private static class PackageSection extends NotifSection {
+ private final String mPackage;
- PackageSectioner() {
- super("PackageSectioner");
+ PackageSection(String pkg) {
+ super("PackageSection_" + pkg);
+ mPackage = pkg;
}
@Override
- public int getSection(ListEntry entry) {
- switch (entry.getRepresentativeEntry().getSbn().getPackageName()) {
- case PACKAGE_1:
- return 1;
- case PACKAGE_2:
- return 2;
- case PACKAGE_3:
- return 3;
- default:
- return 4;
- }
+ public boolean isInSection(ListEntry entry) {
+ return entry.getRepresentativeEntry().getSbn().getPackageName().equals(mPackage);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java
similarity index 82%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescerTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java
index 7ff3240..86c1eb97 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/GroupCoalescerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification.collection.notifcollection;
+package com.android.systemui.statusbar.notification.collection.coalescer;
import static com.android.internal.util.Preconditions.checkNotNull;
@@ -80,7 +80,8 @@
mExecutor,
mClock,
mLog,
- LINGER_DURATION);
+ MIN_LINGER_DURATION,
+ MAX_LINGER_DURATION);
mCoalescer.setNotificationHandler(mListener);
mCoalescer.attach(mListenerService);
@@ -96,7 +97,7 @@
new NotificationEntryBuilder()
.setId(0)
.setPkg(TEST_PACKAGE_A));
- mClock.advanceTime(LINGER_DURATION);
+ mClock.advanceTime(MIN_LINGER_DURATION);
// THEN the event is passed through to the handler
verify(mListener).onNotificationPosted(notif1.sbn, notif1.rankingMap);
@@ -144,12 +145,16 @@
.setId(1)
.setGroup(mContext, GROUP_1));
+ mClock.advanceTime(2);
+
NotifEvent notif2 = mNoMan.postNotif(new NotificationEntryBuilder()
.setPkg(TEST_PACKAGE_A)
.setId(2)
.setGroup(mContext, GROUP_1)
.setGroupSummary(mContext, true));
+ mClock.advanceTime(3);
+
NotifEvent notif3 = mNoMan.postNotif(new NotificationEntryBuilder()
.setPkg(TEST_PACKAGE_A)
.setId(3)
@@ -161,7 +166,7 @@
verify(mListener, never()).onNotificationBatchPosted(anyList());
// WHEN enough time passes
- mClock.advanceTime(LINGER_DURATION);
+ mClock.advanceTime(MIN_LINGER_DURATION);
// THEN the coalesced notifs are applied. The summary is sorted to the front.
verify(mListener).onNotificationBatchPosted(Arrays.asList(
@@ -212,7 +217,7 @@
// WHEN the time runs out on the remainder of the group
clearInvocations(mListener);
- mClock.advanceTime(LINGER_DURATION);
+ mClock.advanceTime(MIN_LINGER_DURATION);
// THEN no lingering batch is applied
verify(mListener, never()).onNotificationBatchPosted(anyList());
@@ -225,11 +230,13 @@
.setPkg(TEST_PACKAGE_A)
.setId(1)
.setGroup(mContext, GROUP_1));
+ mClock.advanceTime(2);
NotifEvent notif2a = mNoMan.postNotif(new NotificationEntryBuilder()
.setPkg(TEST_PACKAGE_A)
.setId(2)
.setContentTitle(mContext, "Version 1")
.setGroup(mContext, GROUP_1));
+ mClock.advanceTime(4);
// WHEN one of them gets updated
NotifEvent notif2b = mNoMan.postNotif(new NotificationEntryBuilder()
@@ -248,7 +255,7 @@
any(RankingMap.class));
// THEN second, the update is emitted
- mClock.advanceTime(LINGER_DURATION);
+ mClock.advanceTime(MIN_LINGER_DURATION);
verify(mListener).onNotificationBatchPosted(Collections.singletonList(
new CoalescedEvent(notif2b.key, 0, notif2b.sbn, notif2b.ranking, null)
));
@@ -308,14 +315,61 @@
.setId(17));
// THEN they have the new rankings when they are eventually emitted
- mClock.advanceTime(LINGER_DURATION);
+ mClock.advanceTime(MIN_LINGER_DURATION);
verify(mListener).onNotificationBatchPosted(Arrays.asList(
new CoalescedEvent(notif1.key, 0, notif1.sbn, ranking1b, null),
new CoalescedEvent(notif2.key, 1, notif2.sbn, ranking2b, null)
));
}
- private static final long LINGER_DURATION = 4700;
+ @Test
+ public void testMaxLingerDuration() {
+ // GIVEN five coalesced notifications that have collectively taken 20ms to arrive, 2ms
+ // longer than the max linger duration
+ NotifEvent notif1 = mNoMan.postNotif(new NotificationEntryBuilder()
+ .setPkg(TEST_PACKAGE_A)
+ .setId(1)
+ .setGroup(mContext, GROUP_1));
+ mClock.advanceTime(4);
+ NotifEvent notif2 = mNoMan.postNotif(new NotificationEntryBuilder()
+ .setPkg(TEST_PACKAGE_A)
+ .setId(2)
+ .setGroup(mContext, GROUP_1));
+ mClock.advanceTime(4);
+ NotifEvent notif3 = mNoMan.postNotif(new NotificationEntryBuilder()
+ .setPkg(TEST_PACKAGE_A)
+ .setId(3)
+ .setGroup(mContext, GROUP_1));
+ mClock.advanceTime(4);
+ NotifEvent notif4 = mNoMan.postNotif(new NotificationEntryBuilder()
+ .setPkg(TEST_PACKAGE_A)
+ .setId(4)
+ .setGroup(mContext, GROUP_1));
+ mClock.advanceTime(4);
+ NotifEvent notif5 = mNoMan.postNotif(new NotificationEntryBuilder()
+ .setPkg(TEST_PACKAGE_A)
+ .setId(5)
+ .setGroup(mContext, GROUP_1));
+ mClock.advanceTime(4);
+
+ // WHEN a sixth notification arrives
+ NotifEvent notif6 = mNoMan.postNotif(new NotificationEntryBuilder()
+ .setPkg(TEST_PACKAGE_A)
+ .setId(6)
+ .setGroup(mContext, GROUP_1));
+
+ // THEN the first five notifications are emitted in a batch
+ verify(mListener).onNotificationBatchPosted(Arrays.asList(
+ new CoalescedEvent(notif1.key, 0, notif1.sbn, notif1.ranking, null),
+ new CoalescedEvent(notif2.key, 1, notif2.sbn, notif2.ranking, null),
+ new CoalescedEvent(notif3.key, 2, notif3.sbn, notif3.ranking, null),
+ new CoalescedEvent(notif4.key, 3, notif4.sbn, notif4.ranking, null),
+ new CoalescedEvent(notif5.key, 4, notif5.sbn, notif5.ranking, null)
+ ));
+ }
+
+ private static final long MIN_LINGER_DURATION = 5;
+ private static final long MAX_LINGER_DURATION = 18;
private static final String TEST_PACKAGE_A = "com.test.package_a";
private static final String TEST_PACKAGE_B = "com.test.package_b";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
index ea6c70a..701cf95 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinatorTest.java
@@ -36,7 +36,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.RankingBuilder;
-import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
@@ -65,7 +65,7 @@
@Mock private ActivityManagerInternal mActivityMangerInternal;
@Mock private IPackageManager mIPackageManager;
@Mock private DeviceProvisionedController mDeviceProvisionedController;
- @Mock private NotifListBuilderImpl mNotifListBuilder;
+ @Mock private NotifPipeline mNotifPipeline;
private Notification mNotification;
private NotificationEntry mEntry;
private DeviceProvisionedCoordinator mDeviceProvisionedCoordinator;
@@ -84,8 +84,8 @@
.build();
ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
- mDeviceProvisionedCoordinator.attach(null, mNotifListBuilder);
- verify(mNotifListBuilder, times(1)).addPreGroupFilter(filterCaptor.capture());
+ mDeviceProvisionedCoordinator.attach(mNotifPipeline);
+ verify(mNotifPipeline, times(1)).addPreGroupFilter(filterCaptor.capture());
mDeviceProvisionedFilter = filterCaptor.getValue();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java
index 01bca0d..6cc8dd9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ForegroundCoordinatorTest.java
@@ -36,12 +36,11 @@
import com.android.systemui.ForegroundServiceController;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.appops.AppOpsController;
-import com.android.systemui.statusbar.notification.collection.NotifCollection;
-import com.android.systemui.statusbar.notification.collection.NotifLifetimeExtender;
-import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
import org.junit.Before;
import org.junit.Test;
@@ -59,8 +58,7 @@
@Mock private Handler mMainHandler;
@Mock private ForegroundServiceController mForegroundServiceController;
@Mock private AppOpsController mAppOpsController;
- @Mock private NotifListBuilderImpl mNotifListBuilder;
- @Mock private NotifCollection mNotifCollection;
+ @Mock private NotifPipeline mNotifPipeline;
private NotificationEntry mEntry;
private Notification mNotification;
@@ -84,9 +82,9 @@
ArgumentCaptor<NotifLifetimeExtender> lifetimeExtenderCaptor =
ArgumentCaptor.forClass(NotifLifetimeExtender.class);
- mForegroundCoordinator.attach(mNotifCollection, mNotifListBuilder);
- verify(mNotifListBuilder, times(1)).addPreGroupFilter(filterCaptor.capture());
- verify(mNotifCollection, times(1)).addNotificationLifetimeExtender(
+ mForegroundCoordinator.attach(mNotifPipeline);
+ verify(mNotifPipeline, times(1)).addPreGroupFilter(filterCaptor.capture());
+ verify(mNotifPipeline, times(1)).addNotificationLifetimeExtender(
lifetimeExtenderCaptor.capture());
mForegroundFilter = filterCaptor.getValue();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
index 979b8a9..5866d90 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
@@ -41,10 +41,11 @@
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
-import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.Before;
@@ -66,7 +67,8 @@
@Mock private BroadcastDispatcher mBroadcastDispatcher;
@Mock private StatusBarStateController mStatusBarStateController;
@Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @Mock private NotifListBuilderImpl mNotifListBuilder;
+ @Mock private HighPriorityProvider mHighPriorityProvider;
+ @Mock private NotifPipeline mNotifPipeline;
private NotificationEntry mEntry;
private KeyguardCoordinator mKeyguardCoordinator;
@@ -78,15 +80,15 @@
mKeyguardCoordinator = new KeyguardCoordinator(
mContext, mMainHandler, mKeyguardStateController, mLockscreenUserManager,
mBroadcastDispatcher, mStatusBarStateController,
- mKeyguardUpdateMonitor);
+ mKeyguardUpdateMonitor, mHighPriorityProvider);
mEntry = new NotificationEntryBuilder()
.setUser(new UserHandle(NOTIF_USER_ID))
.build();
ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
- mKeyguardCoordinator.attach(null, mNotifListBuilder);
- verify(mNotifListBuilder, times(1)).addPreRenderFilter(filterCaptor.capture());
+ mKeyguardCoordinator.attach(mNotifPipeline);
+ verify(mNotifPipeline, times(1)).addPreRenderFilter(filterCaptor.capture());
mKeyguardFilter = filterCaptor.getValue();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
index d3b16c3..e84f9cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
@@ -33,7 +33,7 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.RankingBuilder;
-import com.android.systemui.statusbar.notification.collection.NotifListBuilderImpl;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
@@ -50,7 +50,7 @@
public class RankingCoordinatorTest extends SysuiTestCase {
@Mock private StatusBarStateController mStatusBarStateController;
- @Mock private NotifListBuilderImpl mNotifListBuilder;
+ @Mock private NotifPipeline mNotifPipeline;
private NotificationEntry mEntry;
private RankingCoordinator mRankingCoordinator;
private NotifFilter mRankingFilter;
@@ -62,8 +62,8 @@
mEntry = new NotificationEntryBuilder().build();
ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
- mRankingCoordinator.attach(null, mNotifListBuilder);
- verify(mNotifListBuilder, times(1)).addPreGroupFilter(filterCaptor.capture());
+ mRankingCoordinator.attach(mNotifPipeline);
+ verify(mNotifPipeline, times(1)).addPreGroupFilter(filterCaptor.capture());
mRankingFilter = filterCaptor.getValue();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProviderTest.java
deleted file mode 100644
index 6fa1a89..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/IsHighPriorityProviderTest.java
+++ /dev/null
@@ -1,184 +0,0 @@
-/*
- * 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 com.android.systemui.statusbar.notification.collection.provider;
-
-import static android.app.NotificationManager.IMPORTANCE_HIGH;
-import static android.app.NotificationManager.IMPORTANCE_LOW;
-import static android.app.NotificationManager.IMPORTANCE_MIN;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.Person;
-import android.testing.AndroidTestingRunner;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class IsHighPriorityProviderTest extends SysuiTestCase {
- private IsHighPriorityProvider mIsHighPriorityProvider;
-
- @Before
- public void setup() {
- mIsHighPriorityProvider = new IsHighPriorityProvider();
- }
-
- @Test
- public void testCache() {
- // GIVEN a notification with high importance
- final NotificationEntry entryHigh = new NotificationEntryBuilder()
- .setImportance(IMPORTANCE_HIGH)
- .build();
-
- // GIVEN notification with min importance
- final NotificationEntry entryMin = new NotificationEntryBuilder()
- .setImportance(IMPORTANCE_MIN)
- .build();
-
- // WHEN we get the value for the high priority entry
- assertTrue(mIsHighPriorityProvider.get(entryHigh));
-
- // THEN the value is cached, so even when passed an entryMin, we still get high priority
- assertTrue(mIsHighPriorityProvider.get(entryMin));
-
- // UNTIL the provider is invalidated
- mIsHighPriorityProvider.invalidate();
-
- // THEN the priority is recalculated
- assertFalse(mIsHighPriorityProvider.get(entryMin));
- }
-
- @Test
- public void highImportance() {
- // GIVEN notification has high importance
- final NotificationEntry entry = new NotificationEntryBuilder()
- .setImportance(IMPORTANCE_HIGH)
- .build();
-
- // THEN it has high priority
- assertTrue(mIsHighPriorityProvider.get(entry));
- }
-
- @Test
- public void peopleNotification() {
- // GIVEN notification is low importance but has a person associated with it
- final Notification notification = new Notification.Builder(mContext, "test")
- .addPerson(
- new Person.Builder()
- .setName("name")
- .setKey("abc")
- .setUri("uri")
- .setBot(true)
- .build())
- .build();
-
- final NotificationEntry entry = new NotificationEntryBuilder()
- .setNotification(notification)
- .setImportance(IMPORTANCE_LOW)
- .build();
-
- // THEN it has high priority
- assertTrue(mIsHighPriorityProvider.get(entry));
- }
-
- @Test
- public void messagingStyle() {
- // GIVEN notification is low importance but has messaging style
- final Notification notification = new Notification.Builder(mContext, "test")
- .setStyle(new Notification.MessagingStyle(""))
- .build();
-
- final NotificationEntry entry = new NotificationEntryBuilder()
- .setNotification(notification)
- .build();
-
- // THEN it has high priority
- assertTrue(mIsHighPriorityProvider.get(entry));
- }
-
- @Test
- public void lowImportanceForeground() {
- // GIVEN notification is low importance and is associated with a foreground service
- final Notification notification = mock(Notification.class);
- when(notification.isForegroundService()).thenReturn(true);
-
- final NotificationEntry entry = new NotificationEntryBuilder()
- .setNotification(notification)
- .setImportance(IMPORTANCE_LOW)
- .build();
-
- // THEN it has high priority
- assertTrue(mIsHighPriorityProvider.get(entry));
- }
-
- @Test
- public void minImportanceForeground() {
- // GIVEN notification is low importance and is associated with a foreground service
- final Notification notification = mock(Notification.class);
- when(notification.isForegroundService()).thenReturn(true);
-
- final NotificationEntry entry = new NotificationEntryBuilder()
- .setNotification(notification)
- .setImportance(IMPORTANCE_MIN)
- .build();
-
- // THEN it does NOT have high priority
- assertFalse(mIsHighPriorityProvider.get(entry));
- }
-
- @Test
- public void userChangeTrumpsHighPriorityCharacteristics() {
- // GIVEN notification has high priority characteristics but the user changed the importance
- // to less than IMPORTANCE_DEFAULT (ie: IMPORTANCE_LOW or IMPORTANCE_MIN)
- final Notification notification = new Notification.Builder(mContext, "test")
- .addPerson(
- new Person.Builder()
- .setName("name")
- .setKey("abc")
- .setUri("uri")
- .setBot(true)
- .build())
- .setStyle(new Notification.MessagingStyle(""))
- .setFlag(Notification.FLAG_FOREGROUND_SERVICE, true)
- .build();
-
- final NotificationChannel channel = new NotificationChannel("a", "a",
- IMPORTANCE_LOW);
- channel.lockFields(NotificationChannel.USER_LOCKED_IMPORTANCE);
-
- final NotificationEntry entry = new NotificationEntryBuilder()
- .setNotification(notification)
- .setChannel(channel)
- .build();
-
- // THEN it does NOT have high priority
- assertFalse(mIsHighPriorityProvider.get(entry));
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt
index 0764d0c..867a9b9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/people/PeopleHubViewControllerTest.kt
@@ -178,9 +178,10 @@
private fun fakePersonModel(
id: String,
name: CharSequence,
- clickIntent: PendingIntent
+ clickIntent: PendingIntent,
+ userId: Int = 0
): PersonModel =
- PersonModel(id, name, mock(Drawable::class.java), clickIntent)
+ PersonModel(id, name, mock(Drawable::class.java), clickIntent, userId)
private fun fakePersonViewModel(name: CharSequence): PersonViewModel =
PersonViewModel(name, mock(Drawable::class.java), mock({}.javaClass))
@@ -207,4 +208,4 @@
override fun onDataChanged(data: T) {
lastSeen = Maybe.Just(data)
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java
new file mode 100644
index 0000000..d7214f3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java
@@ -0,0 +1,112 @@
+/*
+ * 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.systemui.statusbar.notification.row;
+
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+import android.widget.RemoteViews;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.notification.NotificationEntryListener;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class NotifRemoteViewCacheImplTest extends SysuiTestCase {
+
+ private NotifRemoteViewCacheImpl mNotifRemoteViewCache;
+ private NotificationEntry mEntry;
+ private NotificationEntryListener mEntryListener;
+ @Mock private RemoteViews mRemoteViews;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mEntry = new NotificationEntryBuilder().build();
+
+ NotificationEntryManager entryManager = mock(NotificationEntryManager.class);
+ mNotifRemoteViewCache = new NotifRemoteViewCacheImpl(entryManager);
+ ArgumentCaptor<NotificationEntryListener> entryListenerCaptor =
+ ArgumentCaptor.forClass(NotificationEntryListener.class);
+ verify(entryManager).addNotificationEntryListener(entryListenerCaptor.capture());
+ mEntryListener = entryListenerCaptor.getValue();
+ }
+
+ @Test
+ public void testPutCachedView() {
+ // GIVEN an initialized cache for an entry.
+ mEntryListener.onPendingEntryAdded(mEntry);
+
+ // WHEN a notification's cached remote views is put in.
+ mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews);
+
+ // THEN the remote view is cached.
+ assertTrue(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED));
+ assertEquals(
+ "Cached remote view is not the one we put in.",
+ mRemoteViews,
+ mNotifRemoteViewCache.getCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED));
+ }
+
+ @Test
+ public void testRemoveCachedView() {
+ // GIVEN a cache with a cached view.
+ mEntryListener.onPendingEntryAdded(mEntry);
+ mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews);
+
+ // WHEN we remove the cached view.
+ mNotifRemoteViewCache.removeCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED);
+
+ // THEN the remote view is not cached.
+ assertFalse(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED));
+ }
+
+ @Test
+ public void testClearCache() {
+ // GIVEN a non-empty cache.
+ mEntryListener.onPendingEntryAdded(mEntry);
+ mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews);
+ mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_EXPANDED, mRemoteViews);
+
+ // WHEN we clear the cache.
+ mNotifRemoteViewCache.clearCache(mEntry);
+
+ // THEN the cache is empty.
+ assertFalse(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED));
+ assertFalse(mNotifRemoteViewCache.hasCachedView(mEntry, FLAG_CONTENT_VIEW_EXPANDED));
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index dc4e498..cb9da6a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -22,30 +22,35 @@
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.Notification;
import android.content.Context;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.Looper;
-import android.service.notification.StatusBarNotification;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
-import android.util.ArrayMap;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RemoteViews;
+import android.widget.TextView;
import androidx.test.filters.SmallTest;
import androidx.test.filters.Suppress;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.InflationTask;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationTestHelper;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams;
@@ -58,6 +63,8 @@
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import java.util.HashMap;
import java.util.concurrent.CountDownLatch;
@@ -74,8 +81,11 @@
private Notification.Builder mBuilder;
private ExpandableNotificationRow mRow;
+ @Mock private NotifRemoteViewCache mCache;
+
@Before
public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
mBuilder = new Notification.Builder(mContext).setSmallIcon(
R.drawable.ic_person)
.setContentTitle("Title")
@@ -84,7 +94,9 @@
ExpandableNotificationRow row = new NotificationTestHelper(mContext, mDependency).createRow(
mBuilder.build());
mRow = spy(row);
- mNotificationInflater = new NotificationContentInflater();
+ mNotificationInflater = new NotificationContentInflater(
+ mCache,
+ mock(NotificationRemoteInputManager.class));
}
@Test
@@ -175,11 +187,13 @@
result,
FLAG_CONTENT_VIEW_EXPANDED,
0,
- new ArrayMap() /* cachedContentViews */, mRow,
+ mock(NotifRemoteViewCache.class),
+ mRow.getEntry(),
+ mRow,
true /* isNewView */, (v, p, r) -> true,
new InflationCallback() {
@Override
- public void handleInflationException(StatusBarNotification notification,
+ public void handleInflationException(NotificationEntry entry,
Exception e) {
countDownLatch.countDown();
throw new RuntimeException("No Exception expected");
@@ -245,6 +259,71 @@
NotificationContentInflater.canReapplyRemoteView(mediaView, decoratedMediaView));
}
+ @Test
+ public void testUsesSameViewWhenCachedPossibleToReuse() throws Exception {
+ // GIVEN a cached view.
+ RemoteViews contractedRemoteView = mBuilder.createContentView();
+ when(mCache.hasCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED))
+ .thenReturn(true);
+ when(mCache.getCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED))
+ .thenReturn(contractedRemoteView);
+
+ // GIVEN existing bound view with same layout id.
+ View view = contractedRemoteView.apply(mContext, null /* parent */);
+ mRow.getPrivateLayout().setContractedChild(view);
+
+ // WHEN inflater inflates
+ inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, mRow);
+
+ // THEN the view should be re-used
+ assertEquals("Binder inflated a new view even though the old one was cached and usable.",
+ view, mRow.getPrivateLayout().getContractedChild());
+ }
+
+ @Test
+ public void testInflatesNewViewWhenCachedNotPossibleToReuse() throws Exception {
+ // GIVEN a cached remote view.
+ RemoteViews contractedRemoteView = mBuilder.createHeadsUpContentView();
+ when(mCache.hasCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED))
+ .thenReturn(true);
+ when(mCache.getCachedView(mRow.getEntry(), FLAG_CONTENT_VIEW_CONTRACTED))
+ .thenReturn(contractedRemoteView);
+
+ // GIVEN existing bound view with different layout id.
+ View view = new TextView(mContext);
+ mRow.getPrivateLayout().setContractedChild(view);
+
+ // WHEN inflater inflates
+ inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, mRow);
+
+ // THEN the view should be a new view
+ assertNotEquals("Binder (somehow) used the same view when inflating.",
+ view, mRow.getPrivateLayout().getContractedChild());
+ }
+
+ @Test
+ public void testInflationCachesCreatedRemoteView() throws Exception {
+ // WHEN inflater inflates
+ inflateAndWait(mNotificationInflater, FLAG_CONTENT_VIEW_CONTRACTED, mRow);
+
+ // THEN inflater informs cache of the new remote view
+ verify(mCache).putCachedView(
+ eq(mRow.getEntry()),
+ eq(FLAG_CONTENT_VIEW_CONTRACTED),
+ any());
+ }
+
+ @Test
+ public void testUnbindRemovesCachedRemoteView() {
+ // WHEN inflated unbinds content
+ mNotificationInflater.unbindContent(mRow.getEntry(), mRow, FLAG_CONTENT_VIEW_HEADS_UP);
+
+ // THEN inflated informs cache to remove remote view
+ verify(mCache).removeCachedView(
+ eq(mRow.getEntry()),
+ eq(FLAG_CONTENT_VIEW_HEADS_UP));
+ }
+
private static void inflateAndWait(NotificationContentInflater inflater,
@InflationFlag int contentToInflate,
ExpandableNotificationRow row)
@@ -261,7 +340,7 @@
inflater.setInflateSynchronously(true);
InflationCallback callback = new InflationCallback() {
@Override
- public void handleInflationException(StatusBarNotification notification,
+ public void handleInflationException(NotificationEntry entry,
Exception e) {
if (!expectingException) {
exceptionHolder.setException(e);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
index 675b3ef..84c6513 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
@@ -16,20 +16,17 @@
package com.android.systemui.statusbar.notification.row;
-import static org.junit.Assert.assertFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.AppOpsManager;
-import android.graphics.drawable.Icon;
import android.util.ArraySet;
import android.view.NotificationHeaderView;
import android.view.View;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
new file mode 100644
index 0000000..61f0b26
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfoTest.java
@@ -0,0 +1,845 @@
+/*
+ * 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.systemui.statusbar.notification.row;
+
+import static android.app.Notification.FLAG_BUBBLE;
+import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.app.NotificationManager.IMPORTANCE_HIGH;
+import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.print.PrintManager.PRINT_SPOOLER_PACKAGE_NAME;
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.INotificationManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
+import android.app.PendingIntent;
+import android.app.Person;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.os.UserHandle;
+import android.service.notification.StatusBarNotification;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.util.Slog;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.Dependency;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.bubbles.BubblesTestActivity;
+import com.android.systemui.statusbar.SbnBuilder;
+import com.android.systemui.statusbar.notification.VisualStabilityManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.mockito.stubbing.Answer;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class NotificationConversationInfoTest extends SysuiTestCase {
+ private static final String TEST_PACKAGE_NAME = "test_package";
+ private static final String TEST_SYSTEM_PACKAGE_NAME = PRINT_SPOOLER_PACKAGE_NAME;
+ private static final int TEST_UID = 1;
+ private static final String TEST_CHANNEL = "test_channel";
+ private static final String TEST_CHANNEL_NAME = "TEST CHANNEL NAME";
+ private static final String CONVERSATION_ID = "convo";
+
+ private TestableLooper mTestableLooper;
+ private NotificationConversationInfo mNotificationInfo;
+ private NotificationChannel mNotificationChannel;
+ private NotificationChannel mConversationChannel;
+ private StatusBarNotification mSbn;
+ private NotificationEntry mEntry;
+ private StatusBarNotification mBubbleSbn;
+ private NotificationEntry mBubbleEntry;
+ @Mock
+ private ShortcutInfo mShortcutInfo;
+ private Drawable mImage;
+
+ @Rule
+ public MockitoRule mockito = MockitoJUnit.rule();
+ @Mock
+ private MetricsLogger mMetricsLogger;
+ @Mock
+ private INotificationManager mMockINotificationManager;
+ @Mock
+ private PackageManager mMockPackageManager;
+ @Mock
+ private VisualStabilityManager mVisualStabilityManager;
+ @Mock
+ private BubbleController mBubbleController;
+ @Mock
+ private LauncherApps mLauncherApps;
+ @Mock
+ private ShortcutManager mShortcutManager;
+ @Mock
+ private NotificationGuts mNotificationGuts;
+
+ @Before
+ public void setUp() throws Exception {
+ mTestableLooper = TestableLooper.get(this);
+
+ mDependency.injectTestDependency(Dependency.BG_LOOPER, mTestableLooper.getLooper());
+ mDependency.injectTestDependency(MetricsLogger.class, mMetricsLogger);
+ mDependency.injectTestDependency(BubbleController.class, mBubbleController);
+ // Inflate the layout
+ final LayoutInflater layoutInflater = LayoutInflater.from(mContext);
+ mNotificationInfo = (NotificationConversationInfo) layoutInflater.inflate(
+ R.layout.notification_conversation_info,
+ null);
+ mNotificationInfo.mShowHomeScreen = true;
+ mNotificationInfo.setGutsParent(mNotificationGuts);
+ doAnswer((Answer<Object>) invocation -> {
+ mNotificationInfo.handleCloseControls(true, false);
+ return null;
+ }).when(mNotificationGuts).closeControls(anyInt(), anyInt(), eq(true), eq(false));
+ // Our view is never attached to a window so the View#post methods in NotificationInfo never
+ // get called. Setting this will skip the post and do the action immediately.
+ mNotificationInfo.mSkipPost = true;
+
+ // PackageManager must return a packageInfo and applicationInfo.
+ final PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = TEST_PACKAGE_NAME;
+ when(mMockPackageManager.getPackageInfo(eq(TEST_PACKAGE_NAME), anyInt()))
+ .thenReturn(packageInfo);
+ final ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.uid = TEST_UID; // non-zero
+ when(mMockPackageManager.getApplicationInfo(eq(TEST_PACKAGE_NAME), anyInt())).thenReturn(
+ applicationInfo);
+ final PackageInfo systemPackageInfo = new PackageInfo();
+ systemPackageInfo.packageName = TEST_SYSTEM_PACKAGE_NAME;
+ when(mMockPackageManager.getPackageInfo(eq(TEST_SYSTEM_PACKAGE_NAME), anyInt()))
+ .thenReturn(systemPackageInfo);
+ when(mMockPackageManager.getPackageInfo(eq("android"), anyInt()))
+ .thenReturn(packageInfo);
+
+ when(mShortcutInfo.getShortLabel()).thenReturn("Convo name");
+ List<ShortcutInfo> shortcuts = Arrays.asList(mShortcutInfo);
+ when(mLauncherApps.getShortcuts(any(), any())).thenReturn(shortcuts);
+ mImage = mContext.getDrawable(R.drawable.ic_star);
+ when(mLauncherApps.getShortcutBadgedIconDrawable(eq(mShortcutInfo),
+ anyInt())).thenReturn(mImage);
+
+ mNotificationChannel = new NotificationChannel(
+ TEST_CHANNEL, TEST_CHANNEL_NAME, IMPORTANCE_LOW);
+
+ Notification notification = new Notification.Builder(mContext, mNotificationChannel.getId())
+ .setShortcutId(CONVERSATION_ID)
+ .setStyle(new Notification.MessagingStyle(new Person.Builder().setName("m").build())
+ .addMessage(new Notification.MessagingStyle.Message(
+ "hello!", 1000, new Person.Builder().setName("other").build())))
+ .build();
+ mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID, 0,
+ notification, UserHandle.CURRENT, null, 0);
+ mEntry = new NotificationEntryBuilder().setSbn(mSbn).build();
+
+ PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0,
+ new Intent(mContext, BubblesTestActivity.class), 0);
+ mBubbleSbn = new SbnBuilder(mSbn).setBubbleMetadata(
+ new Notification.BubbleMetadata.Builder()
+ .setIntent(bubbleIntent)
+ .setIcon(Icon.createWithResource(mContext, R.drawable.android)).build())
+ .build();
+ mBubbleEntry = new NotificationEntryBuilder().setSbn(mBubbleSbn).build();
+
+ mConversationChannel = new NotificationChannel(
+ TEST_CHANNEL + " : " + CONVERSATION_ID, TEST_CHANNEL_NAME, IMPORTANCE_LOW);
+ mConversationChannel.setConversationId(TEST_CHANNEL, CONVERSATION_ID);
+ when(mMockINotificationManager.getConversationNotificationChannel(anyString(), anyInt(),
+ anyString(), eq(TEST_CHANNEL), eq(false), eq(CONVERSATION_ID)))
+ .thenReturn(mConversationChannel);
+ }
+
+ @Test
+ public void testBindNotification_SetsTextShortcutName() {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ final TextView textView = mNotificationInfo.findViewById(R.id.name);
+ assertEquals(mShortcutInfo.getShortLabel(), textView.getText().toString());
+ assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
+ }
+
+ @Test
+ public void testBindNotification_SetsShortcutIcon() {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ final ImageView view = mNotificationInfo.findViewById(R.id.conversation_icon);
+ assertEquals(mImage, view.getDrawable());
+ }
+
+ @Test
+ public void testBindNotification_SetsTextApplicationName() {
+ when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name");
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ final TextView textView = mNotificationInfo.findViewById(R.id.pkg_name);
+ assertTrue(textView.getText().toString().contains("App Name"));
+ assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
+ }
+
+ @Test
+ public void testBindNotification_SetsTextChannelName() {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ final TextView textView = mNotificationInfo.findViewById(R.id.parent_channel_name);
+ assertTrue(textView.getText().toString().contains(mNotificationChannel.getName()));
+ assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
+ }
+
+ @Test
+ public void testBindNotification_SetsTextGroupName() throws Exception {
+ NotificationChannelGroup group = new NotificationChannelGroup("id", "name");
+ when(mMockINotificationManager.getNotificationChannelGroupForPackage(
+ anyString(), anyString(), anyInt())).thenReturn(group);
+ mNotificationChannel.setGroup(group.getId());
+ mConversationChannel.setGroup(group.getId());
+
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ final TextView textView = mNotificationInfo.findViewById(R.id.group_name);
+ assertTrue(textView.getText().toString().contains(group.getName()));
+ assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
+ assertEquals(VISIBLE, textView.getVisibility());
+ assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.group_divider).getVisibility());
+ }
+
+ @Test
+ public void testBindNotification_GroupNameHiddenIfNoGroup() {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ final TextView textView = mNotificationInfo.findViewById(R.id.group_name);
+ assertEquals(VISIBLE, mNotificationInfo.findViewById(R.id.header).getVisibility());
+ assertEquals(GONE, textView.getVisibility());
+ assertEquals(GONE, mNotificationInfo.findViewById(R.id.group_divider).getVisibility());
+ }
+
+ @Test
+ public void testBindNotification_noDelegate() {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
+ assertEquals(GONE, nameView.getVisibility());
+ final TextView dividerView = mNotificationInfo.findViewById(R.id.pkg_divider);
+ assertEquals(GONE, dividerView.getVisibility());
+ }
+
+ @Test
+ public void testBindNotification_delegate() throws Exception {
+ mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, "other", 0, null, TEST_UID, 0,
+ mSbn.getNotification(), UserHandle.CURRENT, null, 0);
+ final ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.uid = 7; // non-zero
+ when(mMockPackageManager.getApplicationInfo(eq("other"), anyInt())).thenReturn(
+ applicationInfo);
+ when(mMockPackageManager.getApplicationLabel(any())).thenReturn("Other");
+
+ NotificationEntry entry = new NotificationEntryBuilder().setSbn(mSbn).build();
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ entry,
+ null,
+ null,
+ null,
+ true);
+ final TextView nameView = mNotificationInfo.findViewById(R.id.delegate_name);
+ assertEquals(VISIBLE, nameView.getVisibility());
+ assertTrue(nameView.getText().toString().contains("Proxied"));
+ final TextView dividerView = mNotificationInfo.findViewById(R.id.pkg_divider);
+ assertEquals(VISIBLE, dividerView.getVisibility());
+ }
+
+ @Test
+ public void testBindNotification_SetsOnClickListenerForSettings() {
+ final CountDownLatch latch = new CountDownLatch(1);
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ (View v, NotificationChannel c, int appUid) -> {
+ assertEquals(mConversationChannel, c);
+ latch.countDown();
+ },
+ null,
+ null,
+ true);
+
+ final View settingsButton = mNotificationInfo.findViewById(R.id.info);
+ settingsButton.performClick();
+ // Verify that listener was triggered.
+ assertEquals(0, latch.getCount());
+ }
+
+ @Test
+ public void testBindNotification_SettingsButtonInvisibleWhenNoClickListener() {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ final View settingsButton = mNotificationInfo.findViewById(R.id.info);
+ assertTrue(settingsButton.getVisibility() != View.VISIBLE);
+ }
+
+ @Test
+ public void testBindNotification_SettingsButtonInvisibleWhenDeviceUnprovisioned() {
+ final CountDownLatch latch = new CountDownLatch(1);
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ (View v, NotificationChannel c, int appUid) -> {
+ assertEquals(mNotificationChannel, c);
+ latch.countDown();
+ },
+ null,
+ null,
+ false);
+ final View settingsButton = mNotificationInfo.findViewById(R.id.info);
+ assertTrue(settingsButton.getVisibility() != View.VISIBLE);
+ }
+
+ @Test
+ public void testBindNotification_bubbleActionVisibleWhenCanBubble() {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mBubbleEntry,
+ null,
+ null,
+ null,
+ true);
+
+ View bubbleView = mNotificationInfo.findViewById(R.id.bubble);
+ assertEquals(View.VISIBLE, bubbleView.getVisibility());
+ }
+
+ @Test
+ public void testBindNotification_bubbleActionVisibleWhenCannotBubble() {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+
+ View bubbleView = mNotificationInfo.findViewById(R.id.bubble);
+ assertEquals(View.GONE, bubbleView.getVisibility());
+ }
+
+ @Test
+ public void testAddToHome() throws Exception {
+ when(mShortcutManager.isRequestPinShortcutSupported()).thenReturn(true);
+
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mBubbleEntry,
+ null,
+ null,
+ null,
+ true);
+
+
+ // Promote it
+ mNotificationInfo.findViewById(R.id.home).performClick();
+ mTestableLooper.processAllMessages();
+
+ verify(mShortcutManager, times(1)).requestPinShortcut(mShortcutInfo, null);
+ verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
+ anyString(), anyInt(), any());
+ }
+
+ @Test
+ public void testSnooze() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mBubbleEntry,
+ null,
+ null,
+ (View v, int hours) -> {
+ latch.countDown();
+ },
+ true);
+
+
+ // Promote it
+ mNotificationInfo.findViewById(R.id.snooze).performClick();
+ mTestableLooper.processAllMessages();
+
+ assertEquals(0, latch.getCount());
+ verify(mMockINotificationManager, never()).updateNotificationChannelForPackage(
+ anyString(), anyInt(), any());
+ }
+
+ @Test
+ public void testBubble_promotesBubble() throws Exception {
+ mNotificationChannel.setAllowBubbles(false);
+ mConversationChannel.setAllowBubbles(false);
+
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mBubbleEntry,
+ null,
+ null,
+ null,
+ true);
+
+ assertFalse(mBubbleEntry.isBubble());
+
+ // Promote it
+ mNotificationInfo.findViewById(R.id.bubble).performClick();
+ mTestableLooper.processAllMessages();
+
+ verify(mBubbleController, times(1)).onUserCreatedBubbleFromNotification(mBubbleEntry);
+ ArgumentCaptor<NotificationChannel> captor =
+ ArgumentCaptor.forClass(NotificationChannel.class);
+ verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+ anyString(), anyInt(), captor.capture());
+ assertTrue(captor.getValue().canBubble());
+ }
+
+ @Test
+ public void testBubble_demotesBubble() throws Exception {
+ mBubbleEntry.getSbn().getNotification().flags |= FLAG_BUBBLE;
+
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mBubbleEntry,
+ null,
+ null,
+ null,
+ true);
+
+ assertTrue(mBubbleEntry.isBubble());
+
+ // Demote it
+ mNotificationInfo.findViewById(R.id.bubble).performClick();
+ mTestableLooper.processAllMessages();
+
+ verify(mBubbleController, times(1)).onUserDemotedBubbleFromNotification(mBubbleEntry);
+ ArgumentCaptor<NotificationChannel> captor =
+ ArgumentCaptor.forClass(NotificationChannel.class);
+ verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+ anyString(), anyInt(), captor.capture());
+ assertFalse(captor.getValue().canBubble());
+ }
+
+ @Test
+ public void testFavorite_favorite() throws Exception {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+
+
+ Button fave = mNotificationInfo.findViewById(R.id.fave);
+ assertEquals(mContext.getString(R.string.notification_conversation_favorite),
+ fave.getText().toString());
+
+ fave.performClick();
+ mTestableLooper.processAllMessages();
+
+ ArgumentCaptor<NotificationChannel> captor =
+ ArgumentCaptor.forClass(NotificationChannel.class);
+ verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+ anyString(), anyInt(), captor.capture());
+ assertTrue(captor.getValue().canBypassDnd());
+ }
+
+ @Test
+ public void testFavorite_unfavorite() throws Exception {
+ mNotificationChannel.setBypassDnd(true);
+ mConversationChannel.setBypassDnd(true);
+
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+
+ Button fave = mNotificationInfo.findViewById(R.id.fave);
+ assertEquals(mContext.getString(R.string.notification_conversation_unfavorite),
+ fave.getText().toString());
+
+ fave.performClick();
+ mTestableLooper.processAllMessages();
+
+ ArgumentCaptor<NotificationChannel> captor =
+ ArgumentCaptor.forClass(NotificationChannel.class);
+ verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+ anyString(), anyInt(), captor.capture());
+ assertFalse(captor.getValue().canBypassDnd());
+ }
+
+ @Test
+ public void testDemote() throws Exception {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+
+
+ ImageButton demote = mNotificationInfo.findViewById(R.id.demote);
+ demote.performClick();
+ mTestableLooper.processAllMessages();
+
+ ArgumentCaptor<NotificationChannel> captor =
+ ArgumentCaptor.forClass(NotificationChannel.class);
+ verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+ anyString(), anyInt(), captor.capture());
+ assertTrue(captor.getValue().isDemoted());
+ }
+
+ @Test
+ public void testMute_mute() throws Exception {
+ mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
+ mConversationChannel.setImportance(IMPORTANCE_DEFAULT);
+
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+
+ Button mute = mNotificationInfo.findViewById(R.id.mute);
+ assertEquals(mContext.getString(R.string.notification_conversation_mute),
+ mute.getText().toString());
+
+ mute.performClick();
+ mTestableLooper.processAllMessages();
+
+ ArgumentCaptor<NotificationChannel> captor =
+ ArgumentCaptor.forClass(NotificationChannel.class);
+ verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+ anyString(), anyInt(), captor.capture());
+ assertEquals(IMPORTANCE_LOW, captor.getValue().getImportance());
+ }
+
+ @Test
+ public void testMute_unmute() throws Exception {
+ mNotificationChannel.setImportance(IMPORTANCE_LOW);
+ mNotificationChannel.setOriginalImportance(IMPORTANCE_HIGH);
+ mConversationChannel.setImportance(IMPORTANCE_LOW);
+ mConversationChannel.setOriginalImportance(IMPORTANCE_HIGH);
+
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+
+
+ Button mute = mNotificationInfo.findViewById(R.id.mute);
+ assertEquals(mContext.getString(R.string.notification_conversation_unmute),
+ mute.getText().toString());
+
+ mute.performClick();
+ mTestableLooper.processAllMessages();
+
+ ArgumentCaptor<NotificationChannel> captor =
+ ArgumentCaptor.forClass(NotificationChannel.class);
+ verify(mMockINotificationManager, times(1)).updateNotificationChannelForPackage(
+ anyString(), anyInt(), captor.capture());
+ assertEquals(IMPORTANCE_HIGH, captor.getValue().getImportance());
+ }
+
+ @Test
+ public void testBindNotification_createsNewChannel() throws Exception {
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ verify(mMockINotificationManager, times(1)).createConversationNotificationChannelForPackage(
+ anyString(), anyInt(), any(), eq(CONVERSATION_ID));
+ }
+
+ @Test
+ public void testBindNotification_doesNotCreateNewChannelIfExists() throws Exception {
+ mNotificationChannel.setConversationId("", CONVERSATION_ID);
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+ verify(mMockINotificationManager, never()).createConversationNotificationChannelForPackage(
+ anyString(), anyInt(), any(), eq(CONVERSATION_ID));
+ }
+
+ @Test
+ public void testAdjustImportanceTemporarilyAllowsReordering() {
+ mNotificationChannel.setImportance(IMPORTANCE_DEFAULT);
+ mConversationChannel.setImportance(IMPORTANCE_DEFAULT);
+ mNotificationInfo.bindNotification(
+ mShortcutManager,
+ mLauncherApps,
+ mMockPackageManager,
+ mMockINotificationManager,
+ mVisualStabilityManager,
+ TEST_PACKAGE_NAME,
+ mNotificationChannel,
+ mEntry,
+ null,
+ null,
+ null,
+ true);
+
+ mNotificationInfo.findViewById(R.id.mute).performClick();
+
+ verify(mVisualStabilityManager).temporarilyAllowReordering();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index ccc9496..4e27770 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -70,6 +70,7 @@
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.phone.StatusBar;
@@ -113,6 +114,7 @@
@Mock private DeviceProvisionedController mDeviceProvisionedController;
@Mock private StatusBar mStatusBar;
@Mock private AccessibilityManager mAccessibilityManager;
+ @Mock private HighPriorityProvider mHighPriorityProvider;
@Before
public void setUp() {
@@ -128,7 +130,7 @@
when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
mGutsManager = new NotificationGutsManager(mContext, mVisualStabilityManager,
- () -> mStatusBar, mHandler, mAccessibilityManager);
+ () -> mStatusBar, mHandler, mAccessibilityManager, mHighPriorityProvider);
mGutsManager.setUpWithPresenter(mPresenter, mStackScroller,
mCheckSaveListener, mOnSettingsClickListener);
mGutsManager.setNotificationActivityStarter(mNotificationActivityStarter);
@@ -391,6 +393,7 @@
.build();
when(row.getIsNonblockable()).thenReturn(false);
+ when(mHighPriorityProvider.isHighPriority(entry)).thenReturn(true);
StatusBarNotification statusBarNotification = entry.getSbn();
mGutsManager.initializeNotificationInfo(row, notificationInfoView);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
index f48c40c..b33d26f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationMenuRowTest.java
@@ -132,16 +132,6 @@
}
@Test
- public void testNoAppOpsInSlowSwipe_biDirectionalSwipe() {
- NotificationMenuRow row = new NotificationMenuRow(mContext, true);
- row.createMenu(mRow, null);
-
- ViewGroup container = (ViewGroup) row.getMenuView();
- // in the new interruption model there is only the blocking item
- assertEquals(1, container.getChildCount());
- }
-
- @Test
public void testIsSnappedAndOnSameSide() {
NotificationMenuRow row = Mockito.spy(new NotificationMenuRow((mContext)));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
index 003d803..518b670 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManagerTest.java
@@ -267,8 +267,6 @@
ExpandableNotificationRow notifRow = mock(ExpandableNotificationRow.class,
RETURNS_DEEP_STUBS);
when(notifRow.getVisibility()).thenReturn(View.VISIBLE);
- when(notifRow.getEntry().isHighPriority())
- .thenReturn(children[i] == ChildType.HIPRI);
when(notifRow.getEntry().getBucket()).thenReturn(
children[i] == ChildType.HIPRI ? BUCKET_ALERTING : BUCKET_SILENT);
when(notifRow.getParent()).thenReturn(mNssl);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index 77a6a26..d9939f4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -53,6 +53,7 @@
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.statusbar.EmptyShadeView;
+import com.android.systemui.statusbar.FeatureFlags;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationPresenter;
@@ -70,6 +71,8 @@
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
+import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
import com.android.systemui.statusbar.notification.logging.NotifLog;
import com.android.systemui.statusbar.notification.people.PeopleHubSectionFooterViewAdapter;
import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
@@ -87,6 +90,7 @@
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.systemui.util.DeviceConfigProxyFake;
+import com.android.systemui.util.leak.LeakDetector;
import org.junit.After;
import org.junit.Before;
@@ -164,9 +168,14 @@
mock(NotificationFilter.class),
mock(NotifLog.class),
mock(NotificationSectionsFeatureManager.class),
- mock(PeopleNotificationIdentifier.class)
+ mock(PeopleNotificationIdentifier.class),
+ mock(HighPriorityProvider.class)
),
- mock(NotificationEntryManager.KeyguardEnvironment.class));
+ mock(NotificationEntryManager.KeyguardEnvironment.class),
+ mock(FeatureFlags.class),
+ () -> mock(NotificationRowBinder.class),
+ () -> mRemoteInputManager,
+ mock(LeakDetector.class));
mDependency.injectTestDependency(NotificationEntryManager.class, mEntryManager);
mEntryManager.setUpForTest(mock(NotificationPresenter.class), null, mHeadsUpManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
index 8decae3..ae87eef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
@@ -64,7 +64,8 @@
mMockNotificiationAreaController = mock(NotificationIconAreaController.class);
mNotificationAreaInner = mock(View.class);
mCenteredNotificationAreaView = mock(View.class);
- when(statusBar.getPanel()).thenReturn(mock(NotificationPanelView.class));
+ when(statusBar.getPanelController()).thenReturn(
+ mock(NotificationPanelViewController.class));
when(mNotificationAreaInner.animate()).thenReturn(mock(ViewPropertyAnimator.class));
when(mMockNotificiationAreaController.getNotificationInnerAreaView()).thenReturn(
mNotificationAreaInner);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
index 46f6cfe..d31f175 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
@@ -86,7 +86,7 @@
@Mock private NotificationIconAreaController mNotificationIconAreaController;
@Mock private StatusBarWindowViewController mStatusBarWindowViewController;
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
- @Mock private NotificationPanelView mNotificationPanel;
+ @Mock private NotificationPanelViewController mNotificationPanel;
@Mock private View mAmbientIndicationContainer;
@Mock private BiometricUnlockController mBiometricUnlockController;
@Mock private LockscreenLockIconController mLockscreenLockIconController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index 0260269..7448dbd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -53,7 +53,8 @@
private final NotificationStackScrollLayout mStackScroller =
mock(NotificationStackScrollLayout.class);
- private final NotificationPanelView mPanelView = mock(NotificationPanelView.class);
+ private final NotificationPanelViewController mPanelView =
+ mock(NotificationPanelViewController.class);
private final DarkIconDispatcher mDarkIconDispatcher = mock(DarkIconDispatcher.class);
private HeadsUpAppearanceController mHeadsUpAppearanceController;
private ExpandableNotificationRow mFirst;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
index 39afbe0..8f645b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarFragmentTest.java
@@ -190,7 +190,7 @@
mFragments.dispatchResume();
processAllMessages();
- verify(mBroadcastDispatcher).registerReceiver(
+ verify(mBroadcastDispatcher).registerReceiverWithHandler(
any(BroadcastReceiver.class),
any(IntentFilter.class),
any(Handler.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index c165e56..1f37ad8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -16,9 +16,13 @@
package com.android.systemui.statusbar.phone;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -26,37 +30,44 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.ActivityManager;
import android.app.StatusBarManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
import android.hardware.biometrics.BiometricSourceType;
+import android.os.PowerManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.util.DisplayMetrics;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityManager;
import androidx.test.filters.SmallTest;
-import com.android.keyguard.KeyguardStatusView;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.util.LatencyTracker;
+import com.android.keyguard.KeyguardClockSwitch;
import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.SystemUIFactory;
+import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.FlingAnimationUtils;
import com.android.systemui.statusbar.KeyguardAffordanceView;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.PulseExpansionHandler;
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
-import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
-import com.android.systemui.statusbar.notification.logging.NotifLog;
import com.android.systemui.statusbar.notification.stack.NotificationRoundnessManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -70,6 +81,7 @@
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.stubbing.Answer;
import java.util.function.Consumer;
@@ -85,8 +97,6 @@
@Mock
private NotificationStackScrollLayout mNotificationStackScrollLayout;
@Mock
- private KeyguardStatusView mKeyguardStatusView;
- @Mock
private KeyguardBottomAreaView mKeyguardBottomArea;
@Mock
private KeyguardBottomAreaView mQsFrame;
@@ -109,27 +119,89 @@
@Mock
private PanelBar mPanelBar;
@Mock
- private KeyguardAffordanceHelper mAffordanceHelper;
- @Mock
private KeyguardUpdateMonitor mUpdateMonitor;
@Mock
private FalsingManager mFalsingManager;
@Mock
private KeyguardBypassController mKeyguardBypassController;
- @Mock private DozeParameters mDozeParameters;
- private NotificationPanelView mNotificationPanelView;
+ @Mock
+ private DozeParameters mDozeParameters;
+ @Mock
+ private NotificationPanelView mView;
+ @Mock
+ private InjectionInflationController mInjectionInflationController;
+ @Mock
+ private DynamicPrivacyController mDynamicPrivacyController;
+ @Mock
+ private PluginManager mPluginManager;
+ @Mock
+ private ShadeController mShadeController;
+ @Mock
+ private NotificationLockscreenUserManager mNotificationLockscreenUserManager;
+ @Mock
+ private NotificationEntryManager mNotificationEntryManager;
+ @Mock
+ private KeyguardStateController mKeyguardStateController;
+ @Mock
+ private DozeLog mDozeLog;
+ @Mock
+ private CommandQueue mCommandQueue;
+ @Mock
+ private VibratorHelper mVibratorHelper;
+ @Mock
+ private LatencyTracker mLatencyTracker;
+ @Mock
+ private PowerManager mPowerManager;
+ @Mock
+ private AccessibilityManager mAccessibilityManager;
+ @Mock
+ private MetricsLogger mMetricsLogger;
+ @Mock
+ private ActivityManager mActivityManager;
+ @Mock
+ private Resources mResources;
+ @Mock
+ private Configuration mConfiguration;
+ private DisplayMetrics mDisplayMetrics = new DisplayMetrics();
+ @Mock
+ private KeyguardClockSwitch mKeyguardClockSwitch;
+ private PanelViewController.TouchHandler mTouchHandler;
+ @Mock
+ private ZenModeController mZenModeController;
+ @Mock
+ private ConfigurationController mConfigurationController;
+ private FlingAnimationUtils.Builder mFlingAnimationUtilsBuilder;
+
+ private NotificationPanelViewController mNotificationPanelViewController;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
+ when(mHeadsUpCallback.getContext()).thenReturn(mContext);
+ when(mView.getResources()).thenReturn(mResources);
+ when(mResources.getConfiguration()).thenReturn(mConfiguration);
+ mConfiguration.orientation = ORIENTATION_PORTRAIT;
+ when(mResources.getDisplayMetrics()).thenReturn(mDisplayMetrics);
+ mDisplayMetrics.density = 100;
+ when(mResources.getBoolean(R.bool.config_enableNotificationShadeDrag)).thenReturn(true);
+ when(mView.getContext()).thenReturn(getContext());
+ when(mView.findViewById(R.id.keyguard_clock_container)).thenReturn(mKeyguardClockSwitch);
+ when(mView.findViewById(R.id.notification_stack_scroller))
+ .thenReturn(mNotificationStackScrollLayout);
when(mNotificationStackScrollLayout.getHeight()).thenReturn(1000);
when(mNotificationStackScrollLayout.getHeadsUpCallback()).thenReturn(mHeadsUpCallback);
- when(mHeadsUpCallback.getContext()).thenReturn(mContext);
- mDependency.injectTestDependency(StatusBarStateController.class,
- mStatusBarStateController);
- mDependency.injectTestDependency(KeyguardUpdateMonitor.class, mUpdateMonitor);
- mDependency.injectMockDependency(ConfigurationController.class);
- mDependency.injectMockDependency(ZenModeController.class);
+ when(mView.findViewById(R.id.keyguard_bottom_area)).thenReturn(mKeyguardBottomArea);
+ when(mKeyguardBottomArea.getLeftView()).thenReturn(mock(KeyguardAffordanceView.class));
+ when(mKeyguardBottomArea.getRightView()).thenReturn(mock(KeyguardAffordanceView.class));
+ when(mView.findViewById(R.id.big_clock_container)).thenReturn(mBigClockContainer);
+ when(mView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame);
+ mFlingAnimationUtilsBuilder = new FlingAnimationUtils.Builder(mDisplayMetrics);
+
+ doAnswer((Answer<Void>) invocation -> {
+ mTouchHandler = invocation.getArgument(0);
+ return null;
+ }).when(mView).setOnTouchListener(any(PanelViewController.TouchHandler.class));
+
NotificationWakeUpCoordinator coordinator =
new NotificationWakeUpCoordinator(
mock(HeadsUpManagerPhone.class),
@@ -143,18 +215,26 @@
mock(NotificationRoundnessManager.class),
mStatusBarStateController,
new FalsingManagerFake());
- mNotificationPanelView = new TestableNotificationPanelView(coordinator, expansionHandler,
- mKeyguardBypassController, mStatusBarStateController);
- mNotificationPanelView.setHeadsUpManager(mHeadsUpManager);
- mNotificationPanelView.setBar(mPanelBar);
-
- when(mKeyguardBottomArea.getLeftView()).thenReturn(mock(KeyguardAffordanceView.class));
- when(mKeyguardBottomArea.getRightView()).thenReturn(mock(KeyguardAffordanceView.class));
+ mNotificationPanelViewController = new NotificationPanelViewController(mView,
+ mInjectionInflationController,
+ coordinator, expansionHandler, mDynamicPrivacyController, mKeyguardBypassController,
+ mFalsingManager, mPluginManager, mShadeController,
+ mNotificationLockscreenUserManager, mNotificationEntryManager,
+ mKeyguardStateController, mStatusBarStateController, mDozeLog,
+ mDozeParameters, mCommandQueue, mVibratorHelper,
+ mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor,
+ mMetricsLogger, mActivityManager, mZenModeController, mConfigurationController,
+ mFlingAnimationUtilsBuilder);
+ mNotificationPanelViewController.initDependencies(mStatusBar, mGroupManager,
+ mNotificationShelf, mNotificationAreaController, mScrimController);
+ mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
+ mNotificationPanelViewController.setBar(mPanelBar);
}
@Test
public void testSetDozing_notifiesNsslAndStateController() {
- mNotificationPanelView.setDozing(true /* dozing */, true /* animate */, null /* touch */);
+ mNotificationPanelViewController.setDozing(true /* dozing */, true /* animate */,
+ null /* touch */);
InOrder inOrder = inOrder(mNotificationStackScrollLayout, mStatusBarStateController);
inOrder.verify(mNotificationStackScrollLayout).setDozing(eq(true), eq(true), eq(null));
inOrder.verify(mStatusBarStateController).setDozeAmount(eq(1f), eq(true));
@@ -162,103 +242,63 @@
@Test
public void testSetExpandedHeight() {
- mNotificationPanelView.setExpandedHeight(200);
- assertThat((int) mNotificationPanelView.getExpandedHeight()).isEqualTo(200);
+ mNotificationPanelViewController.setExpandedHeight(200);
+ assertThat((int) mNotificationPanelViewController.getExpandedHeight()).isEqualTo(200);
}
@Test
public void testAffordanceLaunchingListener() {
Consumer<Boolean> listener = spy((showing) -> { });
- mNotificationPanelView.setExpandedFraction(1f);
- mNotificationPanelView.setLaunchAffordanceListener(listener);
- mNotificationPanelView.launchCamera(false /* animate */,
+ mNotificationPanelViewController.setExpandedFraction(1f);
+ mNotificationPanelViewController.setLaunchAffordanceListener(listener);
+ mNotificationPanelViewController.launchCamera(false /* animate */,
StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP);
verify(listener).accept(eq(true));
- mNotificationPanelView.onAffordanceLaunchEnded();
+ mNotificationPanelViewController.onAffordanceLaunchEnded();
verify(listener).accept(eq(false));
}
@Test
public void testOnTouchEvent_expansionCanBeBlocked() {
- mNotificationPanelView.onTouchEvent(MotionEvent.obtain(0L /* downTime */,
+ onTouchEvent(MotionEvent.obtain(0L /* downTime */,
0L /* eventTime */, MotionEvent.ACTION_DOWN, 0f /* x */, 0f /* y */,
0 /* metaState */));
- mNotificationPanelView.onTouchEvent(MotionEvent.obtain(0L /* downTime */,
+ onTouchEvent(MotionEvent.obtain(0L /* downTime */,
0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 200f /* y */,
0 /* metaState */));
- assertThat((int) mNotificationPanelView.getExpandedHeight()).isEqualTo(200);
- assertThat(mNotificationPanelView.isTrackingBlocked()).isFalse();
+ assertThat((int) mNotificationPanelViewController.getExpandedHeight()).isEqualTo(200);
+ assertThat(mNotificationPanelViewController.isTrackingBlocked()).isFalse();
- mNotificationPanelView.blockExpansionForCurrentTouch();
- mNotificationPanelView.onTouchEvent(MotionEvent.obtain(0L /* downTime */,
+ mNotificationPanelViewController.blockExpansionForCurrentTouch();
+ onTouchEvent(MotionEvent.obtain(0L /* downTime */,
0L /* eventTime */, MotionEvent.ACTION_MOVE, 0f /* x */, 300f /* y */,
0 /* metaState */));
// Expansion should not have changed because it was blocked
- assertThat((int) mNotificationPanelView.getExpandedHeight()).isEqualTo(200);
- assertThat(mNotificationPanelView.isTrackingBlocked()).isTrue();
+ assertThat((int) mNotificationPanelViewController.getExpandedHeight()).isEqualTo(200);
+ assertThat(mNotificationPanelViewController.isTrackingBlocked()).isTrue();
- mNotificationPanelView.onTouchEvent(MotionEvent.obtain(0L /* downTime */,
+ onTouchEvent(MotionEvent.obtain(0L /* downTime */,
0L /* eventTime */, MotionEvent.ACTION_UP, 0f /* x */, 300f /* y */,
0 /* metaState */));
- assertThat(mNotificationPanelView.isTrackingBlocked()).isFalse();
+ assertThat(mNotificationPanelViewController.isTrackingBlocked()).isFalse();
}
@Test
public void testKeyguardStatusBarVisibility_hiddenForBypass() {
when(mUpdateMonitor.shouldListenForFace()).thenReturn(true);
- mNotificationPanelView.mKeyguardUpdateCallback.onBiometricRunningStateChanged(true,
- BiometricSourceType.FACE);
+ mNotificationPanelViewController.mKeyguardUpdateCallback.onBiometricRunningStateChanged(
+ true, BiometricSourceType.FACE);
verify(mKeyguardStatusBar, never()).setVisibility(View.VISIBLE);
when(mKeyguardBypassController.getBypassEnabled()).thenReturn(true);
- mNotificationPanelView.mKeyguardUpdateCallback.onFinishedGoingToSleep(0);
- mNotificationPanelView.mKeyguardUpdateCallback.onBiometricRunningStateChanged(true,
- BiometricSourceType.FACE);
+ mNotificationPanelViewController.mKeyguardUpdateCallback.onFinishedGoingToSleep(0);
+ mNotificationPanelViewController.mKeyguardUpdateCallback.onBiometricRunningStateChanged(
+ true, BiometricSourceType.FACE);
verify(mKeyguardStatusBar, never()).setVisibility(View.VISIBLE);
}
- private class TestableNotificationPanelView extends NotificationPanelView {
- TestableNotificationPanelView(NotificationWakeUpCoordinator coordinator,
- PulseExpansionHandler expansionHandler,
- KeyguardBypassController bypassController,
- SysuiStatusBarStateController statusBarStateController) {
- super(
- NotificationPanelViewTest.this.mContext,
- null,
- new InjectionInflationController(
- SystemUIFactory.getInstance().getRootComponent()),
- coordinator,
- expansionHandler,
- mock(DynamicPrivacyController.class),
- bypassController,
- mFalsingManager,
- mock(PluginManager.class),
- mock(ShadeController.class),
- mock(NotificationLockscreenUserManager.class),
- new NotificationEntryManager(
- mock(NotifLog.class),
- mock(NotificationGroupManager.class),
- mock(NotificationRankingManager.class),
- mock(NotificationEntryManager.KeyguardEnvironment.class)),
- mock(KeyguardStateController.class),
- statusBarStateController,
- mock(DozeLog.class),
- mDozeParameters,
- new CommandQueue(NotificationPanelViewTest.this.mContext));
- mNotificationStackScroller = mNotificationStackScrollLayout;
- mKeyguardStatusView = NotificationPanelViewTest.this.mKeyguardStatusView;
- mKeyguardStatusBar = NotificationPanelViewTest.this.mKeyguardStatusBar;
- mKeyguardBottomArea = NotificationPanelViewTest.this.mKeyguardBottomArea;
- mBigClockContainer = NotificationPanelViewTest.this.mBigClockContainer;
- mQsFrame = NotificationPanelViewTest.this.mQsFrame;
- mAffordanceHelper = NotificationPanelViewTest.this.mAffordanceHelper;
- initDependencies(NotificationPanelViewTest.this.mStatusBar,
- NotificationPanelViewTest.this.mGroupManager,
- NotificationPanelViewTest.this.mNotificationShelf,
- NotificationPanelViewTest.this.mHeadsUpManager,
- NotificationPanelViewTest.this.mNotificationAreaController,
- NotificationPanelViewTest.this.mScrimController);
- }
+ private void onTouchEvent(MotionEvent ev) {
+ mTouchHandler.onTouch(mView, ev);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index b27e84a..5b5eaad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -74,7 +74,7 @@
@Mock
private ViewGroup mContainer;
@Mock
- private NotificationPanelView mNotificationPanelView;
+ private NotificationPanelViewController mNotificationPanelView;
@Mock
private BiometricUnlockController mBiometrucUnlockController;
@Mock
@@ -281,12 +281,12 @@
@Override
public void registerStatusBar(StatusBar statusBar, ViewGroup container,
- NotificationPanelView notificationPanelView,
+ NotificationPanelViewController notificationPanelViewController,
BiometricUnlockController fingerprintUnlockController,
DismissCallbackRegistry dismissCallbackRegistry,
ViewGroup lockIconContainer, View notificationContainer,
KeyguardBypassController bypassController, FalsingManager falsingManager) {
- super.registerStatusBar(statusBar, container, notificationPanelView,
+ super.registerStatusBar(statusBar, container, notificationPanelViewController,
fingerprintUnlockController, dismissCallbackRegistry, lockIconContainer,
notificationContainer, bypassController, falsingManager);
mBouncer = StatusBarKeyguardViewManagerTest.this.mBouncer;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index 86b2a44..fea4b8b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -64,7 +64,6 @@
import com.android.systemui.statusbar.NotificationTestHelper;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.SuperStatusBarViewFactory;
import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -124,10 +123,6 @@
private Intent mContentIntentInner;
@Mock
private NotificationActivityStarter mNotificationActivityStarter;
- @Mock
- private SuperStatusBarViewFactory mSuperStatusBarViewFactory;
- @Mock
- private NotificationPanelView mNotificationPanelView;
private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
private NotificationTestHelper mNotificationTestHelper;
@@ -167,8 +162,6 @@
mActiveNotifications.add(mBubbleNotificationRow.getEntry());
when(mEntryManager.getVisibleNotifications()).thenReturn(mActiveNotifications);
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
- when(mSuperStatusBarViewFactory.getNotificationPanelView())
- .thenReturn(mNotificationPanelView);
mNotificationActivityStarter = (new StatusBarNotificationActivityStarter.Builder(
getContext(), mock(CommandQueue.class), () -> mAssistManager,
@@ -182,9 +175,9 @@
mKeyguardStateController,
mock(NotificationInterruptionStateProvider.class), mock(MetricsLogger.class),
mock(LockPatternUtils.class), mHandler, mHandler, mUiBgExecutor,
- mActivityIntentHelper, mBubbleController, mShadeController,
- mSuperStatusBarViewFactory))
+ mActivityIntentHelper, mBubbleController, mShadeController))
.setStatusBar(mStatusBar)
+ .setNotificationPanelViewController(mock(NotificationPanelViewController.class))
.setNotificationPresenter(mock(NotificationPresenter.class))
.setActivityLaunchAnimator(mock(ActivityLaunchAnimator.class))
.build();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index 1296a97..782e14c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -54,7 +54,7 @@
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.collection.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -108,7 +108,7 @@
StatusBarWindowView statusBarWindowView = mock(StatusBarWindowView.class);
when(statusBarWindowView.getResources()).thenReturn(mContext.getResources());
mStatusBarNotificationPresenter = new StatusBarNotificationPresenter(mContext,
- mock(NotificationPanelView.class), mock(HeadsUpManagerPhone.class),
+ mock(NotificationPanelViewController.class), mock(HeadsUpManagerPhone.class),
statusBarWindowView, mock(NotificationListContainerViewGroup.class),
mock(DozeScrimController.class), mock(ScrimController.class),
mock(ActivityLaunchAnimator.class), mock(DynamicPrivacyController.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 1cdba47..fee4852 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -120,10 +120,12 @@
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.collection.init.NewNotifPipeline;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
+import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.phone.dagger.StatusBarComponent;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -172,6 +174,7 @@
@Mock private KeyguardIndicationController mKeyguardIndicationController;
@Mock private NotificationStackScrollLayout mStackScroller;
@Mock private HeadsUpManagerPhone mHeadsUpManager;
+ @Mock private NotificationPanelViewController mNotificationPanelViewController;
@Mock private NotificationPanelView mNotificationPanelView;
@Mock private IStatusBarService mBarService;
@Mock private IDreamManager mDreamManager;
@@ -211,7 +214,7 @@
@Mock private NotificationWakeUpCoordinator mNotificationWakeUpCoordinator;
@Mock private KeyguardBypassController mKeyguardBypassController;
@Mock private DynamicPrivacyController mDynamicPrivacyController;
- @Mock private NewNotifPipeline mNewNotifPipeline;
+ @Mock private NotifPipelineInitializer mNewNotifPipeline;
@Mock private ZenModeController mZenModeController;
@Mock private AutoHideController mAutoHideController;
@Mock private NotificationViewHierarchyManager mNotificationViewHierarchyManager;
@@ -253,6 +256,7 @@
@Mock private KeyguardDismissUtil mKeyguardDismissUtil;
@Mock private ExtensionController mExtensionController;
@Mock private UserInfoControllerImpl mUserInfoControllerImpl;
+ @Mock private NotificationRowBinderImpl mNotificationRowBinder;
private ShadeController mShadeController;
private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
private InitController mInitController = new InitController();
@@ -285,6 +289,7 @@
mContext.setTheme(R.style.Theme_SystemUI_Light);
when(mStackScroller.generateLayoutParams(any())).thenReturn(new LayoutParams(0, 0));
+ when(mNotificationPanelViewController.getView()).thenReturn(mNotificationPanelView);
when(mNotificationPanelView.getLayoutParams()).thenReturn(new LayoutParams(0, 0));
when(powerManagerService.isInteractive()).thenReturn(true);
when(mStackScroller.getActivatedChild()).thenReturn(null);
@@ -341,7 +346,6 @@
mHeadsUpManager,
mDynamicPrivacyController,
mBypassHeadsUpNotifier,
- true,
() -> mNewNotifPipeline,
new FalsingManagerFake(),
mBroadcastDispatcher,
@@ -410,13 +414,14 @@
mKeyguardDismissUtil,
mExtensionController,
mUserInfoControllerImpl,
+ mNotificationRowBinder,
mDismissCallbackRegistry);
when(mStatusBarWindowView.findViewById(R.id.lock_icon_container)).thenReturn(
mLockIconContainer);
when(mKeyguardViewMediator.registerStatusBar(any(StatusBar.class), any(ViewGroup.class),
- any(NotificationPanelView.class), any(BiometricUnlockController.class),
+ any(NotificationPanelViewController.class), any(BiometricUnlockController.class),
any(ViewGroup.class), any(ViewGroup.class), any(KeyguardBypassController.class)))
.thenReturn(mStatusBarKeyguardViewManager);
@@ -426,7 +431,7 @@
// TODO: we should be able to call mStatusBar.start() and have all the below values
// initialized automatically.
mStatusBar.mStatusBarWindow = mStatusBarWindowView;
- mStatusBar.mNotificationPanel = mNotificationPanelView;
+ mStatusBar.mNotificationPanelViewController = mNotificationPanelViewController;
mStatusBar.mDozeScrimController = mDozeScrimController;
mStatusBar.mNotificationIconAreaController = mNotificationIconAreaController;
mStatusBar.mPresenter = mNotificationPresenter;
@@ -731,20 +736,20 @@
when(mCommandQueue.panelsEnabled()).thenReturn(false);
mStatusBar.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE,
StatusBarManager.DISABLE2_NOTIFICATION_SHADE, false);
- verify(mNotificationPanelView).setQsExpansionEnabled(false);
+ verify(mNotificationPanelViewController).setQsExpansionEnabled(false);
mStatusBar.animateExpandNotificationsPanel();
- verify(mNotificationPanelView, never()).expand(anyBoolean());
+ verify(mNotificationPanelViewController, never()).expand(anyBoolean());
mStatusBar.animateExpandSettingsPanel(null);
- verify(mNotificationPanelView, never()).expand(anyBoolean());
+ verify(mNotificationPanelViewController, never()).expand(anyBoolean());
when(mCommandQueue.panelsEnabled()).thenReturn(true);
mStatusBar.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NONE,
StatusBarManager.DISABLE2_NONE, false);
- verify(mNotificationPanelView).setQsExpansionEnabled(true);
+ verify(mNotificationPanelViewController).setQsExpansionEnabled(true);
mStatusBar.animateExpandNotificationsPanel();
- verify(mNotificationPanelView).expandWithoutQs();
+ verify(mNotificationPanelViewController).expandWithoutQs();
mStatusBar.animateExpandSettingsPanel(null);
- verify(mNotificationPanelView).expandWithQs();
+ verify(mNotificationPanelViewController).expandWithQs();
}
@Test
@@ -834,12 +839,12 @@
when(mDozeServiceHost.getDozingRequested()).thenReturn(true);
mStatusBar.updateIsKeyguard();
// TODO: mNotificationPanelView.expand(false) gets called twice. Should be once.
- verify(mNotificationPanelView, times(2)).expand(eq(false));
- clearInvocations(mNotificationPanelView);
+ verify(mNotificationPanelViewController, times(2)).expand(eq(false));
+ clearInvocations(mNotificationPanelViewController);
mStatusBar.mWakefulnessObserver.onStartedWakingUp();
verify(mDozeServiceHost).stopDozing();
- verify(mNotificationPanelView).expand(eq(false));
+ verify(mNotificationPanelViewController).expand(eq(false));
}
@Test
@@ -848,11 +853,11 @@
when(mStatusBarStateController.isKeyguardRequested()).thenReturn(true);
when(mDozeServiceHost.getDozingRequested()).thenReturn(true);
mStatusBar.updateIsKeyguard();
- clearInvocations(mNotificationPanelView);
+ clearInvocations(mNotificationPanelViewController);
mStatusBar.setBouncerShowing(true);
mStatusBar.mWakefulnessObserver.onStartedWakingUp();
- verify(mNotificationPanelView, never()).expand(anyBoolean());
+ verify(mNotificationPanelViewController, never()).expand(anyBoolean());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowViewTest.java
index 9f899ee..f9848f3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarWindowViewTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.phone;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -26,6 +27,7 @@
import androidx.test.filters.SmallTest;
+import com.android.systemui.R;
import com.android.systemui.SystemUIFactory;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingManagerFake;
@@ -40,6 +42,7 @@
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.InjectionInflationController;
@@ -74,12 +77,16 @@
@Mock private DozeLog mDozeLog;
@Mock private DozeParameters mDozeParameters;
@Mock private DockManager mDockManager;
+ @Mock private NotificationPanelViewController mNotificationPanelViewController;
+ @Mock private NotificationStackScrollLayout mNotificationStackScrollLayout;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mView = new StatusBarWindowView(getContext(), null);
+ mView = spy(new StatusBarWindowView(getContext(), null));
+ when(mView.findViewById(R.id.notification_stack_scroller))
+ .thenReturn(mNotificationStackScrollLayout);
when(mStatusBarStateController.isDozing()).thenReturn(false);
mDependency.injectTestDependency(ShadeController.class, mShadeController);
@@ -104,7 +111,8 @@
new CommandQueue(mContext),
mShadeController,
mDockManager,
- mView);
+ mView,
+ mNotificationPanelViewController);
mController.setupExpandedStatusBar();
mController.setService(mStatusBar);
mController.setDragDownHelper(mDragDownHelper);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
index c4caeb3..13f3a5f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerBaseTest.java
@@ -16,6 +16,9 @@
package com.android.systemui.statusbar.policy;
+import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
+import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
+
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
@@ -39,6 +42,7 @@
import android.os.Handler;
import android.provider.Settings;
import android.provider.Settings.Global;
+import android.telephony.NetworkRegistrationInfo;
import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
@@ -180,7 +184,7 @@
protected void setupNetworkController() {
// For now just pretend to be the data sim, so we can test that too.
mSubId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
- when(mMockTm.isDataCapable()).thenReturn(true);
+ when(mMockTm.isDataConnectionEnabled()).thenReturn(true);
setDefaultSubId(mSubId);
setSubscriptions(mSubId);
mMobileSignalController = mNetworkController.mMobileSignalControllers.get(mSubId);
@@ -358,7 +362,13 @@
}
public void updateDataConnectionState(int dataState, int dataNetType) {
- when(mServiceState.getDataNetworkType()).thenReturn(dataNetType);
+ NetworkRegistrationInfo fakeRegInfo = new NetworkRegistrationInfo.Builder()
+ .setTransportType(TRANSPORT_TYPE_WWAN)
+ .setDomain(DOMAIN_PS)
+ .setAccessNetworkTechnology(dataNetType)
+ .build();
+ when(mServiceState.getNetworkRegistrationInfo(DOMAIN_PS, TRANSPORT_TYPE_WWAN))
+ .thenReturn(fakeRegInfo);
mPhoneStateListener.onDataConnectionStateChanged(dataState, dataNetType);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
index 95b055c..f6c750d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerDataTest.java
@@ -1,5 +1,8 @@
package com.android.systemui.statusbar.policy;
+import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
+import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
+
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.anyInt;
@@ -121,7 +124,7 @@
@Test
public void testNoInternetIcon_withDefaultSub() {
setupNetworkController();
- when(mMockTm.isDataCapable()).thenReturn(false);
+ when(mMockTm.isDataConnectionEnabled()).thenReturn(false);
setupDefaultSignal();
updateDataConnectionState(TelephonyManager.DATA_CONNECTED, 0);
setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
@@ -135,7 +138,7 @@
@Test
public void testDataDisabledIcon_withDefaultSub() {
setupNetworkController();
- when(mMockTm.isDataCapable()).thenReturn(false);
+ when(mMockTm.isDataConnectionEnabled()).thenReturn(false);
setupDefaultSignal();
updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED, 0);
setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
@@ -149,7 +152,7 @@
@Test
public void testNonDefaultSIM_showsFullSignal_connected() {
setupNetworkController();
- when(mMockTm.isDataCapable()).thenReturn(false);
+ when(mMockTm.isDataConnectionEnabled()).thenReturn(false);
setupDefaultSignal();
setDefaultSubId(mSubId + 1);
updateDataConnectionState(TelephonyManager.DATA_CONNECTED, 0);
@@ -164,7 +167,7 @@
@Test
public void testNonDefaultSIM_showsFullSignal_disconnected() {
setupNetworkController();
- when(mMockTm.isDataCapable()).thenReturn(false);
+ when(mMockTm.isDataConnectionEnabled()).thenReturn(false);
setupDefaultSignal();
setDefaultSubId(mSubId + 1);
updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED, 0);
@@ -418,7 +421,13 @@
// State from NR_5G to NONE NR_STATE_RESTRICTED, showing corresponding icon
doReturn(NetworkRegistrationInfo.NR_STATE_RESTRICTED).when(mServiceState).getNrState();
- doReturn(TelephonyManager.NETWORK_TYPE_LTE).when(mServiceState).getDataNetworkType();
+ NetworkRegistrationInfo fakeRegInfo = new NetworkRegistrationInfo.Builder()
+ .setTransportType(TRANSPORT_TYPE_WWAN)
+ .setDomain(DOMAIN_PS)
+ .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE)
+ .build();
+ doReturn(fakeRegInfo).when(mServiceState)
+ .getNetworkRegistrationInfo(DOMAIN_PS, TRANSPORT_TYPE_WWAN);
mPhoneStateListener.onDataConnectionStateChanged(TelephonyManager.DATA_CONNECTED,
TelephonyManager.NETWORK_TYPE_LTE);
@@ -429,7 +438,7 @@
@Test
public void testDataDisabledIcon_UserNotSetup() {
setupNetworkController();
- when(mMockTm.isDataCapable()).thenReturn(false);
+ when(mMockTm.isDataConnectionEnabled()).thenReturn(false);
setupDefaultSignal();
updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED, 0);
setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_CELLULAR, false, false);
@@ -444,7 +453,7 @@
@Test
public void testAlwaysShowDataRatIcon() {
setupDefaultSignal();
- when(mMockTm.isDataCapable()).thenReturn(false);
+ when(mMockTm.isDataConnectionEnabled()).thenReturn(false);
updateDataConnectionState(TelephonyManager.DATA_DISCONNECTED,
TelephonyManager.NETWORK_TYPE_GSM);
@@ -480,8 +489,13 @@
verifyDataIndicators(TelephonyIcons.ICON_LTE);
- when(mServiceState.getDataNetworkType())
- .thenReturn(TelephonyManager.NETWORK_TYPE_HSPA);
+ NetworkRegistrationInfo fakeRegInfo = new NetworkRegistrationInfo.Builder()
+ .setTransportType(TRANSPORT_TYPE_WWAN)
+ .setDomain(DOMAIN_PS)
+ .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_HSPA)
+ .build();
+ when(mServiceState.getNetworkRegistrationInfo(DOMAIN_PS, TRANSPORT_TYPE_WWAN))
+ .thenReturn(fakeRegInfo);
updateServiceState();
verifyDataIndicators(TelephonyIcons.ICON_H);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
index 4103d71..cd89d3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerSignalTest.java
@@ -30,12 +30,12 @@
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
-import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.TelephonyIntents;
import com.android.settingslib.graph.SignalDrawable;
import com.android.settingslib.net.DataUsageController;
@@ -418,7 +418,7 @@
intent.putExtra(TelephonyIntents.EXTRA_SHOW_PLMN, showPlmn);
intent.putExtra(TelephonyIntents.EXTRA_PLMN, plmn);
- intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, mSubId);
+ SubscriptionManager.putSubscriptionIdExtra(intent, mSubId);
return intent;
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
index 3451183..32da4c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/NetworkControllerWifiTest.java
@@ -39,7 +39,8 @@
setWifiState(true, testSsid);
setWifiLevel(0);
- verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[0][0]);
+ // Connected, but still not validated - does not show
+ verifyLastWifiIcon(false, WifiIcons.WIFI_SIGNAL_STRENGTH[0][0]);
for (int testLevel = 0; testLevel < WifiIcons.WIFI_LEVEL_COUNT; testLevel++) {
setWifiLevel(testLevel);
@@ -47,7 +48,8 @@
setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, true, true);
verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[1][testLevel]);
setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, false, true);
- verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[0][testLevel]);
+ // Icon does not show if not validated
+ verifyLastWifiIcon(false, WifiIcons.WIFI_SIGNAL_STRENGTH[0][testLevel]);
}
}
@@ -70,7 +72,7 @@
testSsid);
setConnectivityViaBroadcast(NetworkCapabilities.TRANSPORT_WIFI, false, true);
verifyLastQsWifiIcon(true, true, WifiIcons.QS_WIFI_SIGNAL_STRENGTH[0][testLevel],
- testSsid);
+ null);
}
}
@@ -132,7 +134,7 @@
verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[1][testLevel]);
setConnectivityViaCallback(NetworkCapabilities.TRANSPORT_WIFI, false, true);
- verifyLastWifiIcon(true, WifiIcons.WIFI_SIGNAL_STRENGTH[0][testLevel]);
+ verifyLastWifiIcon(false, WifiIcons.WIFI_SIGNAL_STRENGTH[0][testLevel]);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt
index 709a1a8..a785d4b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/animation/PhysicsAnimatorTest.kt
@@ -19,6 +19,7 @@
import org.junit.After
import org.junit.Assert
import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertTrue
import org.junit.Before
@@ -154,6 +155,7 @@
verify(mockEndListener).onAnimationEnd(
testView,
DynamicAnimation.TRANSLATION_X,
+ wasFling = false,
canceled = false,
finalValue = 10f,
finalVelocity = 0f,
@@ -175,6 +177,7 @@
verify(mockEndListener).onAnimationEnd(
testView,
DynamicAnimation.TRANSLATION_Y,
+ wasFling = false,
canceled = false,
finalValue = 500f,
finalVelocity = 0f,
@@ -214,8 +217,8 @@
verifyUpdateListenerCalls(animator, mockUpdateListener)
verify(mockEndListener, times(1)).onAnimationEnd(
- eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), anyFloat(), anyFloat(),
- eq(true))
+ eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), eq(false), anyFloat(),
+ anyFloat(), eq(true))
verify(mockEndAction, times(1)).run()
animator
@@ -329,13 +332,24 @@
// The original end listener should also have been called, with allEnded = true since it was
// provided to an animator that animated only TRANSLATION_X.
verify(mockEndListener, times(1))
- .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_X, false, 200f, 0f, true)
+ .onAnimationEnd(
+ testView, DynamicAnimation.TRANSLATION_X,
+ wasFling = false,
+ canceled = false,
+ finalValue = 200f,
+ finalVelocity = 0f,
+ allRelevantPropertyAnimsEnded = true)
verifyNoMoreInteractions(mockEndListener)
// The second end listener should have been called, but with allEnded = false since it was
// provided to an animator that animated both TRANSLATION_X and TRANSLATION_Y.
verify(secondEndListener, times(1))
- .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_X, false, 200f, 0f, false)
+ .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_X,
+ wasFling = false,
+ canceled = false,
+ finalValue = 200f,
+ finalVelocity = 0f,
+ allRelevantPropertyAnimsEnded = false)
verifyNoMoreInteractions(secondEndListener)
PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(animator, DynamicAnimation.TRANSLATION_Y)
@@ -345,7 +359,12 @@
verifyNoMoreInteractions(mockEndListener)
verify(secondEndListener, times(1))
- .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_Y, false, 4000f, 0f, true)
+ .onAnimationEnd(testView, DynamicAnimation.TRANSLATION_Y,
+ wasFling = false,
+ canceled = false,
+ finalValue = 4000f,
+ finalVelocity = 0f,
+ allRelevantPropertyAnimsEnded = true)
verifyNoMoreInteractions(secondEndListener)
}
@@ -364,8 +383,8 @@
assertEquals(10f, testView.translationX, 1f)
verify(mockEndListener, times(1))
.onAnimationEnd(
- eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), eq(10f),
- anyFloat(), eq(true))
+ eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(true), eq(false),
+ eq(10f), anyFloat(), eq(true))
animator
.fling(
@@ -381,8 +400,39 @@
assertEquals(-5f, testView.translationX, 1f)
verify(mockEndListener, times(1))
.onAnimationEnd(
- eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(false), eq(-5f),
- anyFloat(), eq(true))
+ eq(testView), eq(DynamicAnimation.TRANSLATION_X), eq(true), eq(false),
+ eq(-5f), anyFloat(), eq(true))
+ }
+
+ @Test
+ fun testIsPropertyAnimating() {
+ PhysicsAnimatorTestUtils.setAllAnimationsBlock(false)
+
+ testView.physicsAnimator
+ .spring(DynamicAnimation.TRANSLATION_X, 500f, springConfig)
+ .fling(DynamicAnimation.TRANSLATION_Y, 10f, flingConfig)
+ .spring(DynamicAnimation.TRANSLATION_Z, 1000f, springConfig)
+ .start()
+
+ // All of the properties we just started should be animating.
+ assertTrue(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_X))
+ assertTrue(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Y))
+ assertTrue(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Z))
+
+ // Block until x and y end.
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(testView.physicsAnimator,
+ DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y)
+
+ // Verify that x and y are no longer animating, but that Z is (it's springing to 1000f).
+ assertFalse(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_X))
+ assertFalse(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Y))
+ assertTrue(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Z))
+
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_Z)
+
+ assertFalse(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_X))
+ assertFalse(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Y))
+ assertFalse(testView.physicsAnimator.isPropertyAnimating(DynamicAnimation.TRANSLATION_Z))
}
@Test
@@ -395,6 +445,118 @@
assertEquals(200f, testView.translationX, 1f)
}
+ @Test
+ fun testFlingThenSpring() {
+ PhysicsAnimatorTestUtils.setAllAnimationsBlock(false)
+
+ // Start at 500f and fling hard to the left. We should quickly reach the 250f minimum, fly
+ // past it since there's so much velocity remaining, then spring back to 250f.
+ testView.translationX = 500f
+ animator
+ .flingThenSpring(
+ DynamicAnimation.TRANSLATION_X,
+ -5000f,
+ flingConfig.apply { min = 250f },
+ springConfig)
+ .addUpdateListener(mockUpdateListener)
+ .addEndListener(mockEndListener)
+ .withEndActions(mockEndAction::run)
+ .start()
+
+ // Block until we pass the minimum.
+ PhysicsAnimatorTestUtils.blockUntilFirstAnimationFrameWhereTrue(
+ animator) { v -> v.translationX <= 250f }
+
+ // Double check that the view is there.
+ assertTrue(testView.translationX <= 250f)
+
+ // The update listener should have been called with a value < 500f, and then a value less
+ // than or equal to the 250f minimum.
+ verifyAnimationUpdateFrames(animator, DynamicAnimation.TRANSLATION_X,
+ { u -> u.value < 500f },
+ { u -> u.value <= 250f })
+
+ // Despite the fact that the fling has ended, the end listener shouldn't have been called
+ // since we're about to begin springing the same property.
+ verifyNoMoreInteractions(mockEndListener)
+ verifyNoMoreInteractions(mockEndAction)
+
+ // Wait for the spring to finish.
+ PhysicsAnimatorTestUtils.blockUntilAnimationsEnd(DynamicAnimation.TRANSLATION_X)
+
+ // Make sure we continued past 250f since the spring should have been started with some
+ // remaining negative velocity from the fling.
+ verifyAnimationUpdateFrames(animator, DynamicAnimation.TRANSLATION_X,
+ { u -> u.value < 250f })
+
+ // At this point, the animation end listener should have been called once, and only once,
+ // when the spring ended at 250f.
+ verify(mockEndListener).onAnimationEnd(testView, DynamicAnimation.TRANSLATION_X,
+ wasFling = false,
+ canceled = false,
+ finalValue = 250f,
+ finalVelocity = 0f,
+ allRelevantPropertyAnimsEnded = true)
+ verifyNoMoreInteractions(mockEndListener)
+
+ // The end action should also have been called once.
+ verify(mockEndAction, times(1)).run()
+ verifyNoMoreInteractions(mockEndAction)
+
+ assertEquals(250f, testView.translationX)
+ }
+
+ @Test
+ fun testFlingThenSpring_objectOutsideFlingBounds() {
+ // Start the view at x = -500, well outside the fling bounds of min = 0f, with strong
+ // negative velocity.
+ testView.translationX = -500f
+ animator
+ .flingThenSpring(
+ DynamicAnimation.TRANSLATION_X,
+ -5000f,
+ flingConfig.apply { min = 0f },
+ springConfig)
+ .addUpdateListener(mockUpdateListener)
+ .addEndListener(mockEndListener)
+ .withEndActions(mockEndAction::run)
+ .start()
+
+ // The initial -5000f velocity should result in frames to the left of -500f before the view
+ // springs back towards 0f.
+ verifyAnimationUpdateFrames(
+ animator, DynamicAnimation.TRANSLATION_X,
+ { u -> u.value < -500f },
+ { u -> u.value > -500f })
+
+ // We should end up at the fling min.
+ assertEquals(0f, testView.translationX, 1f)
+ }
+
+ @Test
+ fun testFlingToMinMaxThenSpring() {
+ // Start at x = 500f.
+ testView.translationX = 500f
+
+ // Fling to the left at the very sad rate of -1 pixels per second. That won't get us much of
+ // anywhere, and certainly not to the 0f min.
+ animator
+ // Good thing we have flingToMinMaxThenSpring!
+ .flingThenSpring(
+ DynamicAnimation.TRANSLATION_X,
+ -10000f,
+ flingConfig.apply { min = 0f },
+ springConfig,
+ flingMustReachMinOrMax = true)
+ .addUpdateListener(mockUpdateListener)
+ .addEndListener(mockEndListener)
+ .withEndActions(mockEndAction::run)
+ .start()
+
+ // Thanks, flingToMinMaxThenSpring, for adding enough velocity to get us here.
+ assertEquals(0f, testView.translationX, 1f)
+ }
+
/**
* Verifies that the calls to the mock update listener match the animation update frames
* reported by the test internal listener, in order.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index 2854665..80aa6f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -69,7 +69,7 @@
@Test
public void testRegisteredWithDispatcher() {
- verify(mBroadcastDispatcher).registerReceiver(any(BroadcastReceiver.class),
+ verify(mBroadcastDispatcher).registerReceiverWithHandler(any(BroadcastReceiver.class),
any(IntentFilter.class),
any(Handler.class)); // VolumeDialogControllerImpl does not call with user
}
diff --git a/packages/Tethering/Android.bp b/packages/Tethering/Android.bp
index 08552cb..e441fb5 100644
--- a/packages/Tethering/Android.bp
+++ b/packages/Tethering/Android.bp
@@ -16,11 +16,11 @@
java_defaults {
name: "TetheringAndroidLibraryDefaults",
- platform_apis: true,
+ sdk_version: "system_current",
srcs: [
"src/**/*.java",
":framework-tethering-shared-srcs",
- ":net-module-utils-srcs",
+ ":tethering-module-utils-srcs",
":services-tethering-shared-srcs",
],
static_libs: [
@@ -29,9 +29,11 @@
"netlink-client",
"networkstack-aidl-interfaces-unstable-java",
"android.hardware.tetheroffload.control-V1.0-java",
+ "net-utils-framework-common",
],
libs: [
"framework-tethering",
+ "unsupportedappusage",
],
manifest: "AndroidManifestBase.xml",
@@ -45,9 +47,9 @@
// Due to b/143733063, APK can't access a jni lib that is in APEX (but not in the APK).
cc_library {
- name: "libtetheroffloadjni",
+ name: "libtetherutilsjni",
srcs: [
- "jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp",
+ "jni/android_net_util_TetheringUtils.cpp",
],
shared_libs: [
"libcgrouprc",
@@ -79,7 +81,7 @@
// Common defaults for compiling the actual APK.
java_defaults {
name: "TetheringAppDefaults",
- platform_apis: true,
+ sdk_version: "system_current",
privileged: true,
// Build system doesn't track transitive dependeicies for jni_libs, list all the dependencies
// explicitly.
@@ -87,7 +89,7 @@
"libcgrouprc",
"libnativehelper_compat_libc++",
"libvndksupport",
- "libtetheroffloadjni",
+ "libtetherutilsjni",
],
resource_dirs: [
"res",
@@ -122,4 +124,5 @@
use_embedded_native_libs: true,
// The permission configuration *must* be included to ensure security of the device
required: ["NetworkPermissionConfig"],
+ apex_available: ["com.android.tethering"],
}
diff --git a/packages/Tethering/AndroidManifest.xml b/packages/Tethering/AndroidManifest.xml
index 87a8c3f..c71d0d7 100644
--- a/packages/Tethering/AndroidManifest.xml
+++ b/packages/Tethering/AndroidManifest.xml
@@ -32,8 +32,11 @@
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.MANAGE_USB" />
<uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
+ <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.READ_NETWORK_USAGE_HISTORY" />
+ <uses-permission android:name="android.permission.TETHER_PRIVILEGED" />
<uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
+ <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<application
diff --git a/packages/Tethering/common/TetheringLib/Android.bp b/packages/Tethering/common/TetheringLib/Android.bp
index 5785707..264ce44 100644
--- a/packages/Tethering/common/TetheringLib/Android.bp
+++ b/packages/Tethering/common/TetheringLib/Android.bp
@@ -47,6 +47,16 @@
libs: [
"android_system_stubs_current",
],
+
+ hostdex: true, // for hiddenapi check
+ visibility: [
+ "//frameworks/base/packages/Tethering:__subpackages__",
+ //TODO(b/147200698) remove below lines when the platform is built with stubs
+ "//frameworks/base",
+ "//frameworks/base/services",
+ "//frameworks/base/services/core",
+ ],
+ apex_available: ["com.android.tethering"],
}
filegroup {
diff --git a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
index a49ab85..11e5718 100644
--- a/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
+++ b/packages/Tethering/common/TetheringLib/src/android/net/TetheringManager.java
@@ -15,8 +15,6 @@
*/
package android.net;
-import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
-
import android.annotation.NonNull;
import android.content.Context;
import android.net.ConnectivityManager.OnTetheringEventCallback;
@@ -52,6 +50,103 @@
private TetheringConfigurationParcel mTetheringConfiguration;
private TetherStatesParcel mTetherStatesParcel;
+ /**
+ * Broadcast Action: A tetherable connection has come or gone.
+ * Uses {@code TetheringManager.EXTRA_AVAILABLE_TETHER},
+ * {@code TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY},
+ * {@code TetheringManager.EXTRA_ACTIVE_TETHER}, and
+ * {@code TetheringManager.EXTRA_ERRORED_TETHER} to indicate
+ * the current state of tethering. Each include a list of
+ * interface names in that state (may be empty).
+ */
+ public static final String ACTION_TETHER_STATE_CHANGED =
+ "android.net.conn.TETHER_STATE_CHANGED";
+
+ /**
+ * gives a String[] listing all the interfaces configured for
+ * tethering and currently available for tethering.
+ */
+ public static final String EXTRA_AVAILABLE_TETHER = "availableArray";
+
+ /**
+ * gives a String[] listing all the interfaces currently in local-only
+ * mode (ie, has DHCPv4+IPv6-ULA support and no packet forwarding)
+ */
+ public static final String EXTRA_ACTIVE_LOCAL_ONLY = "localOnlyArray";
+
+ /**
+ * gives a String[] listing all the interfaces currently tethered
+ * (ie, has DHCPv4 support and packets potentially forwarded/NATed)
+ */
+ public static final String EXTRA_ACTIVE_TETHER = "tetherArray";
+
+ /**
+ * gives a String[] listing all the interfaces we tried to tether and
+ * failed. Use {@link #getLastTetherError} to find the error code
+ * for any interfaces listed here.
+ */
+ public static final String EXTRA_ERRORED_TETHER = "erroredArray";
+
+ /**
+ * Invalid tethering type.
+ * @see #startTethering.
+ */
+ public static final int TETHERING_INVALID = -1;
+
+ /**
+ * Wifi tethering type.
+ * @see #startTethering.
+ */
+ public static final int TETHERING_WIFI = 0;
+
+ /**
+ * USB tethering type.
+ * @see #startTethering.
+ */
+ public static final int TETHERING_USB = 1;
+
+ /**
+ * Bluetooth tethering type.
+ * @see #startTethering.
+ */
+ public static final int TETHERING_BLUETOOTH = 2;
+
+ /**
+ * Wifi P2p tethering type.
+ * Wifi P2p tethering is set through events automatically, and don't
+ * need to start from #startTethering.
+ */
+ public static final int TETHERING_WIFI_P2P = 3;
+
+ /**
+ * Extra used for communicating with the TetherService. Includes the type of tethering to
+ * enable if any.
+ */
+ public static final String EXTRA_ADD_TETHER_TYPE = "extraAddTetherType";
+
+ /**
+ * Extra used for communicating with the TetherService. Includes the type of tethering for
+ * which to cancel provisioning.
+ */
+ public static final String EXTRA_REM_TETHER_TYPE = "extraRemTetherType";
+
+ /**
+ * Extra used for communicating with the TetherService. True to schedule a recheck of tether
+ * provisioning.
+ */
+ public static final String EXTRA_SET_ALARM = "extraSetAlarm";
+
+ /**
+ * Tells the TetherService to run a provision check now.
+ */
+ public static final String EXTRA_RUN_PROVISION = "extraRunProvision";
+
+ /**
+ * Extra used for communicating with the TetherService. Contains the {@link ResultReceiver}
+ * which will receive provisioning results. Can be left empty.
+ */
+ public static final String EXTRA_PROVISION_CALLBACK = "extraProvisionCallback";
+
public static final int TETHER_ERROR_NO_ERROR = 0;
public static final int TETHER_ERROR_UNKNOWN_IFACE = 1;
public static final int TETHER_ERROR_SERVICE_UNAVAIL = 2;
@@ -470,7 +565,7 @@
* failed. Re-attempting to tether may cause them to reset to the Tethered
* state. Alternatively, causing the interface to be destroyed and recreated
* may cause them to reset to the available state.
- * {@link ConnectivityManager#getLastTetherError} can be used to get more
+ * {@link TetheringManager#getLastTetherError} can be used to get more
* information on the cause of the errors.
*
* @return an array of 0 or more String indicating the interface names
diff --git a/packages/Tethering/jarjar-rules.txt b/packages/Tethering/jarjar-rules.txt
index dd9eab7..c6efa41 100644
--- a/packages/Tethering/jarjar-rules.txt
+++ b/packages/Tethering/jarjar-rules.txt
@@ -11,5 +11,8 @@
rule com.android.internal.util.Preconditions* com.android.networkstack.tethering.util.Preconditions@1
rule com.android.internal.util.State* com.android.networkstack.tethering.util.State@1
rule com.android.internal.util.StateMachine* com.android.networkstack.tethering.util.StateMachine@1
+rule com.android.internal.util.TrafficStatsConstants* com.android.networkstack.tethering.util.TrafficStatsConstants@1
rule android.net.LocalLog* com.android.networkstack.tethering.LocalLog@1
+
+rule android.net.shared.Inet4AddressUtils* com.android.networkstack.tethering.shared.Inet4AddressUtils@1
diff --git a/packages/Tethering/jni/android_net_util_TetheringUtils.cpp b/packages/Tethering/jni/android_net_util_TetheringUtils.cpp
new file mode 100644
index 0000000..1cf8f98
--- /dev/null
+++ b/packages/Tethering/jni/android_net_util_TetheringUtils.cpp
@@ -0,0 +1,256 @@
+/*
+ * 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.
+ */
+
+#include <errno.h>
+#include <error.h>
+#include <hidl/HidlSupport.h>
+#include <jni.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedUtfChars.h>
+#include <linux/netfilter/nfnetlink.h>
+#include <linux/netlink.h>
+#include <net/if.h>
+#include <netinet/icmp6.h>
+#include <sys/socket.h>
+#include <android-base/unique_fd.h>
+#include <android/hardware/tetheroffload/config/1.0/IOffloadConfig.h>
+
+#define LOG_TAG "TetheringUtils"
+#include <utils/Log.h>
+
+namespace android {
+
+using hardware::hidl_handle;
+using hardware::hidl_string;
+using hardware::tetheroffload::config::V1_0::IOffloadConfig;
+
+namespace {
+
+inline const sockaddr * asSockaddr(const sockaddr_nl *nladdr) {
+ return reinterpret_cast<const sockaddr *>(nladdr);
+}
+
+int conntrackSocket(unsigned groups) {
+ base::unique_fd s(socket(AF_NETLINK, SOCK_DGRAM, NETLINK_NETFILTER));
+ if (s.get() < 0) return -errno;
+
+ const struct sockaddr_nl bind_addr = {
+ .nl_family = AF_NETLINK,
+ .nl_pad = 0,
+ .nl_pid = 0,
+ .nl_groups = groups,
+ };
+ if (bind(s.get(), asSockaddr(&bind_addr), sizeof(bind_addr)) != 0) {
+ return -errno;
+ }
+
+ const struct sockaddr_nl kernel_addr = {
+ .nl_family = AF_NETLINK,
+ .nl_pad = 0,
+ .nl_pid = 0,
+ .nl_groups = groups,
+ };
+ if (connect(s.get(), asSockaddr(&kernel_addr), sizeof(kernel_addr)) != 0) {
+ return -errno;
+ }
+
+ return s.release();
+}
+
+// Return a hidl_handle that owns the file descriptor owned by fd, and will
+// auto-close it (otherwise there would be double-close problems).
+//
+// Rely upon the compiler to eliminate the constexprs used for clarity.
+hidl_handle handleFromFileDescriptor(base::unique_fd fd) {
+ hidl_handle h;
+
+ static constexpr int kNumFds = 1;
+ static constexpr int kNumInts = 0;
+ native_handle_t *nh = native_handle_create(kNumFds, kNumInts);
+ nh->data[0] = fd.release();
+
+ static constexpr bool kTakeOwnership = true;
+ h.setTo(nh, kTakeOwnership);
+
+ return h;
+}
+
+} // namespace
+
+static jboolean android_net_util_configOffload(
+ JNIEnv* /* env */) {
+ sp<IOffloadConfig> configInterface = IOffloadConfig::getService();
+ if (configInterface.get() == nullptr) {
+ ALOGD("Could not find IOffloadConfig service.");
+ return false;
+ }
+
+ // Per the IConfigOffload definition:
+ //
+ // fd1 A file descriptor bound to the following netlink groups
+ // (NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY).
+ //
+ // fd2 A file descriptor bound to the following netlink groups
+ // (NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY).
+ base::unique_fd
+ fd1(conntrackSocket(NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY)),
+ fd2(conntrackSocket(NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY));
+ if (fd1.get() < 0 || fd2.get() < 0) {
+ ALOGE("Unable to create conntrack handles: %d/%s", errno, strerror(errno));
+ return false;
+ }
+
+ hidl_handle h1(handleFromFileDescriptor(std::move(fd1))),
+ h2(handleFromFileDescriptor(std::move(fd2)));
+
+ bool rval(false);
+ hidl_string msg;
+ const auto status = configInterface->setHandles(h1, h2,
+ [&rval, &msg](bool success, const hidl_string& errMsg) {
+ rval = success;
+ msg = errMsg;
+ });
+ if (!status.isOk() || !rval) {
+ ALOGE("IOffloadConfig::setHandles() error: '%s' / '%s'",
+ status.description().c_str(), msg.c_str());
+ // If status is somehow not ok, make sure rval captures this too.
+ rval = false;
+ }
+
+ return rval;
+}
+
+static void android_net_util_setupRaSocket(JNIEnv *env, jobject clazz, jobject javaFd,
+ jint ifIndex)
+{
+ static const int kLinkLocalHopLimit = 255;
+
+ int fd = jniGetFDFromFileDescriptor(env, javaFd);
+
+ // Set an ICMPv6 filter that only passes Router Solicitations.
+ struct icmp6_filter rs_only;
+ ICMP6_FILTER_SETBLOCKALL(&rs_only);
+ ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &rs_only);
+ socklen_t len = sizeof(rs_only);
+ if (setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, &rs_only, len) != 0) {
+ jniThrowExceptionFmt(env, "java/net/SocketException",
+ "setsockopt(ICMP6_FILTER): %s", strerror(errno));
+ return;
+ }
+
+ // Most/all of the rest of these options can be set via Java code, but
+ // because we're here on account of setting an icmp6_filter go ahead
+ // and do it all natively for now.
+
+ // Set the multicast hoplimit to 255 (link-local only).
+ int hops = kLinkLocalHopLimit;
+ len = sizeof(hops);
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &hops, len) != 0) {
+ jniThrowExceptionFmt(env, "java/net/SocketException",
+ "setsockopt(IPV6_MULTICAST_HOPS): %s", strerror(errno));
+ return;
+ }
+
+ // Set the unicast hoplimit to 255 (link-local only).
+ hops = kLinkLocalHopLimit;
+ len = sizeof(hops);
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &hops, len) != 0) {
+ jniThrowExceptionFmt(env, "java/net/SocketException",
+ "setsockopt(IPV6_UNICAST_HOPS): %s", strerror(errno));
+ return;
+ }
+
+ // Explicitly disable multicast loopback.
+ int off = 0;
+ len = sizeof(off);
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &off, len) != 0) {
+ jniThrowExceptionFmt(env, "java/net/SocketException",
+ "setsockopt(IPV6_MULTICAST_LOOP): %s", strerror(errno));
+ return;
+ }
+
+ // Specify the IPv6 interface to use for outbound multicast.
+ len = sizeof(ifIndex);
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &ifIndex, len) != 0) {
+ jniThrowExceptionFmt(env, "java/net/SocketException",
+ "setsockopt(IPV6_MULTICAST_IF): %s", strerror(errno));
+ return;
+ }
+
+ // Additional options to be considered:
+ // - IPV6_TCLASS
+ // - IPV6_RECVPKTINFO
+ // - IPV6_RECVHOPLIMIT
+
+ // Bind to [::].
+ const struct sockaddr_in6 sin6 = {
+ .sin6_family = AF_INET6,
+ .sin6_port = 0,
+ .sin6_flowinfo = 0,
+ .sin6_addr = IN6ADDR_ANY_INIT,
+ .sin6_scope_id = 0,
+ };
+ auto sa = reinterpret_cast<const struct sockaddr *>(&sin6);
+ len = sizeof(sin6);
+ if (bind(fd, sa, len) != 0) {
+ jniThrowExceptionFmt(env, "java/net/SocketException",
+ "bind(IN6ADDR_ANY): %s", strerror(errno));
+ return;
+ }
+
+ // Join the all-routers multicast group, ff02::2%index.
+ struct ipv6_mreq all_rtrs = {
+ .ipv6mr_multiaddr = {{{0xff,2,0,0,0,0,0,0,0,0,0,0,0,0,0,2}}},
+ .ipv6mr_interface = ifIndex,
+ };
+ len = sizeof(all_rtrs);
+ if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, &all_rtrs, len) != 0) {
+ jniThrowExceptionFmt(env, "java/net/SocketException",
+ "setsockopt(IPV6_JOIN_GROUP): %s", strerror(errno));
+ return;
+ }
+}
+
+/*
+ * JNI registration.
+ */
+static const JNINativeMethod gMethods[] = {
+ /* name, signature, funcPtr */
+ { "configOffload", "()Z", (void*) android_net_util_configOffload },
+ { "setupRaSocket", "(Ljava/io/FileDescriptor;I)V", (void*) android_net_util_setupRaSocket },
+};
+
+int register_android_net_util_TetheringUtils(JNIEnv* env) {
+ return jniRegisterNativeMethods(env,
+ "android/net/util/TetheringUtils",
+ gMethods, NELEM(gMethods));
+}
+
+extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
+ JNIEnv *env;
+ if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ ALOGE("ERROR: GetEnv failed");
+ return JNI_ERR;
+ }
+
+ if (register_android_net_util_TetheringUtils(env) < 0) {
+ return JNI_ERR;
+ }
+
+ return JNI_VERSION_1_6;
+}
+
+}; // namespace android
diff --git a/packages/Tethering/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp b/packages/Tethering/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp
deleted file mode 100644
index 663154a..0000000
--- a/packages/Tethering/jni/com_android_server_connectivity_tethering_OffloadHardwareInterface.cpp
+++ /dev/null
@@ -1,162 +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.
- */
-
-#include <errno.h>
-#include <error.h>
-#include <hidl/HidlSupport.h>
-#include <jni.h>
-#include <nativehelper/JNIHelp.h>
-#include <linux/netfilter/nfnetlink.h>
-#include <linux/netlink.h>
-#include <sys/socket.h>
-#include <android-base/unique_fd.h>
-#include <android/hardware/tetheroffload/config/1.0/IOffloadConfig.h>
-
-#define LOG_TAG "OffloadHardwareInterface"
-#include <utils/Log.h>
-
-namespace android {
-
-using hardware::hidl_handle;
-using hardware::hidl_string;
-using hardware::tetheroffload::config::V1_0::IOffloadConfig;
-
-namespace {
-
-inline const sockaddr * asSockaddr(const sockaddr_nl *nladdr) {
- return reinterpret_cast<const sockaddr *>(nladdr);
-}
-
-int conntrackSocket(unsigned groups) {
- base::unique_fd s(socket(AF_NETLINK, SOCK_DGRAM, NETLINK_NETFILTER));
- if (s.get() < 0) return -errno;
-
- const struct sockaddr_nl bind_addr = {
- .nl_family = AF_NETLINK,
- .nl_pad = 0,
- .nl_pid = 0,
- .nl_groups = groups,
- };
- if (bind(s.get(), asSockaddr(&bind_addr), sizeof(bind_addr)) != 0) {
- return -errno;
- }
-
- const struct sockaddr_nl kernel_addr = {
- .nl_family = AF_NETLINK,
- .nl_pad = 0,
- .nl_pid = 0,
- .nl_groups = groups,
- };
- if (connect(s.get(), asSockaddr(&kernel_addr), sizeof(kernel_addr)) != 0) {
- return -errno;
- }
-
- return s.release();
-}
-
-// Return a hidl_handle that owns the file descriptor owned by fd, and will
-// auto-close it (otherwise there would be double-close problems).
-//
-// Rely upon the compiler to eliminate the constexprs used for clarity.
-hidl_handle handleFromFileDescriptor(base::unique_fd fd) {
- hidl_handle h;
-
- static constexpr int kNumFds = 1;
- static constexpr int kNumInts = 0;
- native_handle_t *nh = native_handle_create(kNumFds, kNumInts);
- nh->data[0] = fd.release();
-
- static constexpr bool kTakeOwnership = true;
- h.setTo(nh, kTakeOwnership);
-
- return h;
-}
-
-} // namespace
-
-static jboolean android_server_connectivity_tethering_OffloadHardwareInterface_configOffload(
- JNIEnv* /* env */) {
- sp<IOffloadConfig> configInterface = IOffloadConfig::getService();
- if (configInterface.get() == nullptr) {
- ALOGD("Could not find IOffloadConfig service.");
- return false;
- }
-
- // Per the IConfigOffload definition:
- //
- // fd1 A file descriptor bound to the following netlink groups
- // (NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY).
- //
- // fd2 A file descriptor bound to the following netlink groups
- // (NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY).
- base::unique_fd
- fd1(conntrackSocket(NF_NETLINK_CONNTRACK_NEW | NF_NETLINK_CONNTRACK_DESTROY)),
- fd2(conntrackSocket(NF_NETLINK_CONNTRACK_UPDATE | NF_NETLINK_CONNTRACK_DESTROY));
- if (fd1.get() < 0 || fd2.get() < 0) {
- ALOGE("Unable to create conntrack handles: %d/%s", errno, strerror(errno));
- return false;
- }
-
- hidl_handle h1(handleFromFileDescriptor(std::move(fd1))),
- h2(handleFromFileDescriptor(std::move(fd2)));
-
- bool rval(false);
- hidl_string msg;
- const auto status = configInterface->setHandles(h1, h2,
- [&rval, &msg](bool success, const hidl_string& errMsg) {
- rval = success;
- msg = errMsg;
- });
- if (!status.isOk() || !rval) {
- ALOGE("IOffloadConfig::setHandles() error: '%s' / '%s'",
- status.description().c_str(), msg.c_str());
- // If status is somehow not ok, make sure rval captures this too.
- rval = false;
- }
-
- return rval;
-}
-
-/*
- * JNI registration.
- */
-static const JNINativeMethod gMethods[] = {
- /* name, signature, funcPtr */
- { "configOffload", "()Z",
- (void*) android_server_connectivity_tethering_OffloadHardwareInterface_configOffload },
-};
-
-int register_android_server_connectivity_tethering_OffloadHardwareInterface(JNIEnv* env) {
- return jniRegisterNativeMethods(env,
- "com/android/server/connectivity/tethering/OffloadHardwareInterface",
- gMethods, NELEM(gMethods));
-}
-
-extern "C" jint JNI_OnLoad(JavaVM* vm, void*) {
- JNIEnv *env;
- if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
- ALOGE("ERROR: GetEnv failed");
- return JNI_ERR;
- }
-
- if (register_android_server_connectivity_tethering_OffloadHardwareInterface(env) < 0) {
- return JNI_ERR;
- }
-
- return JNI_VERSION_1_6;
-}
-
-}; // namespace android
diff --git a/packages/Tethering/res/values/config.xml b/packages/Tethering/res/values/config.xml
index 37e679d..19e8d69 100644
--- a/packages/Tethering/res/values/config.xml
+++ b/packages/Tethering/res/values/config.xml
@@ -1,7 +1,153 @@
<?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.
+-->
<resources>
<!--
OEMs that wish to change the below settings must do so via a runtime resource overlay package
and *NOT* by changing this file. This file is part of the tethering mainline module.
+ TODO: define two resources for each config item: a default_* resource and a config_* resource,
+ config_* is empty by default but may be overridden by RROs.
-->
+ <!-- List of regexpressions describing the interface (if any) that represent tetherable
+ USB interfaces. If the device doesn't want to support tethering over USB this should
+ be empty. An example would be "usb.*" -->
+ <string-array translatable="false" name="config_tether_usb_regexs">
+ <item>"usb\\d"</item>
+ <item>"rndis\\d"</item>
+ </string-array>
+
+ <!-- List of regexpressions describing the interface (if any) that represent tetherable
+ Wifi interfaces. If the device doesn't want to support tethering over Wifi this
+ should be empty. An example would be "softap.*" -->
+ <string-array translatable="false" name="config_tether_wifi_regexs">
+ <item>"wlan\\d"</item>
+ <item>"softap\\d"</item>
+ </string-array>
+
+ <!-- List of regexpressions describing the interface (if any) that represent tetherable
+ Wifi P2P interfaces. If the device doesn't want to support tethering over Wifi P2p this
+ should be empty. An example would be "p2p-p2p\\d-.*" -->
+ <string-array translatable="false" name="config_tether_wifi_p2p_regexs">
+ <item>"p2p-p2p\\d-.*"</item>
+ </string-array>
+
+ <!-- List of regexpressions describing the interface (if any) that represent tetherable
+ bluetooth interfaces. If the device doesn't want to support tethering over bluetooth this
+ should be empty. -->
+ <string-array translatable="false" name="config_tether_bluetooth_regexs">
+ <item>"bt-pan"</item>
+ </string-array>
+
+ <!-- Use the old dnsmasq DHCP server for tethering instead of the framework implementation. -->
+ <bool translatable="false" name="config_tether_enable_legacy_dhcp_server">false</bool>
+
+ <!-- Dhcp range (min, max) to use for tethering purposes -->
+ <string-array translatable="false" name="config_tether_dhcp_range">
+ </string-array>
+
+ <!-- Array of ConnectivityManager.TYPE_{BLUETOOTH, ETHERNET, MOBILE, MOBILE_DUN, MOBILE_HIPRI,
+ WIFI} values allowable for tethering.
+
+ Common options are [1, 4] for TYPE_WIFI and TYPE_MOBILE_DUN or
+ [1,7,0] for TYPE_WIFI, TYPE_BLUETOOTH, and TYPE_MOBILE.
+
+ This list is also modified by code within the framework, including:
+
+ - TYPE_ETHERNET (9) is prepended to this list, and
+
+ - the return value of TelephonyManager.isTetheringApnRequired()
+ determines how the array is further modified:
+
+ * TRUE (DUN REQUIRED).
+ TYPE_MOBILE is removed (if present).
+ TYPE_MOBILE_HIPRI is removed (if present).
+ TYPE_MOBILE_DUN is appended (if not already present).
+
+ * FALSE (DUN NOT REQUIRED).
+ TYPE_MOBILE_DUN is removed (if present).
+ If both of TYPE_MOBILE{,_HIPRI} are not present:
+ TYPE_MOBILE is appended.
+ TYPE_MOBILE_HIPRI is appended.
+
+ For other changes applied to this list, now and in the future, see
+ com.android.server.connectivity.tethering.TetheringConfiguration.
+
+ Note also: the order of this is important. The first upstream type
+ for which a satisfying network exists is used.
+ -->
+ <integer-array translatable="false" name="config_tether_upstream_types">
+ </integer-array>
+
+ <!-- When true, the tethering upstream network follows the current default
+ Internet network (except when the current default network is mobile,
+ in which case a DUN network will be used if required).
+
+ When true, overrides the config_tether_upstream_types setting above.
+ -->
+ <bool translatable="false" name="config_tether_upstream_automatic">true</bool>
+
+
+ <!-- If the mobile hotspot feature requires provisioning, a package name and class name
+ can be provided to launch a supported application that provisions the devices.
+ EntitlementManager will send an intent to Settings with the specified package name and
+ class name in extras to launch provision app.
+ TODO: note what extras here.
+
+ See EntitlementManager#runUiTetherProvisioning and
+ packages/apps/Settings/src/com/android/settings/network/TetherProvisioningActivity.java
+ for more details.
+
+ For ui-less/periodic recheck support see config_mobile_hotspot_provision_app_no_ui
+ -->
+ <!-- The first element is the package name and the second element is the class name
+ of the provisioning app -->
+ <string-array translatable="false" name="config_mobile_hotspot_provision_app">
+ <!--
+ <item>com.example.provisioning</item>
+ <item>com.example.provisioning.Activity</item>
+ -->
+ </string-array>
+
+ <!-- If the mobile hotspot feature requires provisioning, an action can be provided
+ that will be broadcast in non-ui cases for checking the provisioning status.
+ EntitlementManager will pass the specified name to Settings and Settings would
+ launch provisioning app by sending an intent with the package name.
+
+ A second broadcast, action defined by config_mobile_hotspot_provision_response,
+ will be sent back to notify if provisioning succeeded or not. The response will
+ match that of the activity in config_mobile_hotspot_provision_app, but instead
+ contained within the int extra "EntitlementResult".
+ TODO: provide the system api for "EntitlementResult" extra and note it here.
+
+ See EntitlementManager#runSilentTetherProvisioning and
+ packages/apps/Settings/src/com/android/settings/wifi/tether/TetherService.java for more
+ details.
+ -->
+ <string translatable="false" name="config_mobile_hotspot_provision_app_no_ui"></string>
+
+ <!-- Sent in response to a provisioning check. The caller must hold the
+ permission android.permission.TETHER_PRIVILEGED for Settings to
+ receive this response.
+
+ See config_mobile_hotspot_provision_response
+ -->
+ <string translatable="false" name="config_mobile_hotspot_provision_response"></string>
+
+ <!-- Number of hours between each background provisioning call -->
+ <integer translatable="false" name="config_mobile_hotspot_provision_check_period">24</integer>
+
+ <!-- ComponentName of the service used to run no ui tether provisioning. -->
+ <string translatable="false" name="config_wifi_tether_enable">com.android.settings/.wifi.tether.TetherService</string>
</resources>
diff --git a/packages/Tethering/res/values/overlayable.xml b/packages/Tethering/res/values/overlayable.xml
new file mode 100644
index 0000000..e089d9d
--- /dev/null
+++ b/packages/Tethering/res/values/overlayable.xml
@@ -0,0 +1,35 @@
+<?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.
+-->
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+ <overlayable name="TetheringConfig">
+ <policy type="product|system|vendor">
+ <item type="array" name="config_tether_usb_regexs"/>
+ <item type="array" name="config_tether_wifi_regexs"/>
+ <item type="array" name="config_tether_wifi_p2p_regexs"/>
+ <item type="array" name="config_tether_bluetooth_regexs"/>
+ <item type="array" name="config_tether_dhcp_range"/>
+ <item type="bool" name="config_tether_enable_legacy_dhcp_server"/>
+ <item type="array" name="config_tether_upstream_types"/>
+ <item type="bool" name="config_tether_upstream_automatic"/>
+ <!-- Configuration values for tethering entitlement check -->
+ <item type="array" name="config_mobile_hotspot_provision_app"/>
+ <item type="string" name="config_mobile_hotspot_provision_app_no_ui"/>
+ <item type="string" name="config_mobile_hotspot_provision_response"/>
+ <item type="integer" name="config_mobile_hotspot_provision_check_period"/>
+ <item type="string" name="config_wifi_tether_enable"/>
+ </policy>
+ </overlayable>
+</resources>
diff --git a/packages/Tethering/res/values/strings.xml b/packages/Tethering/res/values/strings.xml
index ca866a9..792bce9 100644
--- a/packages/Tethering/res/values/strings.xml
+++ b/packages/Tethering/res/values/strings.xml
@@ -1,4 +1,18 @@
<?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.
+-->
<resources>
<!-- Shown when the device is tethered -->
<!-- Strings for tethered notification title [CHAR LIMIT=200] -->
@@ -9,8 +23,11 @@
<!-- This notification is shown when tethering has been disabled on a user's device.
The device is managed by the user's employer. Tethering can't be turned on unless the
IT administrator allows it. The noun "admin" is another reference for "IT administrator." -->
- <!-- Strings for tether disabling notification title [CHAR LIMIT=200] -->
+ <!-- Strings for tether disabling notification title [CHAR LIMIT=200] -->
<string name="disable_tether_notification_title">Tethering is disabled</string>
- <!-- Strings for tether disabling notification message [CHAR LIMIT=200] -->
+ <!-- Strings for tether disabling notification message [CHAR LIMIT=200] -->
<string name="disable_tether_notification_message">Contact your admin for details</string>
+
+ <!-- Strings for tether notification channel name [CHAR LIMIT=200] -->
+ <string name="notification_channel_tethering_status">Hotspot & tethering status</string>
</resources>
\ No newline at end of file
diff --git a/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java b/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java
index 1fe2328..d6bc063 100644
--- a/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java
+++ b/packages/Tethering/src/android/net/dhcp/DhcpServingParamsParcelExt.java
@@ -20,11 +20,11 @@
import android.annotation.NonNull;
import android.net.LinkAddress;
-
-import com.google.android.collect.Sets;
+import android.util.ArraySet;
import java.net.Inet4Address;
import java.util.Collection;
+import java.util.Collections;
import java.util.Set;
/**
@@ -68,7 +68,7 @@
* but it must always be set explicitly.
*/
public DhcpServingParamsParcelExt setDefaultRouters(@NonNull Inet4Address... defaultRouters) {
- return setDefaultRouters(Sets.newArraySet(defaultRouters));
+ return setDefaultRouters(newArraySet(defaultRouters));
}
/**
@@ -96,7 +96,7 @@
* <p>This may be an empty list of servers, but it must always be set explicitly.
*/
public DhcpServingParamsParcelExt setDnsServers(@NonNull Inet4Address... dnsServers) {
- return setDnsServers(Sets.newArraySet(dnsServers));
+ return setDnsServers(newArraySet(dnsServers));
}
/**
@@ -126,7 +126,7 @@
* and do not need to be set here.
*/
public DhcpServingParamsParcelExt setExcludedAddrs(@NonNull Inet4Address... excludedAddrs) {
- return setExcludedAddrs(Sets.newArraySet(excludedAddrs));
+ return setExcludedAddrs(newArraySet(excludedAddrs));
}
/**
@@ -169,4 +169,10 @@
}
return res;
}
+
+ private static ArraySet<Inet4Address> newArraySet(Inet4Address... addrs) {
+ ArraySet<Inet4Address> addrSet = new ArraySet<>(addrs.length);
+ Collections.addAll(addrSet, addrs);
+ return addrSet;
+ }
}
diff --git a/packages/Tethering/src/android/net/ip/IpServer.java b/packages/Tethering/src/android/net/ip/IpServer.java
index 8fde520..4306cf0b 100644
--- a/packages/Tethering/src/android/net/ip/IpServer.java
+++ b/packages/Tethering/src/android/net/ip/IpServer.java
@@ -17,40 +17,38 @@
package android.net.ip;
import static android.net.InetAddresses.parseNumericAddress;
+import static android.net.RouteInfo.RTN_UNICAST;
import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
import static android.net.util.NetworkConstants.FF;
import static android.net.util.NetworkConstants.RFC7421_PREFIX_LENGTH;
import static android.net.util.NetworkConstants.asByte;
+import static android.net.util.TetheringMessageBase.BASE_IPSERVER;
-import android.net.ConnectivityManager;
import android.net.INetd;
import android.net.INetworkStackStatusCallback;
-import android.net.INetworkStatsService;
-import android.net.InterfaceConfiguration;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.RouteInfo;
+import android.net.TetheringManager;
import android.net.dhcp.DhcpServerCallbacks;
import android.net.dhcp.DhcpServingParamsParcel;
import android.net.dhcp.DhcpServingParamsParcelExt;
import android.net.dhcp.IDhcpServer;
import android.net.ip.RouterAdvertisementDaemon.RaParams;
+import android.net.shared.NetdUtils;
+import android.net.shared.RouteUtils;
import android.net.util.InterfaceParams;
import android.net.util.InterfaceSet;
-import android.net.util.NetdService;
import android.net.util.SharedLog;
-import android.os.INetworkManagementService;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.util.Log;
-import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.util.MessageUtils;
-import com.android.internal.util.Protocol;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
@@ -119,7 +117,7 @@
*
* @param who the calling instance of IpServer
* @param state one of STATE_*
- * @param lastError one of ConnectivityManager.TETHER_ERROR_*
+ * @param lastError one of TetheringManager.TETHER_ERROR_*
*/
public void updateInterfaceState(IpServer who, int state, int lastError) { }
@@ -144,36 +142,31 @@
return InterfaceParams.getByName(ifName);
}
- public INetd getNetdService() {
- return NetdService.getInstance();
- }
-
/** Create a DhcpServer instance to be used by IpServer. */
public abstract void makeDhcpServer(String ifName, DhcpServingParamsParcel params,
DhcpServerCallbacks cb);
}
- private static final int BASE_IFACE = Protocol.BASE_TETHERING + 100;
// request from the user that it wants to tether
- public static final int CMD_TETHER_REQUESTED = BASE_IFACE + 2;
+ public static final int CMD_TETHER_REQUESTED = BASE_IPSERVER + 1;
// request from the user that it wants to untether
- public static final int CMD_TETHER_UNREQUESTED = BASE_IFACE + 3;
+ public static final int CMD_TETHER_UNREQUESTED = BASE_IPSERVER + 2;
// notification that this interface is down
- public static final int CMD_INTERFACE_DOWN = BASE_IFACE + 4;
+ public static final int CMD_INTERFACE_DOWN = BASE_IPSERVER + 3;
// notification from the master SM that it had trouble enabling IP Forwarding
- public static final int CMD_IP_FORWARDING_ENABLE_ERROR = BASE_IFACE + 7;
+ public static final int CMD_IP_FORWARDING_ENABLE_ERROR = BASE_IPSERVER + 4;
// notification from the master SM that it had trouble disabling IP Forwarding
- public static final int CMD_IP_FORWARDING_DISABLE_ERROR = BASE_IFACE + 8;
+ public static final int CMD_IP_FORWARDING_DISABLE_ERROR = BASE_IPSERVER + 5;
// notification from the master SM that it had trouble starting tethering
- public static final int CMD_START_TETHERING_ERROR = BASE_IFACE + 9;
+ public static final int CMD_START_TETHERING_ERROR = BASE_IPSERVER + 6;
// notification from the master SM that it had trouble stopping tethering
- public static final int CMD_STOP_TETHERING_ERROR = BASE_IFACE + 10;
+ public static final int CMD_STOP_TETHERING_ERROR = BASE_IPSERVER + 7;
// notification from the master SM that it had trouble setting the DNS forwarders
- public static final int CMD_SET_DNS_FORWARDERS_ERROR = BASE_IFACE + 11;
+ public static final int CMD_SET_DNS_FORWARDERS_ERROR = BASE_IPSERVER + 8;
// the upstream connection has changed
- public static final int CMD_TETHER_CONNECTION_CHANGED = BASE_IFACE + 12;
+ public static final int CMD_TETHER_CONNECTION_CHANGED = BASE_IPSERVER + 9;
// new IPv6 tethering parameters need to be processed
- public static final int CMD_IPV6_TETHER_UPDATE = BASE_IFACE + 13;
+ public static final int CMD_IPV6_TETHER_UPDATE = BASE_IPSERVER + 10;
private final State mInitialState;
private final State mLocalHotspotState;
@@ -181,9 +174,7 @@
private final State mUnavailableState;
private final SharedLog mLog;
- private final INetworkManagementService mNMService;
private final INetd mNetd;
- private final INetworkStatsService mStatsService;
private final Callback mCallback;
private final InterfaceController mInterfaceCtrl;
@@ -211,16 +202,14 @@
private int mDhcpServerStartIndex = 0;
private IDhcpServer mDhcpServer;
private RaParams mLastRaParams;
+ private LinkAddress mIpv4Address;
public IpServer(
String ifaceName, Looper looper, int interfaceType, SharedLog log,
- INetworkManagementService nMService, INetworkStatsService statsService,
- Callback callback, boolean usingLegacyDhcp, Dependencies deps) {
+ INetd netd, Callback callback, boolean usingLegacyDhcp, Dependencies deps) {
super(ifaceName, looper);
mLog = log.forSubComponent(ifaceName);
- mNMService = nMService;
- mNetd = deps.getNetdService();
- mStatsService = statsService;
+ mNetd = netd;
mCallback = callback;
mInterfaceCtrl = new InterfaceController(ifaceName, mNetd, mLog);
mIfaceName = ifaceName;
@@ -229,7 +218,7 @@
mUsingLegacyDhcp = usingLegacyDhcp;
mDeps = deps;
resetLinkProperties();
- mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
+ mLastError = TetheringManager.TETHER_ERROR_NO_ERROR;
mServingMode = STATE_AVAILABLE;
mInitialState = new InitialState();
@@ -250,7 +239,7 @@
}
/**
- * Tethering downstream type. It would be one of ConnectivityManager#TETHERING_*.
+ * Tethering downstream type. It would be one of TetheringManager#TETHERING_*.
*/
public int interfaceType() {
return mInterfaceType;
@@ -348,13 +337,13 @@
}
});
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ throw new IllegalStateException(e);
}
});
}
private void handleError() {
- mLastError = ConnectivityManager.TETHER_ERROR_DHCPSERVER_ERROR;
+ mLastError = TetheringManager.TETHER_ERROR_DHCPSERVER_ERROR;
transitionTo(mInitialState);
}
}
@@ -389,14 +378,15 @@
public void callback(int statusCode) {
if (statusCode != STATUS_SUCCESS) {
mLog.e("Error stopping DHCP server: " + statusCode);
- mLastError = ConnectivityManager.TETHER_ERROR_DHCPSERVER_ERROR;
+ mLastError = TetheringManager.TETHER_ERROR_DHCPSERVER_ERROR;
// Not much more we can do here
}
}
});
mDhcpServer = null;
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ mLog.e("Error stopping DHCP", e);
+ // Not much more we can do here
}
}
}
@@ -415,83 +405,69 @@
// NOTE: All of configureIPv4() will be refactored out of existence
// into calls to InterfaceController, shared with startIPv4().
mInterfaceCtrl.clearIPv4Address();
+ mIpv4Address = null;
}
- // TODO: Refactor this in terms of calls to InterfaceController.
private boolean configureIPv4(boolean enabled) {
if (VDBG) Log.d(TAG, "configureIPv4(" + enabled + ")");
// TODO: Replace this hard-coded information with dynamically selected
// config passed down to us by a higher layer IP-coordinating element.
- String ipAsString = null;
+ final Inet4Address srvAddr;
int prefixLen = 0;
- if (mInterfaceType == ConnectivityManager.TETHERING_USB) {
- ipAsString = USB_NEAR_IFACE_ADDR;
- prefixLen = USB_PREFIX_LENGTH;
- } else if (mInterfaceType == ConnectivityManager.TETHERING_WIFI) {
- ipAsString = getRandomWifiIPv4Address();
- prefixLen = WIFI_HOST_IFACE_PREFIX_LENGTH;
- } else if (mInterfaceType == ConnectivityManager.TETHERING_WIFI_P2P) {
- ipAsString = WIFI_P2P_IFACE_ADDR;
- prefixLen = WIFI_P2P_IFACE_PREFIX_LENGTH;
- } else {
- // BT configures the interface elsewhere: only start DHCP.
- final Inet4Address srvAddr = (Inet4Address) parseNumericAddress(BLUETOOTH_IFACE_ADDR);
- return configureDhcp(enabled, srvAddr, BLUETOOTH_DHCP_PREFIX_LENGTH);
+ try {
+ if (mInterfaceType == TetheringManager.TETHERING_USB) {
+ srvAddr = (Inet4Address) parseNumericAddress(USB_NEAR_IFACE_ADDR);
+ prefixLen = USB_PREFIX_LENGTH;
+ } else if (mInterfaceType == TetheringManager.TETHERING_WIFI) {
+ srvAddr = (Inet4Address) parseNumericAddress(getRandomWifiIPv4Address());
+ prefixLen = WIFI_HOST_IFACE_PREFIX_LENGTH;
+ } else if (mInterfaceType == TetheringManager.TETHERING_WIFI_P2P) {
+ srvAddr = (Inet4Address) parseNumericAddress(WIFI_P2P_IFACE_ADDR);
+ prefixLen = WIFI_P2P_IFACE_PREFIX_LENGTH;
+ } else {
+ // BT configures the interface elsewhere: only start DHCP.
+ // TODO: make all tethering types behave the same way, and delete the bluetooth
+ // code that calls into NetworkManagementService directly.
+ srvAddr = (Inet4Address) parseNumericAddress(BLUETOOTH_IFACE_ADDR);
+ mIpv4Address = new LinkAddress(srvAddr, BLUETOOTH_DHCP_PREFIX_LENGTH);
+ return configureDhcp(enabled, srvAddr, BLUETOOTH_DHCP_PREFIX_LENGTH);
+ }
+ mIpv4Address = new LinkAddress(srvAddr, prefixLen);
+ } catch (IllegalArgumentException e) {
+ mLog.e("Error selecting ipv4 address", e);
+ if (!enabled) stopDhcp();
+ return false;
}
- final LinkAddress linkAddr;
- try {
- final InterfaceConfiguration ifcg = mNMService.getInterfaceConfig(mIfaceName);
- if (ifcg == null) {
- mLog.e("Received null interface config");
- return false;
- }
+ final Boolean setIfaceUp;
+ if (mInterfaceType == TetheringManager.TETHERING_WIFI) {
+ // The WiFi stack has ownership of the interface up/down state.
+ // It is unclear whether the Bluetooth or USB stacks will manage their own
+ // state.
+ setIfaceUp = null;
+ } else {
+ setIfaceUp = enabled;
+ }
+ if (!mInterfaceCtrl.setInterfaceConfiguration(mIpv4Address, setIfaceUp)) {
+ mLog.e("Error configuring interface");
+ if (!enabled) stopDhcp();
+ return false;
+ }
- InetAddress addr = parseNumericAddress(ipAsString);
- linkAddr = new LinkAddress(addr, prefixLen);
- ifcg.setLinkAddress(linkAddr);
- if (mInterfaceType == ConnectivityManager.TETHERING_WIFI) {
- // The WiFi stack has ownership of the interface up/down state.
- // It is unclear whether the Bluetooth or USB stacks will manage their own
- // state.
- ifcg.ignoreInterfaceUpDownStatus();
- } else {
- if (enabled) {
- ifcg.setInterfaceUp();
- } else {
- ifcg.setInterfaceDown();
- }
- }
- ifcg.clearFlag("running");
-
- // TODO: this may throw if the interface is already gone. Do proper handling and
- // simplify the DHCP server start/stop.
- mNMService.setInterfaceConfig(mIfaceName, ifcg);
-
- if (!configureDhcp(enabled, (Inet4Address) addr, prefixLen)) {
- return false;
- }
- } catch (Exception e) {
- mLog.e("Error configuring interface " + e);
- if (!enabled) {
- try {
- // Calling stopDhcp several times is fine
- stopDhcp();
- } catch (Exception dhcpError) {
- mLog.e("Error stopping DHCP", dhcpError);
- }
- }
+ if (!configureDhcp(enabled, srvAddr, prefixLen)) {
return false;
}
// Directly-connected route.
- final RouteInfo route = new RouteInfo(linkAddr);
+ final IpPrefix ipv4Prefix = new IpPrefix(mIpv4Address.getAddress(),
+ mIpv4Address.getPrefixLength());
+ final RouteInfo route = new RouteInfo(ipv4Prefix, null, null, RTN_UNICAST);
if (enabled) {
- mLinkProperties.addLinkAddress(linkAddr);
+ mLinkProperties.addLinkAddress(mIpv4Address);
mLinkProperties.addRoute(route);
} else {
- mLinkProperties.removeLinkAddress(linkAddr);
+ mLinkProperties.removeLinkAddress(mIpv4Address);
mLinkProperties.removeRoute(route);
}
return true;
@@ -583,14 +559,12 @@
if (!deprecatedPrefixes.isEmpty()) {
final ArrayList<RouteInfo> toBeRemoved =
getLocalRoutesFor(mIfaceName, deprecatedPrefixes);
- try {
- final int removalFailures = mNMService.removeRoutesFromLocalNetwork(toBeRemoved);
- if (removalFailures > 0) {
- mLog.e(String.format("Failed to remove %d IPv6 routes from local table.",
- removalFailures));
- }
- } catch (RemoteException e) {
- mLog.e("Failed to remove IPv6 routes from local table: " + e);
+ // Remove routes from local network.
+ final int removalFailures = RouteUtils.removeRoutesFromLocalNetwork(
+ mNetd, toBeRemoved);
+ if (removalFailures > 0) {
+ mLog.e(String.format("Failed to remove %d IPv6 routes from local table.",
+ removalFailures));
}
for (RouteInfo route : toBeRemoved) mLinkProperties.removeRoute(route);
@@ -607,13 +581,18 @@
final ArrayList<RouteInfo> toBeAdded =
getLocalRoutesFor(mIfaceName, addedPrefixes);
try {
- // It's safe to call addInterfaceToLocalNetwork() even if
- // the interface is already in the local_network. Note also
- // that adding routes that already exist does not cause an
- // error (EEXIST is silently ignored).
- mNMService.addInterfaceToLocalNetwork(mIfaceName, toBeAdded);
- } catch (Exception e) {
- mLog.e("Failed to add IPv6 routes to local table: " + e);
+ // It's safe to call networkAddInterface() even if
+ // the interface is already in the local_network.
+ mNetd.networkAddInterface(INetd.LOCAL_NET_ID, mIfaceName);
+ try {
+ // Add routes from local network. Note that adding routes that
+ // already exist does not cause an error (EEXIST is silently ignored).
+ RouteUtils.addRoutesToLocalNetwork(mNetd, mIfaceName, toBeAdded);
+ } catch (IllegalStateException e) {
+ mLog.e("Failed to add IPv6 routes to local table: " + e);
+ }
+ } catch (ServiceSpecificException | RemoteException e) {
+ mLog.e("Failed to add " + mIfaceName + " to local table: ", e);
}
for (RouteInfo route : toBeAdded) mLinkProperties.addRoute(route);
@@ -727,7 +706,7 @@
logMessage(this, message.what);
switch (message.what) {
case CMD_TETHER_REQUESTED:
- mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
+ mLastError = TetheringManager.TETHER_ERROR_NO_ERROR;
switch (message.arg1) {
case STATE_LOCAL_ONLY:
transitionTo(mLocalHotspotState);
@@ -756,15 +735,17 @@
@Override
public void enter() {
if (!startIPv4()) {
- mLastError = ConnectivityManager.TETHER_ERROR_IFACE_CFG_ERROR;
+ mLastError = TetheringManager.TETHER_ERROR_IFACE_CFG_ERROR;
return;
}
try {
- mNMService.tetherInterface(mIfaceName);
- } catch (Exception e) {
+ final IpPrefix ipv4Prefix = new IpPrefix(mIpv4Address.getAddress(),
+ mIpv4Address.getPrefixLength());
+ NetdUtils.tetherInterface(mNetd, mIfaceName, ipv4Prefix);
+ } catch (RemoteException | ServiceSpecificException e) {
mLog.e("Error Tethering: " + e);
- mLastError = ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR;
+ mLastError = TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR;
return;
}
@@ -783,9 +764,9 @@
stopIPv6();
try {
- mNMService.untetherInterface(mIfaceName);
- } catch (Exception e) {
- mLastError = ConnectivityManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
+ NetdUtils.untetherInterface(mNetd, mIfaceName);
+ } catch (RemoteException | ServiceSpecificException e) {
+ mLastError = TetheringManager.TETHER_ERROR_UNTETHER_IFACE_ERROR;
mLog.e("Failed to untether interface: " + e);
}
@@ -815,7 +796,7 @@
case CMD_START_TETHERING_ERROR:
case CMD_STOP_TETHERING_ERROR:
case CMD_SET_DNS_FORWARDERS_ERROR:
- mLastError = ConnectivityManager.TETHER_ERROR_MASTER_ERROR;
+ mLastError = TetheringManager.TETHER_ERROR_MASTER_ERROR;
transitionTo(mInitialState);
break;
default:
@@ -834,7 +815,7 @@
@Override
public void enter() {
super.enter();
- if (mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+ if (mLastError != TetheringManager.TETHER_ERROR_NO_ERROR) {
transitionTo(mInitialState);
}
@@ -870,7 +851,7 @@
@Override
public void enter() {
super.enter();
- if (mLastError != ConnectivityManager.TETHER_ERROR_NO_ERROR) {
+ if (mLastError != TetheringManager.TETHER_ERROR_NO_ERROR) {
transitionTo(mInitialState);
}
@@ -897,20 +878,14 @@
// to remove their rules, which generates errors.
// Just do the best we can.
try {
- // About to tear down NAT; gather remaining statistics.
- mStatsService.forceUpdate();
- } catch (Exception e) {
- if (VDBG) Log.e(TAG, "Exception in forceUpdate: " + e.toString());
+ mNetd.ipfwdRemoveInterfaceForward(mIfaceName, upstreamIface);
+ } catch (RemoteException | ServiceSpecificException e) {
+ mLog.e("Exception in ipfwdRemoveInterfaceForward: " + e.toString());
}
try {
- mNMService.stopInterfaceForwarding(mIfaceName, upstreamIface);
- } catch (Exception e) {
- if (VDBG) Log.e(TAG, "Exception in removeInterfaceForward: " + e.toString());
- }
- try {
- mNMService.disableNat(mIfaceName, upstreamIface);
- } catch (Exception e) {
- if (VDBG) Log.e(TAG, "Exception in disableNat: " + e.toString());
+ mNetd.tetherRemoveForward(mIfaceName, upstreamIface);
+ } catch (RemoteException | ServiceSpecificException e) {
+ mLog.e("Exception in disableNat: " + e.toString());
}
}
@@ -946,12 +921,12 @@
for (String ifname : added) {
try {
- mNMService.enableNat(mIfaceName, ifname);
- mNMService.startInterfaceForwarding(mIfaceName, ifname);
- } catch (Exception e) {
- mLog.e("Exception enabling NAT: " + e);
+ mNetd.tetherAddForward(mIfaceName, ifname);
+ mNetd.ipfwdAddInterfaceForward(mIfaceName, ifname);
+ } catch (RemoteException | ServiceSpecificException e) {
+ mLog.e("Exception enabling NAT: " + e.toString());
cleanupUpstream();
- mLastError = ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR;
+ mLastError = TetheringManager.TETHER_ERROR_ENABLE_NAT_ERROR;
transitionTo(mInitialState);
return true;
}
@@ -996,7 +971,7 @@
class UnavailableState extends State {
@Override
public void enter() {
- mLastError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
+ mLastError = TetheringManager.TETHER_ERROR_NO_ERROR;
sendInterfaceState(STATE_UNAVAILABLE);
}
}
@@ -1007,7 +982,7 @@
String ifname, HashSet<IpPrefix> prefixes) {
final ArrayList<RouteInfo> localRoutes = new ArrayList<RouteInfo>();
for (IpPrefix ipp : prefixes) {
- localRoutes.add(new RouteInfo(ipp, null, ifname));
+ localRoutes.add(new RouteInfo(ipp, null, ifname, RTN_UNICAST));
}
return localRoutes;
}
@@ -1019,7 +994,7 @@
try {
return Inet6Address.getByAddress(null, dnsBytes, 0);
} catch (UnknownHostException e) {
- Slog.wtf(TAG, "Failed to construct Inet6Address from: " + localPrefix);
+ Log.wtf(TAG, "Failed to construct Inet6Address from: " + localPrefix);
return null;
}
}
diff --git a/packages/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java b/packages/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
index 4147413..6f017dc 100644
--- a/packages/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
+++ b/packages/Tethering/src/android/net/ip/RouterAdvertisementDaemon.java
@@ -22,14 +22,14 @@
import static android.system.OsConstants.IPPROTO_ICMPV6;
import static android.system.OsConstants.SOCK_RAW;
import static android.system.OsConstants.SOL_SOCKET;
-import static android.system.OsConstants.SO_BINDTODEVICE;
import static android.system.OsConstants.SO_SNDTIMEO;
import android.net.IpPrefix;
import android.net.LinkAddress;
-import android.net.NetworkUtils;
import android.net.TrafficStats;
import android.net.util.InterfaceParams;
+import android.net.util.SocketUtils;
+import android.net.util.TetheringUtils;
import android.system.ErrnoException;
import android.system.Os;
import android.system.StructTimeval;
@@ -38,8 +38,6 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.TrafficStatsConstants;
-import libcore.io.IoBridge;
-
import java.io.FileDescriptor;
import java.io.IOException;
import java.net.Inet6Address;
@@ -611,9 +609,8 @@
// Setting SNDTIMEO is purely for defensive purposes.
Os.setsockoptTimeval(
mSocket, SOL_SOCKET, SO_SNDTIMEO, StructTimeval.fromMillis(send_timout_ms));
- Os.setsockoptIfreq(mSocket, SOL_SOCKET, SO_BINDTODEVICE, mInterface.name);
- NetworkUtils.protectFromVpn(mSocket);
- NetworkUtils.setupRaSocket(mSocket, mInterface.index);
+ SocketUtils.bindSocketToInterface(mSocket, mInterface.name);
+ TetheringUtils.setupRaSocket(mSocket, mInterface.index);
} catch (ErrnoException | IOException e) {
Log.e(TAG, "Failed to create RA daemon socket: " + e);
return false;
@@ -627,7 +624,7 @@
private void closeSocket() {
if (mSocket != null) {
try {
- IoBridge.closeAndSignalBlockedThreads(mSocket);
+ SocketUtils.closeSocket(mSocket);
} catch (IOException ignored) { }
}
mSocket = null;
@@ -671,7 +668,7 @@
}
private final class UnicastResponder extends Thread {
- private final InetSocketAddress mSolicitor = new InetSocketAddress();
+ private final InetSocketAddress mSolicitor = new InetSocketAddress(0);
// The recycled buffer for receiving Router Solicitations from clients.
// If the RS is larger than IPV6_MIN_MTU the packets are truncated.
// This is fine since currently only byte 0 is examined anyway.
diff --git a/media/java/android/media/RouteSessionInfo.aidl b/packages/Tethering/src/android/net/util/TetheringMessageBase.java
similarity index 64%
copy from media/java/android/media/RouteSessionInfo.aidl
copy to packages/Tethering/src/android/net/util/TetheringMessageBase.java
index fb5d836..1b763ce 100644
--- a/media/java/android/media/RouteSessionInfo.aidl
+++ b/packages/Tethering/src/android/net/util/TetheringMessageBase.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2019 The Android Open Source Project
+ * 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.
@@ -13,7 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+package android.net.util;
-package android.media;
+/**
+ * This class defines Message.what base addresses for various state machine.
+ */
+public class TetheringMessageBase {
+ public static final int BASE_MASTER = 0;
+ public static final int BASE_IPSERVER = 100;
-parcelable RouteSessionInfo;
+}
diff --git a/packages/Tethering/src/android/net/util/TetheringUtils.java b/packages/Tethering/src/android/net/util/TetheringUtils.java
new file mode 100644
index 0000000..fa543bd
--- /dev/null
+++ b/packages/Tethering/src/android/net/util/TetheringUtils.java
@@ -0,0 +1,49 @@
+/*
+ * 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.net.util;
+
+import java.io.FileDescriptor;
+import java.net.SocketException;
+
+/**
+ * Native methods for tethering utilization.
+ *
+ * {@hide}
+ */
+public class TetheringUtils {
+
+ /**
+ * Offload management process need to know conntrack rules to support NAT, but it may not have
+ * permission to create netlink netfilter sockets. Create two netlink netfilter sockets and
+ * share them with offload management process.
+ */
+ public static native boolean configOffload();
+
+ /**
+ * Configures a socket for receiving ICMPv6 router solicitations and sending advertisements.
+ * @param fd the socket's {@link FileDescriptor}.
+ * @param ifIndex the interface index.
+ */
+ public static native void setupRaSocket(FileDescriptor fd, int ifIndex)
+ throws SocketException;
+
+ /**
+ * Read s as an unsigned 16-bit integer.
+ */
+ public static int uint16(short s) {
+ return s & 0xffff;
+ }
+}
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java b/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
index ba5d08d..1cabc8d 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/EntitlementManager.java
@@ -16,18 +16,16 @@
package com.android.server.connectivity.tethering;
-import static android.net.ConnectivityManager.EXTRA_ADD_TETHER_TYPE;
-import static android.net.ConnectivityManager.EXTRA_PROVISION_CALLBACK;
-import static android.net.ConnectivityManager.EXTRA_RUN_PROVISION;
-import static android.net.ConnectivityManager.TETHERING_BLUETOOTH;
-import static android.net.ConnectivityManager.TETHERING_INVALID;
-import static android.net.ConnectivityManager.TETHERING_USB;
-import static android.net.ConnectivityManager.TETHERING_WIFI;
-import static android.net.ConnectivityManager.TETHER_ERROR_ENTITLEMENT_UNKONWN;
-import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
-import static android.net.ConnectivityManager.TETHER_ERROR_PROVISION_FAILED;
-
-import static com.android.internal.R.string.config_wifi_tether_enable;
+import static android.net.TetheringManager.EXTRA_ADD_TETHER_TYPE;
+import static android.net.TetheringManager.EXTRA_PROVISION_CALLBACK;
+import static android.net.TetheringManager.EXTRA_RUN_PROVISION;
+import static android.net.TetheringManager.TETHERING_BLUETOOTH;
+import static android.net.TetheringManager.TETHERING_INVALID;
+import static android.net.TetheringManager.TETHERING_USB;
+import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKONWN;
+import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_PROVISION_FAILED;
import android.app.AlarmManager;
import android.app.PendingIntent;
@@ -36,9 +34,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.res.Resources;
import android.net.util.SharedLog;
-import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@@ -48,7 +44,6 @@
import android.os.ResultReceiver;
import android.os.SystemClock;
import android.os.SystemProperties;
-import android.os.UserHandle;
import android.provider.Settings;
import android.telephony.CarrierConfigManager;
import android.util.ArraySet;
@@ -56,12 +51,13 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.StateMachine;
+import com.android.networkstack.tethering.R;
import java.io.PrintWriter;
/**
* Re-check tethering provisioning for enabled downstream tether types.
- * Reference ConnectivityManager.TETHERING_{@code *} for each tether type.
+ * Reference TetheringManager.TETHERING_{@code *} for each tether type.
*
* All methods of this class must be accessed from the thread of tethering
* state machine.
@@ -77,9 +73,7 @@
"com.android.server.connectivity.tethering.PROVISIONING_RECHECK_ALARM";
private static final String EXTRA_SUBID = "subId";
- // {@link ComponentName} of the Service used to run tether provisioning.
- private static final ComponentName TETHER_SERVICE = ComponentName.unflattenFromString(
- Resources.getSystem().getString(config_wifi_tether_enable));
+ private final ComponentName mSilentProvisioningService;
private static final int MS_PER_HOUR = 60 * 60 * 1000;
private static final int EVENT_START_PROVISIONING = 0;
private static final int EVENT_STOP_PROVISIONING = 1;
@@ -88,9 +82,9 @@
private static final int EVENT_GET_ENTITLEMENT_VALUE = 4;
// The ArraySet contains enabled downstream types, ex:
- // {@link ConnectivityManager.TETHERING_WIFI}
- // {@link ConnectivityManager.TETHERING_USB}
- // {@link ConnectivityManager.TETHERING_BLUETOOTH}
+ // {@link TetheringManager.TETHERING_WIFI}
+ // {@link TetheringManager.TETHERING_USB}
+ // {@link TetheringManager.TETHERING_BLUETOOTH}
private final ArraySet<Integer> mCurrentTethers;
private final Context mContext;
private final int mPermissionChangeMessageCode;
@@ -98,8 +92,8 @@
private final SparseIntArray mEntitlementCacheValue;
private final EntitlementHandler mHandler;
private final StateMachine mTetherMasterSM;
- // Key: ConnectivityManager.TETHERING_*(downstream).
- // Value: ConnectivityManager.TETHER_ERROR_{NO_ERROR or PROVISION_FAILED}(provisioning result).
+ // Key: TetheringManager.TETHERING_*(downstream).
+ // Value: TetheringManager.TETHER_ERROR_{NO_ERROR or PROVISION_FAILED}(provisioning result).
private final SparseIntArray mCellularPermitted;
private PendingIntent mProvisioningRecheckAlarm;
private boolean mCellularUpstreamPermitted = true;
@@ -124,6 +118,8 @@
mHandler = new EntitlementHandler(masterHandler.getLooper());
mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_PROVISIONING_ALARM),
null, mHandler);
+ mSilentProvisioningService = ComponentName.unflattenFromString(
+ mContext.getResources().getString(R.string.config_wifi_tether_enable));
}
public void setOnUiEntitlementFailedListener(final OnUiEntitlementFailedListener listener) {
@@ -135,7 +131,7 @@
/**
* Ui entitlement check fails in |downstream|.
*
- * @param downstream tethering type from ConnectivityManager.TETHERING_{@code *}.
+ * @param downstream tethering type from TetheringManager.TETHERING_{@code *}.
*/
void onUiEntitlementFailed(int downstream);
}
@@ -171,7 +167,7 @@
* This is called when tethering starts.
* Launch provisioning app if upstream is cellular.
*
- * @param downstreamType tethering type from ConnectivityManager.TETHERING_{@code *}
+ * @param downstreamType tethering type from TetheringManager.TETHERING_{@code *}
* @param showProvisioningUi a boolean indicating whether to show the
* provisioning app UI if there is one.
*/
@@ -196,9 +192,9 @@
// till upstream change to cellular.
if (mUsingCellularAsUpstream) {
if (showProvisioningUi) {
- runUiTetherProvisioning(type, config.subId);
+ runUiTetherProvisioning(type, config.activeDataSubId);
} else {
- runSilentTetherProvisioning(type, config.subId);
+ runSilentTetherProvisioning(type, config.activeDataSubId);
}
mNeedReRunProvisioningUi = false;
} else {
@@ -212,7 +208,7 @@
/**
* Tell EntitlementManager that a given type of tethering has been disabled
*
- * @param type tethering type from ConnectivityManager.TETHERING_{@code *}
+ * @param type tethering type from TetheringManager.TETHERING_{@code *}
*/
public void stopProvisioningIfNeeded(int type) {
mHandler.sendMessage(mHandler.obtainMessage(EVENT_STOP_PROVISIONING, type, 0));
@@ -270,9 +266,9 @@
if (mCellularPermitted.indexOfKey(downstream) < 0) {
if (mNeedReRunProvisioningUi) {
mNeedReRunProvisioningUi = false;
- runUiTetherProvisioning(downstream, config.subId);
+ runUiTetherProvisioning(downstream, config.activeDataSubId);
} else {
- runSilentTetherProvisioning(downstream, config.subId);
+ runSilentTetherProvisioning(downstream, config.activeDataSubId);
}
}
}
@@ -298,7 +294,7 @@
/**
* Re-check tethering provisioning for all enabled tether types.
- * Reference ConnectivityManager.TETHERING_{@code *} for each tether type.
+ * Reference TetheringManager.TETHERING_{@code *} for each tether type.
*
* @param config an object that encapsulates the various tethering configuration elements.
* Note: this method is only called from TetherMaster on the handler thread.
@@ -336,7 +332,8 @@
.getSystemService(Context.CARRIER_CONFIG_SERVICE);
if (configManager == null) return null;
- final PersistableBundle carrierConfig = configManager.getConfigForSubId(config.subId);
+ final PersistableBundle carrierConfig = configManager.getConfigForSubId(
+ config.activeDataSubId);
if (CarrierConfigManager.isConfigForIdentifiedCarrier(carrierConfig)) {
return carrierConfig;
@@ -364,7 +361,7 @@
/**
* Run no UI tethering provisioning check.
- * @param type tethering type from ConnectivityManager.TETHERING_{@code *}
+ * @param type tethering type from TetheringManager.TETHERING_{@code *}
* @param subId default data subscription ID.
*/
@VisibleForTesting
@@ -378,13 +375,10 @@
intent.putExtra(EXTRA_RUN_PROVISION, true);
intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver);
intent.putExtra(EXTRA_SUBID, subId);
- intent.setComponent(TETHER_SERVICE);
- final long ident = Binder.clearCallingIdentity();
- try {
- mContext.startServiceAsUser(intent, UserHandle.CURRENT);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
+ intent.setComponent(mSilentProvisioningService);
+ // Only admin user can change tethering and SilentTetherProvisioning don't need to
+ // show UI, it is fine to always start setting's background service as system user.
+ mContext.startService(intent);
}
private void runUiTetherProvisioning(int type, int subId) {
@@ -394,7 +388,7 @@
/**
* Run the UI-enabled tethering provisioning check.
- * @param type tethering type from ConnectivityManager.TETHERING_{@code *}
+ * @param type tethering type from TetheringManager.TETHERING_{@code *}
* @param subId default data subscription ID.
* @param receiver to receive entitlement check result.
*/
@@ -402,17 +396,14 @@
protected void runUiTetherProvisioning(int type, int subId, ResultReceiver receiver) {
if (DBG) mLog.i("runUiTetherProvisioning: " + type);
- Intent intent = new Intent(Settings.ACTION_TETHER_PROVISIONING);
+ Intent intent = new Intent(Settings.ACTION_TETHER_PROVISIONING_UI);
intent.putExtra(EXTRA_ADD_TETHER_TYPE, type);
intent.putExtra(EXTRA_PROVISION_CALLBACK, receiver);
intent.putExtra(EXTRA_SUBID, subId);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- final long ident = Binder.clearCallingIdentity();
- try {
- mContext.startActivityAsUser(intent, UserHandle.CURRENT);
- } finally {
- Binder.restoreCallingIdentity(ident);
- }
+ // Only launch entitlement UI for system user. Entitlement UI should not appear for other
+ // user because only admin user is allowed to change tethering.
+ mContext.startActivity(intent);
}
// Not needed to check if this don't run on the handler thread because it's private.
@@ -468,7 +459,7 @@
* Add the mapping between provisioning result and tethering type.
* Notify UpstreamNetworkMonitor if Cellular permission changes.
*
- * @param type tethering type from ConnectivityManager.TETHERING_{@code *}
+ * @param type tethering type from TetheringManager.TETHERING_{@code *}
* @param resultCode Provisioning result
*/
protected void addDownstreamMapping(int type, int resultCode) {
@@ -483,7 +474,7 @@
/**
* Remove the mapping for input tethering type.
- * @param type tethering type from ConnectivityManager.TETHERING_{@code *}
+ * @param type tethering type from TetheringManager.TETHERING_{@code *}
*/
protected void removeDownstreamMapping(int type) {
mLog.i("removeDownstreamMapping: " + type);
@@ -632,7 +623,7 @@
/**
* Update the last entitlement value to internal cache
*
- * @param type tethering type from ConnectivityManager.TETHERING_{@code *}
+ * @param type tethering type from TetheringManager.TETHERING_{@code *}
* @param resultCode last entitlement value
* @return the last updated entitlement value
*/
@@ -671,7 +662,7 @@
receiver.send(cacheValue, null);
} else {
ResultReceiver proxy = buildProxyReceiver(downstream, false/* notifyFail */, receiver);
- runUiTetherProvisioning(downstream, config.subId, proxy);
+ runUiTetherProvisioning(downstream, config.activeDataSubId, proxy);
}
}
}
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java b/packages/Tethering/src/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java
index 9305414..66b9ade 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/IPv6TetheringCoordinator.java
@@ -29,6 +29,7 @@
import java.net.Inet6Address;
import java.net.InetAddress;
+import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
@@ -257,7 +258,7 @@
final LinkProperties lp = new LinkProperties();
final IpPrefix local48 = makeUniqueLocalPrefix(ulp, (short) 0, 48);
- lp.addRoute(new RouteInfo(local48, null, null));
+ lp.addRoute(new RouteInfo(local48, null, null, RouteInfo.RTN_UNICAST));
final IpPrefix local64 = makeUniqueLocalPrefix(ulp, subnetId, 64);
// Because this is a locally-generated ULA, we don't have an upstream
@@ -273,7 +274,13 @@
final byte[] bytes = Arrays.copyOf(in6addr, in6addr.length);
bytes[7] = (byte) (subnetId >> 8);
bytes[8] = (byte) subnetId;
- return new IpPrefix(bytes, prefixlen);
+ final InetAddress addr;
+ try {
+ addr = InetAddress.getByAddress(bytes);
+ } catch (UnknownHostException e) {
+ throw new IllegalStateException("Invalid address length: " + bytes.length, e);
+ }
+ return new IpPrefix(addr, prefixlen);
}
// Generates a Unique Locally-assigned Prefix:
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java
index 16734d8..cc36f4a 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadController.java
@@ -16,35 +16,40 @@
package com.android.server.connectivity.tethering;
+import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
+import static android.net.NetworkStats.METERED_NO;
+import static android.net.NetworkStats.ROAMING_NO;
import static android.net.NetworkStats.SET_DEFAULT;
-import static android.net.NetworkStats.STATS_PER_UID;
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
-import static android.net.TrafficStats.UID_TETHERING;
+import static android.net.NetworkStats.UID_TETHERING;
import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.usage.NetworkStatsManager;
import android.content.ContentResolver;
-import android.net.ITetheringStatsProvider;
+import android.net.InetAddresses;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkStats;
+import android.net.NetworkStats.Entry;
import android.net.RouteInfo;
import android.net.netlink.ConntrackMessage;
import android.net.netlink.NetlinkConstants;
import android.net.netlink.NetlinkSocket;
-import android.net.util.IpUtils;
+import android.net.netstats.provider.AbstractNetworkStatsProvider;
+import android.net.netstats.provider.NetworkStatsProviderCallback;
import android.net.util.SharedLog;
import android.os.Handler;
-import android.os.INetworkManagementService;
-import android.os.Looper;
-import android.os.RemoteException;
-import android.os.SystemClock;
import android.provider.Settings;
import android.system.ErrnoException;
import android.system.OsConstants;
import android.text.TextUtils;
+import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.connectivity.tethering.OffloadHardwareInterface.ForwardedStats;
@@ -73,13 +78,19 @@
private static final String ANYIP = "0.0.0.0";
private static final ForwardedStats EMPTY_STATS = new ForwardedStats();
+ @VisibleForTesting
+ enum StatsType {
+ STATS_PER_IFACE,
+ STATS_PER_UID,
+ }
+
private enum UpdateType { IF_NEEDED, FORCE };
private final Handler mHandler;
private final OffloadHardwareInterface mHwInterface;
private final ContentResolver mContentResolver;
- private final INetworkManagementService mNms;
- private final ITetheringStatsProvider mStatsProvider;
+ private final @NonNull OffloadTetheringStatsProvider mStatsProvider;
+ private final @Nullable NetworkStatsProviderCallback mStatsProviderCb;
private final SharedLog mLog;
private final HashMap<String, LinkProperties> mDownstreams;
private boolean mConfigInitialized;
@@ -109,22 +120,23 @@
private int mNatUpdateNetlinkErrors;
public OffloadController(Handler h, OffloadHardwareInterface hwi,
- ContentResolver contentResolver, INetworkManagementService nms, SharedLog log) {
+ ContentResolver contentResolver, NetworkStatsManager nsm, SharedLog log) {
mHandler = h;
mHwInterface = hwi;
mContentResolver = contentResolver;
- mNms = nms;
mStatsProvider = new OffloadTetheringStatsProvider();
mLog = log.forSubComponent(TAG);
mDownstreams = new HashMap<>();
mExemptPrefixes = new HashSet<>();
mLastLocalPrefixStrs = new HashSet<>();
-
+ NetworkStatsProviderCallback providerCallback = null;
try {
- mNms.registerTetheringStatsProvider(mStatsProvider, getClass().getSimpleName());
- } catch (RemoteException e) {
- mLog.e("Cannot register offload stats provider: " + e);
+ providerCallback = nsm.registerNetworkStatsProvider(
+ getClass().getSimpleName(), mStatsProvider);
+ } catch (RuntimeException e) {
+ Log.wtf(TAG, "Cannot register offload stats provider: " + e);
}
+ mStatsProviderCb = providerCallback;
}
/** Start hardware offload. */
@@ -173,7 +185,7 @@
// and we need to synchronize stats and limits between
// software and hardware forwarding.
updateStatsForAllUpstreams();
- forceTetherStatsPoll();
+ mStatsProvider.pushTetherStats();
}
@Override
@@ -186,7 +198,7 @@
// limits set take into account any software tethering
// traffic that has been happening in the meantime.
updateStatsForAllUpstreams();
- forceTetherStatsPoll();
+ mStatsProvider.pushTetherStats();
// [2] (Re)Push all state.
computeAndPushLocalPrefixes(UpdateType.FORCE);
pushAllDownstreamState();
@@ -204,14 +216,11 @@
// the HAL queued the callback.
// TODO: rev the HAL so that it provides an interface name.
- // Fetch current stats, so that when our notification reaches
- // NetworkStatsService and triggers a poll, we will respond with
- // current data (which will be above the limit that was reached).
- // Note that if we just changed upstream, this is unnecessary but harmless.
- // The stats for the previous upstream were already updated on this thread
- // just after the upstream was changed, so they are also up-to-date.
updateStatsForCurrentUpstream();
- forceTetherStatsPoll();
+ mStatsProvider.pushTetherStats();
+ // Push stats to service does not cause the service react to it immediately.
+ // Inform the service about limit reached.
+ if (mStatsProviderCb != null) mStatsProviderCb.onLimitReached();
}
@Override
@@ -253,42 +262,37 @@
return mConfigInitialized && mControlInitialized;
}
- private class OffloadTetheringStatsProvider extends ITetheringStatsProvider.Stub {
- @Override
- public NetworkStats getTetherStats(int how) {
- // getTetherStats() is the only function in OffloadController that can be called from
- // a different thread. Do not attempt to update stats by querying the offload HAL
- // synchronously from a different thread than our Handler thread. http://b/64771555.
- Runnable updateStats = () -> {
- updateStatsForCurrentUpstream();
- };
- if (Looper.myLooper() == mHandler.getLooper()) {
- updateStats.run();
- } else {
- mHandler.post(updateStats);
- }
+ @VisibleForTesting
+ class OffloadTetheringStatsProvider extends AbstractNetworkStatsProvider {
+ // These stats must only ever be touched on the handler thread.
+ @NonNull
+ private NetworkStats mIfaceStats = new NetworkStats(0L, 0);
+ @NonNull
+ private NetworkStats mUidStats = new NetworkStats(0L, 0);
- NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 0);
- NetworkStats.Entry entry = new NetworkStats.Entry();
- entry.set = SET_DEFAULT;
- entry.tag = TAG_NONE;
- entry.uid = (how == STATS_PER_UID) ? UID_TETHERING : UID_ALL;
+ @VisibleForTesting
+ @NonNull
+ NetworkStats getTetherStats(@NonNull StatsType how) {
+ NetworkStats stats = new NetworkStats(0L, 0);
+ final int uid = (how == StatsType.STATS_PER_UID) ? UID_TETHERING : UID_ALL;
- for (Map.Entry<String, ForwardedStats> kv : mForwardedStats.entrySet()) {
- ForwardedStats value = kv.getValue();
- entry.iface = kv.getKey();
- entry.rxBytes = value.rxBytes;
- entry.txBytes = value.txBytes;
- stats.addValues(entry);
+ for (final Map.Entry<String, ForwardedStats> kv : mForwardedStats.entrySet()) {
+ final ForwardedStats value = kv.getValue();
+ final Entry entry = new Entry(kv.getKey(), uid, SET_DEFAULT, TAG_NONE, METERED_NO,
+ ROAMING_NO, DEFAULT_NETWORK_NO, value.rxBytes, 0L, value.txBytes, 0L, 0L);
+ stats = stats.addValues(entry);
}
return stats;
}
@Override
- public void setInterfaceQuota(String iface, long quotaBytes) {
+ public void setLimit(String iface, long quotaBytes) {
+ mLog.i("setLimit: " + iface + "," + quotaBytes);
+ // Listen for all iface is necessary since upstream might be changed after limit
+ // is set.
mHandler.post(() -> {
- if (quotaBytes == ITetheringStatsProvider.QUOTA_UNLIMITED) {
+ if (quotaBytes == QUOTA_UNLIMITED) {
mInterfaceQuotas.remove(iface);
} else {
mInterfaceQuotas.put(iface, quotaBytes);
@@ -296,6 +300,42 @@
maybeUpdateDataLimit(iface);
});
}
+
+ /**
+ * Push stats to service, but does not cause a force polling. Note that this can only be
+ * called on the handler thread.
+ */
+ public void pushTetherStats() {
+ // TODO: remove the accumulated stats and report the diff from HAL directly.
+ if (null == mStatsProviderCb) return;
+ final NetworkStats ifaceDiff =
+ getTetherStats(StatsType.STATS_PER_IFACE).subtract(mIfaceStats);
+ final NetworkStats uidDiff =
+ getTetherStats(StatsType.STATS_PER_UID).subtract(mUidStats);
+ try {
+ mStatsProviderCb.onStatsUpdated(0 /* token */, ifaceDiff, uidDiff);
+ mIfaceStats = mIfaceStats.add(ifaceDiff);
+ mUidStats = mUidStats.add(uidDiff);
+ } catch (RuntimeException e) {
+ mLog.e("Cannot report network stats: ", e);
+ }
+ }
+
+ @Override
+ public void requestStatsUpdate(int token) {
+ mLog.i("requestStatsUpdate: " + token);
+ // Do not attempt to update stats by querying the offload HAL
+ // synchronously from a different thread than the Handler thread. http://b/64771555.
+ mHandler.post(() -> {
+ updateStatsForCurrentUpstream();
+ pushTetherStats();
+ });
+ }
+
+ @Override
+ public void setAlert(long quotaBytes) {
+ // TODO: Ask offload HAL to notify alert without stopping traffic.
+ }
}
private String currentUpstreamInterface() {
@@ -353,14 +393,6 @@
}
}
- private void forceTetherStatsPoll() {
- try {
- mNms.tetherLimitReached(mStatsProvider);
- } catch (RemoteException e) {
- mLog.e("Cannot report data limit reached: " + e);
- }
- }
-
/** Set current tethering upstream LinkProperties. */
public void setUpstreamLinkProperties(LinkProperties lp) {
if (!started() || Objects.equals(mUpstreamLinkProperties, lp)) return;
@@ -477,9 +509,10 @@
if (!ri.hasGateway()) continue;
final String gateway = ri.getGateway().getHostAddress();
- if (ri.isIPv4Default()) {
+ final InetAddress address = ri.getDestination().getAddress();
+ if (ri.isDefaultRoute() && address instanceof Inet4Address) {
v4gateway = gateway;
- } else if (ri.isIPv6Default()) {
+ } else if (ri.isDefaultRoute() && address instanceof Inet6Address) {
v6gateways.add(gateway);
}
}
@@ -547,7 +580,10 @@
private static boolean shouldIgnoreDownstreamRoute(RouteInfo route) {
// Ignore any link-local routes.
- if (!route.getDestinationLinkAddress().isGlobalPreferred()) return true;
+ final IpPrefix destination = route.getDestination();
+ final LinkAddress linkAddr = new LinkAddress(destination.getAddress(),
+ destination.getPrefixLength());
+ if (!linkAddr.isGlobalPreferred()) return true;
return false;
}
@@ -588,7 +624,7 @@
return;
}
- if (!IpUtils.isValidUdpOrTcpPort(srcPort)) {
+ if (!isValidUdpOrTcpPort(srcPort)) {
mLog.e("Invalid src port: " + srcPort);
return;
}
@@ -599,7 +635,7 @@
return;
}
- if (!IpUtils.isValidUdpOrTcpPort(dstPort)) {
+ if (!isValidUdpOrTcpPort(dstPort)) {
mLog.e("Invalid dst port: " + dstPort);
return;
}
@@ -628,7 +664,7 @@
private static Inet4Address parseIPv4Address(String addrString) {
try {
- final InetAddress ip = InetAddress.parseNumericAddress(addrString);
+ final InetAddress ip = InetAddresses.parseNumericAddress(addrString);
// TODO: Consider other sanitization steps here, including perhaps:
// not eql to 0.0.0.0
// not within 169.254.0.0/16
@@ -668,4 +704,8 @@
return 180;
}
}
+
+ private static boolean isValidUdpOrTcpPort(int port) {
+ return port > 0 && port < 65536;
+ }
}
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
index 00a6773..90b9d3f 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/OffloadHardwareInterface.java
@@ -16,7 +16,7 @@
package com.android.server.connectivity.tethering;
-import static com.android.internal.util.BitUtils.uint16;
+import static android.net.util.TetheringUtils.uint16;
import android.hardware.tetheroffload.control.V1_0.IOffloadControl;
import android.hardware.tetheroffload.control.V1_0.ITetheringOffloadCallback;
@@ -24,10 +24,13 @@
import android.hardware.tetheroffload.control.V1_0.NetworkProtocol;
import android.hardware.tetheroffload.control.V1_0.OffloadCallbackEvent;
import android.net.util.SharedLog;
+import android.net.util.TetheringUtils;
import android.os.Handler;
import android.os.RemoteException;
import android.system.OsConstants;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.ArrayList;
@@ -47,8 +50,6 @@
private static final String NO_IPV4_ADDRESS = "";
private static final String NO_IPV4_GATEWAY = "";
- private static native boolean configOffload();
-
private final Handler mHandler;
private final SharedLog mLog;
private IOffloadControl mOffloadControl;
@@ -92,6 +93,12 @@
txBytes = 0;
}
+ @VisibleForTesting
+ public ForwardedStats(long rxBytes, long txBytes) {
+ this.rxBytes = rxBytes;
+ this.txBytes = txBytes;
+ }
+
/** Add Tx/Rx bytes. */
public void add(ForwardedStats other) {
rxBytes += other.rxBytes;
@@ -107,8 +114,6 @@
public OffloadHardwareInterface(Handler h, SharedLog log) {
mHandler = h;
mLog = log.forSubComponent(TAG);
-
- System.loadLibrary("tetheroffloadjni");
}
/** Get default value indicating whether offload is supported. */
@@ -118,7 +123,7 @@
/** Configure offload management process. */
public boolean initOffloadConfig() {
- return configOffload();
+ return TetheringUtils.configOffload();
}
/** Initialize the tethering offload HAL. */
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
index 3d414ee..37e0cc1 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/Tethering.java
@@ -20,23 +20,25 @@
import static android.hardware.usb.UsbManager.USB_CONFIGURED;
import static android.hardware.usb.UsbManager.USB_CONNECTED;
import static android.hardware.usb.UsbManager.USB_FUNCTION_RNDIS;
-import static android.net.ConnectivityManager.ACTION_TETHER_STATE_CHANGED;
+import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
-import static android.net.ConnectivityManager.EXTRA_ACTIVE_LOCAL_ONLY;
-import static android.net.ConnectivityManager.EXTRA_ACTIVE_TETHER;
-import static android.net.ConnectivityManager.EXTRA_AVAILABLE_TETHER;
-import static android.net.ConnectivityManager.EXTRA_ERRORED_TETHER;
import static android.net.ConnectivityManager.EXTRA_NETWORK_INFO;
-import static android.net.ConnectivityManager.TETHERING_BLUETOOTH;
-import static android.net.ConnectivityManager.TETHERING_INVALID;
-import static android.net.ConnectivityManager.TETHERING_USB;
-import static android.net.ConnectivityManager.TETHERING_WIFI;
-import static android.net.ConnectivityManager.TETHERING_WIFI_P2P;
-import static android.net.ConnectivityManager.TETHER_ERROR_MASTER_ERROR;
-import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
-import static android.net.ConnectivityManager.TETHER_ERROR_SERVICE_UNAVAIL;
-import static android.net.ConnectivityManager.TETHER_ERROR_UNAVAIL_IFACE;
-import static android.net.ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
+import static android.net.TetheringManager.ACTION_TETHER_STATE_CHANGED;
+import static android.net.TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY;
+import static android.net.TetheringManager.EXTRA_ACTIVE_TETHER;
+import static android.net.TetheringManager.EXTRA_AVAILABLE_TETHER;
+import static android.net.TetheringManager.EXTRA_ERRORED_TETHER;
+import static android.net.TetheringManager.TETHERING_BLUETOOTH;
+import static android.net.TetheringManager.TETHERING_INVALID;
+import static android.net.TetheringManager.TETHERING_USB;
+import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHERING_WIFI_P2P;
+import static android.net.TetheringManager.TETHER_ERROR_MASTER_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_SERVICE_UNAVAIL;
+import static android.net.TetheringManager.TETHER_ERROR_UNAVAIL_IFACE;
+import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_IFACE;
+import static android.net.util.TetheringMessageBase.BASE_MASTER;
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME;
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE;
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_STATE;
@@ -49,8 +51,10 @@
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import android.app.Notification;
+import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.app.usage.NetworkStatsManager;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothPan;
import android.bluetooth.BluetoothProfile;
@@ -61,19 +65,18 @@
import android.content.IntentFilter;
import android.content.res.Resources;
import android.hardware.usb.UsbManager;
+import android.net.ConnectivityManager;
import android.net.INetd;
-import android.net.INetworkPolicyManager;
-import android.net.INetworkStatsService;
import android.net.ITetheringEventCallback;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkInfo;
-import android.net.NetworkUtils;
import android.net.TetherStatesParcel;
import android.net.TetheringConfigurationParcel;
import android.net.ip.IpServer;
+import android.net.shared.NetdUtils;
import android.net.util.BaseNetdUnsolicitedEventListener;
import android.net.util.InterfaceSet;
import android.net.util.PrefixUtils;
@@ -86,12 +89,12 @@
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
-import android.os.INetworkManagementService;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.os.ServiceSpecificException;
import android.os.UserHandle;
import android.os.UserManager;
import android.telephony.PhoneStateListener;
@@ -101,12 +104,12 @@
import android.util.Log;
import android.util.SparseArray;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
-import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.MessageUtils;
-import com.android.internal.util.Protocol;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import com.android.networkstack.tethering.R;
@@ -120,6 +123,8 @@
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
/**
*
@@ -137,6 +142,8 @@
};
private static final SparseArray<String> sMagicDecoderRing =
MessageUtils.findMessageNames(sMessageClasses);
+ // Keep in sync with NETID_UNSET in system/netd/include/netid_client.h
+ private static final int NETID_UNSET = 0;
private static class TetherState {
public final IpServer ipServer;
@@ -170,10 +177,6 @@
private final Context mContext;
private final ArrayMap<String, TetherState> mTetherStates;
private final BroadcastReceiver mStateReceiver;
- // Stopship: replace mNMService before production.
- private final INetworkManagementService mNMService;
- private final INetworkStatsService mStatsService;
- private final INetworkPolicyManager mPolicyManager;
private final Looper mLooper;
private final StateMachine mTetherMasterSM;
private final OffloadController mOffloadController;
@@ -185,10 +188,10 @@
private final TetheringDependencies mDeps;
private final EntitlementManager mEntitlementMgr;
private final Handler mHandler;
- private final PhoneStateListener mPhoneStateListener;
private final INetd mNetd;
private final NetdCallback mNetdCallback;
private final UserRestrictionActionListener mTetheringRestriction;
+ private final ActiveDataSubIdListener mActiveDataSubIdListener;
private int mActiveDataSubId = INVALID_SUBSCRIPTION_ID;
// All the usage of mTetheringEventCallback should run in the same thread.
private ITetheringEventCallback mTetheringEventCallback = null;
@@ -203,14 +206,12 @@
private boolean mWifiTetherRequested;
private Network mTetherUpstream;
private TetherStatesParcel mTetherStatesParcel;
+ private boolean mDataSaverEnabled = false;
public Tethering(TetheringDependencies deps) {
mLog.mark("Tethering.constructed");
mDeps = deps;
mContext = mDeps.getContext();
- mNMService = mDeps.getINetworkManagementService();
- mStatsService = mDeps.getINetworkStatsService();
- mPolicyManager = mDeps.getINetworkPolicyManager();
mNetd = mDeps.getINetd(mContext);
mLooper = mDeps.getTetheringLooper();
@@ -221,12 +222,13 @@
mTetherMasterSM = new TetherMasterSM("TetherMaster", mLooper, deps);
mTetherMasterSM.start();
+ final NetworkStatsManager statsManager =
+ (NetworkStatsManager) mContext.getSystemService(Context.NETWORK_STATS_SERVICE);
mHandler = mTetherMasterSM.getHandler();
mOffloadController = new OffloadController(mHandler,
- mDeps.getOffloadHardwareInterface(mHandler, mLog),
- mContext.getContentResolver(), mNMService,
- mLog);
- mUpstreamNetworkMonitor = deps.getUpstreamNetworkMonitor(mContext, mTetherMasterSM, mLog,
+ mDeps.getOffloadHardwareInterface(mHandler, mLog), mContext.getContentResolver(),
+ statsManager, mLog);
+ mUpstreamNetworkMonitor = mDeps.getUpstreamNetworkMonitor(mContext, mTetherMasterSM, mLog,
TetherMasterSM.EVENT_UPSTREAM_CALLBACK);
mForwardedDownstreams = new HashSet<>();
@@ -252,26 +254,6 @@
mEntitlementMgr.reevaluateSimCardProvisioning(mConfig);
});
- mPhoneStateListener = new PhoneStateListener(mLooper) {
- @Override
- public void onActiveDataSubscriptionIdChanged(int subId) {
- mLog.log("OBSERVED active data subscription change, from " + mActiveDataSubId
- + " to " + subId);
- if (subId == mActiveDataSubId) return;
-
- mActiveDataSubId = subId;
- updateConfiguration();
- // To avoid launching unexpected provisioning checks, ignore re-provisioning when
- // no CarrierConfig loaded yet. Assume reevaluateSimCardProvisioning() will be
- // triggered again when CarrierConfig is loaded.
- if (mEntitlementMgr.getCarrierConfig(mConfig) != null) {
- mEntitlementMgr.reevaluateSimCardProvisioning(mConfig);
- } else {
- mLog.log("IGNORED reevaluate provisioning due to no carrier config loaded");
- }
- }
- };
-
mStateReceiver = new StateReceiver();
mNetdCallback = new NetdCallback();
@@ -282,8 +264,10 @@
}
final UserManager userManager = (UserManager) mContext.getSystemService(
- Context.USER_SERVICE);
+ Context.USER_SERVICE);
mTetheringRestriction = new UserRestrictionActionListener(userManager, this);
+ final TetheringThreadExecutor executor = new TetheringThreadExecutor(mHandler);
+ mActiveDataSubIdListener = new ActiveDataSubIdListener(executor);
// Load tethering configuration.
updateConfiguration();
@@ -294,8 +278,8 @@
private void startStateMachineUpdaters(Handler handler) {
mCarrierConfigChange.startListening();
- mContext.getSystemService(TelephonyManager.class).listen(
- mPhoneStateListener, PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
+ mContext.getSystemService(TelephonyManager.class).listen(mActiveDataSubIdListener,
+ PhoneStateListener.LISTEN_ACTIVE_DATA_SUBSCRIPTION_ID_CHANGE);
IntentFilter filter = new IntentFilter();
filter.addAction(UsbManager.ACTION_USB_STATE);
@@ -304,14 +288,45 @@
filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
filter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
filter.addAction(UserManager.ACTION_USER_RESTRICTIONS_CHANGED);
+ filter.addAction(ACTION_RESTRICT_BACKGROUND_CHANGED);
mContext.registerReceiver(mStateReceiver, filter, null, handler);
+ }
- filter = new IntentFilter();
- filter.addAction(Intent.ACTION_MEDIA_SHARED);
- filter.addAction(Intent.ACTION_MEDIA_UNSHARED);
- filter.addDataScheme("file");
- mContext.registerReceiver(mStateReceiver, filter, null, handler);
+ private class TetheringThreadExecutor implements Executor {
+ private final Handler mTetherHandler;
+ TetheringThreadExecutor(Handler handler) {
+ mTetherHandler = handler;
+ }
+ @Override
+ public void execute(Runnable command) {
+ if (!mTetherHandler.post(command)) {
+ throw new RejectedExecutionException(mTetherHandler + " is shutting down");
+ }
+ }
+ }
+ private class ActiveDataSubIdListener extends PhoneStateListener {
+ ActiveDataSubIdListener(Executor executor) {
+ super(executor);
+ }
+
+ @Override
+ public void onActiveDataSubscriptionIdChanged(int subId) {
+ mLog.log("OBSERVED active data subscription change, from " + mActiveDataSubId
+ + " to " + subId);
+ if (subId == mActiveDataSubId) return;
+
+ mActiveDataSubId = subId;
+ updateConfiguration();
+ // To avoid launching unexpected provisioning checks, ignore re-provisioning
+ // when no CarrierConfig loaded yet. Assume reevaluateSimCardProvisioning()
+ // ill be triggered again when CarrierConfig is loaded.
+ if (mEntitlementMgr.getCarrierConfig(mConfig) != null) {
+ mEntitlementMgr.reevaluateSimCardProvisioning(mConfig);
+ } else {
+ mLog.log("IGNORED reevaluate provisioning, no carrier config loaded");
+ }
+ }
}
private WifiManager getWifiManager() {
@@ -326,8 +341,7 @@
}
private void maybeDunSettingChanged() {
- final boolean isDunRequired = TetheringConfiguration.checkDunRequired(
- mContext, mActiveDataSubId);
+ final boolean isDunRequired = TetheringConfiguration.checkDunRequired(mContext);
if (isDunRequired == mConfig.isDunRequired) return;
updateConfiguration();
}
@@ -401,7 +415,6 @@
}
}
-
void interfaceRemoved(String iface) {
if (VDBG) Log.d(TAG, "interfaceRemoved " + iface);
synchronized (mPublicSync) {
@@ -472,7 +485,7 @@
}
private void setBluetoothTethering(final boolean enable, final ResultReceiver receiver) {
- final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ final BluetoothAdapter adapter = mDeps.getBluetoothAdapter();
if (adapter == null || !adapter.isEnabled()) {
Log.w(TAG, "Tried to enable bluetooth tethering with null or disabled adapter. null: "
+ (adapter == null));
@@ -633,8 +646,7 @@
reportTetherStateChanged(mTetherStatesParcel);
final Intent bcast = new Intent(ACTION_TETHER_STATE_CHANGED);
- bcast.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING
- | Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ bcast.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
bcast.putStringArrayListExtra(EXTRA_AVAILABLE_TETHER, availableList);
bcast.putStringArrayListExtra(EXTRA_ACTIVE_LOCAL_ONLY, localOnlyList);
bcast.putStringArrayListExtra(EXTRA_ACTIVE_TETHER, tetherList);
@@ -651,19 +663,19 @@
if (usbTethered) {
if (wifiTethered || bluetoothTethered) {
- showTetheredNotification(SystemMessage.NOTE_TETHER_GENERAL);
+ showTetheredNotification(R.drawable.stat_sys_tether_general);
} else {
- showTetheredNotification(SystemMessage.NOTE_TETHER_USB);
+ showTetheredNotification(R.drawable.stat_sys_tether_usb);
}
} else if (wifiTethered) {
if (bluetoothTethered) {
- showTetheredNotification(SystemMessage.NOTE_TETHER_GENERAL);
+ showTetheredNotification(R.drawable.stat_sys_tether_general);
} else {
/* We now have a status bar icon for WifiTethering, so drop the notification */
clearTetheredNotification();
}
} else if (bluetoothTethered) {
- showTetheredNotification(SystemMessage.NOTE_TETHER_BLUETOOTH);
+ showTetheredNotification(R.drawable.stat_sys_tether_bluetooth);
} else {
clearTetheredNotification();
}
@@ -676,30 +688,22 @@
@VisibleForTesting
protected void showTetheredNotification(int id, boolean tetheringOn) {
NotificationManager notificationManager =
- (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ (NotificationManager) mContext.createContextAsUser(UserHandle.ALL, 0)
+ .getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager == null) {
return;
}
- int icon = 0;
- switch(id) {
- case SystemMessage.NOTE_TETHER_USB:
- icon = R.drawable.stat_sys_tether_usb;
- break;
- case SystemMessage.NOTE_TETHER_BLUETOOTH:
- icon = R.drawable.stat_sys_tether_bluetooth;
- break;
- case SystemMessage.NOTE_TETHER_GENERAL:
- default:
- icon = R.drawable.stat_sys_tether_general;
- break;
- }
+ final NotificationChannel channel = new NotificationChannel(
+ "TETHERING_STATUS",
+ mContext.getResources().getString(R.string.notification_channel_tethering_status),
+ NotificationManager.IMPORTANCE_LOW);
+ notificationManager.createNotificationChannel(channel);
if (mLastNotificationId != 0) {
- if (mLastNotificationId == icon) {
+ if (mLastNotificationId == id) {
return;
}
- notificationManager.cancelAsUser(null, mLastNotificationId,
- UserHandle.ALL);
+ notificationManager.cancel(null, mLastNotificationId);
mLastNotificationId = 0;
}
@@ -707,8 +711,8 @@
intent.setClassName("com.android.settings", "com.android.settings.TetherSettings");
intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
- PendingIntent pi = PendingIntent.getActivityAsUser(mContext, 0, intent, 0,
- null, UserHandle.CURRENT);
+ PendingIntent pi = PendingIntent.getActivity(
+ mContext.createContextAsUser(UserHandle.CURRENT, 0), 0, intent, 0, null);
Resources r = mContext.getResources();
final CharSequence title;
@@ -723,32 +727,30 @@
}
if (mTetheredNotificationBuilder == null) {
- mTetheredNotificationBuilder = new Notification.Builder(mContext,
- SystemNotificationChannels.NETWORK_STATUS);
+ mTetheredNotificationBuilder = new Notification.Builder(mContext, channel.getId());
mTetheredNotificationBuilder.setWhen(0)
.setOngoing(true)
.setColor(mContext.getColor(
- com.android.internal.R.color.system_notification_accent_color))
+ android.R.color.system_notification_accent_color))
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setCategory(Notification.CATEGORY_STATUS);
}
- mTetheredNotificationBuilder.setSmallIcon(icon)
+ mTetheredNotificationBuilder.setSmallIcon(id)
.setContentTitle(title)
.setContentText(message)
.setContentIntent(pi);
mLastNotificationId = id;
- notificationManager.notifyAsUser(null, mLastNotificationId,
- mTetheredNotificationBuilder.buildInto(new Notification()), UserHandle.ALL);
+ notificationManager.notify(null, mLastNotificationId, mTetheredNotificationBuilder.build());
}
@VisibleForTesting
protected void clearTetheredNotification() {
NotificationManager notificationManager =
- (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
+ (NotificationManager) mContext.createContextAsUser(UserHandle.ALL, 0)
+ .getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager != null && mLastNotificationId != 0) {
- notificationManager.cancelAsUser(null, mLastNotificationId,
- UserHandle.ALL);
+ notificationManager.cancel(null, mLastNotificationId);
mLastNotificationId = 0;
}
}
@@ -773,6 +775,9 @@
} else if (action.equals(UserManager.ACTION_USER_RESTRICTIONS_CHANGED)) {
mLog.log("OBSERVED user restrictions changed");
handleUserRestrictionAction();
+ } else if (action.equals(ACTION_RESTRICT_BACKGROUND_CHANGED)) {
+ mLog.log("OBSERVED data saver changed");
+ handleDataSaverChanged();
}
}
@@ -883,6 +888,20 @@
private void handleUserRestrictionAction() {
mTetheringRestriction.onUserRestrictionsChanged();
}
+
+ private void handleDataSaverChanged() {
+ final ConnectivityManager connMgr = (ConnectivityManager) mContext.getSystemService(
+ Context.CONNECTIVITY_SERVICE);
+ final boolean isDataSaverEnabled = connMgr.getRestrictBackgroundStatus()
+ != ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
+
+ if (mDataSaverEnabled == isDataSaverEnabled) return;
+
+ mDataSaverEnabled = isDataSaverEnabled;
+ if (mDataSaverEnabled) {
+ untetherAll();
+ }
+ }
}
@VisibleForTesting
@@ -1003,8 +1022,8 @@
String[] ifaces = null;
try {
- ifaces = mNMService.listInterfaces();
- } catch (Exception e) {
+ ifaces = mNetd.interfaceGetList();
+ } catch (RemoteException | ServiceSpecificException e) {
Log.e(TAG, "Error listing Interfaces", e);
return;
}
@@ -1163,7 +1182,6 @@
}
class TetherMasterSM extends StateMachine {
- private static final int BASE_MASTER = Protocol.BASE_TETHERING;
// an interface SM has requested Tethering/Local Hotspot
static final int EVENT_IFACE_SERVING_STATE_ACTIVE = BASE_MASTER + 1;
// an interface SM has unrequested Tethering/Local Hotspot
@@ -1180,7 +1198,6 @@
static final int EVENT_IFACE_UPDATE_LINKPROPERTIES = BASE_MASTER + 7;
// Events from EntitlementManager to choose upstream again.
static final int EVENT_UPSTREAM_PERMISSION_CHANGED = BASE_MASTER + 8;
-
private final State mInitialState;
private final State mTetherModeAliveState;
@@ -1265,25 +1282,25 @@
protected boolean turnOnMasterTetherSettings() {
final TetheringConfiguration cfg = mConfig;
try {
- mNMService.setIpForwardingEnabled(true);
- } catch (Exception e) {
+ mNetd.ipfwdEnableForwarding(TAG);
+ } catch (RemoteException | ServiceSpecificException e) {
mLog.e(e);
transitionTo(mSetIpForwardingEnabledErrorState);
return false;
}
+
// TODO: Randomize DHCPv4 ranges, especially in hotspot mode.
// Legacy DHCP server is disabled if passed an empty ranges array
final String[] dhcpRanges = cfg.enableLegacyDhcpServer
- ? cfg.legacyDhcpRanges
- : new String[0];
+ ? cfg.legacyDhcpRanges : new String[0];
try {
- // TODO: Find a more accurate method name (startDHCPv4()?).
- mNMService.startTethering(dhcpRanges);
- } catch (Exception e) {
+ NetdUtils.tetherStart(mNetd, true /** usingLegacyDnsProxy */, dhcpRanges);
+ } catch (RemoteException | ServiceSpecificException e) {
try {
- mNMService.stopTethering();
- mNMService.startTethering(dhcpRanges);
- } catch (Exception ee) {
+ // Stop and retry.
+ mNetd.tetherStop();
+ NetdUtils.tetherStart(mNetd, true /** usingLegacyDnsProxy */, dhcpRanges);
+ } catch (RemoteException | ServiceSpecificException ee) {
mLog.e(ee);
transitionTo(mStartTetheringErrorState);
return false;
@@ -1295,15 +1312,15 @@
protected boolean turnOffMasterTetherSettings() {
try {
- mNMService.stopTethering();
- } catch (Exception e) {
+ mNetd.tetherStop();
+ } catch (RemoteException | ServiceSpecificException e) {
mLog.e(e);
transitionTo(mStopTetheringErrorState);
return false;
}
try {
- mNMService.setIpForwardingEnabled(false);
- } catch (Exception e) {
+ mNetd.ipfwdDisableForwarding(TAG);
+ } catch (RemoteException | ServiceSpecificException e) {
mLog.e(e);
transitionTo(mSetIpForwardingDisabledErrorState);
return false;
@@ -1366,19 +1383,25 @@
protected void setDnsForwarders(final Network network, final LinkProperties lp) {
// TODO: Set v4 and/or v6 DNS per available connectivity.
- String[] dnsServers = mConfig.defaultIPv4DNS;
final Collection<InetAddress> dnses = lp.getDnsServers();
// TODO: Properly support the absence of DNS servers.
+ final String[] dnsServers;
if (dnses != null && !dnses.isEmpty()) {
- // TODO: remove this invocation of NetworkUtils.makeStrings().
- dnsServers = NetworkUtils.makeStrings(dnses);
+ dnsServers = new String[dnses.size()];
+ int i = 0;
+ for (InetAddress dns : dnses) {
+ dnsServers[i++] = dns.getHostAddress();
+ }
+ } else {
+ dnsServers = mConfig.defaultIPv4DNS;
}
+ final int netId = (network != null) ? network.netId : NETID_UNSET;
try {
- mNMService.setDnsForwarders(network, dnsServers);
+ mNetd.tetherDnsSet(netId, dnsServers);
mLog.log(String.format(
"SET DNS forwarders: network=%s dnsServers=%s",
network, Arrays.toString(dnsServers)));
- } catch (Exception e) {
+ } catch (RemoteException | ServiceSpecificException e) {
// TODO: Investigate how this can fail and what exactly
// happens if/when such failures occur.
mLog.e("setting DNS forwarders failed, " + e);
@@ -1681,8 +1704,8 @@
Log.e(TAG, "Error in startTethering");
notify(IpServer.CMD_START_TETHERING_ERROR);
try {
- mNMService.setIpForwardingEnabled(false);
- } catch (Exception e) { }
+ mNetd.ipfwdDisableForwarding(TAG);
+ } catch (RemoteException | ServiceSpecificException e) { }
}
}
@@ -1692,8 +1715,8 @@
Log.e(TAG, "Error in stopTethering");
notify(IpServer.CMD_STOP_TETHERING_ERROR);
try {
- mNMService.setIpForwardingEnabled(false);
- } catch (Exception e) { }
+ mNetd.ipfwdDisableForwarding(TAG);
+ } catch (RemoteException | ServiceSpecificException e) { }
}
}
@@ -1703,11 +1726,11 @@
Log.e(TAG, "Error in setDnsForwarders");
notify(IpServer.CMD_SET_DNS_FORWARDERS_ERROR);
try {
- mNMService.stopTethering();
- } catch (Exception e) { }
+ mNetd.tetherStop();
+ } catch (RemoteException | ServiceSpecificException e) { }
try {
- mNMService.setIpForwardingEnabled(false);
- } catch (Exception e) { }
+ mNetd.ipfwdDisableForwarding(TAG);
+ } catch (RemoteException | ServiceSpecificException e) { }
}
}
@@ -1867,7 +1890,7 @@
}
}
- void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer, @Nullable String[] args) {
// Binder.java closes the resource for us.
@SuppressWarnings("resource")
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
@@ -1976,15 +1999,6 @@
mLog.log(String.format("OBSERVED iface=%s state=%s error=%s", iface, state, error));
- try {
- // Notify that we're tethering (or not) this interface.
- // This is how data saver for instance knows if the user explicitly
- // turned on tethering (thus keeping us from being in data saver mode).
- mPolicyManager.onTetheringChanged(iface, state == IpServer.STATE_TETHERED);
- } catch (RemoteException e) {
- // Not really very much we can do here.
- }
-
// If TetherMasterSM is in ErrorState, TetherMasterSM stays there.
// Thus we give a chance for TetherMasterSM to recover to InitialState
// by sending CMD_CLEAR_ERROR
@@ -2048,7 +2062,7 @@
mLog.log("adding TetheringInterfaceStateMachine for: " + iface);
final TetherState tetherState = new TetherState(
- new IpServer(iface, mLooper, interfaceType, mLog, mNMService, mStatsService,
+ new IpServer(iface, mLooper, interfaceType, mLog, mNetd,
makeControlCallback(), mConfig.enableLegacyDhcpServer,
mDeps.getIpServerDependencies()));
mTetherStates.put(iface, tetherState);
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
index 0ab4d63..068c346 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringConfiguration.java
@@ -21,31 +21,19 @@
import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
-import static android.provider.Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER;
+import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
-import static com.android.internal.R.array.config_mobile_hotspot_provision_app;
-import static com.android.internal.R.array.config_tether_bluetooth_regexs;
-import static com.android.internal.R.array.config_tether_dhcp_range;
-import static com.android.internal.R.array.config_tether_upstream_types;
-import static com.android.internal.R.array.config_tether_usb_regexs;
-import static com.android.internal.R.array.config_tether_wifi_p2p_regexs;
-import static com.android.internal.R.array.config_tether_wifi_regexs;
-import static com.android.internal.R.bool.config_tether_upstream_automatic;
-import static com.android.internal.R.integer.config_mobile_hotspot_provision_check_period;
-import static com.android.internal.R.string.config_mobile_hotspot_provision_app_no_ui;
-
-import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
-import android.net.ConnectivityManager;
import android.net.TetheringConfigurationParcel;
import android.net.util.SharedLog;
-import android.provider.Settings;
+import android.provider.DeviceConfig;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.networkstack.tethering.R;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -85,6 +73,12 @@
private static final String[] DEFAULT_IPV4_DNS = {"8.8.4.4", "8.8.8.8"};
+ /**
+ * Use the old dnsmasq DHCP server for tethering instead of the framework implementation.
+ */
+ public static final String TETHER_ENABLE_LEGACY_DHCP_SERVER =
+ "tether_enable_legacy_dhcp_server";
+
public final String[] tetherableUsbRegexs;
public final String[] tetherableWifiRegexs;
public final String[] tetherableWifiP2pRegexs;
@@ -100,35 +94,38 @@
public final String provisioningAppNoUi;
public final int provisioningCheckPeriod;
- public final int subId;
+ public final int activeDataSubId;
public TetheringConfiguration(Context ctx, SharedLog log, int id) {
final SharedLog configLog = log.forSubComponent("config");
- subId = id;
- Resources res = getResources(ctx, subId);
+ activeDataSubId = id;
+ Resources res = getResources(ctx, activeDataSubId);
- tetherableUsbRegexs = getResourceStringArray(res, config_tether_usb_regexs);
+ tetherableUsbRegexs = getResourceStringArray(res, R.array.config_tether_usb_regexs);
// TODO: Evaluate deleting this altogether now that Wi-Fi always passes
// us an interface name. Careful consideration needs to be given to
// implications for Settings and for provisioning checks.
- tetherableWifiRegexs = getResourceStringArray(res, config_tether_wifi_regexs);
- tetherableWifiP2pRegexs = getResourceStringArray(res, config_tether_wifi_p2p_regexs);
- tetherableBluetoothRegexs = getResourceStringArray(res, config_tether_bluetooth_regexs);
+ tetherableWifiRegexs = getResourceStringArray(res, R.array.config_tether_wifi_regexs);
+ tetherableWifiP2pRegexs = getResourceStringArray(
+ res, R.array.config_tether_wifi_p2p_regexs);
+ tetherableBluetoothRegexs = getResourceStringArray(
+ res, R.array.config_tether_bluetooth_regexs);
- isDunRequired = checkDunRequired(ctx, subId);
+ isDunRequired = checkDunRequired(ctx);
- chooseUpstreamAutomatically = getResourceBoolean(res, config_tether_upstream_automatic);
+ chooseUpstreamAutomatically = getResourceBoolean(
+ res, R.bool.config_tether_upstream_automatic);
preferredUpstreamIfaceTypes = getUpstreamIfaceTypes(res, isDunRequired);
legacyDhcpRanges = getLegacyDhcpRanges(res);
defaultIPv4DNS = copy(DEFAULT_IPV4_DNS);
- enableLegacyDhcpServer = getEnableLegacyDhcpServer(ctx);
+ enableLegacyDhcpServer = getEnableLegacyDhcpServer(res);
- provisioningApp = getResourceStringArray(res, config_mobile_hotspot_provision_app);
+ provisioningApp = getResourceStringArray(res, R.array.config_mobile_hotspot_provision_app);
provisioningAppNoUi = getProvisioningAppNoUi(res);
provisioningCheckPeriod = getResourceInteger(res,
- config_mobile_hotspot_provision_check_period,
+ R.integer.config_mobile_hotspot_provision_check_period,
0 /* No periodic re-check */);
configLog.log(toString());
@@ -166,8 +163,8 @@
/** Does the dumping.*/
public void dump(PrintWriter pw) {
- pw.print("subId: ");
- pw.println(subId);
+ pw.print("activeDataSubId: ");
+ pw.println(activeDataSubId);
dumpStringArray(pw, "tetherableUsbRegexs", tetherableUsbRegexs);
dumpStringArray(pw, "tetherableWifiRegexs", tetherableWifiRegexs);
@@ -179,8 +176,8 @@
pw.print("chooseUpstreamAutomatically: ");
pw.println(chooseUpstreamAutomatically);
- dumpStringArray(pw, "preferredUpstreamIfaceTypes",
- preferredUpstreamNames(preferredUpstreamIfaceTypes));
+ pw.print("legacyPreredUpstreamIfaceTypes: ");
+ pw.println(Arrays.toString(toIntArray(preferredUpstreamIfaceTypes)));
dumpStringArray(pw, "legacyDhcpRanges", legacyDhcpRanges);
dumpStringArray(pw, "defaultIPv4DNS", defaultIPv4DNS);
@@ -196,7 +193,7 @@
/** Returns the string representation of this object.*/
public String toString() {
final StringJoiner sj = new StringJoiner(" ");
- sj.add(String.format("subId:%d", subId));
+ sj.add(String.format("activeDataSubId:%d", activeDataSubId));
sj.add(String.format("tetherableUsbRegexs:%s", makeString(tetherableUsbRegexs)));
sj.add(String.format("tetherableWifiRegexs:%s", makeString(tetherableWifiRegexs)));
sj.add(String.format("tetherableWifiP2pRegexs:%s", makeString(tetherableWifiP2pRegexs)));
@@ -205,7 +202,7 @@
sj.add(String.format("isDunRequired:%s", isDunRequired));
sj.add(String.format("chooseUpstreamAutomatically:%s", chooseUpstreamAutomatically));
sj.add(String.format("preferredUpstreamIfaceTypes:%s",
- makeString(preferredUpstreamNames(preferredUpstreamIfaceTypes))));
+ toIntArray(preferredUpstreamIfaceTypes)));
sj.add(String.format("provisioningApp:%s", makeString(provisioningApp)));
sj.add(String.format("provisioningAppNoUi:%s", provisioningAppNoUi));
sj.add(String.format("enableLegacyDhcpServer:%s", enableLegacyDhcpServer));
@@ -234,29 +231,16 @@
return sj.toString();
}
- private static String[] preferredUpstreamNames(Collection<Integer> upstreamTypes) {
- String[] upstreamNames = null;
-
- if (upstreamTypes != null) {
- upstreamNames = new String[upstreamTypes.size()];
- int i = 0;
- for (Integer netType : upstreamTypes) {
- upstreamNames[i] = ConnectivityManager.getNetworkTypeName(netType);
- i++;
- }
- }
-
- return upstreamNames;
- }
-
/** Check whether dun is required. */
- public static boolean checkDunRequired(Context ctx, int id) {
+ public static boolean checkDunRequired(Context ctx) {
final TelephonyManager tm = (TelephonyManager) ctx.getSystemService(TELEPHONY_SERVICE);
- return (tm != null) ? tm.isTetheringApnRequired(id) : false;
+ // TelephonyManager would uses the active data subscription, which should be the one used
+ // by tethering.
+ return (tm != null) ? tm.isTetheringApnRequired() : false;
}
private static Collection<Integer> getUpstreamIfaceTypes(Resources res, boolean dunRequired) {
- final int[] ifaceTypes = res.getIntArray(config_tether_upstream_types);
+ final int[] ifaceTypes = res.getIntArray(R.array.config_tether_upstream_types);
final ArrayList<Integer> upstreamIfaceTypes = new ArrayList<>(ifaceTypes.length);
for (int i : ifaceTypes) {
switch (i) {
@@ -306,7 +290,7 @@
}
private static String[] getLegacyDhcpRanges(Resources res) {
- final String[] fromResource = getResourceStringArray(res, config_tether_dhcp_range);
+ final String[] fromResource = getResourceStringArray(res, R.array.config_tether_dhcp_range);
if ((fromResource.length > 0) && (fromResource.length % 2 == 0)) {
return fromResource;
}
@@ -315,7 +299,7 @@
private static String getProvisioningAppNoUi(Resources res) {
try {
- return res.getString(config_mobile_hotspot_provision_app_no_ui);
+ return res.getString(R.string.config_mobile_hotspot_provision_app_no_ui);
} catch (Resources.NotFoundException e) {
return "";
}
@@ -346,10 +330,14 @@
}
}
- private static boolean getEnableLegacyDhcpServer(Context ctx) {
- final ContentResolver cr = ctx.getContentResolver();
- final int intVal = Settings.Global.getInt(cr, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0);
- return intVal != 0;
+ private boolean getEnableLegacyDhcpServer(final Resources res) {
+ return getResourceBoolean(res, R.bool.config_tether_enable_legacy_dhcp_server)
+ || getDeviceConfigBoolean(TETHER_ENABLE_LEGACY_DHCP_SERVER);
+ }
+
+ @VisibleForTesting
+ protected boolean getDeviceConfigBoolean(final String name) {
+ return DeviceConfig.getBoolean(NAMESPACE_CONNECTIVITY, name, false /** defaultValue */);
}
private Resources getResources(Context ctx, int subId) {
@@ -386,24 +374,28 @@
return false;
}
+ private static int[] toIntArray(Collection<Integer> values) {
+ final int[] result = new int[values.size()];
+ int index = 0;
+ for (Integer value : values) {
+ result[index++] = value;
+ }
+ return result;
+ }
+
/**
* Convert this TetheringConfiguration to a TetheringConfigurationParcel.
*/
public TetheringConfigurationParcel toStableParcelable() {
final TetheringConfigurationParcel parcel = new TetheringConfigurationParcel();
- parcel.subId = subId;
+ parcel.subId = activeDataSubId;
parcel.tetherableUsbRegexs = tetherableUsbRegexs;
parcel.tetherableWifiRegexs = tetherableWifiRegexs;
parcel.tetherableBluetoothRegexs = tetherableBluetoothRegexs;
parcel.isDunRequired = isDunRequired;
parcel.chooseUpstreamAutomatically = chooseUpstreamAutomatically;
- int[] preferredTypes = new int[preferredUpstreamIfaceTypes.size()];
- int index = 0;
- for (Integer type : preferredUpstreamIfaceTypes) {
- preferredTypes[index++] = type;
- }
- parcel.preferredUpstreamIfaceTypes = preferredTypes;
+ parcel.preferredUpstreamIfaceTypes = toIntArray(preferredUpstreamIfaceTypes);
parcel.legacyDhcpRanges = legacyDhcpRanges;
parcel.defaultIPv4DNS = defaultIPv4DNS;
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java
index b16b329..e019c3a 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringDependencies.java
@@ -16,18 +16,15 @@
package com.android.server.connectivity.tethering;
+import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.net.INetd;
-import android.net.INetworkPolicyManager;
-import android.net.INetworkStatsService;
import android.net.NetworkRequest;
import android.net.ip.IpServer;
import android.net.util.SharedLog;
import android.os.Handler;
import android.os.IBinder;
-import android.os.INetworkManagementService;
import android.os.Looper;
-import android.os.ServiceManager;
import com.android.internal.util.StateMachine;
@@ -97,33 +94,6 @@
}
/**
- * Get a reference to INetworkManagementService to registerTetheringStatsProvider from
- * OffloadController. Note: This should be removed soon by Usage refactor work in R
- * development cycle.
- */
- public INetworkManagementService getINetworkManagementService() {
- return INetworkManagementService.Stub.asInterface(
- ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
- }
-
- /**
- * Get a reference to INetworkStatsService to force update tethering usage.
- * Note: This should be removed in R development cycle.
- */
- public INetworkStatsService getINetworkStatsService() {
- return INetworkStatsService.Stub.asInterface(
- ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
- }
-
- /**
- * Get a reference to INetworkPolicyManager to be used by tethering.
- */
- public INetworkPolicyManager getINetworkPolicyManager() {
- return INetworkPolicyManager.Stub.asInterface(
- ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
- }
-
- /**
* Get a reference to INetd to be used by tethering.
*/
public INetd getINetd(Context context) {
@@ -140,4 +110,9 @@
* Get Context of TetheringSerice.
*/
public abstract Context getContext();
+
+ /**
+ * Get a reference to BluetoothAdapter to be used by tethering.
+ */
+ public abstract BluetoothAdapter getBluetoothAdapter();
}
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java
index 6334c20..4dd6830 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringInterfaceUtils.java
@@ -22,14 +22,19 @@
import android.net.RouteInfo;
import android.net.util.InterfaceSet;
-import java.net.Inet4Address;
-import java.net.Inet6Address;
+import com.android.net.module.util.NetUtils;
+
import java.net.InetAddress;
+import java.net.UnknownHostException;
/**
* @hide
*/
public final class TetheringInterfaceUtils {
+ private static final InetAddress IN6ADDR_ANY = getByAddress(
+ new byte[] {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0});
+ private static final InetAddress INADDR_ANY = getByAddress(new byte[] {0, 0, 0, 0});
+
/**
* Get upstream interfaces for tethering based on default routes for IPv4/IPv6.
* @return null if there is no usable interface, or a set of at least one interface otherwise.
@@ -40,7 +45,7 @@
}
final LinkProperties lp = ns.linkProperties;
- final String if4 = getInterfaceForDestination(lp, Inet4Address.ANY);
+ final String if4 = getInterfaceForDestination(lp, INADDR_ANY);
final String if6 = getIPv6Interface(ns);
return (if4 == null && if6 == null) ? null : new InterfaceSet(if4, if6);
@@ -76,14 +81,22 @@
&& ns.networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR);
return canTether
- ? getInterfaceForDestination(ns.linkProperties, Inet6Address.ANY)
+ ? getInterfaceForDestination(ns.linkProperties, IN6ADDR_ANY)
: null;
}
private static String getInterfaceForDestination(LinkProperties lp, InetAddress dst) {
final RouteInfo ri = (lp != null)
- ? RouteInfo.selectBestRoute(lp.getAllRoutes(), dst)
+ ? NetUtils.selectBestRoute(lp.getAllRoutes(), dst)
: null;
return (ri != null) ? ri.getInterface() : null;
}
+
+ private static InetAddress getByAddress(final byte[] addr) {
+ try {
+ return InetAddress.getByAddress(null, addr);
+ } catch (UnknownHostException e) {
+ throw new AssertionError("illegal address length" + addr.length);
+ }
+ }
}
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java
index ba30845..cb7d392 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/TetheringService.java
@@ -21,15 +21,17 @@
import static android.net.TetheringManager.TETHER_ERROR_NO_CHANGE_TETHERING_PERMISSION;
import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
import static android.net.TetheringManager.TETHER_ERROR_UNSUPPORTED;
+import static android.net.dhcp.IDhcpServer.STATUS_UNKNOWN_ERROR;
import android.app.Service;
+import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.Intent;
-import android.net.ConnectivityManager;
import android.net.IIntResultListener;
import android.net.INetworkStackConnector;
import android.net.ITetheringConnector;
import android.net.ITetheringEventCallback;
+import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.dhcp.DhcpServerCallbacks;
import android.net.dhcp.DhcpServingParamsParcel;
@@ -41,7 +43,6 @@
import android.os.Looper;
import android.os.RemoteException;
import android.os.ResultReceiver;
-import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.UserManager;
import android.provider.Settings;
@@ -84,6 +85,7 @@
*/
@VisibleForTesting
public Tethering makeTethering(TetheringDependencies deps) {
+ System.loadLibrary("tetherutilsjni");
return new Tethering(deps);
}
@@ -305,9 +307,15 @@
mDeps = new TetheringDependencies() {
@Override
public NetworkRequest getDefaultNetworkRequest() {
- ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService(
- Context.CONNECTIVITY_SERVICE);
- return cm.getDefaultRequest();
+ // TODO: b/147280869, add a proper system API to replace this.
+ final NetworkRequest trackDefaultRequest = new NetworkRequest.Builder()
+ .clearCapabilities()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .build();
+ return trackDefaultRequest;
}
@Override
@@ -339,7 +347,10 @@
service.makeDhcpServer(ifName, params, cb);
} catch (RemoteException e) {
- e.rethrowFromSystemServer();
+ Log.e(TAG, "Fail to make dhcp server");
+ try {
+ cb.onDhcpServerCreated(STATUS_UNKNOWN_ERROR, null);
+ } catch (RemoteException re) { }
}
}
};
@@ -352,7 +363,7 @@
IBinder connector;
try {
final long before = System.currentTimeMillis();
- while ((connector = ServiceManager.getService(
+ while ((connector = (IBinder) mContext.getSystemService(
Context.NETWORK_STACK_SERVICE)) == null) {
if (System.currentTimeMillis() - before > NETWORKSTACK_TIMEOUT_MS) {
Log.wtf(TAG, "Timeout, fail to get INetworkStackConnector");
@@ -366,6 +377,11 @@
}
return INetworkStackConnector.Stub.asInterface(connector);
}
+
+ @Override
+ public BluetoothAdapter getBluetoothAdapter() {
+ return BluetoothAdapter.getDefaultAdapter();
+ }
};
}
return mDeps;
diff --git a/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java b/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
index dc38c49a..2875f71 100644
--- a/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
+++ b/packages/Tethering/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitor.java
@@ -16,11 +16,15 @@
package com.android.server.connectivity.tethering;
+import static android.net.ConnectivityManager.TYPE_BLUETOOTH;
+import static android.net.ConnectivityManager.TYPE_ETHERNET;
+import static android.net.ConnectivityManager.TYPE_MOBILE;
import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
-import static android.net.ConnectivityManager.TYPE_NONE;
-import static android.net.ConnectivityManager.getNetworkTypeName;
+import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
@@ -35,10 +39,11 @@
import android.net.util.PrefixUtils;
import android.net.util.SharedLog;
import android.os.Handler;
-import android.os.Process;
import android.util.Log;
+import android.util.SparseIntArray;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
import com.android.internal.util.StateMachine;
import java.util.HashMap;
@@ -79,11 +84,25 @@
public static final int EVENT_ON_LINKPROPERTIES = 2;
public static final int EVENT_ON_LOST = 3;
public static final int NOTIFY_LOCAL_PREFIXES = 10;
+ // This value is used by deprecated preferredUpstreamIfaceTypes selection which is default
+ // disabled.
+ @VisibleForTesting
+ public static final int TYPE_NONE = -1;
private static final int CALLBACK_LISTEN_ALL = 1;
private static final int CALLBACK_DEFAULT_INTERNET = 2;
private static final int CALLBACK_MOBILE_REQUEST = 3;
+ private static final SparseIntArray sLegacyTypeToTransport = new SparseIntArray();
+ static {
+ sLegacyTypeToTransport.put(TYPE_MOBILE, NetworkCapabilities.TRANSPORT_CELLULAR);
+ sLegacyTypeToTransport.put(TYPE_MOBILE_DUN, NetworkCapabilities.TRANSPORT_CELLULAR);
+ sLegacyTypeToTransport.put(TYPE_MOBILE_HIPRI, NetworkCapabilities.TRANSPORT_CELLULAR);
+ sLegacyTypeToTransport.put(TYPE_WIFI, NetworkCapabilities.TRANSPORT_WIFI);
+ sLegacyTypeToTransport.put(TYPE_BLUETOOTH, NetworkCapabilities.TRANSPORT_BLUETOOTH);
+ sLegacyTypeToTransport.put(TYPE_ETHERNET, NetworkCapabilities.TRANSPORT_ETHERNET);
+ }
+
private final Context mContext;
private final SharedLog mLog;
private final StateMachine mTarget;
@@ -130,15 +149,15 @@
*/
public void startTrackDefaultNetwork(NetworkRequest defaultNetworkRequest,
EntitlementManager entitle) {
- // This is not really a "request", just a way of tracking the system default network.
- // It's guaranteed not to actually bring up any networks because it's the same request
- // as the ConnectivityService default request, and thus shares fate with it. We can't
- // use registerDefaultNetworkCallback because it will not track the system default
- // network if there is a VPN that applies to our UID.
+
+ // defaultNetworkRequest is not really a "request", just a way of tracking the system
+ // default network. It's guaranteed not to actually bring up any networks because it's
+ // the should be the same request as the ConnectivityService default request, and thus
+ // shares fate with it. We can't use registerDefaultNetworkCallback because it will not
+ // track the system default network if there is a VPN that applies to our UID.
if (mDefaultNetworkCallback == null) {
- final NetworkRequest trackDefaultRequest = new NetworkRequest(defaultNetworkRequest);
mDefaultNetworkCallback = new UpstreamNetworkCallback(CALLBACK_DEFAULT_INTERNET);
- cm().requestNetwork(trackDefaultRequest, mDefaultNetworkCallback, mHandler);
+ cm().requestNetwork(defaultNetworkRequest, mDefaultNetworkCallback, mHandler);
}
if (mEntitlementMgr == null) {
mEntitlementMgr = entitle;
@@ -198,19 +217,28 @@
mLog.e("registerMobileNetworkRequest() already registered");
return;
}
- // The following use of the legacy type system cannot be removed until
- // after upstream selection no longer finds networks by legacy type.
- // See also http://b/34364553 .
- final int legacyType = mDunRequired ? TYPE_MOBILE_DUN : TYPE_MOBILE_HIPRI;
- final NetworkRequest mobileUpstreamRequest = new NetworkRequest.Builder()
- .setCapabilities(ConnectivityManager.networkCapabilitiesForType(legacyType))
- .build();
+ final NetworkRequest mobileUpstreamRequest;
+ if (mDunRequired) {
+ mobileUpstreamRequest = new NetworkRequest.Builder()
+ .addCapability(NET_CAPABILITY_DUN)
+ .removeCapability(NET_CAPABILITY_NOT_RESTRICTED)
+ .addTransportType(TRANSPORT_CELLULAR).build();
+ } else {
+ mobileUpstreamRequest = new NetworkRequest.Builder()
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addTransportType(TRANSPORT_CELLULAR).build();
+ }
// The existing default network and DUN callbacks will be notified.
// Therefore, to avoid duplicate notifications, we only register a no-op.
mMobileNetworkCallback = new UpstreamNetworkCallback(CALLBACK_MOBILE_REQUEST);
+ // The following use of the legacy type system cannot be removed until
+ // upstream selection no longer finds networks by legacy type.
+ // See also http://b/34364553 .
+ final int legacyType = mDunRequired ? TYPE_MOBILE_DUN : TYPE_MOBILE_HIPRI;
+
// TODO: Change the timeout from 0 (no onUnavailable callback) to some
// moderate callback timeout. This might be useful for updating some UI.
// Additionally, we log a message to aid in any subsequent debugging.
@@ -239,7 +267,7 @@
final TypeStatePair typeStatePair = findFirstAvailableUpstreamByType(
mNetworkMap.values(), preferredTypes, isCellularUpstreamPermitted());
- mLog.log("preferred upstream type: " + getNetworkTypeName(typeStatePair.type));
+ mLog.log("preferred upstream type: " + typeStatePair.type);
switch (typeStatePair.type) {
case TYPE_MOBILE_DUN:
@@ -328,13 +356,6 @@
network, newNc));
}
- // Log changes in upstream network signal strength, if available.
- if (network.equals(mTetheringUpstreamNetwork) && newNc.hasSignalStrength()) {
- final int newSignal = newNc.getSignalStrength();
- final String prevSignal = getSignalStrength(prev.networkCapabilities);
- mLog.logf("upstream network signal strength: %s -> %s", prevSignal, newSignal);
- }
-
mNetworkMap.put(network, new UpstreamNetworkState(
prev.linkProperties, newNc, network));
// TODO: If sufficient information is available to select a more
@@ -363,16 +384,6 @@
notifyTarget(EVENT_ON_LINKPROPERTIES, network);
}
- private void handleSuspended(Network network) {
- if (!network.equals(mTetheringUpstreamNetwork)) return;
- mLog.log("SUSPENDED current upstream: " + network);
- }
-
- private void handleResumed(Network network) {
- if (!network.equals(mTetheringUpstreamNetwork)) return;
- mLog.log("RESUMED current upstream: " + network);
- }
-
private void handleLost(Network network) {
// There are few TODOs within ConnectivityService's rematching code
// pertaining to spurious onLost() notifications.
@@ -462,20 +473,6 @@
}
@Override
- public void onNetworkSuspended(Network network) {
- if (mCallbackType == CALLBACK_LISTEN_ALL) {
- handleSuspended(network);
- }
- }
-
- @Override
- public void onNetworkResumed(Network network) {
- if (mCallbackType == CALLBACK_LISTEN_ALL) {
- handleResumed(network);
- }
- }
-
- @Override
public void onLost(Network network) {
if (mCallbackType == CALLBACK_DEFAULT_INTERNET) {
mDefaultInternetNetwork = null;
@@ -519,18 +516,15 @@
for (int type : preferredTypes) {
NetworkCapabilities nc;
try {
- nc = ConnectivityManager.networkCapabilitiesForType(type);
+ nc = networkCapabilitiesForType(type);
} catch (IllegalArgumentException iae) {
- Log.e(TAG, "No NetworkCapabilities mapping for legacy type: "
- + ConnectivityManager.getNetworkTypeName(type));
+ Log.e(TAG, "No NetworkCapabilities mapping for legacy type: " + type);
continue;
}
if (!isCellularUpstreamPermitted && isCellular(nc)) {
continue;
}
- nc.setSingleUid(Process.myUid());
-
for (UpstreamNetworkState value : netStates) {
if (!nc.satisfiedByNetworkCapabilities(value.networkCapabilities)) {
continue;
@@ -557,11 +551,6 @@
return prefixSet;
}
- private static String getSignalStrength(NetworkCapabilities nc) {
- if (nc == null || !nc.hasSignalStrength()) return "unknown";
- return Integer.toString(nc.getSignalStrength());
- }
-
private static boolean isCellular(UpstreamNetworkState ns) {
return (ns != null) && isCellular(ns.networkCapabilities);
}
@@ -589,4 +578,28 @@
return null;
}
+
+ /**
+ * Given a legacy type (TYPE_WIFI, ...) returns the corresponding NetworkCapabilities instance.
+ * This function is used for deprecated legacy type and be disabled by default.
+ */
+ @VisibleForTesting
+ public static NetworkCapabilities networkCapabilitiesForType(int type) {
+ final NetworkCapabilities nc = new NetworkCapabilities();
+
+ // Map from type to transports.
+ final int notFound = -1;
+ final int transport = sLegacyTypeToTransport.get(type, notFound);
+ Preconditions.checkArgument(transport != notFound, "unknown legacy type: " + type);
+ nc.addTransportType(transport);
+
+ if (type == TYPE_MOBILE_DUN) {
+ nc.addCapability(NetworkCapabilities.NET_CAPABILITY_DUN);
+ // DUN is restricted network, see NetworkCapabilities#FORCE_RESTRICTED_CAPABILITIES.
+ nc.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
+ } else {
+ nc.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+ }
+ return nc;
+ }
}
diff --git a/packages/Tethering/tests/unit/Android.bp b/packages/Tethering/tests/unit/Android.bp
index 81a0548..53782fed 100644
--- a/packages/Tethering/tests/unit/Android.bp
+++ b/packages/Tethering/tests/unit/Android.bp
@@ -20,7 +20,11 @@
srcs: [
"src/**/*.java",
],
- test_suites: ["device-tests"],
+ test_suites: [
+ "device-tests",
+ "mts",
+ ],
+ compile_multilib: "both",
static_libs: [
"androidx.test.rules",
"frameworks-base-testutils",
diff --git a/packages/Tethering/tests/unit/jarjar-rules.txt b/packages/Tethering/tests/unit/jarjar-rules.txt
index 64fdebd..921fbed 100644
--- a/packages/Tethering/tests/unit/jarjar-rules.txt
+++ b/packages/Tethering/tests/unit/jarjar-rules.txt
@@ -7,5 +7,6 @@
rule com.android.internal.util.Preconditions* com.android.networkstack.tethering.util.Preconditions@1
rule com.android.internal.util.State* com.android.networkstack.tethering.util.State@1
rule com.android.internal.util.StateMachine* com.android.networkstack.tethering.util.StateMachine@1
+rule com.android.internal.util.TrafficStatsConstants* com.android.networkstack.tethering.util.TrafficStatsConstants@1
rule android.net.LocalLog* com.android.networkstack.tethering.LocalLog@1
diff --git a/packages/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java b/packages/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java
index e01ac7f..e8add98 100644
--- a/packages/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java
+++ b/packages/Tethering/tests/unit/src/android/net/dhcp/DhcpServingParamsParcelExtTest.java
@@ -18,8 +18,6 @@
import static android.net.InetAddresses.parseNumericAddress;
-import static com.google.android.collect.Sets.newHashSet;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -34,6 +32,8 @@
import org.junit.runner.RunWith;
import java.net.Inet4Address;
+import java.util.Arrays;
+import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@@ -47,9 +47,10 @@
private static final int TEST_LEASE_TIME_SECS = 120;
private static final int TEST_MTU = 1000;
private static final Set<Inet4Address> TEST_ADDRESS_SET =
- newHashSet(inet4Addr("192.168.1.123"), inet4Addr("192.168.1.124"));
+ new HashSet<Inet4Address>(Arrays.asList(
+ new Inet4Address[] {inet4Addr("192.168.1.123"), inet4Addr("192.168.1.124")}));
private static final Set<Integer> TEST_ADDRESS_SET_PARCELED =
- newHashSet(0xc0a8017b, 0xc0a8017c);
+ new HashSet<Integer>(Arrays.asList(new Integer[] {0xc0a8017b, 0xc0a8017c}));
private DhcpServingParamsParcelExt mParcel;
diff --git a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
index 4358cd6..1f50b6b 100644
--- a/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
+++ b/packages/Tethering/tests/unit/src/android/net/ip/IpServerTest.java
@@ -16,13 +16,14 @@
package android.net.ip;
-import static android.net.ConnectivityManager.TETHERING_BLUETOOTH;
-import static android.net.ConnectivityManager.TETHERING_USB;
-import static android.net.ConnectivityManager.TETHERING_WIFI;
-import static android.net.ConnectivityManager.TETHERING_WIFI_P2P;
-import static android.net.ConnectivityManager.TETHER_ERROR_ENABLE_NAT_ERROR;
-import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
-import static android.net.ConnectivityManager.TETHER_ERROR_TETHER_IFACE_ERROR;
+import static android.net.INetd.IF_STATE_UP;
+import static android.net.TetheringManager.TETHERING_BLUETOOTH;
+import static android.net.TetheringManager.TETHERING_USB;
+import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHERING_WIFI_P2P;
+import static android.net.TetheringManager.TETHER_ERROR_ENABLE_NAT_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_TETHER_IFACE_ERROR;
import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
import static android.net.ip.IpServer.STATE_AVAILABLE;
import static android.net.ip.IpServer.STATE_LOCAL_ONLY;
@@ -51,8 +52,7 @@
import static org.mockito.Mockito.when;
import android.net.INetd;
-import android.net.INetworkStatsService;
-import android.net.InterfaceConfiguration;
+import android.net.InterfaceConfigurationParcel;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
@@ -64,7 +64,6 @@
import android.net.util.InterfaceParams;
import android.net.util.InterfaceSet;
import android.net.util.SharedLog;
-import android.os.INetworkManagementService;
import android.os.RemoteException;
import android.os.test.TestLooper;
import android.text.TextUtils;
@@ -89,6 +88,8 @@
private static final String IFACE_NAME = "testnet1";
private static final String UPSTREAM_IFACE = "upstream0";
private static final String UPSTREAM_IFACE2 = "upstream1";
+ private static final String BLUETOOTH_IFACE_ADDR = "192.168.42.1";
+ private static final int BLUETOOTH_DHCP_PREFIX_LENGTH = 24;
private static final int DHCP_LEASE_TIME_SECS = 3600;
private static final InterfaceParams TEST_IFACE_PARAMS = new InterfaceParams(
@@ -96,11 +97,8 @@
private static final int MAKE_DHCPSERVER_TIMEOUT_MS = 1000;
- @Mock private INetworkManagementService mNMService;
@Mock private INetd mNetd;
- @Mock private INetworkStatsService mStatsService;
@Mock private IpServer.Callback mCallback;
- @Mock private InterfaceConfiguration mInterfaceConfiguration;
@Mock private SharedLog mSharedLog;
@Mock private IDhcpServer mDhcpServer;
@Mock private RouterAdvertisementDaemon mRaDaemon;
@@ -112,6 +110,7 @@
private final ArgumentCaptor<LinkProperties> mLinkPropertiesCaptor =
ArgumentCaptor.forClass(LinkProperties.class);
private IpServer mIpServer;
+ private InterfaceConfigurationParcel mInterfaceConfiguration;
private void initStateMachine(int interfaceType) throws Exception {
initStateMachine(interfaceType, false /* usingLegacyDhcp */);
@@ -131,17 +130,20 @@
}).when(mDependencies).makeDhcpServer(any(), mDhcpParamsCaptor.capture(), any());
when(mDependencies.getRouterAdvertisementDaemon(any())).thenReturn(mRaDaemon);
when(mDependencies.getInterfaceParams(IFACE_NAME)).thenReturn(TEST_IFACE_PARAMS);
- when(mDependencies.getNetdService()).thenReturn(mNetd);
-
+ mInterfaceConfiguration = new InterfaceConfigurationParcel();
+ mInterfaceConfiguration.flags = new String[0];
+ if (interfaceType == TETHERING_BLUETOOTH) {
+ mInterfaceConfiguration.ipv4Addr = BLUETOOTH_IFACE_ADDR;
+ mInterfaceConfiguration.prefixLength = BLUETOOTH_DHCP_PREFIX_LENGTH;
+ }
mIpServer = new IpServer(
- IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog,
- mNMService, mStatsService, mCallback, usingLegacyDhcp, mDependencies);
+ IFACE_NAME, mLooper.getLooper(), interfaceType, mSharedLog, mNetd,
+ mCallback, usingLegacyDhcp, mDependencies);
mIpServer.start();
// Starting the state machine always puts us in a consistent state and notifies
// the rest of the world that we've changed from an unknown to available state.
mLooper.dispatchAll();
- reset(mNMService, mStatsService, mCallback);
- when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration);
+ reset(mNetd, mCallback);
when(mRaDaemon.start()).thenReturn(true);
}
@@ -158,8 +160,7 @@
if (upstreamIface != null) {
dispatchTetherConnectionChanged(upstreamIface);
}
- reset(mNMService, mStatsService, mCallback);
- when(mNMService.getInterfaceConfig(IFACE_NAME)).thenReturn(mInterfaceConfiguration);
+ reset(mNetd, mCallback);
}
@Before public void setUp() throws Exception {
@@ -169,15 +170,14 @@
@Test
public void startsOutAvailable() {
- mIpServer = new IpServer(IFACE_NAME, mLooper.getLooper(),
- TETHERING_BLUETOOTH, mSharedLog, mNMService, mStatsService, mCallback,
- false /* usingLegacyDhcp */, mDependencies);
+ mIpServer = new IpServer(IFACE_NAME, mLooper.getLooper(), TETHERING_BLUETOOTH, mSharedLog,
+ mNetd, mCallback, false /* usingLegacyDhcp */, mDependencies);
mIpServer.start();
mLooper.dispatchAll();
verify(mCallback).updateInterfaceState(
mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
verify(mCallback).updateLinkProperties(eq(mIpServer), any(LinkProperties.class));
- verifyNoMoreInteractions(mCallback, mNMService, mStatsService);
+ verifyNoMoreInteractions(mCallback, mNetd);
}
@Test
@@ -196,7 +196,7 @@
// None of these commands should trigger us to request action from
// the rest of the system.
dispatchCommand(command);
- verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+ verifyNoMoreInteractions(mNetd, mCallback);
}
}
@@ -208,7 +208,7 @@
verify(mCallback).updateInterfaceState(
mIpServer, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR);
verify(mCallback).updateLinkProperties(eq(mIpServer), any(LinkProperties.class));
- verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+ verifyNoMoreInteractions(mNetd, mCallback);
}
@Test
@@ -216,13 +216,17 @@
initStateMachine(TETHERING_BLUETOOTH);
dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
- InOrder inOrder = inOrder(mCallback, mNMService);
- inOrder.verify(mNMService).tetherInterface(IFACE_NAME);
+ InOrder inOrder = inOrder(mCallback, mNetd);
+ inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
+ inOrder.verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
+ // One for ipv4 route, one for ipv6 link local route.
+ inOrder.verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(IFACE_NAME),
+ any(), any());
inOrder.verify(mCallback).updateInterfaceState(
mIpServer, STATE_TETHERED, TETHER_ERROR_NO_ERROR);
inOrder.verify(mCallback).updateLinkProperties(
eq(mIpServer), any(LinkProperties.class));
- verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+ verifyNoMoreInteractions(mNetd, mCallback);
}
@Test
@@ -230,14 +234,16 @@
initTetheredStateMachine(TETHERING_BLUETOOTH, null);
dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED);
- InOrder inOrder = inOrder(mNMService, mNetd, mStatsService, mCallback);
- inOrder.verify(mNMService).untetherInterface(IFACE_NAME);
+ InOrder inOrder = inOrder(mNetd, mCallback);
+ inOrder.verify(mNetd).tetherApplyDnsInterfaces();
+ inOrder.verify(mNetd).tetherInterfaceRemove(IFACE_NAME);
+ inOrder.verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
inOrder.verify(mCallback).updateInterfaceState(
mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
inOrder.verify(mCallback).updateLinkProperties(
eq(mIpServer), any(LinkProperties.class));
- verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+ verifyNoMoreInteractions(mNetd, mCallback);
}
@Test
@@ -245,16 +251,19 @@
initStateMachine(TETHERING_USB);
dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
- InOrder inOrder = inOrder(mCallback, mNMService);
- inOrder.verify(mNMService).getInterfaceConfig(IFACE_NAME);
- inOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration);
- inOrder.verify(mNMService).tetherInterface(IFACE_NAME);
+ InOrder inOrder = inOrder(mCallback, mNetd);
+ inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
+ IFACE_NAME.equals(cfg.ifName) && assertContainsFlag(cfg.flags, IF_STATE_UP)));
+ inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
+ inOrder.verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
+ inOrder.verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(IFACE_NAME),
+ any(), any());
inOrder.verify(mCallback).updateInterfaceState(
mIpServer, STATE_TETHERED, TETHER_ERROR_NO_ERROR);
inOrder.verify(mCallback).updateLinkProperties(
eq(mIpServer), mLinkPropertiesCaptor.capture());
assertIPv4AddressAndDirectlyConnectedRoute(mLinkPropertiesCaptor.getValue());
- verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+ verifyNoMoreInteractions(mNetd, mCallback);
}
@Test
@@ -262,16 +271,19 @@
initStateMachine(TETHERING_WIFI_P2P);
dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_LOCAL_ONLY);
- InOrder inOrder = inOrder(mCallback, mNMService);
- inOrder.verify(mNMService).getInterfaceConfig(IFACE_NAME);
- inOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration);
- inOrder.verify(mNMService).tetherInterface(IFACE_NAME);
+ InOrder inOrder = inOrder(mCallback, mNetd);
+ inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg ->
+ IFACE_NAME.equals(cfg.ifName) && assertContainsFlag(cfg.flags, IF_STATE_UP)));
+ inOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
+ inOrder.verify(mNetd).networkAddInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
+ inOrder.verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(IFACE_NAME),
+ any(), any());
inOrder.verify(mCallback).updateInterfaceState(
mIpServer, STATE_LOCAL_ONLY, TETHER_ERROR_NO_ERROR);
inOrder.verify(mCallback).updateLinkProperties(
eq(mIpServer), mLinkPropertiesCaptor.capture());
assertIPv4AddressAndDirectlyConnectedRoute(mLinkPropertiesCaptor.getValue());
- verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+ verifyNoMoreInteractions(mNetd, mCallback);
}
@Test
@@ -281,10 +293,10 @@
// Telling the state machine about its upstream interface triggers
// a little more configuration.
dispatchTetherConnectionChanged(UPSTREAM_IFACE);
- InOrder inOrder = inOrder(mNMService);
- inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE);
- inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
- verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+ InOrder inOrder = inOrder(mNetd);
+ inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE);
+ inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
+ verifyNoMoreInteractions(mNetd, mCallback);
}
@Test
@@ -292,49 +304,44 @@
initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
- InOrder inOrder = inOrder(mNMService, mStatsService);
- inOrder.verify(mStatsService).forceUpdate();
- inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
- inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
- inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
- inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
- verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+ InOrder inOrder = inOrder(mNetd);
+ inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
+ inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
+ inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
+ inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
+ verifyNoMoreInteractions(mNetd, mCallback);
}
@Test
public void handlesChangingUpstreamNatFailure() throws Exception {
initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE);
- doThrow(RemoteException.class).when(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
+ doThrow(RemoteException.class).when(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
- InOrder inOrder = inOrder(mNMService, mStatsService);
- inOrder.verify(mStatsService).forceUpdate();
- inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
- inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
- inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
- inOrder.verify(mStatsService).forceUpdate();
- inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
- inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE2);
+ InOrder inOrder = inOrder(mNetd);
+ inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
+ inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
+ inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
+ inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
+ inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE2);
}
@Test
public void handlesChangingUpstreamInterfaceForwardingFailure() throws Exception {
initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE);
- doThrow(RemoteException.class).when(mNMService).startInterfaceForwarding(
+ doThrow(RemoteException.class).when(mNetd).ipfwdAddInterfaceForward(
IFACE_NAME, UPSTREAM_IFACE2);
dispatchTetherConnectionChanged(UPSTREAM_IFACE2);
- InOrder inOrder = inOrder(mNMService, mStatsService);
- inOrder.verify(mStatsService).forceUpdate();
- inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
- inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
- inOrder.verify(mNMService).enableNat(IFACE_NAME, UPSTREAM_IFACE2);
- inOrder.verify(mNMService).startInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
- inOrder.verify(mStatsService).forceUpdate();
- inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE2);
- inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE2);
+ InOrder inOrder = inOrder(mNetd);
+ inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
+ inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
+ inOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE2);
+ inOrder.verify(mNetd).ipfwdAddInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
+ inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE2);
+ inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE2);
}
@Test
@@ -342,17 +349,18 @@
initTetheredStateMachine(TETHERING_BLUETOOTH, UPSTREAM_IFACE);
dispatchCommand(IpServer.CMD_TETHER_UNREQUESTED);
- InOrder inOrder = inOrder(mNMService, mNetd, mStatsService, mCallback);
- inOrder.verify(mStatsService).forceUpdate();
- inOrder.verify(mNMService).stopInterfaceForwarding(IFACE_NAME, UPSTREAM_IFACE);
- inOrder.verify(mNMService).disableNat(IFACE_NAME, UPSTREAM_IFACE);
- inOrder.verify(mNMService).untetherInterface(IFACE_NAME);
+ InOrder inOrder = inOrder(mNetd, mCallback);
+ inOrder.verify(mNetd).ipfwdRemoveInterfaceForward(IFACE_NAME, UPSTREAM_IFACE);
+ inOrder.verify(mNetd).tetherRemoveForward(IFACE_NAME, UPSTREAM_IFACE);
+ inOrder.verify(mNetd).tetherApplyDnsInterfaces();
+ inOrder.verify(mNetd).tetherInterfaceRemove(IFACE_NAME);
+ inOrder.verify(mNetd).networkRemoveInterface(INetd.LOCAL_NET_ID, IFACE_NAME);
inOrder.verify(mNetd).interfaceSetCfg(argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
inOrder.verify(mCallback).updateInterfaceState(
mIpServer, STATE_AVAILABLE, TETHER_ERROR_NO_ERROR);
inOrder.verify(mCallback).updateLinkProperties(
eq(mIpServer), any(LinkProperties.class));
- verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+ verifyNoMoreInteractions(mNetd, mCallback);
}
@Test
@@ -361,13 +369,14 @@
initTetheredStateMachine(TETHERING_USB, null);
if (shouldThrow) {
- doThrow(RemoteException.class).when(mNMService).untetherInterface(IFACE_NAME);
+ doThrow(RemoteException.class).when(mNetd).tetherInterfaceRemove(IFACE_NAME);
}
dispatchCommand(IpServer.CMD_INTERFACE_DOWN);
- InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mCallback);
- usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
- usbTeardownOrder.verify(mNMService).setInterfaceConfig(
- IFACE_NAME, mInterfaceConfiguration);
+ InOrder usbTeardownOrder = inOrder(mNetd, mCallback);
+ // Currently IpServer interfaceSetCfg twice to stop IPv4. One just set interface down
+ // Another one is set IPv4 to 0.0.0.0/0 as clearng ipv4 address.
+ usbTeardownOrder.verify(mNetd, times(2)).interfaceSetCfg(
+ argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
usbTeardownOrder.verify(mCallback).updateInterfaceState(
mIpServer, STATE_UNAVAILABLE, TETHER_ERROR_NO_ERROR);
usbTeardownOrder.verify(mCallback).updateLinkProperties(
@@ -380,12 +389,15 @@
public void usbShouldBeTornDownOnTetherError() throws Exception {
initStateMachine(TETHERING_USB);
- doThrow(RemoteException.class).when(mNMService).tetherInterface(IFACE_NAME);
+ doThrow(RemoteException.class).when(mNetd).tetherInterfaceAdd(IFACE_NAME);
dispatchCommand(IpServer.CMD_TETHER_REQUESTED, STATE_TETHERED);
- InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mCallback);
- usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
- usbTeardownOrder.verify(mNMService).setInterfaceConfig(
- IFACE_NAME, mInterfaceConfiguration);
+ InOrder usbTeardownOrder = inOrder(mNetd, mCallback);
+ usbTeardownOrder.verify(mNetd).interfaceSetCfg(
+ argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
+ usbTeardownOrder.verify(mNetd).tetherInterfaceAdd(IFACE_NAME);
+
+ usbTeardownOrder.verify(mNetd, times(2)).interfaceSetCfg(
+ argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
usbTeardownOrder.verify(mCallback).updateInterfaceState(
mIpServer, STATE_AVAILABLE, TETHER_ERROR_TETHER_IFACE_ERROR);
usbTeardownOrder.verify(mCallback).updateLinkProperties(
@@ -397,11 +409,13 @@
public void shouldTearDownUsbOnUpstreamError() throws Exception {
initTetheredStateMachine(TETHERING_USB, null);
- doThrow(RemoteException.class).when(mNMService).enableNat(anyString(), anyString());
+ doThrow(RemoteException.class).when(mNetd).tetherAddForward(anyString(), anyString());
dispatchTetherConnectionChanged(UPSTREAM_IFACE);
- InOrder usbTeardownOrder = inOrder(mNMService, mInterfaceConfiguration, mCallback);
- usbTeardownOrder.verify(mInterfaceConfiguration).setInterfaceDown();
- usbTeardownOrder.verify(mNMService).setInterfaceConfig(IFACE_NAME, mInterfaceConfiguration);
+ InOrder usbTeardownOrder = inOrder(mNetd, mCallback);
+ usbTeardownOrder.verify(mNetd).tetherAddForward(IFACE_NAME, UPSTREAM_IFACE);
+
+ usbTeardownOrder.verify(mNetd, times(2)).interfaceSetCfg(
+ argThat(cfg -> IFACE_NAME.equals(cfg.ifName)));
usbTeardownOrder.verify(mCallback).updateInterfaceState(
mIpServer, STATE_AVAILABLE, TETHER_ERROR_ENABLE_NAT_ERROR);
usbTeardownOrder.verify(mCallback).updateLinkProperties(
@@ -413,11 +427,11 @@
public void ignoresDuplicateUpstreamNotifications() throws Exception {
initTetheredStateMachine(TETHERING_WIFI, UPSTREAM_IFACE);
- verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+ verifyNoMoreInteractions(mNetd, mCallback);
for (int i = 0; i < 5; i++) {
dispatchTetherConnectionChanged(UPSTREAM_IFACE);
- verifyNoMoreInteractions(mNMService, mStatsService, mCallback);
+ verifyNoMoreInteractions(mNetd, mCallback);
}
}
@@ -510,8 +524,10 @@
}
assertNotNull("missing IPv4 address", addr4);
+ final IpPrefix destination = new IpPrefix(addr4.getAddress(), addr4.getPrefixLength());
// Assert the presence of the associated directly connected route.
- final RouteInfo directlyConnected = new RouteInfo(addr4, null, lp.getInterfaceName());
+ final RouteInfo directlyConnected = new RouteInfo(destination, null, lp.getInterfaceName(),
+ RouteInfo.RTN_UNICAST);
assertTrue("missing directly connected route: '" + directlyConnected.toString() + "'",
lp.getRoutes().contains(directlyConnected));
}
@@ -523,4 +539,12 @@
// never see an empty interface name in any LinkProperties update.
assertFalse(TextUtils.isEmpty(lp.getInterfaceName()));
}
+
+ private boolean assertContainsFlag(String[] flags, String match) {
+ for (String flag : flags) {
+ if (flag.equals(match)) return true;
+ }
+ fail("Missing flag: " + match);
+ return false;
+ }
}
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
index 99cf9e9..4f07461 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/EntitlementManagerTest.java
@@ -16,12 +16,13 @@
package com.android.server.connectivity.tethering;
-import static android.net.ConnectivityManager.TETHERING_BLUETOOTH;
-import static android.net.ConnectivityManager.TETHERING_USB;
-import static android.net.ConnectivityManager.TETHERING_WIFI;
-import static android.net.ConnectivityManager.TETHER_ERROR_ENTITLEMENT_UNKONWN;
-import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
-import static android.net.ConnectivityManager.TETHER_ERROR_PROVISION_FAILED;
+import static android.net.TetheringManager.TETHERING_BLUETOOTH;
+import static android.net.TetheringManager.TETHERING_USB;
+import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKONWN;
+import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_PROVISION_FAILED;
+import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
@@ -39,7 +40,6 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.net.util.SharedLog;
@@ -49,18 +49,16 @@
import android.os.ResultReceiver;
import android.os.SystemProperties;
import android.os.test.TestLooper;
-import android.provider.Settings;
+import android.provider.DeviceConfig;
import android.telephony.CarrierConfigManager;
-import android.test.mock.MockContentResolver;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.internal.R;
import com.android.internal.util.State;
import com.android.internal.util.StateMachine;
import com.android.internal.util.test.BroadcastInterceptingContext;
-import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.networkstack.tethering.R;
import org.junit.After;
import org.junit.Before;
@@ -94,7 +92,6 @@
private final PersistableBundle mCarrierConfig = new PersistableBundle();
private final TestLooper mLooper = new TestLooper();
private Context mMockContext;
- private MockContentResolver mContentResolver;
private TestStateMachine mSM;
private WrappedEntitlementManager mEnMgr;
@@ -110,11 +107,6 @@
public Resources getResources() {
return mResources;
}
-
- @Override
- public ContentResolver getContentResolver() {
- return mContentResolver;
- }
}
public class WrappedEntitlementManager extends EntitlementManager {
@@ -151,28 +143,33 @@
MockitoAnnotations.initMocks(this);
mMockingSession = mockitoSession()
.initMocks(this)
- .spyStatic(SystemProperties.class)
+ .mockStatic(SystemProperties.class)
+ .mockStatic(DeviceConfig.class)
.strictness(Strictness.WARN)
.startMocking();
// Don't disable tethering provisioning unless requested.
doReturn(false).when(
() -> SystemProperties.getBoolean(
eq(EntitlementManager.DISABLE_PROVISIONING_SYSPROP_KEY), anyBoolean()));
+ doReturn(false).when(
+ () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY),
+ eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean()));
when(mResources.getStringArray(R.array.config_tether_dhcp_range))
- .thenReturn(new String[0]);
+ .thenReturn(new String[0]);
when(mResources.getStringArray(R.array.config_tether_usb_regexs))
- .thenReturn(new String[0]);
+ .thenReturn(new String[0]);
when(mResources.getStringArray(R.array.config_tether_wifi_regexs))
- .thenReturn(new String[0]);
+ .thenReturn(new String[0]);
when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs))
- .thenReturn(new String[0]);
+ .thenReturn(new String[0]);
when(mResources.getIntArray(R.array.config_tether_upstream_types))
- .thenReturn(new int[0]);
+ .thenReturn(new int[0]);
+ when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
+ false);
+ when(mResources.getString(R.string.config_wifi_tether_enable)).thenReturn("");
when(mLog.forSubComponent(anyString())).thenReturn(mLog);
- mContentResolver = new MockContentResolver();
- mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
mMockContext = new MockContext(mContext);
mSM = new TestStateMachine();
mEnMgr = new WrappedEntitlementManager(mMockContext, mSM, mLog, EVENT_EM_UPDATE);
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/OffloadControllerTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/OffloadControllerTest.java
index 8574f54..7e62e5a 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/OffloadControllerTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/OffloadControllerTest.java
@@ -16,20 +16,26 @@
package com.android.server.connectivity.tethering;
+import static android.net.NetworkStats.DEFAULT_NETWORK_NO;
+import static android.net.NetworkStats.METERED_NO;
+import static android.net.NetworkStats.ROAMING_NO;
import static android.net.NetworkStats.SET_DEFAULT;
-import static android.net.NetworkStats.STATS_PER_IFACE;
-import static android.net.NetworkStats.STATS_PER_UID;
import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkStats.UID_ALL;
-import static android.net.TrafficStats.UID_TETHERING;
+import static android.net.NetworkStats.UID_TETHERING;
+import static android.net.RouteInfo.RTN_UNICAST;
import static android.provider.Settings.Global.TETHER_OFFLOAD_DISABLED;
+import static com.android.server.connectivity.tethering.OffloadController.StatsType.STATS_PER_IFACE;
+import static com.android.server.connectivity.tethering.OffloadController.StatsType.STATS_PER_UID;
import static com.android.server.connectivity.tethering.OffloadHardwareInterface.ForwardedStats;
import static com.android.testutils.MiscAssertsKt.assertContainsAll;
import static com.android.testutils.MiscAssertsKt.assertThrows;
+import static com.android.testutils.NetworkStatsUtilsKt.orderInsensitiveEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyObject;
@@ -38,11 +44,14 @@
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
+import android.annotation.NonNull;
+import android.app.usage.NetworkStatsManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.net.ITetheringStatsProvider;
@@ -50,10 +59,12 @@
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkStats;
+import android.net.NetworkStats.Entry;
import android.net.RouteInfo;
+import android.net.netstats.provider.AbstractNetworkStatsProvider;
+import android.net.netstats.provider.NetworkStatsProviderCallback;
import android.net.util.SharedLog;
import android.os.Handler;
-import android.os.INetworkManagementService;
import android.os.Looper;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
@@ -96,11 +107,13 @@
@Mock private OffloadHardwareInterface mHardware;
@Mock private ApplicationInfo mApplicationInfo;
@Mock private Context mContext;
- @Mock private INetworkManagementService mNMService;
+ @Mock private NetworkStatsManager mStatsManager;
+ @Mock private NetworkStatsProviderCallback mTetherStatsProviderCb;
private final ArgumentCaptor<ArrayList> mStringArrayCaptor =
ArgumentCaptor.forClass(ArrayList.class);
- private final ArgumentCaptor<ITetheringStatsProvider.Stub> mTetherStatsProviderCaptor =
- ArgumentCaptor.forClass(ITetheringStatsProvider.Stub.class);
+ private final ArgumentCaptor<OffloadController.OffloadTetheringStatsProvider>
+ mTetherStatsProviderCaptor =
+ ArgumentCaptor.forClass(OffloadController.OffloadTetheringStatsProvider.class);
private final ArgumentCaptor<OffloadHardwareInterface.ControlCallback> mControlCallbackCaptor =
ArgumentCaptor.forClass(OffloadHardwareInterface.ControlCallback.class);
private MockContentResolver mContentResolver;
@@ -113,6 +126,8 @@
mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
when(mContext.getContentResolver()).thenReturn(mContentResolver);
FakeSettingsProvider.clearSettingsProvider();
+ when(mStatsManager.registerNetworkStatsProvider(anyString(), any()))
+ .thenReturn(mTetherStatsProviderCb);
}
@After public void tearDown() throws Exception {
@@ -138,9 +153,9 @@
private OffloadController makeOffloadController() throws Exception {
OffloadController offload = new OffloadController(new Handler(Looper.getMainLooper()),
- mHardware, mContentResolver, mNMService, new SharedLog("test"));
- verify(mNMService).registerTetheringStatsProvider(
- mTetherStatsProviderCaptor.capture(), anyString());
+ mHardware, mContentResolver, mStatsManager, new SharedLog("test"));
+ verify(mStatsManager).registerNetworkStatsProvider(anyString(),
+ mTetherStatsProviderCaptor.capture());
return offload;
}
@@ -269,7 +284,7 @@
final String ipv4Addr = "192.0.2.5";
final String linkAddr = ipv4Addr + "/24";
lp.addLinkAddress(new LinkAddress(linkAddr));
- lp.addRoute(new RouteInfo(new IpPrefix("192.0.2.0/24")));
+ lp.addRoute(new RouteInfo(new IpPrefix("192.0.2.0/24"), null, null, RTN_UNICAST));
offload.setUpstreamLinkProperties(lp);
// IPv4 prefixes and addresses on the upstream are simply left as whole
// prefixes (already passed in from UpstreamNetworkMonitor code). If a
@@ -285,7 +300,7 @@
inOrder.verifyNoMoreInteractions();
final String ipv4Gateway = "192.0.2.1";
- lp.addRoute(new RouteInfo(InetAddress.getByName(ipv4Gateway)));
+ lp.addRoute(new RouteInfo(null, InetAddress.getByName(ipv4Gateway), null, RTN_UNICAST));
offload.setUpstreamLinkProperties(lp);
// No change in local addresses means no call to setLocalPrefixes().
inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture());
@@ -296,7 +311,7 @@
inOrder.verifyNoMoreInteractions();
final String ipv6Gw1 = "fe80::cafe";
- lp.addRoute(new RouteInfo(InetAddress.getByName(ipv6Gw1)));
+ lp.addRoute(new RouteInfo(null, InetAddress.getByName(ipv6Gw1), null, RTN_UNICAST));
offload.setUpstreamLinkProperties(lp);
// No change in local addresses means no call to setLocalPrefixes().
inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture());
@@ -310,7 +325,7 @@
inOrder.verifyNoMoreInteractions();
final String ipv6Gw2 = "fe80::d00d";
- lp.addRoute(new RouteInfo(InetAddress.getByName(ipv6Gw2)));
+ lp.addRoute(new RouteInfo(null, InetAddress.getByName(ipv6Gw2), null, RTN_UNICAST));
offload.setUpstreamLinkProperties(lp);
// No change in local addresses means no call to setLocalPrefixes().
inOrder.verify(mHardware, never()).setLocalPrefixes(mStringArrayCaptor.capture());
@@ -327,8 +342,10 @@
final LinkProperties stacked = new LinkProperties();
stacked.setInterfaceName("stacked");
stacked.addLinkAddress(new LinkAddress("192.0.2.129/25"));
- stacked.addRoute(new RouteInfo(InetAddress.getByName("192.0.2.254")));
- stacked.addRoute(new RouteInfo(InetAddress.getByName("fe80::bad:f00")));
+ stacked.addRoute(new RouteInfo(null, InetAddress.getByName("192.0.2.254"), null,
+ RTN_UNICAST));
+ stacked.addRoute(new RouteInfo(null, InetAddress.getByName("fe80::bad:f00"), null,
+ RTN_UNICAST));
assertTrue(lp.addStackedLink(stacked));
offload.setUpstreamLinkProperties(lp);
// No change in local addresses means no call to setLocalPrefixes().
@@ -348,7 +365,7 @@
// removed from "local prefixes" and /128s added for the upstream IPv6
// addresses. This is not yet implemented, and for now we simply
// expect to see these /128s.
- lp.addRoute(new RouteInfo(new IpPrefix("2001:db8::/64")));
+ lp.addRoute(new RouteInfo(new IpPrefix("2001:db8::/64"), null, null, RTN_UNICAST));
// "2001:db8::/64" plus "assigned" ASCII in hex
lp.addLinkAddress(new LinkAddress("2001:db8::6173:7369:676e:6564/64"));
// "2001:db8::/64" plus "random" ASCII in hex
@@ -381,12 +398,11 @@
inOrder.verifyNoMoreInteractions();
}
- private void assertNetworkStats(String iface, ForwardedStats stats, NetworkStats.Entry entry) {
- assertEquals(iface, entry.iface);
- assertEquals(stats.rxBytes, entry.rxBytes);
- assertEquals(stats.txBytes, entry.txBytes);
- assertEquals(SET_DEFAULT, entry.set);
- assertEquals(TAG_NONE, entry.tag);
+ private static @NonNull Entry buildTestEntry(@NonNull OffloadController.StatsType how,
+ @NonNull String iface, long rxBytes, long txBytes) {
+ return new Entry(iface, how == STATS_PER_IFACE ? UID_ALL : UID_TETHERING, SET_DEFAULT,
+ TAG_NONE, METERED_NO, ROAMING_NO, DEFAULT_NETWORK_NO, rxBytes, 0L,
+ txBytes, 0L, 0L);
}
@Test
@@ -397,19 +413,16 @@
final OffloadController offload = makeOffloadController();
offload.start();
+ final OffloadController.OffloadTetheringStatsProvider provider =
+ mTetherStatsProviderCaptor.getValue();
+
final String ethernetIface = "eth1";
final String mobileIface = "rmnet_data0";
- ForwardedStats ethernetStats = new ForwardedStats();
- ethernetStats.rxBytes = 12345;
- ethernetStats.txBytes = 54321;
-
- ForwardedStats mobileStats = new ForwardedStats();
- mobileStats.rxBytes = 999;
- mobileStats.txBytes = 99999;
-
- when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(ethernetStats);
- when(mHardware.getForwardedStats(eq(mobileIface))).thenReturn(mobileStats);
+ when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(
+ new ForwardedStats(12345, 54321));
+ when(mHardware.getForwardedStats(eq(mobileIface))).thenReturn(
+ new ForwardedStats(999, 99999));
InOrder inOrder = inOrder(mHardware);
@@ -429,10 +442,35 @@
// Expect that we fetch stats from the previous upstream.
inOrder.verify(mHardware, times(1)).getForwardedStats(eq(mobileIface));
- ethernetStats = new ForwardedStats();
- ethernetStats.rxBytes = 100000;
- ethernetStats.txBytes = 100000;
- when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(ethernetStats);
+ // Verify that the fetched stats are stored.
+ final NetworkStats ifaceStats = provider.getTetherStats(STATS_PER_IFACE);
+ final NetworkStats uidStats = provider.getTetherStats(STATS_PER_UID);
+ final NetworkStats expectedIfaceStats = new NetworkStats(0L, 2)
+ .addValues(buildTestEntry(STATS_PER_IFACE, mobileIface, 999, 99999))
+ .addValues(buildTestEntry(STATS_PER_IFACE, ethernetIface, 12345, 54321));
+
+ final NetworkStats expectedUidStats = new NetworkStats(0L, 2)
+ .addValues(buildTestEntry(STATS_PER_UID, mobileIface, 999, 99999))
+ .addValues(buildTestEntry(STATS_PER_UID, ethernetIface, 12345, 54321));
+
+ assertTrue(orderInsensitiveEquals(expectedIfaceStats, ifaceStats));
+ assertTrue(orderInsensitiveEquals(expectedUidStats, uidStats));
+
+ final ArgumentCaptor<NetworkStats> ifaceStatsCaptor = ArgumentCaptor.forClass(
+ NetworkStats.class);
+ final ArgumentCaptor<NetworkStats> uidStatsCaptor = ArgumentCaptor.forClass(
+ NetworkStats.class);
+
+ // Force pushing stats update to verify the stats reported.
+ provider.pushTetherStats();
+ verify(mTetherStatsProviderCb, times(1)).onStatsUpdated(anyInt(),
+ ifaceStatsCaptor.capture(), uidStatsCaptor.capture());
+ assertTrue(orderInsensitiveEquals(expectedIfaceStats, ifaceStatsCaptor.getValue()));
+ assertTrue(orderInsensitiveEquals(expectedUidStats, uidStatsCaptor.getValue()));
+
+
+ when(mHardware.getForwardedStats(eq(ethernetIface))).thenReturn(
+ new ForwardedStats(100000, 100000));
offload.setUpstreamLinkProperties(null);
// Expect that we first clear the HAL's upstream parameters.
inOrder.verify(mHardware, times(1)).setUpstreamParameters(
@@ -440,37 +478,38 @@
// Expect that we fetch stats from the previous upstream.
inOrder.verify(mHardware, times(1)).getForwardedStats(eq(ethernetIface));
- ITetheringStatsProvider provider = mTetherStatsProviderCaptor.getValue();
- NetworkStats stats = provider.getTetherStats(STATS_PER_IFACE);
- NetworkStats perUidStats = provider.getTetherStats(STATS_PER_UID);
- waitForIdle();
// There is no current upstream, so no stats are fetched.
inOrder.verify(mHardware, never()).getForwardedStats(any());
inOrder.verifyNoMoreInteractions();
- assertEquals(2, stats.size());
- assertEquals(2, perUidStats.size());
+ // Verify that the stored stats is accumulated.
+ final NetworkStats ifaceStatsAccu = provider.getTetherStats(STATS_PER_IFACE);
+ final NetworkStats uidStatsAccu = provider.getTetherStats(STATS_PER_UID);
+ final NetworkStats expectedIfaceStatsAccu = new NetworkStats(0L, 2)
+ .addValues(buildTestEntry(STATS_PER_IFACE, mobileIface, 999, 99999))
+ .addValues(buildTestEntry(STATS_PER_IFACE, ethernetIface, 112345, 154321));
- NetworkStats.Entry entry = null;
- for (int i = 0; i < stats.size(); i++) {
- assertEquals(UID_ALL, stats.getValues(i, entry).uid);
- assertEquals(UID_TETHERING, perUidStats.getValues(i, entry).uid);
- }
+ final NetworkStats expectedUidStatsAccu = new NetworkStats(0L, 2)
+ .addValues(buildTestEntry(STATS_PER_UID, mobileIface, 999, 99999))
+ .addValues(buildTestEntry(STATS_PER_UID, ethernetIface, 112345, 154321));
- int ethernetPosition = ethernetIface.equals(stats.getValues(0, entry).iface) ? 0 : 1;
- int mobilePosition = 1 - ethernetPosition;
+ assertTrue(orderInsensitiveEquals(expectedIfaceStatsAccu, ifaceStatsAccu));
+ assertTrue(orderInsensitiveEquals(expectedUidStatsAccu, uidStatsAccu));
- entry = stats.getValues(mobilePosition, entry);
- assertNetworkStats(mobileIface, mobileStats, entry);
- entry = perUidStats.getValues(mobilePosition, entry);
- assertNetworkStats(mobileIface, mobileStats, entry);
+ // Verify that only diff of stats is reported.
+ reset(mTetherStatsProviderCb);
+ provider.pushTetherStats();
+ final NetworkStats expectedIfaceStatsDiff = new NetworkStats(0L, 2)
+ .addValues(buildTestEntry(STATS_PER_IFACE, mobileIface, 0, 0))
+ .addValues(buildTestEntry(STATS_PER_IFACE, ethernetIface, 100000, 100000));
- ethernetStats.rxBytes = 12345 + 100000;
- ethernetStats.txBytes = 54321 + 100000;
- entry = stats.getValues(ethernetPosition, entry);
- assertNetworkStats(ethernetIface, ethernetStats, entry);
- entry = perUidStats.getValues(ethernetPosition, entry);
- assertNetworkStats(ethernetIface, ethernetStats, entry);
+ final NetworkStats expectedUidStatsDiff = new NetworkStats(0L, 2)
+ .addValues(buildTestEntry(STATS_PER_UID, mobileIface, 0, 0))
+ .addValues(buildTestEntry(STATS_PER_UID, ethernetIface, 100000, 100000));
+ verify(mTetherStatsProviderCb, times(1)).onStatsUpdated(anyInt(),
+ ifaceStatsCaptor.capture(), uidStatsCaptor.capture());
+ assertTrue(orderInsensitiveEquals(expectedIfaceStatsDiff, ifaceStatsCaptor.getValue()));
+ assertTrue(orderInsensitiveEquals(expectedUidStatsDiff, uidStatsCaptor.getValue()));
}
@Test
@@ -490,19 +529,19 @@
lp.setInterfaceName(ethernetIface);
offload.setUpstreamLinkProperties(lp);
- ITetheringStatsProvider provider = mTetherStatsProviderCaptor.getValue();
+ AbstractNetworkStatsProvider provider = mTetherStatsProviderCaptor.getValue();
final InOrder inOrder = inOrder(mHardware);
when(mHardware.setUpstreamParameters(any(), any(), any(), any())).thenReturn(true);
when(mHardware.setDataLimit(anyString(), anyLong())).thenReturn(true);
// Applying an interface quota to the current upstream immediately sends it to the hardware.
- provider.setInterfaceQuota(ethernetIface, ethernetLimit);
+ provider.setLimit(ethernetIface, ethernetLimit);
waitForIdle();
inOrder.verify(mHardware).setDataLimit(ethernetIface, ethernetLimit);
inOrder.verifyNoMoreInteractions();
// Applying an interface quota to another upstream does not take any immediate action.
- provider.setInterfaceQuota(mobileIface, mobileLimit);
+ provider.setLimit(mobileIface, mobileLimit);
waitForIdle();
inOrder.verify(mHardware, never()).setDataLimit(anyString(), anyLong());
@@ -515,7 +554,7 @@
// Setting a limit of ITetheringStatsProvider.QUOTA_UNLIMITED causes the limit to be set
// to Long.MAX_VALUE.
- provider.setInterfaceQuota(mobileIface, ITetheringStatsProvider.QUOTA_UNLIMITED);
+ provider.setLimit(mobileIface, ITetheringStatsProvider.QUOTA_UNLIMITED);
waitForIdle();
inOrder.verify(mHardware).setDataLimit(mobileIface, Long.MAX_VALUE);
@@ -523,7 +562,7 @@
when(mHardware.setUpstreamParameters(any(), any(), any(), any())).thenReturn(false);
lp.setInterfaceName(ethernetIface);
offload.setUpstreamLinkProperties(lp);
- provider.setInterfaceQuota(mobileIface, mobileLimit);
+ provider.setLimit(mobileIface, mobileLimit);
waitForIdle();
inOrder.verify(mHardware, never()).setDataLimit(anyString(), anyLong());
@@ -532,7 +571,7 @@
when(mHardware.setDataLimit(anyString(), anyLong())).thenReturn(false);
lp.setInterfaceName(mobileIface);
offload.setUpstreamLinkProperties(lp);
- provider.setInterfaceQuota(mobileIface, mobileLimit);
+ provider.setLimit(mobileIface, mobileLimit);
waitForIdle();
inOrder.verify(mHardware).getForwardedStats(ethernetIface);
inOrder.verify(mHardware).stopOffloadControl();
@@ -548,7 +587,7 @@
OffloadHardwareInterface.ControlCallback callback = mControlCallbackCaptor.getValue();
callback.onStoppedLimitReached();
- verify(mNMService, times(1)).tetherLimitReached(mTetherStatsProviderCaptor.getValue());
+ verify(mTetherStatsProviderCb, times(1)).onStatsUpdated(anyInt(), any(), any());
}
@Test
@@ -574,13 +613,15 @@
final LinkProperties usbLinkProperties = new LinkProperties();
usbLinkProperties.setInterfaceName(RNDIS0);
usbLinkProperties.addLinkAddress(new LinkAddress("192.168.42.1/24"));
- usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(USB_PREFIX)));
+ usbLinkProperties.addRoute(
+ new RouteInfo(new IpPrefix(USB_PREFIX), null, null, RTN_UNICAST));
offload.notifyDownstreamLinkProperties(usbLinkProperties);
inOrder.verify(mHardware, times(1)).addDownstreamPrefix(RNDIS0, USB_PREFIX);
inOrder.verifyNoMoreInteractions();
// [2] Routes for IPv6 link-local prefixes should never be added.
- usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(IPV6_LINKLOCAL)));
+ usbLinkProperties.addRoute(
+ new RouteInfo(new IpPrefix(IPV6_LINKLOCAL), null, null, RTN_UNICAST));
offload.notifyDownstreamLinkProperties(usbLinkProperties);
inOrder.verify(mHardware, never()).addDownstreamPrefix(eq(RNDIS0), anyString());
inOrder.verifyNoMoreInteractions();
@@ -588,7 +629,8 @@
// [3] Add an IPv6 prefix for good measure. Only new offload-able
// prefixes should be passed to the HAL.
usbLinkProperties.addLinkAddress(new LinkAddress("2001:db8::1/64"));
- usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(IPV6_DOC_PREFIX)));
+ usbLinkProperties.addRoute(
+ new RouteInfo(new IpPrefix(IPV6_DOC_PREFIX), null, null, RTN_UNICAST));
offload.notifyDownstreamLinkProperties(usbLinkProperties);
inOrder.verify(mHardware, times(1)).addDownstreamPrefix(RNDIS0, IPV6_DOC_PREFIX);
inOrder.verifyNoMoreInteractions();
@@ -601,8 +643,10 @@
// [5] Differences in local routes are converted into addDownstream()
// and removeDownstream() invocations accordingly.
- usbLinkProperties.removeRoute(new RouteInfo(new IpPrefix(IPV6_DOC_PREFIX), null, RNDIS0));
- usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(IPV6_DISCARD_PREFIX)));
+ usbLinkProperties.removeRoute(
+ new RouteInfo(new IpPrefix(IPV6_DOC_PREFIX), null, RNDIS0, RTN_UNICAST));
+ usbLinkProperties.addRoute(
+ new RouteInfo(new IpPrefix(IPV6_DISCARD_PREFIX), null, null, RTN_UNICAST));
offload.notifyDownstreamLinkProperties(usbLinkProperties);
inOrder.verify(mHardware, times(1)).removeDownstreamPrefix(RNDIS0, IPV6_DOC_PREFIX);
inOrder.verify(mHardware, times(1)).addDownstreamPrefix(RNDIS0, IPV6_DISCARD_PREFIX);
@@ -646,9 +690,10 @@
// Verify forwarded stats behaviour.
verify(mHardware, times(1)).getForwardedStats(eq(RMNET0));
verify(mHardware, times(1)).getForwardedStats(eq(WLAN0));
+ // TODO: verify the exact stats reported.
+ verify(mTetherStatsProviderCb, times(1)).onStatsUpdated(anyInt(), any(), any());
+ verifyNoMoreInteractions(mTetherStatsProviderCb);
verifyNoMoreInteractions(mHardware);
- verify(mNMService, times(1)).tetherLimitReached(mTetherStatsProviderCaptor.getValue());
- verifyNoMoreInteractions(mNMService);
}
@Test
@@ -680,19 +725,23 @@
final LinkProperties usbLinkProperties = new LinkProperties();
usbLinkProperties.setInterfaceName(RNDIS0);
usbLinkProperties.addLinkAddress(new LinkAddress("192.168.42.1/24"));
- usbLinkProperties.addRoute(new RouteInfo(new IpPrefix(USB_PREFIX)));
+ usbLinkProperties.addRoute(
+ new RouteInfo(new IpPrefix(USB_PREFIX), null, null, RTN_UNICAST));
offload.notifyDownstreamLinkProperties(usbLinkProperties);
final LinkProperties wifiLinkProperties = new LinkProperties();
wifiLinkProperties.setInterfaceName(WLAN0);
wifiLinkProperties.addLinkAddress(new LinkAddress("192.168.43.1/24"));
- wifiLinkProperties.addRoute(new RouteInfo(new IpPrefix(WIFI_PREFIX)));
- wifiLinkProperties.addRoute(new RouteInfo(new IpPrefix(IPV6_LINKLOCAL)));
+ wifiLinkProperties.addRoute(
+ new RouteInfo(new IpPrefix(WIFI_PREFIX), null, null, RTN_UNICAST));
+ wifiLinkProperties.addRoute(
+ new RouteInfo(new IpPrefix(IPV6_LINKLOCAL), null, null, RTN_UNICAST));
// Use a benchmark prefix (RFC 5180 + erratum), since the documentation
// prefix is included in the excluded prefix list.
wifiLinkProperties.addLinkAddress(new LinkAddress("2001:2::1/64"));
wifiLinkProperties.addLinkAddress(new LinkAddress("2001:2::2/64"));
- wifiLinkProperties.addRoute(new RouteInfo(new IpPrefix("2001:2::/64")));
+ wifiLinkProperties.addRoute(
+ new RouteInfo(new IpPrefix("2001:2::/64"), null, null, RTN_UNICAST));
offload.notifyDownstreamLinkProperties(wifiLinkProperties);
offload.removeDownstreamInterface(RNDIS0);
@@ -707,8 +756,8 @@
// Verify forwarded stats behaviour.
verify(mHardware, times(1)).getForwardedStats(eq(RMNET0));
verify(mHardware, times(1)).getForwardedStats(eq(WLAN0));
- verify(mNMService, times(1)).tetherLimitReached(mTetherStatsProviderCaptor.getValue());
- verifyNoMoreInteractions(mNMService);
+ verify(mTetherStatsProviderCb, times(1)).onStatsUpdated(anyInt(), any(), any());
+ verifyNoMoreInteractions(mTetherStatsProviderCb);
// TODO: verify local prefixes and downstreams are also pushed to the HAL.
verify(mHardware, times(1)).setLocalPrefixes(mStringArrayCaptor.capture());
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
index 30bff35..3635964 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringConfigurationTest.java
@@ -21,41 +21,38 @@
import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
import static android.net.ConnectivityManager.TYPE_WIFI;
-import static android.provider.Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER;
+import static android.provider.DeviceConfig.NAMESPACE_CONNECTIVITY;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
-import static com.android.internal.R.array.config_mobile_hotspot_provision_app;
-import static com.android.internal.R.array.config_tether_bluetooth_regexs;
-import static com.android.internal.R.array.config_tether_dhcp_range;
-import static com.android.internal.R.array.config_tether_upstream_types;
-import static com.android.internal.R.array.config_tether_usb_regexs;
-import static com.android.internal.R.array.config_tether_wifi_regexs;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.when;
-import android.content.ContentResolver;
import android.content.Context;
import android.content.res.Resources;
import android.net.util.SharedLog;
-import android.provider.Settings;
+import android.provider.DeviceConfig;
import android.telephony.TelephonyManager;
-import android.test.mock.MockContentResolver;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.internal.util.test.BroadcastInterceptingContext;
-import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.networkstack.tethering.R;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
import java.util.Arrays;
import java.util.Iterator;
@@ -70,9 +67,10 @@
@Mock private TelephonyManager mTelephonyManager;
@Mock private Resources mResources;
@Mock private Resources mResourcesForSubId;
- private MockContentResolver mContentResolver;
private Context mMockContext;
private boolean mHasTelephonyManager;
+ private boolean mEnableLegacyDhcpServer;
+ private MockitoSession mMockingSession;
private class MockTetheringConfiguration extends TetheringConfiguration {
MockTetheringConfiguration(Context ctx, SharedLog log, int id) {
@@ -102,32 +100,44 @@
}
return super.getSystemService(name);
}
-
- @Override
- public ContentResolver getContentResolver() {
- return mContentResolver;
- }
}
@Before
public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- when(mResources.getStringArray(config_tether_dhcp_range)).thenReturn(new String[0]);
- when(mResources.getStringArray(config_tether_usb_regexs)).thenReturn(new String[0]);
- when(mResources.getStringArray(config_tether_wifi_regexs))
+ // TODO: use a dependencies class instead of mock statics.
+ mMockingSession = mockitoSession()
+ .initMocks(this)
+ .mockStatic(DeviceConfig.class)
+ .strictness(Strictness.WARN)
+ .startMocking();
+ doReturn(false).when(
+ () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY),
+ eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean()));
+
+ when(mResources.getStringArray(R.array.config_tether_dhcp_range)).thenReturn(
+ new String[0]);
+ when(mResources.getStringArray(R.array.config_tether_usb_regexs)).thenReturn(new String[0]);
+ when(mResources.getStringArray(R.array.config_tether_wifi_regexs))
.thenReturn(new String[]{ "test_wlan\\d" });
- when(mResources.getStringArray(config_tether_bluetooth_regexs)).thenReturn(new String[0]);
- when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(new int[0]);
- when(mResources.getStringArray(config_mobile_hotspot_provision_app))
+ when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs)).thenReturn(
+ new String[0]);
+ when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(new int[0]);
+ when(mResources.getStringArray(R.array.config_mobile_hotspot_provision_app))
.thenReturn(new String[0]);
- mContentResolver = new MockContentResolver();
- mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+ when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
+ false);
mHasTelephonyManager = true;
mMockContext = new MockContext(mContext);
+ mEnableLegacyDhcpServer = false;
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mMockingSession.finishMocking();
}
private TetheringConfiguration getTetheringConfiguration(int... legacyTetherUpstreamTypes) {
- when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(
+ when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(
legacyTetherUpstreamTypes);
return new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
}
@@ -145,7 +155,7 @@
@Test
public void testDunFromTelephonyManagerMeansDun() {
- when(mTelephonyManager.isTetheringApnRequired(anyInt())).thenReturn(true);
+ when(mTelephonyManager.isTetheringApnRequired()).thenReturn(true);
final TetheringConfiguration cfgWifi = getTetheringConfiguration(TYPE_WIFI);
final TetheringConfiguration cfgMobileWifiHipri = getTetheringConfiguration(
@@ -169,7 +179,7 @@
@Test
public void testDunNotRequiredFromTelephonyManagerMeansNoDun() {
- when(mTelephonyManager.isTetheringApnRequired(anyInt())).thenReturn(false);
+ when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false);
final TetheringConfiguration cfgWifi = getTetheringConfiguration(TYPE_WIFI);
final TetheringConfiguration cfgMobileWifiHipri = getTetheringConfiguration(
@@ -211,8 +221,8 @@
@Test
public void testNoDefinedUpstreamTypesAddsEthernet() {
- when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(new int[]{});
- when(mTelephonyManager.isTetheringApnRequired(anyInt())).thenReturn(false);
+ when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(new int[]{});
+ when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false);
final TetheringConfiguration cfg = new TetheringConfiguration(
mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
@@ -233,9 +243,9 @@
@Test
public void testDefinedUpstreamTypesSansEthernetAddsEthernet() {
- when(mResources.getIntArray(config_tether_upstream_types)).thenReturn(
+ when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(
new int[]{TYPE_WIFI, TYPE_MOBILE_HIPRI});
- when(mTelephonyManager.isTetheringApnRequired(anyInt())).thenReturn(false);
+ when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false);
final TetheringConfiguration cfg = new TetheringConfiguration(
mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
@@ -251,9 +261,9 @@
@Test
public void testDefinedUpstreamTypesWithEthernetDoesNotAddEthernet() {
- when(mResources.getIntArray(config_tether_upstream_types))
+ when(mResources.getIntArray(R.array.config_tether_upstream_types))
.thenReturn(new int[]{TYPE_WIFI, TYPE_ETHERNET, TYPE_MOBILE_HIPRI});
- when(mTelephonyManager.isTetheringApnRequired(anyInt())).thenReturn(false);
+ when(mTelephonyManager.isTetheringApnRequired()).thenReturn(false);
final TetheringConfiguration cfg = new TetheringConfiguration(
mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
@@ -269,19 +279,38 @@
@Test
public void testNewDhcpServerDisabled() {
- Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 1);
+ when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
+ true);
+ doReturn(false).when(
+ () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY),
+ eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean()));
- final TetheringConfiguration cfg = new TetheringConfiguration(
- mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
- assertTrue(cfg.enableLegacyDhcpServer);
+ final TetheringConfiguration enableByRes =
+ new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+ assertTrue(enableByRes.enableLegacyDhcpServer);
+
+ when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
+ false);
+ doReturn(true).when(
+ () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY),
+ eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean()));
+
+ final TetheringConfiguration enableByDevConfig =
+ new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+ assertTrue(enableByDevConfig.enableLegacyDhcpServer);
}
@Test
public void testNewDhcpServerEnabled() {
- Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0);
+ when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
+ false);
+ doReturn(false).when(
+ () -> DeviceConfig.getBoolean(eq(NAMESPACE_CONNECTIVITY),
+ eq(TetheringConfiguration.TETHER_ENABLE_LEGACY_DHCP_SERVER), anyBoolean()));
- final TetheringConfiguration cfg = new TetheringConfiguration(
- mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+ final TetheringConfiguration cfg =
+ new TetheringConfiguration(mMockContext, mLog, INVALID_SUBSCRIPTION_ID);
+
assertFalse(cfg.enableLegacyDhcpServer);
}
@@ -300,16 +329,17 @@
private void setUpResourceForSubId() {
when(mResourcesForSubId.getStringArray(
- config_tether_dhcp_range)).thenReturn(new String[0]);
+ R.array.config_tether_dhcp_range)).thenReturn(new String[0]);
when(mResourcesForSubId.getStringArray(
- config_tether_usb_regexs)).thenReturn(new String[0]);
+ R.array.config_tether_usb_regexs)).thenReturn(new String[0]);
when(mResourcesForSubId.getStringArray(
- config_tether_wifi_regexs)).thenReturn(new String[]{ "test_wlan\\d" });
+ R.array.config_tether_wifi_regexs)).thenReturn(new String[]{ "test_wlan\\d" });
when(mResourcesForSubId.getStringArray(
- config_tether_bluetooth_regexs)).thenReturn(new String[0]);
- when(mResourcesForSubId.getIntArray(config_tether_upstream_types)).thenReturn(new int[0]);
+ R.array.config_tether_bluetooth_regexs)).thenReturn(new String[0]);
+ when(mResourcesForSubId.getIntArray(R.array.config_tether_upstream_types)).thenReturn(
+ new int[0]);
when(mResourcesForSubId.getStringArray(
- config_mobile_hotspot_provision_app)).thenReturn(PROVISIONING_APP_NAME);
+ R.array.config_mobile_hotspot_provision_app)).thenReturn(PROVISIONING_APP_NAME);
}
}
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
index 9d9ad10..5910624 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/TetheringTest.java
@@ -19,15 +19,18 @@
import static android.hardware.usb.UsbManager.USB_CONFIGURED;
import static android.hardware.usb.UsbManager.USB_CONNECTED;
import static android.hardware.usb.UsbManager.USB_FUNCTION_RNDIS;
-import static android.net.ConnectivityManager.ACTION_TETHER_STATE_CHANGED;
-import static android.net.ConnectivityManager.EXTRA_ACTIVE_LOCAL_ONLY;
-import static android.net.ConnectivityManager.EXTRA_ACTIVE_TETHER;
-import static android.net.ConnectivityManager.EXTRA_AVAILABLE_TETHER;
-import static android.net.ConnectivityManager.TETHERING_USB;
-import static android.net.ConnectivityManager.TETHERING_WIFI;
-import static android.net.ConnectivityManager.TETHER_ERROR_NO_ERROR;
-import static android.net.ConnectivityManager.TETHER_ERROR_UNKNOWN_IFACE;
-import static android.net.ConnectivityManager.TYPE_WIFI_P2P;
+import static android.net.ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED;
+import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
+import static android.net.ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
+import static android.net.RouteInfo.RTN_UNICAST;
+import static android.net.TetheringManager.ACTION_TETHER_STATE_CHANGED;
+import static android.net.TetheringManager.EXTRA_ACTIVE_LOCAL_ONLY;
+import static android.net.TetheringManager.EXTRA_ACTIVE_TETHER;
+import static android.net.TetheringManager.EXTRA_AVAILABLE_TETHER;
+import static android.net.TetheringManager.TETHERING_USB;
+import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHER_ERROR_NO_ERROR;
+import static android.net.TetheringManager.TETHER_ERROR_UNKNOWN_IFACE;
import static android.net.dhcp.IDhcpServer.STATUS_SUCCESS;
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME;
import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_MODE;
@@ -35,7 +38,6 @@
import static android.net.wifi.WifiManager.IFACE_IP_MODE_LOCAL_ONLY;
import static android.net.wifi.WifiManager.IFACE_IP_MODE_TETHERED;
import static android.net.wifi.WifiManager.WIFI_AP_STATE_ENABLED;
-import static android.provider.Settings.Global.TETHER_ENABLE_LEGACY_DHCP_SERVER;
import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import static org.junit.Assert.assertArrayEquals;
@@ -49,10 +51,10 @@
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
@@ -60,6 +62,8 @@
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
+import android.app.usage.NetworkStatsManager;
+import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -68,20 +72,18 @@
import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
import android.hardware.usb.UsbManager;
+import android.net.ConnectivityManager;
import android.net.INetd;
-import android.net.INetworkPolicyManager;
-import android.net.INetworkStatsService;
import android.net.ITetheringEventCallback;
-import android.net.InterfaceConfiguration;
+import android.net.InetAddresses;
+import android.net.InterfaceConfigurationParcel;
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.MacAddress;
import android.net.Network;
import android.net.NetworkCapabilities;
-import android.net.NetworkInfo;
import android.net.NetworkRequest;
-import android.net.NetworkUtils;
import android.net.RouteInfo;
import android.net.TetherStatesParcel;
import android.net.TetheringConfigurationParcel;
@@ -100,7 +102,6 @@
import android.net.wifi.p2p.WifiP2pManager;
import android.os.Bundle;
import android.os.Handler;
-import android.os.INetworkManagementService;
import android.os.Looper;
import android.os.PersistableBundle;
import android.os.RemoteException;
@@ -121,6 +122,7 @@
import com.android.internal.util.StateMachine;
import com.android.internal.util.test.BroadcastInterceptingContext;
import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.networkstack.tethering.R;
import org.junit.After;
import org.junit.Before;
@@ -134,6 +136,7 @@
import java.net.Inet6Address;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Vector;
@RunWith(AndroidJUnit4.class)
@@ -146,14 +149,13 @@
private static final String TEST_USB_IFNAME = "test_rndis0";
private static final String TEST_WLAN_IFNAME = "test_wlan0";
private static final String TEST_P2P_IFNAME = "test_p2p-p2p0-0";
+ private static final String TETHERING_NAME = "Tethering";
private static final int DHCPSERVER_START_TIMEOUT_MS = 1000;
@Mock private ApplicationInfo mApplicationInfo;
@Mock private Context mContext;
- @Mock private INetworkManagementService mNMService;
- @Mock private INetworkStatsService mStatsService;
- @Mock private INetworkPolicyManager mPolicyManager;
+ @Mock private NetworkStatsManager mStatsManager;
@Mock private OffloadHardwareInterface mOffloadHardwareInterface;
@Mock private Resources mResources;
@Mock private TelephonyManager mTelephonyManager;
@@ -167,6 +169,7 @@
@Mock private INetd mNetd;
@Mock private UserManager mUserManager;
@Mock private NetworkRequest mNetworkRequest;
+ @Mock private ConnectivityManager mCm;
private final MockIpServerDependencies mIpServerDependencies =
spy(new MockIpServerDependencies());
@@ -184,6 +187,7 @@
private BroadcastReceiver mBroadcastReceiver;
private Tethering mTethering;
private PhoneStateListener mPhoneStateListener;
+ private InterfaceConfigurationParcel mInterfaceConfiguration;
private class TestContext extends BroadcastInterceptingContext {
TestContext(Context base) {
@@ -216,6 +220,8 @@
if (Context.USB_SERVICE.equals(name)) return mUsbManager;
if (Context.TELEPHONY_SERVICE.equals(name)) return mTelephonyManager;
if (Context.USER_SERVICE.equals(name)) return mUserManager;
+ if (Context.NETWORK_STATS_SERVICE.equals(name)) return mStatsManager;
+ if (Context.CONNECTIVITY_SERVICE.equals(name)) return mCm;
return super.getSystemService(name);
}
@@ -224,6 +230,11 @@
if (TelephonyManager.class.equals(serviceClass)) return Context.TELEPHONY_SERVICE;
return super.getSystemServiceName(serviceClass);
}
+
+ @Override
+ public Context createContextAsUser(UserHandle user, int flags) {
+ return mContext;
+ }
}
public class MockIpServerDependencies extends IpServer.Dependencies {
@@ -247,11 +258,6 @@
}
@Override
- public INetd getNetdService() {
- return mNetd;
- }
-
- @Override
public void makeDhcpServer(String ifName, DhcpServingParamsParcel params,
DhcpServerCallbacks cb) {
new Thread(() -> {
@@ -270,6 +276,11 @@
}
@Override
+ protected boolean getDeviceConfigBoolean(final String name) {
+ return false;
+ }
+
+ @Override
protected Resources getResourcesForSubIdWrapper(Context ctx, int subId) {
return mResources;
}
@@ -328,21 +339,6 @@
}
@Override
- public INetworkManagementService getINetworkManagementService() {
- return mNMService;
- }
-
- @Override
- public INetworkStatsService getINetworkStatsService() {
- return mStatsService;
- }
-
- @Override
- public INetworkPolicyManager getINetworkPolicyManager() {
- return mPolicyManager;
- }
-
- @Override
public INetd getINetd(Context context) {
return mNetd;
}
@@ -356,6 +352,12 @@
public Context getContext() {
return mServiceContext;
}
+
+ @Override
+ public BluetoothAdapter getBluetoothAdapter() {
+ // TODO: add test for bluetooth tethering.
+ return null;
+ }
}
private static UpstreamNetworkState buildMobileUpstreamState(boolean withIPv4,
@@ -365,23 +367,26 @@
if (withIPv4) {
prop.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0),
- NetworkUtils.numericToInetAddress("10.0.0.1"), TEST_MOBILE_IFNAME));
+ InetAddresses.parseNumericAddress("10.0.0.1"),
+ TEST_MOBILE_IFNAME, RTN_UNICAST));
}
if (withIPv6) {
- prop.addDnsServer(NetworkUtils.numericToInetAddress("2001:db8::2"));
+ prop.addDnsServer(InetAddresses.parseNumericAddress("2001:db8::2"));
prop.addLinkAddress(
- new LinkAddress(NetworkUtils.numericToInetAddress("2001:db8::"),
+ new LinkAddress(InetAddresses.parseNumericAddress("2001:db8::"),
NetworkConstants.RFC7421_PREFIX_LENGTH));
prop.addRoute(new RouteInfo(new IpPrefix(Inet6Address.ANY, 0),
- NetworkUtils.numericToInetAddress("2001:db8::1"), TEST_MOBILE_IFNAME));
+ InetAddresses.parseNumericAddress("2001:db8::1"),
+ TEST_MOBILE_IFNAME, RTN_UNICAST));
}
if (with464xlat) {
final LinkProperties stackedLink = new LinkProperties();
stackedLink.setInterfaceName(TEST_XLAT_MOBILE_IFNAME);
stackedLink.addRoute(new RouteInfo(new IpPrefix(Inet4Address.ANY, 0),
- NetworkUtils.numericToInetAddress("192.0.0.1"), TEST_XLAT_MOBILE_IFNAME));
+ InetAddresses.parseNumericAddress("192.0.0.1"),
+ TEST_XLAT_MOBILE_IFNAME, RTN_UNICAST));
prop.addStackedLink(stackedLink);
}
@@ -411,32 +416,33 @@
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- when(mResources.getStringArray(com.android.internal.R.array.config_tether_dhcp_range))
+ when(mResources.getStringArray(R.array.config_tether_dhcp_range))
.thenReturn(new String[0]);
- when(mResources.getStringArray(com.android.internal.R.array.config_tether_usb_regexs))
+ when(mResources.getStringArray(R.array.config_tether_usb_regexs))
.thenReturn(new String[] { "test_rndis\\d" });
- when(mResources.getStringArray(com.android.internal.R.array.config_tether_wifi_regexs))
+ when(mResources.getStringArray(R.array.config_tether_wifi_regexs))
.thenReturn(new String[]{ "test_wlan\\d" });
- when(mResources.getStringArray(com.android.internal.R.array.config_tether_wifi_p2p_regexs))
+ when(mResources.getStringArray(R.array.config_tether_wifi_p2p_regexs))
.thenReturn(new String[]{ "test_p2p-p2p\\d-.*" });
- when(mResources.getStringArray(com.android.internal.R.array.config_tether_bluetooth_regexs))
+ when(mResources.getStringArray(R.array.config_tether_bluetooth_regexs))
.thenReturn(new String[0]);
- when(mResources.getIntArray(com.android.internal.R.array.config_tether_upstream_types))
- .thenReturn(new int[0]);
- when(mResources.getBoolean(com.android.internal.R.bool.config_tether_upstream_automatic))
- .thenReturn(false);
- when(mNMService.listInterfaces())
+ when(mResources.getIntArray(R.array.config_tether_upstream_types)).thenReturn(new int[0]);
+ when(mResources.getBoolean(R.bool.config_tether_upstream_automatic)).thenReturn(false);
+ when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
+ false);
+ when(mNetd.interfaceGetList())
.thenReturn(new String[] {
TEST_MOBILE_IFNAME, TEST_WLAN_IFNAME, TEST_USB_IFNAME, TEST_P2P_IFNAME});
- when(mNMService.getInterfaceConfig(anyString()))
- .thenReturn(new InterfaceConfiguration());
+ when(mResources.getString(R.string.config_wifi_tether_enable)).thenReturn("");
+ mInterfaceConfiguration = new InterfaceConfigurationParcel();
+ mInterfaceConfiguration.flags = new String[0];
when(mRouterAdvertisementDaemon.start())
.thenReturn(true);
mServiceContext = new TestContext(mContext);
+ when(mContext.getSystemService(Context.NOTIFICATION_SERVICE)).thenReturn(null);
mContentResolver = new MockContentResolver(mServiceContext);
mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
- Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 0);
mIntents = new Vector<>();
mBroadcastReceiver = new BroadcastReceiver() {
@Override
@@ -447,7 +453,7 @@
mServiceContext.registerReceiver(mBroadcastReceiver,
new IntentFilter(ACTION_TETHER_STATE_CHANGED));
mTethering = makeTethering();
- verify(mNMService).registerTetheringStatsProvider(any(), anyString());
+ verify(mStatsManager, times(1)).registerNetworkStatsProvider(anyString(), any());
verify(mNetd).registerUnsolicitedEventListener(any());
final ArgumentCaptor<PhoneStateListener> phoneListenerCaptor =
ArgumentCaptor.forClass(PhoneStateListener.class);
@@ -491,16 +497,15 @@
p2pInfo.groupFormed = isGroupFormed;
p2pInfo.isGroupOwner = isGroupOwner;
- NetworkInfo networkInfo = new NetworkInfo(TYPE_WIFI_P2P, 0, null, null);
+ WifiP2pGroup group = mock(WifiP2pGroup.class);
+ when(group.isGroupOwner()).thenReturn(isGroupOwner);
+ when(group.getInterface()).thenReturn(ifname);
- WifiP2pGroup group = new WifiP2pGroup();
- group.setIsGroupOwner(isGroupOwner);
- group.setInterface(ifname);
+ final Intent intent = mock(Intent.class);
+ when(intent.getAction()).thenReturn(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
+ when(intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO)).thenReturn(p2pInfo);
+ when(intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP)).thenReturn(group);
- final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
- intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_INFO, p2pInfo);
- intent.putExtra(WifiP2pManager.EXTRA_NETWORK_INFO, networkInfo);
- intent.putExtra(WifiP2pManager.EXTRA_WIFI_P2P_GROUP, group);
mServiceContext.sendBroadcastAsUserMultiplePermissions(intent, UserHandle.ALL,
P2P_RECEIVER_PERMISSIONS_FOR_BROADCAST);
}
@@ -519,10 +524,11 @@
}
private void verifyInterfaceServingModeStarted(String ifname) throws Exception {
- verify(mNMService, times(1)).getInterfaceConfig(ifname);
- verify(mNMService, times(1))
- .setInterfaceConfig(eq(ifname), any(InterfaceConfiguration.class));
- verify(mNMService, times(1)).tetherInterface(ifname);
+ verify(mNetd, times(1)).interfaceSetCfg(any(InterfaceConfigurationParcel.class));
+ verify(mNetd, times(1)).tetherInterfaceAdd(ifname);
+ verify(mNetd, times(1)).networkAddInterface(INetd.LOCAL_NET_ID, ifname);
+ verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(ifname),
+ anyString(), anyString());
}
private void verifyTetheringBroadcast(String ifname, String whichExtra) {
@@ -554,7 +560,7 @@
verify(mWifiManager).updateInterfaceIpState(
TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
}
- verifyNoMoreInteractions(mNMService);
+ verifyNoMoreInteractions(mNetd);
verifyNoMoreInteractions(mWifiManager);
}
@@ -577,14 +583,14 @@
prepareUsbTethering(upstreamState);
// This should produce no activity of any kind.
- verifyNoMoreInteractions(mNMService);
+ verifyNoMoreInteractions(mNetd);
// Pretend we then receive USB configured broadcast.
sendUsbBroadcast(true, true, true);
mLooper.dispatchAll();
// Now we should see the start of tethering mechanics (in this case:
// tetherMatchingInterfaces() which starts by fetching all interfaces).
- verify(mNMService, times(1)).listInterfaces();
+ verify(mNetd, times(1)).interfaceGetList();
// UpstreamNetworkMonitor should receive selected upstream
verify(mUpstreamNetworkMonitor, times(1)).selectPreferredUpstreamType(any());
@@ -614,9 +620,9 @@
verifyInterfaceServingModeStarted(TEST_WLAN_IFNAME);
verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
- verify(mNMService, times(1)).setIpForwardingEnabled(true);
- verify(mNMService, times(1)).startTethering(any(String[].class));
- verifyNoMoreInteractions(mNMService);
+ verify(mNetd, times(1)).ipfwdEnableForwarding(TETHERING_NAME);
+ verify(mNetd, times(1)).tetherStartWithConfiguration(any());
+ verifyNoMoreInteractions(mNetd);
verify(mWifiManager).updateInterfaceIpState(
TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
verify(mWifiManager).updateInterfaceIpState(
@@ -634,16 +640,16 @@
mTethering.interfaceRemoved(TEST_WLAN_IFNAME);
mLooper.dispatchAll();
- verify(mNMService, times(1)).untetherInterface(TEST_WLAN_IFNAME);
- // {g,s}etInterfaceConfig() called twice for enabling and disabling IPv4.
- verify(mNMService, times(2)).getInterfaceConfig(TEST_WLAN_IFNAME);
- verify(mNMService, times(2))
- .setInterfaceConfig(eq(TEST_WLAN_IFNAME), any(InterfaceConfiguration.class));
- verify(mNMService, times(1)).stopTethering();
- verify(mNMService, times(1)).setIpForwardingEnabled(false);
+ verify(mNetd, times(1)).tetherApplyDnsInterfaces();
+ verify(mNetd, times(1)).tetherInterfaceRemove(TEST_WLAN_IFNAME);
+ verify(mNetd, times(1)).networkRemoveInterface(INetd.LOCAL_NET_ID, TEST_WLAN_IFNAME);
+ // interfaceSetCfg() called once for enabling and twice disabling IPv4.
+ verify(mNetd, times(3)).interfaceSetCfg(any(InterfaceConfigurationParcel.class));
+ verify(mNetd, times(1)).tetherStop();
+ verify(mNetd, times(1)).ipfwdDisableForwarding(TETHERING_NAME);
verify(mWifiManager, times(3)).updateInterfaceIpState(
TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
- verifyNoMoreInteractions(mNMService);
+ verifyNoMoreInteractions(mNetd);
verifyNoMoreInteractions(mWifiManager);
// Asking for the last error after the per-interface state machine
// has been reaped yields an unknown interface error.
@@ -680,8 +686,8 @@
UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
runUsbTethering(upstreamState);
- verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
- verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+ verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+ verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
sendIPv6TetherUpdates(upstreamState);
verify(mRouterAdvertisementDaemon, never()).buildNewRa(any(), notNull());
@@ -690,7 +696,8 @@
@Test
public void workingMobileUsbTethering_IPv4LegacyDhcp() {
- Settings.Global.putInt(mContentResolver, TETHER_ENABLE_LEGACY_DHCP_SERVER, 1);
+ when(mResources.getBoolean(R.bool.config_tether_enable_legacy_dhcp_server)).thenReturn(
+ true);
sendConfigurationChanged();
final UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
runUsbTethering(upstreamState);
@@ -704,8 +711,8 @@
UpstreamNetworkState upstreamState = buildMobileIPv6UpstreamState();
runUsbTethering(upstreamState);
- verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
- verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+ verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+ verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
sendIPv6TetherUpdates(upstreamState);
verify(mRouterAdvertisementDaemon, times(1)).buildNewRa(any(), notNull());
@@ -717,8 +724,8 @@
UpstreamNetworkState upstreamState = buildMobileDualStackUpstreamState();
runUsbTethering(upstreamState);
- verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
- verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+ verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+ verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
verify(mRouterAdvertisementDaemon, times(1)).start();
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
@@ -732,12 +739,11 @@
UpstreamNetworkState upstreamState = buildMobile464xlatUpstreamState();
runUsbTethering(upstreamState);
- verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME);
- verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+ verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME);
+ verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
- verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
- verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME,
- TEST_XLAT_MOBILE_IFNAME);
+ verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME);
+ verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
sendIPv6TetherUpdates(upstreamState);
verify(mRouterAdvertisementDaemon, times(1)).buildNewRa(any(), notNull());
@@ -750,9 +756,9 @@
UpstreamNetworkState upstreamState = buildMobileIPv6UpstreamState();
runUsbTethering(upstreamState);
- verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+ verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
- verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+ verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
// Then 464xlat comes up
upstreamState = buildMobile464xlatUpstreamState();
@@ -768,20 +774,18 @@
mLooper.dispatchAll();
// Forwarding is added for 464xlat
- verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME);
- verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME,
- TEST_XLAT_MOBILE_IFNAME);
+ verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME);
+ verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_XLAT_MOBILE_IFNAME);
// Forwarding was not re-added for v6 (still times(1))
- verify(mNMService, times(1)).enableNat(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
- verify(mNMService, times(1)).startInterfaceForwarding(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+ verify(mNetd, times(1)).tetherAddForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
+ verify(mNetd, times(1)).ipfwdAddInterfaceForward(TEST_USB_IFNAME, TEST_MOBILE_IFNAME);
// DHCP not restarted on downstream (still times(1))
verify(mDhcpServer, timeout(DHCPSERVER_START_TIMEOUT_MS).times(1)).start(any());
}
@Test
public void configTetherUpstreamAutomaticIgnoresConfigTetherUpstreamTypes() throws Exception {
- when(mResources.getBoolean(com.android.internal.R.bool.config_tether_upstream_automatic))
- .thenReturn(true);
+ when(mResources.getBoolean(R.bool.config_tether_upstream_automatic)).thenReturn(true);
sendConfigurationChanged();
// Setup IPv6
@@ -816,7 +820,7 @@
mLooper.dispatchAll();
verify(mWifiManager, times(1)).startTetheredHotspot(null);
verifyNoMoreInteractions(mWifiManager);
- verifyNoMoreInteractions(mNMService);
+ verifyNoMoreInteractions(mNetd);
// Emulate externally-visible WifiManager effects, causing the
// per-interface state machine to start up, and telling us that
@@ -829,7 +833,7 @@
verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
verify(mWifiManager).updateInterfaceIpState(
TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
- verifyNoMoreInteractions(mNMService);
+ verifyNoMoreInteractions(mNetd);
verifyNoMoreInteractions(mWifiManager);
}
@@ -843,7 +847,7 @@
mLooper.dispatchAll();
verify(mWifiManager, times(1)).startTetheredHotspot(null);
verifyNoMoreInteractions(mWifiManager);
- verifyNoMoreInteractions(mNMService);
+ verifyNoMoreInteractions(mNetd);
// Emulate externally-visible WifiManager effects, causing the
// per-interface state machine to start up, and telling us that
@@ -854,9 +858,11 @@
verifyInterfaceServingModeStarted(TEST_WLAN_IFNAME);
verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
- verify(mNMService, times(1)).setIpForwardingEnabled(true);
- verify(mNMService, times(1)).startTethering(any(String[].class));
- verifyNoMoreInteractions(mNMService);
+ verify(mNetd, times(1)).ipfwdEnableForwarding(TETHERING_NAME);
+ verify(mNetd, times(1)).tetherStartWithConfiguration(any());
+ verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(TEST_WLAN_IFNAME),
+ anyString(), anyString());
+ verifyNoMoreInteractions(mNetd);
verify(mWifiManager).updateInterfaceIpState(
TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
verify(mWifiManager).updateInterfaceIpState(
@@ -874,8 +880,8 @@
/////
// We do not currently emulate any upstream being found.
//
- // This is why there are no calls to verify mNMService.enableNat() or
- // mNMService.startInterfaceForwarding().
+ // This is why there are no calls to verify mNetd.tetherAddForward() or
+ // mNetd.ipfwdAddInterfaceForward().
/////
// Emulate pressing the WiFi tethering button.
@@ -883,7 +889,7 @@
mLooper.dispatchAll();
verify(mWifiManager, times(1)).stopSoftAp();
verifyNoMoreInteractions(mWifiManager);
- verifyNoMoreInteractions(mNMService);
+ verifyNoMoreInteractions(mNetd);
// Emulate externally-visible WifiManager effects, when tethering mode
// is being torn down.
@@ -891,16 +897,16 @@
mTethering.interfaceRemoved(TEST_WLAN_IFNAME);
mLooper.dispatchAll();
- verify(mNMService, times(1)).untetherInterface(TEST_WLAN_IFNAME);
- // {g,s}etInterfaceConfig() called twice for enabling and disabling IPv4.
- verify(mNMService, atLeastOnce()).getInterfaceConfig(TEST_WLAN_IFNAME);
- verify(mNMService, atLeastOnce())
- .setInterfaceConfig(eq(TEST_WLAN_IFNAME), any(InterfaceConfiguration.class));
- verify(mNMService, times(1)).stopTethering();
- verify(mNMService, times(1)).setIpForwardingEnabled(false);
+ verify(mNetd, times(1)).tetherApplyDnsInterfaces();
+ verify(mNetd, times(1)).tetherInterfaceRemove(TEST_WLAN_IFNAME);
+ verify(mNetd, times(1)).networkRemoveInterface(INetd.LOCAL_NET_ID, TEST_WLAN_IFNAME);
+ // interfaceSetCfg() called once for enabling and twice for disabling IPv4.
+ verify(mNetd, times(3)).interfaceSetCfg(any(InterfaceConfigurationParcel.class));
+ verify(mNetd, times(1)).tetherStop();
+ verify(mNetd, times(1)).ipfwdDisableForwarding(TETHERING_NAME);
verify(mWifiManager, times(3)).updateInterfaceIpState(
TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
- verifyNoMoreInteractions(mNMService);
+ verifyNoMoreInteractions(mNetd);
verifyNoMoreInteractions(mWifiManager);
// Asking for the last error after the per-interface state machine
// has been reaped yields an unknown interface error.
@@ -911,14 +917,14 @@
@Test
public void failureEnablingIpForwarding() throws Exception {
when(mWifiManager.startTetheredHotspot(any(SoftApConfiguration.class))).thenReturn(true);
- doThrow(new RemoteException()).when(mNMService).setIpForwardingEnabled(true);
+ doThrow(new RemoteException()).when(mNetd).ipfwdEnableForwarding(TETHERING_NAME);
// Emulate pressing the WiFi tethering button.
mTethering.startTethering(TETHERING_WIFI, null, false);
mLooper.dispatchAll();
verify(mWifiManager, times(1)).startTetheredHotspot(null);
verifyNoMoreInteractions(mWifiManager);
- verifyNoMoreInteractions(mNMService);
+ verifyNoMoreInteractions(mNetd);
// Emulate externally-visible WifiManager effects, causing the
// per-interface state machine to start up, and telling us that
@@ -927,15 +933,15 @@
sendWifiApStateChanged(WIFI_AP_STATE_ENABLED, TEST_WLAN_IFNAME, IFACE_IP_MODE_TETHERED);
mLooper.dispatchAll();
- // We verify get/set called thrice here: twice for setup (on NMService) and once during
- // teardown (on Netd) because all events happen over the course of the single
+ // We verify get/set called three times here: twice for setup and once during
+ // teardown because all events happen over the course of the single
// dispatchAll() above. Note that once the IpServer IPv4 address config
// code is refactored the two calls during shutdown will revert to one.
- verify(mNMService, times(2)).getInterfaceConfig(TEST_WLAN_IFNAME);
- verify(mNMService, times(2))
- .setInterfaceConfig(eq(TEST_WLAN_IFNAME), any(InterfaceConfiguration.class));
- verify(mNetd, times(1)).interfaceSetCfg(argThat(p -> TEST_WLAN_IFNAME.equals(p.ifName)));
- verify(mNMService, times(1)).tetherInterface(TEST_WLAN_IFNAME);
+ verify(mNetd, times(3)).interfaceSetCfg(argThat(p -> TEST_WLAN_IFNAME.equals(p.ifName)));
+ verify(mNetd, times(1)).tetherInterfaceAdd(TEST_WLAN_IFNAME);
+ verify(mNetd, times(1)).networkAddInterface(INetd.LOCAL_NET_ID, TEST_WLAN_IFNAME);
+ verify(mNetd, times(2)).networkAddRoute(eq(INetd.LOCAL_NET_ID), eq(TEST_WLAN_IFNAME),
+ anyString(), anyString());
verify(mWifiManager).updateInterfaceIpState(
TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_UNSPECIFIED);
verify(mWifiManager).updateInterfaceIpState(
@@ -945,18 +951,20 @@
assertEquals(3, mTetheringDependencies.mIsTetheringSupportedCalls);
verifyTetheringBroadcast(TEST_WLAN_IFNAME, EXTRA_AVAILABLE_TETHER);
// This is called, but will throw.
- verify(mNMService, times(1)).setIpForwardingEnabled(true);
+ verify(mNetd, times(1)).ipfwdEnableForwarding(TETHERING_NAME);
// This never gets called because of the exception thrown above.
- verify(mNMService, times(0)).startTethering(any(String[].class));
+ verify(mNetd, times(0)).tetherStartWithConfiguration(any());
// When the master state machine transitions to an error state it tells
// downstream interfaces, which causes us to tell Wi-Fi about the error
// so it can take down AP mode.
- verify(mNMService, times(1)).untetherInterface(TEST_WLAN_IFNAME);
+ verify(mNetd, times(1)).tetherApplyDnsInterfaces();
+ verify(mNetd, times(1)).tetherInterfaceRemove(TEST_WLAN_IFNAME);
+ verify(mNetd, times(1)).networkRemoveInterface(INetd.LOCAL_NET_ID, TEST_WLAN_IFNAME);
verify(mWifiManager).updateInterfaceIpState(
TEST_WLAN_IFNAME, WifiManager.IFACE_IP_MODE_CONFIGURATION_ERROR);
verifyNoMoreInteractions(mWifiManager);
- verifyNoMoreInteractions(mNMService);
+ verifyNoMoreInteractions(mNetd);
}
private void runUserRestrictionsChange(
@@ -1210,12 +1218,12 @@
@Test
public void testMultiSimAware() throws Exception {
final TetheringConfiguration initailConfig = mTethering.getTetheringConfiguration();
- assertEquals(INVALID_SUBSCRIPTION_ID, initailConfig.subId);
+ assertEquals(INVALID_SUBSCRIPTION_ID, initailConfig.activeDataSubId);
final int fakeSubId = 1234;
mPhoneStateListener.onActiveDataSubscriptionIdChanged(fakeSubId);
final TetheringConfiguration newConfig = mTethering.getTetheringConfiguration();
- assertEquals(fakeSubId, newConfig.subId);
+ assertEquals(fakeSubId, newConfig.activeDataSubId);
}
private void workingWifiP2pGroupOwner(
@@ -1228,9 +1236,9 @@
verifyInterfaceServingModeStarted(TEST_P2P_IFNAME);
verifyTetheringBroadcast(TEST_P2P_IFNAME, EXTRA_AVAILABLE_TETHER);
- verify(mNMService, times(1)).setIpForwardingEnabled(true);
- verify(mNMService, times(1)).startTethering(any(String[].class));
- verifyNoMoreInteractions(mNMService);
+ verify(mNetd, times(1)).ipfwdEnableForwarding(TETHERING_NAME);
+ verify(mNetd, times(1)).tetherStartWithConfiguration(any());
+ verifyNoMoreInteractions(mNetd);
verifyTetheringBroadcast(TEST_P2P_IFNAME, EXTRA_ACTIVE_LOCAL_ONLY);
verify(mUpstreamNetworkMonitor, times(1)).startObserveAllNetworks();
// This will be called twice, one is on entering IpServer.STATE_AVAILABLE,
@@ -1245,16 +1253,16 @@
mTethering.interfaceRemoved(TEST_P2P_IFNAME);
mLooper.dispatchAll();
- verify(mNMService, times(1)).untetherInterface(TEST_P2P_IFNAME);
- // {g,s}etInterfaceConfig() called twice for enabling and disabling IPv4.
- verify(mNMService, times(2)).getInterfaceConfig(TEST_P2P_IFNAME);
- verify(mNMService, times(2))
- .setInterfaceConfig(eq(TEST_P2P_IFNAME), any(InterfaceConfiguration.class));
- verify(mNMService, times(1)).stopTethering();
- verify(mNMService, times(1)).setIpForwardingEnabled(false);
+ verify(mNetd, times(1)).tetherApplyDnsInterfaces();
+ verify(mNetd, times(1)).tetherInterfaceRemove(TEST_P2P_IFNAME);
+ verify(mNetd, times(1)).networkRemoveInterface(INetd.LOCAL_NET_ID, TEST_P2P_IFNAME);
+ // interfaceSetCfg() called once for enabling and twice for disabling IPv4.
+ verify(mNetd, times(3)).interfaceSetCfg(any(InterfaceConfigurationParcel.class));
+ verify(mNetd, times(1)).tetherStop();
+ verify(mNetd, times(1)).ipfwdDisableForwarding(TETHERING_NAME);
verify(mUpstreamNetworkMonitor, never()).getCurrentPreferredUpstream();
verify(mUpstreamNetworkMonitor, never()).selectPreferredUpstreamType(any());
- verifyNoMoreInteractions(mNMService);
+ verifyNoMoreInteractions(mNetd);
// Asking for the last error after the per-interface state machine
// has been reaped yields an unknown interface error.
assertEquals(TETHER_ERROR_UNKNOWN_IFACE, mTethering.getLastTetherError(TEST_P2P_IFNAME));
@@ -1268,12 +1276,11 @@
sendWifiP2pConnectionChanged(true, false, TEST_P2P_IFNAME);
mLooper.dispatchAll();
- verify(mNMService, never()).getInterfaceConfig(TEST_P2P_IFNAME);
- verify(mNMService, never())
- .setInterfaceConfig(eq(TEST_P2P_IFNAME), any(InterfaceConfiguration.class));
- verify(mNMService, never()).tetherInterface(TEST_P2P_IFNAME);
- verify(mNMService, never()).setIpForwardingEnabled(true);
- verify(mNMService, never()).startTethering(any(String[].class));
+ verify(mNetd, never()).interfaceSetCfg(any(InterfaceConfigurationParcel.class));
+ verify(mNetd, never()).tetherInterfaceAdd(TEST_P2P_IFNAME);
+ verify(mNetd, never()).networkAddInterface(INetd.LOCAL_NET_ID, TEST_P2P_IFNAME);
+ verify(mNetd, never()).ipfwdEnableForwarding(TETHERING_NAME);
+ verify(mNetd, never()).tetherStartWithConfiguration(any());
// Emulate externally-visible WifiP2pManager effects, when wifi p2p group
// is being removed.
@@ -1281,13 +1288,13 @@
mTethering.interfaceRemoved(TEST_P2P_IFNAME);
mLooper.dispatchAll();
- verify(mNMService, never()).untetherInterface(TEST_P2P_IFNAME);
- verify(mNMService, never()).getInterfaceConfig(TEST_P2P_IFNAME);
- verify(mNMService, never())
- .setInterfaceConfig(eq(TEST_P2P_IFNAME), any(InterfaceConfiguration.class));
- verify(mNMService, never()).stopTethering();
- verify(mNMService, never()).setIpForwardingEnabled(false);
- verifyNoMoreInteractions(mNMService);
+ verify(mNetd, never()).tetherApplyDnsInterfaces();
+ verify(mNetd, never()).tetherInterfaceRemove(TEST_P2P_IFNAME);
+ verify(mNetd, never()).networkRemoveInterface(INetd.LOCAL_NET_ID, TEST_P2P_IFNAME);
+ verify(mNetd, never()).interfaceSetCfg(any(InterfaceConfigurationParcel.class));
+ verify(mNetd, never()).tetherStop();
+ verify(mNetd, never()).ipfwdDisableForwarding(TETHERING_NAME);
+ verifyNoMoreInteractions(mNetd);
// Asking for the last error after the per-interface state machine
// has been reaped yields an unknown interface error.
assertEquals(TETHER_ERROR_UNKNOWN_IFACE, mTethering.getLastTetherError(TEST_P2P_IFNAME));
@@ -1306,7 +1313,7 @@
private void workingWifiP2pGroupOwnerLegacyMode(
boolean emulateInterfaceStatusChanged) throws Exception {
// change to legacy mode and update tethering information by chaning SIM
- when(mResources.getStringArray(com.android.internal.R.array.config_tether_wifi_p2p_regexs))
+ when(mResources.getStringArray(R.array.config_tether_wifi_p2p_regexs))
.thenReturn(new String[]{});
final int fakeSubId = 1234;
mPhoneStateListener.onActiveDataSubscriptionIdChanged(fakeSubId);
@@ -1317,12 +1324,11 @@
sendWifiP2pConnectionChanged(true, true, TEST_P2P_IFNAME);
mLooper.dispatchAll();
- verify(mNMService, never()).getInterfaceConfig(TEST_P2P_IFNAME);
- verify(mNMService, never())
- .setInterfaceConfig(eq(TEST_P2P_IFNAME), any(InterfaceConfiguration.class));
- verify(mNMService, never()).tetherInterface(TEST_P2P_IFNAME);
- verify(mNMService, never()).setIpForwardingEnabled(true);
- verify(mNMService, never()).startTethering(any(String[].class));
+ verify(mNetd, never()).interfaceSetCfg(any(InterfaceConfigurationParcel.class));
+ verify(mNetd, never()).tetherInterfaceAdd(TEST_P2P_IFNAME);
+ verify(mNetd, never()).networkAddInterface(INetd.LOCAL_NET_ID, TEST_P2P_IFNAME);
+ verify(mNetd, never()).ipfwdEnableForwarding(TETHERING_NAME);
+ verify(mNetd, never()).tetherStartWithConfiguration(any());
assertEquals(TETHER_ERROR_UNKNOWN_IFACE, mTethering.getLastTetherError(TEST_P2P_IFNAME));
}
@Test
@@ -1345,6 +1351,50 @@
workingWifiP2pGroupClient(false);
}
+ private void setDataSaverEnabled(boolean enabled) {
+ final Intent intent = new Intent(ACTION_RESTRICT_BACKGROUND_CHANGED);
+ mServiceContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+
+ final int status = enabled ? RESTRICT_BACKGROUND_STATUS_ENABLED
+ : RESTRICT_BACKGROUND_STATUS_DISABLED;
+ when(mCm.getRestrictBackgroundStatus()).thenReturn(status);
+ mLooper.dispatchAll();
+ }
+
+ @Test
+ public void testDataSaverChanged() {
+ // Start Tethering.
+ final UpstreamNetworkState upstreamState = buildMobileIPv4UpstreamState();
+ runUsbTethering(upstreamState);
+ assertContains(Arrays.asList(mTethering.getTetheredIfaces()), TEST_USB_IFNAME);
+ // Data saver is ON.
+ setDataSaverEnabled(true);
+ // Verify that tethering should be disabled.
+ verify(mUsbManager, times(1)).setCurrentFunctions(UsbManager.FUNCTION_NONE);
+ mTethering.interfaceRemoved(TEST_USB_IFNAME);
+ mLooper.dispatchAll();
+ assertEquals(mTethering.getTetheredIfaces(), new String[0]);
+ reset(mUsbManager);
+
+ runUsbTethering(upstreamState);
+ // Verify that user can start tethering again without turning OFF data saver.
+ assertContains(Arrays.asList(mTethering.getTetheredIfaces()), TEST_USB_IFNAME);
+
+ // If data saver is keep ON with change event, tethering should not be OFF this time.
+ setDataSaverEnabled(true);
+ verify(mUsbManager, times(0)).setCurrentFunctions(UsbManager.FUNCTION_NONE);
+ assertContains(Arrays.asList(mTethering.getTetheredIfaces()), TEST_USB_IFNAME);
+
+ // If data saver is turned OFF, it should not change tethering.
+ setDataSaverEnabled(false);
+ verify(mUsbManager, times(0)).setCurrentFunctions(UsbManager.FUNCTION_NONE);
+ assertContains(Arrays.asList(mTethering.getTetheredIfaces()), TEST_USB_IFNAME);
+ }
+
+ private static <T> void assertContains(Collection<T> collection, T element) {
+ assertTrue(element + " not found in " + collection, collection.contains(element));
+ }
+
// TODO: Test that a request for hotspot mode doesn't interfere with an
// already operating tethering mode interface.
}
diff --git a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
index c90abbb..5ed75bf 100644
--- a/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
+++ b/packages/Tethering/tests/unit/src/com/android/server/connectivity/tethering/UpstreamNetworkMonitorTest.java
@@ -18,13 +18,14 @@
import static android.net.ConnectivityManager.TYPE_MOBILE_DUN;
import static android.net.ConnectivityManager.TYPE_MOBILE_HIPRI;
-import static android.net.ConnectivityManager.TYPE_NONE;
import static android.net.ConnectivityManager.TYPE_WIFI;
import static android.net.NetworkCapabilities.NET_CAPABILITY_DUN;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
+import static com.android.server.connectivity.tethering.UpstreamNetworkMonitor.TYPE_NONE;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
@@ -538,13 +539,15 @@
mUNM.selectPreferredUpstreamType(preferredTypes));
verify(mEntitleMgr, times(1)).maybeRunProvisioning();
}
+
private void assertSatisfiesLegacyType(int legacyType, UpstreamNetworkState ns) {
if (legacyType == TYPE_NONE) {
assertTrue(ns == null);
return;
}
- final NetworkCapabilities nc = ConnectivityManager.networkCapabilitiesForType(legacyType);
+ final NetworkCapabilities nc =
+ UpstreamNetworkMonitor.networkCapabilitiesForType(legacyType);
assertTrue(nc.satisfiedByNetworkCapabilities(ns.networkCapabilities));
}
diff --git a/packages/services/PacProcessor/jni/Android.bp b/packages/services/PacProcessor/jni/Android.bp
index 2a94237..61f8143 100644
--- a/packages/services/PacProcessor/jni/Android.bp
+++ b/packages/services/PacProcessor/jni/Android.bp
@@ -37,4 +37,10 @@
"-Wunused",
"-Wunreachable-code",
],
+ sanitize: {
+ cfi: true,
+ diag: {
+ cfi: true,
+ },
+ },
}
diff --git a/proto/Android.bp b/proto/Android.bp
index 65bccbb..7cf6ce7 100644
--- a/proto/Android.bp
+++ b/proto/Android.bp
@@ -27,3 +27,8 @@
srcs: ["src/metrics_constants/metrics_constants.proto"],
sdk_version: "system_current",
}
+
+filegroup {
+ name: "system-messages-proto-src",
+ srcs: ["src/system_messages.proto"],
+}
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index 068707f..21100458ad 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -237,6 +237,9 @@
// Inform the user that the current network may not support using a randomized MAC address.
NOTE_NETWORK_NO_MAC_RANDOMIZATION_SUPPORT = 56;
+ // Inform the user that EAP failure occurs
+ NOTE_WIFI_EAP_FAILURE = 57;
+
// ADD_NEW_IDS_ABOVE_THIS_LINE
// Legacy IDs with arbitrary values appear below
// Legacy IDs existed as stable non-conflicting constants prior to the O release
diff --git a/rs/java/android/renderscript/BaseObj.java b/rs/java/android/renderscript/BaseObj.java
index b7e05d9..7b5514b 100644
--- a/rs/java/android/renderscript/BaseObj.java
+++ b/rs/java/android/renderscript/BaseObj.java
@@ -16,8 +16,10 @@
package android.renderscript;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
import dalvik.system.CloseGuard;
+
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
diff --git a/rs/java/android/renderscript/Element.java b/rs/java/android/renderscript/Element.java
index b8eb3a1..0941907 100644
--- a/rs/java/android/renderscript/Element.java
+++ b/rs/java/android/renderscript/Element.java
@@ -16,7 +16,7 @@
package android.renderscript;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* <p>An Element represents one item within an {@link
diff --git a/rs/java/android/renderscript/FileA3D.java b/rs/java/android/renderscript/FileA3D.java
index 9a6b0bc..7cc2825 100644
--- a/rs/java/android/renderscript/FileA3D.java
+++ b/rs/java/android/renderscript/FileA3D.java
@@ -16,13 +16,13 @@
package android.renderscript;
-import java.io.File;
-import java.io.InputStream;
-
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.AssetManager;
import android.content.res.Resources;
+import java.io.File;
+import java.io.InputStream;
+
/**
* @hide
* @deprecated in API 16
diff --git a/rs/java/android/renderscript/Font.java b/rs/java/android/renderscript/Font.java
index 583350e..df9d801 100644
--- a/rs/java/android/renderscript/Font.java
+++ b/rs/java/android/renderscript/Font.java
@@ -16,17 +16,16 @@
package android.renderscript;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.os.Environment;
+
import java.io.File;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
-import android.os.Environment;
-
-import android.annotation.UnsupportedAppUsage;
-import android.content.res.AssetManager;
-import android.content.res.Resources;
-
/**
* @hide
* @deprecated in API 16
diff --git a/rs/java/android/renderscript/Matrix4f.java b/rs/java/android/renderscript/Matrix4f.java
index 026c9fb..a9469c9 100644
--- a/rs/java/android/renderscript/Matrix4f.java
+++ b/rs/java/android/renderscript/Matrix4f.java
@@ -16,8 +16,7 @@
package android.renderscript;
-import android.annotation.UnsupportedAppUsage;
-import java.lang.Math;
+import android.compat.annotation.UnsupportedAppUsage;
/**
diff --git a/rs/java/android/renderscript/Mesh.java b/rs/java/android/renderscript/Mesh.java
index 5321dcb..826225a 100644
--- a/rs/java/android/renderscript/Mesh.java
+++ b/rs/java/android/renderscript/Mesh.java
@@ -16,7 +16,8 @@
package android.renderscript;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
import java.util.Vector;
/**
diff --git a/rs/java/android/renderscript/Program.java b/rs/java/android/renderscript/Program.java
index e28d646..ff07218 100644
--- a/rs/java/android/renderscript/Program.java
+++ b/rs/java/android/renderscript/Program.java
@@ -17,14 +17,14 @@
package android.renderscript;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.res.Resources;
+import android.util.Log;
+
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
-import android.annotation.UnsupportedAppUsage;
-import android.content.res.Resources;
-import android.util.Log;
-
/**
* @hide
diff --git a/rs/java/android/renderscript/ProgramFragment.java b/rs/java/android/renderscript/ProgramFragment.java
index 3dde9b6..8805312 100644
--- a/rs/java/android/renderscript/ProgramFragment.java
+++ b/rs/java/android/renderscript/ProgramFragment.java
@@ -16,7 +16,7 @@
package android.renderscript;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
diff --git a/rs/java/android/renderscript/ProgramFragmentFixedFunction.java b/rs/java/android/renderscript/ProgramFragmentFixedFunction.java
index d05d41d..c741ce6 100644
--- a/rs/java/android/renderscript/ProgramFragmentFixedFunction.java
+++ b/rs/java/android/renderscript/ProgramFragmentFixedFunction.java
@@ -16,7 +16,7 @@
package android.renderscript;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
diff --git a/rs/java/android/renderscript/ProgramRaster.java b/rs/java/android/renderscript/ProgramRaster.java
index 33000ac..a21696c 100644
--- a/rs/java/android/renderscript/ProgramRaster.java
+++ b/rs/java/android/renderscript/ProgramRaster.java
@@ -16,7 +16,7 @@
package android.renderscript;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
diff --git a/rs/java/android/renderscript/ProgramStore.java b/rs/java/android/renderscript/ProgramStore.java
index 622fe21..7e61347 100644
--- a/rs/java/android/renderscript/ProgramStore.java
+++ b/rs/java/android/renderscript/ProgramStore.java
@@ -16,7 +16,7 @@
package android.renderscript;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
diff --git a/rs/java/android/renderscript/ProgramVertex.java b/rs/java/android/renderscript/ProgramVertex.java
index 83d9ea7..9257234 100644
--- a/rs/java/android/renderscript/ProgramVertex.java
+++ b/rs/java/android/renderscript/ProgramVertex.java
@@ -38,7 +38,7 @@
**/
package android.renderscript;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
diff --git a/rs/java/android/renderscript/ProgramVertexFixedFunction.java b/rs/java/android/renderscript/ProgramVertexFixedFunction.java
index 579d3bb..03c2eaf 100644
--- a/rs/java/android/renderscript/ProgramVertexFixedFunction.java
+++ b/rs/java/android/renderscript/ProgramVertexFixedFunction.java
@@ -16,7 +16,7 @@
package android.renderscript;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
diff --git a/rs/java/android/renderscript/RSSurfaceView.java b/rs/java/android/renderscript/RSSurfaceView.java
index 561373c..6bdde38 100644
--- a/rs/java/android/renderscript/RSSurfaceView.java
+++ b/rs/java/android/renderscript/RSSurfaceView.java
@@ -16,7 +16,7 @@
package android.renderscript;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
diff --git a/rs/java/android/renderscript/RenderScript.java b/rs/java/android/renderscript/RenderScript.java
index f4c2777..39efe73 100644
--- a/rs/java/android/renderscript/RenderScript.java
+++ b/rs/java/android/renderscript/RenderScript.java
@@ -16,7 +16,7 @@
package android.renderscript;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
@@ -447,44 +447,33 @@
validate();
return rsnAllocationCreateTyped(mContext, type, mip, usage, pointer);
}
- native long rsnAllocationCreateFromBitmap(long con, long type, int mip, long bitmapHandle,
+
+ native long rsnAllocationCreateFromBitmap(long con, long type, int mip, Bitmap bmp,
int usage);
synchronized long nAllocationCreateFromBitmap(long type, int mip, Bitmap bmp, int usage) {
validate();
- return rsnAllocationCreateFromBitmap(mContext, type, mip, bmp.getNativeInstance(), usage);
+ return rsnAllocationCreateFromBitmap(mContext, type, mip, bmp, usage);
}
- native long rsnAllocationCreateBitmapBackedAllocation(long con, long type, int mip, long bitmapHandle,
+ native long rsnAllocationCreateBitmapBackedAllocation(long con, long type, int mip, Bitmap bmp,
int usage);
synchronized long nAllocationCreateBitmapBackedAllocation(long type, int mip, Bitmap bmp,
int usage) {
validate();
- return rsnAllocationCreateBitmapBackedAllocation(mContext, type, mip, bmp.getNativeInstance(),
- usage);
+ return rsnAllocationCreateBitmapBackedAllocation(mContext, type, mip, bmp, usage);
}
- native long rsnAllocationCubeCreateFromBitmap(long con, long type, int mip, long bitmapHandle,
+ native long rsnAllocationCubeCreateFromBitmap(long con, long type, int mip, Bitmap bmp,
int usage);
synchronized long nAllocationCubeCreateFromBitmap(long type, int mip, Bitmap bmp, int usage) {
validate();
- return rsnAllocationCubeCreateFromBitmap(mContext, type, mip, bmp.getNativeInstance(),
- usage);
- }
- native long rsnAllocationCreateBitmapRef(long con, long type, long bitmapHandle);
- synchronized long nAllocationCreateBitmapRef(long type, Bitmap bmp) {
- validate();
- return rsnAllocationCreateBitmapRef(mContext, type, bmp.getNativeInstance());
- }
- native long rsnAllocationCreateFromAssetStream(long con, int mips, int assetStream, int usage);
- synchronized long nAllocationCreateFromAssetStream(int mips, int assetStream, int usage) {
- validate();
- return rsnAllocationCreateFromAssetStream(mContext, mips, assetStream, usage);
+ return rsnAllocationCubeCreateFromBitmap(mContext, type, mip, bmp, usage);
}
- native void rsnAllocationCopyToBitmap(long con, long alloc, long bitmapHandle);
+ native void rsnAllocationCopyToBitmap(long con, long alloc, Bitmap bmp);
synchronized void nAllocationCopyToBitmap(long alloc, Bitmap bmp) {
validate();
- rsnAllocationCopyToBitmap(mContext, alloc, bmp.getNativeInstance());
+ rsnAllocationCopyToBitmap(mContext, alloc, bmp);
}
native void rsnAllocationSyncAll(long con, long alloc, int src);
@@ -537,10 +526,10 @@
validate();
rsnAllocationGenerateMipmaps(mContext, alloc);
}
- native void rsnAllocationCopyFromBitmap(long con, long alloc, long bitmapHandle);
+ native void rsnAllocationCopyFromBitmap(long con, long alloc, Bitmap bmp);
synchronized void nAllocationCopyFromBitmap(long alloc, Bitmap bmp) {
validate();
- rsnAllocationCopyFromBitmap(mContext, alloc, bmp.getNativeInstance());
+ rsnAllocationCopyFromBitmap(mContext, alloc, bmp);
}
diff --git a/rs/java/android/renderscript/RenderScriptCacheDir.java b/rs/java/android/renderscript/RenderScriptCacheDir.java
index 1797bef..862d032 100644
--- a/rs/java/android/renderscript/RenderScriptCacheDir.java
+++ b/rs/java/android/renderscript/RenderScriptCacheDir.java
@@ -16,7 +16,8 @@
package android.renderscript;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
+
import java.io.File;
/**
diff --git a/rs/java/android/renderscript/RenderScriptGL.java b/rs/java/android/renderscript/RenderScriptGL.java
index 6fac83e..dafaf36 100644
--- a/rs/java/android/renderscript/RenderScriptGL.java
+++ b/rs/java/android/renderscript/RenderScriptGL.java
@@ -16,7 +16,7 @@
package android.renderscript;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.view.Surface;
diff --git a/rs/java/android/renderscript/Script.java b/rs/java/android/renderscript/Script.java
index 9ad9aea..d1d3a76 100644
--- a/rs/java/android/renderscript/Script.java
+++ b/rs/java/android/renderscript/Script.java
@@ -16,7 +16,7 @@
package android.renderscript;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.util.SparseArray;
/**
diff --git a/rs/jni/Android.mk b/rs/jni/Android.mk
index 0854b95..f9ef0b7 100644
--- a/rs/jni/Android.mk
+++ b/rs/jni/Android.mk
@@ -12,7 +12,6 @@
libRS \
libcutils \
liblog \
- libhwui \
libutils \
libui \
libgui \
diff --git a/rs/jni/android_renderscript_RenderScript.cpp b/rs/jni/android_renderscript_RenderScript.cpp
index dfee961..5ae895d 100644
--- a/rs/jni/android_renderscript_RenderScript.cpp
+++ b/rs/jni/android_renderscript_RenderScript.cpp
@@ -32,10 +32,10 @@
#include "jni.h"
#include <nativehelper/JNIHelp.h>
+#include <android/graphics/bitmap.h>
#include "android_runtime/AndroidRuntime.h"
#include "android_runtime/android_view_Surface.h"
#include "android_runtime/android_util_AssetManager.h"
-#include "android/graphics/GraphicsJNI.h"
#include "android/native_window.h"
#include "android/native_window_jni.h"
@@ -1319,27 +1319,28 @@
rsAllocationGenerateMipmaps((RsContext)con, (RsAllocation)alloc);
}
+static size_t computeByteSize(const android::graphics::Bitmap& bitmap) {
+ AndroidBitmapInfo info = bitmap.getInfo();
+ return info.height * info.stride;
+}
+
static jlong
nAllocationCreateFromBitmap(JNIEnv *_env, jobject _this, jlong con, jlong type, jint mip,
- jlong bitmapPtr, jint usage)
+ jobject jbitmap, jint usage)
{
- SkBitmap bitmap;
- bitmap::toBitmap(bitmapPtr).getSkBitmap(&bitmap);
-
+ android::graphics::Bitmap bitmap(_env, jbitmap);
const void* ptr = bitmap.getPixels();
jlong id = (jlong)(uintptr_t)rsAllocationCreateFromBitmap((RsContext)con,
(RsType)type, (RsAllocationMipmapControl)mip,
- ptr, bitmap.computeByteSize(), usage);
+ ptr, computeByteSize(bitmap), usage);
return id;
}
static jlong
nAllocationCreateBitmapBackedAllocation(JNIEnv *_env, jobject _this, jlong con, jlong type,
- jint mip, jlong bitmapPtr, jint usage)
+ jint mip, jobject jbitmap, jint usage)
{
- SkBitmap bitmap;
- bitmap::toBitmap(bitmapPtr).getSkBitmap(&bitmap);
-
+ android::graphics::Bitmap bitmap(_env, jbitmap);
const void* ptr = bitmap.getPixels();
jlong id = (jlong)(uintptr_t)rsAllocationCreateTyped((RsContext)con,
(RsType)type, (RsAllocationMipmapControl)mip,
@@ -1349,40 +1350,35 @@
static jlong
nAllocationCubeCreateFromBitmap(JNIEnv *_env, jobject _this, jlong con, jlong type, jint mip,
- jlong bitmapPtr, jint usage)
+ jobject jbitmap, jint usage)
{
- SkBitmap bitmap;
- bitmap::toBitmap(bitmapPtr).getSkBitmap(&bitmap);
-
+ android::graphics::Bitmap bitmap(_env, jbitmap);
const void* ptr = bitmap.getPixels();
jlong id = (jlong)(uintptr_t)rsAllocationCubeCreateFromBitmap((RsContext)con,
(RsType)type, (RsAllocationMipmapControl)mip,
- ptr, bitmap.computeByteSize(), usage);
+ ptr, computeByteSize(bitmap), usage);
return id;
}
static void
-nAllocationCopyFromBitmap(JNIEnv *_env, jobject _this, jlong con, jlong alloc, jlong bitmapPtr)
+nAllocationCopyFromBitmap(JNIEnv *_env, jobject _this, jlong con, jlong alloc, jobject jbitmap)
{
- SkBitmap bitmap;
- bitmap::toBitmap(bitmapPtr).getSkBitmap(&bitmap);
- int w = bitmap.width();
- int h = bitmap.height();
+ android::graphics::Bitmap bitmap(_env, jbitmap);
+ int w = bitmap.getInfo().width;
+ int h = bitmap.getInfo().height;
const void* ptr = bitmap.getPixels();
rsAllocation2DData((RsContext)con, (RsAllocation)alloc, 0, 0,
0, RS_ALLOCATION_CUBEMAP_FACE_POSITIVE_X,
- w, h, ptr, bitmap.computeByteSize(), 0);
+ w, h, ptr, computeByteSize(bitmap), 0);
}
static void
-nAllocationCopyToBitmap(JNIEnv *_env, jobject _this, jlong con, jlong alloc, jlong bitmapPtr)
+nAllocationCopyToBitmap(JNIEnv *_env, jobject _this, jlong con, jlong alloc, jobject jbitmap)
{
- SkBitmap bitmap;
- bitmap::toBitmap(bitmapPtr).getSkBitmap(&bitmap);
-
+ android::graphics::Bitmap bitmap(_env, jbitmap);
void* ptr = bitmap.getPixels();
- rsAllocationCopyToBitmap((RsContext)con, (RsAllocation)alloc, ptr, bitmap.computeByteSize());
+ rsAllocationCopyToBitmap((RsContext)con, (RsAllocation)alloc, ptr, computeByteSize(bitmap));
bitmap.notifyPixelsChanged();
}
@@ -2867,12 +2863,12 @@
{"rsnTypeGetNativeData", "(JJ[J)V", (void*)nTypeGetNativeData },
{"rsnAllocationCreateTyped", "(JJIIJ)J", (void*)nAllocationCreateTyped },
-{"rsnAllocationCreateFromBitmap", "(JJIJI)J", (void*)nAllocationCreateFromBitmap },
-{"rsnAllocationCreateBitmapBackedAllocation", "(JJIJI)J", (void*)nAllocationCreateBitmapBackedAllocation },
-{"rsnAllocationCubeCreateFromBitmap","(JJIJI)J", (void*)nAllocationCubeCreateFromBitmap },
+{"rsnAllocationCreateFromBitmap", "(JJILandroid/graphics/Bitmap;I)J", (void*)nAllocationCreateFromBitmap },
+{"rsnAllocationCreateBitmapBackedAllocation", "(JJILandroid/graphics/Bitmap;I)J", (void*)nAllocationCreateBitmapBackedAllocation },
+{"rsnAllocationCubeCreateFromBitmap","(JJILandroid/graphics/Bitmap;I)J", (void*)nAllocationCubeCreateFromBitmap },
-{"rsnAllocationCopyFromBitmap", "(JJJ)V", (void*)nAllocationCopyFromBitmap },
-{"rsnAllocationCopyToBitmap", "(JJJ)V", (void*)nAllocationCopyToBitmap },
+{"rsnAllocationCopyFromBitmap", "(JJLandroid/graphics/Bitmap;)V", (void*)nAllocationCopyFromBitmap },
+{"rsnAllocationCopyToBitmap", "(JJLandroid/graphics/Bitmap;)V", (void*)nAllocationCopyToBitmap },
{"rsnAllocationSyncAll", "(JJI)V", (void*)nAllocationSyncAll },
{"rsnAllocationSetupBufferQueue", "(JJI)V", (void*)nAllocationSetupBufferQueue },
diff --git a/services/Android.bp b/services/Android.bp
index ede4c3e..5afed6c 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -61,6 +61,7 @@
"services.devicepolicy",
"services.midi",
"services.net",
+ "services.people",
"services.print",
"services.restrictions",
"services.startop",
@@ -112,7 +113,7 @@
srcs: [":services-sources"],
installable: false,
// TODO: remove the --hide options below
- args: " --show-single-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES,process=android.annotation.SystemApi.Process.SYSTEM_SERVER\\)" +
+ args: " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.MODULE_LIBRARIES,process=android.annotation.SystemApi.Process.SYSTEM_SERVER\\)" +
" --hide-annotation android.annotation.Hide" +
" --hide-package com.google.android.startop.iorap" +
" --hide ReferencesHidden" +
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 5eaa80a..c3965c4 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -36,8 +36,12 @@
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.graphics.Rect;
import android.graphics.Region;
import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerGlobal;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
@@ -54,6 +58,7 @@
import android.view.Display;
import android.view.KeyEvent;
import android.view.MagnificationSpec;
+import android.view.SurfaceControl;
import android.view.View;
import android.view.WindowInfo;
import android.view.accessibility.AccessibilityCache;
@@ -90,6 +95,7 @@
private static final String LOG_TAG = "AbstractAccessibilityServiceConnection";
private static final int WAIT_WINDOWS_TIMEOUT_MILLIS = 5000;
+ protected static final String TAKE_SCREENSHOT = "takeScreenshot";
protected final Context mContext;
protected final SystemSupport mSystemSupport;
protected final WindowManagerInternal mWindowManagerService;
@@ -934,6 +940,54 @@
mInvocationHandler.setSoftKeyboardCallbackEnabled(enabled);
}
+ @Nullable
+ @Override
+ public Bitmap takeScreenshot(int displayId) {
+ synchronized (mLock) {
+ if (!hasRightsToCurrentUserLocked()) {
+ return null;
+ }
+
+ if (!mSecurityPolicy.canTakeScreenshotLocked(this)) {
+ return null;
+ }
+ }
+
+ if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
+ return null;
+ }
+
+ final Display display = DisplayManagerGlobal.getInstance()
+ .getRealDisplay(displayId);
+ if (display == null) {
+ return null;
+ }
+ final Point displaySize = new Point();
+ display.getRealSize(displaySize);
+
+ final int rotation = display.getRotation();
+ Bitmap screenShot = null;
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ final Rect crop = new Rect(0, 0, displaySize.x, displaySize.y);
+ // TODO (b/145893483): calling new API with the display as a parameter
+ // when surface control supported.
+ screenShot = SurfaceControl.screenshot(crop, displaySize.x, displaySize.y,
+ rotation);
+ if (screenShot != null) {
+ // Optimization for telling the bitmap that all of the pixels are known to be
+ // opaque (false). This is meant as a drawing hint, as in some cases a bitmap
+ // that is known to be opaque can take a faster drawing case than one that may
+ // have non-opaque per-pixel alpha values.
+ screenShot.setHasAlpha(false);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ return screenShot;
+ }
+
@Override
public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
@@ -1018,6 +1072,20 @@
}
}
+ /**
+ * Gets windowId of given token.
+ *
+ * @param token The token
+ * @return window id
+ */
+ @Override
+ public int getWindowIdForLeashToken(@NonNull IBinder token) {
+ synchronized (mLock) {
+ // TODO: Add a method to lookup window ID by given leash token.
+ return -1;
+ }
+ }
+
public void resetLocked() {
mSystemSupport.getKeyEventDispatcher().flush(this);
try {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 6a6e2b2..c9fdd5a 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -35,6 +35,7 @@
import android.app.ActivityOptions;
import android.app.AlertDialog;
import android.app.PendingIntent;
+import android.app.RemoteAction;
import android.appwidget.AppWidgetManagerInternal;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
@@ -148,6 +149,8 @@
// their capabilities are ready.
private static final int WAIT_MOTION_INJECTOR_TIMEOUT_MILLIS = 1000;
+ static final String FUNCTION_REGISTER_SYSTEM_ACTION = "registerSystemAction";
+ static final String FUNCTION_UNREGISTER_SYSTEM_ACTION = "unregisterSystemAction";
private static final String FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE =
"registerUiTestAutomationService";
@@ -253,6 +256,27 @@
}
}
+ @VisibleForTesting
+ AccessibilityManagerService(
+ Context context,
+ PackageManager packageManager,
+ AccessibilitySecurityPolicy securityPolicy,
+ SystemActionPerformer systemActionPerformer,
+ AccessibilityWindowManager a11yWindowManager,
+ AccessibilityDisplayListener a11yDisplayListener) {
+ mContext = context;
+ mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ mWindowManagerService = LocalServices.getService(WindowManagerInternal.class);
+ mMainHandler = new MainHandler(mContext.getMainLooper());
+ mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class);
+ mPackageManager = packageManager;
+ mSecurityPolicy = securityPolicy;
+ mSystemActionPerformer = systemActionPerformer;
+ mA11yWindowManager = a11yWindowManager;
+ mA11yDisplayListener = a11yDisplayListener;
+ init();
+ }
+
/**
* Creates a new instance.
*
@@ -260,21 +284,24 @@
*/
public AccessibilityManagerService(Context context) {
mContext = context;
- mPackageManager = mContext.getPackageManager();
- mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+ mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
mWindowManagerService = LocalServices.getService(WindowManagerInternal.class);
- mSecurityPolicy = new AccessibilitySecurityPolicy(mContext, this);
mMainHandler = new MainHandler(mContext.getMainLooper());
+ mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class);
+ mPackageManager = mContext.getPackageManager();
+ mSecurityPolicy = new AccessibilitySecurityPolicy(mContext, this);
mSystemActionPerformer = new SystemActionPerformer(mContext, mWindowManagerService);
mA11yWindowManager = new AccessibilityWindowManager(mLock, mMainHandler,
mWindowManagerService, this, mSecurityPolicy, this);
mA11yDisplayListener = new AccessibilityDisplayListener(mContext, mMainHandler);
- mSecurityPolicy.setAccessibilityWindowManager(mA11yWindowManager);
- mActivityTaskManagerService = LocalServices.getService(ActivityTaskManagerInternal.class);
+ init();
+ }
+ private void init() {
+ mSecurityPolicy.setAccessibilityWindowManager(mA11yWindowManager);
registerBroadcastReceivers();
new AccessibilityContentObserver(mMainHandler).register(
- context.getContentResolver());
+ mContext.getContentResolver());
}
@Override
@@ -384,6 +411,7 @@
if (reboundAService) {
onUserStateChangedLocked(userState);
}
+ migrateAccessibilityButtonSettingsIfNecessaryLocked(userState, packageName);
}
}
@@ -623,6 +651,30 @@
event.recycle();
}
+ /**
+ * This is the implementation of AccessibilityManager system API.
+ * System UI calls into this method through AccessibilityManager system API to register a
+ * system action.
+ */
+ @Override
+ public void registerSystemAction(RemoteAction action, int actionId) {
+ mSecurityPolicy.enforceCallingPermission(Manifest.permission.MANAGE_ACCESSIBILITY,
+ FUNCTION_REGISTER_SYSTEM_ACTION);
+ mSystemActionPerformer.registerSystemAction(actionId, action);
+ }
+
+ /**
+ * This is the implementation of AccessibilityManager system API.
+ * System UI calls into this method through AccessibilityManager system API to unregister a
+ * system action.
+ */
+ @Override
+ public void unregisterSystemAction(int actionId) {
+ mSecurityPolicy.enforceCallingPermission(Manifest.permission.MANAGE_ACCESSIBILITY,
+ FUNCTION_UNREGISTER_SYSTEM_ACTION);
+ mSystemActionPerformer.unregisterSystemAction(actionId);
+ }
+
@Override
public List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(int userId) {
synchronized (mLock) {
@@ -760,9 +812,7 @@
userState.setTouchExplorationEnabledLocked(touchExplorationEnabled);
userState.setDisplayMagnificationEnabledLocked(false);
- userState.setNavBarMagnificationEnabledLocked(false);
userState.disableShortcutMagnificationLocked();
-
userState.setAutoclickEnabledLocked(false);
userState.mEnabledServices.clear();
userState.mEnabledServices.add(service);
@@ -802,17 +852,20 @@
* navigation area has been clicked.
*
* @param displayId The logical display id.
+ * @param targetName The flattened {@link ComponentName} string or the class name of a system
+ * class implementing a supported accessibility feature, or {@code null} if there's no
+ * specified target.
*/
@Override
- public void notifyAccessibilityButtonClicked(int displayId) {
+ public void notifyAccessibilityButtonClicked(int displayId, String targetName) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Caller does not hold permission "
+ android.Manifest.permission.STATUS_BAR_SERVICE);
}
- synchronized (mLock) {
- notifyAccessibilityButtonClickedLocked(displayId);
- }
+ mMainHandler.sendMessage(obtainMessage(
+ AccessibilityManagerService::performAccessibilityShortcutInternal, this,
+ displayId, ACCESSIBILITY_BUTTON, targetName));
}
/**
@@ -972,6 +1025,7 @@
// the state since the context in which the current user
// state was used has changed since it was inactive.
onUserStateChangedLocked(userState);
+ migrateAccessibilityButtonSettingsIfNecessaryLocked(userState, null);
if (announceNewUser) {
// Schedule announcement of the current user if needed.
@@ -1081,66 +1135,6 @@
}
}
- // TODO(a11y shortcut): Remove this function and Use #performAccessibilityShortcutInternal(
- // ACCESSIBILITY_BUTTON) instead, after the new Settings shortcut Ui merged.
- private void notifyAccessibilityButtonClickedLocked(int displayId) {
- final AccessibilityUserState state = getCurrentUserStateLocked();
-
- int potentialTargets = state.isNavBarMagnificationEnabledLocked() ? 1 : 0;
- for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
- final AccessibilityServiceConnection service = state.mBoundServices.get(i);
- if (service.mRequestAccessibilityButton) {
- potentialTargets++;
- }
- }
-
- if (potentialTargets == 0) {
- return;
- }
- if (potentialTargets == 1) {
- if (state.isNavBarMagnificationEnabledLocked()) {
- mMainHandler.sendMessage(obtainMessage(
- AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this,
- displayId));
- return;
- } else {
- for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
- final AccessibilityServiceConnection service = state.mBoundServices.get(i);
- if (service.mRequestAccessibilityButton) {
- service.notifyAccessibilityButtonClickedLocked(displayId);
- return;
- }
- }
- }
- } else {
- if (state.getServiceAssignedToAccessibilityButtonLocked() == null
- && !state.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
- mMainHandler.sendMessage(obtainMessage(
- AccessibilityManagerService::showAccessibilityTargetsSelection, this,
- displayId, ACCESSIBILITY_BUTTON));
- } else if (state.isNavBarMagnificationEnabledLocked()
- && state.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
- mMainHandler.sendMessage(obtainMessage(
- AccessibilityManagerService::sendAccessibilityButtonToInputFilter, this,
- displayId));
- return;
- } else {
- for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
- final AccessibilityServiceConnection service = state.mBoundServices.get(i);
- if (service.mRequestAccessibilityButton && (service.mComponentName.equals(
- state.getServiceAssignedToAccessibilityButtonLocked()))) {
- service.notifyAccessibilityButtonClickedLocked(displayId);
- return;
- }
- }
- }
- // The user may have turned off the assigned service or feature
- mMainHandler.sendMessage(obtainMessage(
- AccessibilityManagerService::showAccessibilityTargetsSelection, this,
- displayId, ACCESSIBILITY_BUTTON));
- }
- }
-
private void sendAccessibilityButtonToInputFilter(int displayId) {
synchronized (mLock) {
if (mHasInputFilter && mInputFilter != null) {
@@ -1153,8 +1147,8 @@
@ShortcutType int shortcutType) {
Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ intent.putExtra(AccessibilityManager.EXTRA_SHORTCUT_TYPE, shortcutType);
final Bundle bundle = ActivityOptions.makeBasic().setLaunchDisplayId(displayId).toBundle();
- bundle.putInt(AccessibilityManager.EXTRA_SHORTCUT_TYPE, shortcutType);
mContext.startActivityAsUser(intent, bundle, UserHandle.of(mCurrentUserId));
}
@@ -1584,8 +1578,7 @@
if (userState.isDisplayMagnificationEnabledLocked()) {
flags |= AccessibilityInputFilter.FLAG_FEATURE_SCREEN_MAGNIFIER;
}
- if (userState.isNavBarMagnificationEnabledLocked()
- || userState.isShortcutKeyMagnificationEnabledLocked()) {
+ if (userState.isShortcutMagnificationEnabledLocked()) {
flags |= AccessibilityInputFilter.FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER;
}
if (userHasMagnificationServicesLocked(userState)) {
@@ -1835,14 +1828,8 @@
mContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
0, userState.mUserId) == 1;
- final boolean navBarMagnificationEnabled = Settings.Secure.getIntForUser(
- mContext.getContentResolver(),
- Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
- 0, userState.mUserId) == 1;
- if ((displayMagnificationEnabled != userState.isDisplayMagnificationEnabledLocked())
- || (navBarMagnificationEnabled != userState.isNavBarMagnificationEnabledLocked())) {
+ if ((displayMagnificationEnabled != userState.isDisplayMagnificationEnabledLocked())) {
userState.setDisplayMagnificationEnabledLocked(displayMagnificationEnabled);
- userState.setNavBarMagnificationEnabledLocked(navBarMagnificationEnabled);
return true;
}
return false;
@@ -2037,8 +2024,7 @@
// displays in one display. It's not a real display and there's no input events for it.
final ArrayList<Display> displays = getValidDisplayList();
if (userState.isDisplayMagnificationEnabledLocked()
- || userState.isNavBarMagnificationEnabledLocked()
- || userState.isShortcutKeyMagnificationEnabledLocked()) {
+ || userState.isShortcutMagnificationEnabledLocked()) {
for (int i = 0; i < displays.size(); i++) {
final Display display = displays.get(i);
getMagnificationController().register(display.getDisplayId());
@@ -2157,6 +2143,85 @@
scheduleNotifyClientsOfServicesStateChangeLocked(userState);
}
+ /**
+ * 1) Check if the service assigned to accessibility button target sdk version > Q.
+ * If it isn't, remove it from the list and associated setting.
+ * (It happens when an accessibility service package is downgraded.)
+ * 2) Check if an enabled service targeting sdk version > Q and requesting a11y button is
+ * assigned to a shortcut. If it isn't, assigns it to the accessibility button.
+ * (It happens when an enabled accessibility service package is upgraded.)
+ *
+ * @param packageName The package name to check, or {@code null} to check all services.
+ */
+ private void migrateAccessibilityButtonSettingsIfNecessaryLocked(
+ AccessibilityUserState userState, @Nullable String packageName) {
+ final Set<String> buttonTargets =
+ userState.getShortcutTargetsLocked(ACCESSIBILITY_BUTTON);
+ int lastSize = buttonTargets.size();
+ buttonTargets.removeIf(name -> {
+ if (packageName != null && name != null && !name.contains(packageName)) {
+ return false;
+ }
+ final ComponentName componentName = ComponentName.unflattenFromString(name);
+ if (componentName == null) {
+ return false;
+ }
+ final AccessibilityServiceInfo serviceInfo =
+ userState.getInstalledServiceInfoLocked(componentName);
+ if (serviceInfo == null) {
+ return false;
+ }
+ if (serviceInfo.getResolveInfo().serviceInfo.applicationInfo
+ .targetSdkVersion > Build.VERSION_CODES.Q) {
+ return false;
+ }
+ // A11y services targeting sdk version <= Q should not be in the list.
+ return true;
+ });
+ boolean changed = (lastSize != buttonTargets.size());
+ lastSize = buttonTargets.size();
+
+ final Set<String> shortcutKeyTargets =
+ userState.getShortcutTargetsLocked(ACCESSIBILITY_SHORTCUT_KEY);
+ userState.mEnabledServices.forEach(componentName -> {
+ if (packageName != null && componentName != null
+ && !packageName.equals(componentName.getPackageName())) {
+ return;
+ }
+ final AccessibilityServiceInfo serviceInfo =
+ userState.getInstalledServiceInfoLocked(componentName);
+ if (serviceInfo == null) {
+ return;
+ }
+ final boolean requestA11yButton = (serviceInfo.flags
+ & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
+ if (!(serviceInfo.getResolveInfo().serviceInfo.applicationInfo
+ .targetSdkVersion > Build.VERSION_CODES.Q && requestA11yButton)) {
+ return;
+ }
+ final String serviceName = serviceInfo.getComponentName().flattenToString();
+ if (TextUtils.isEmpty(serviceName)) {
+ return;
+ }
+ if (shortcutKeyTargets.contains(serviceName) || buttonTargets.contains(serviceName)) {
+ return;
+ }
+ // For enabled a11y services targeting sdk version > Q and requesting a11y button should
+ // be assigned to a shortcut.
+ buttonTargets.add(serviceName);
+ });
+ changed |= (lastSize != buttonTargets.size());
+ if (!changed) {
+ return;
+ }
+
+ // Update setting key with new value.
+ persistColonDelimitedSetToSettingLocked(
+ Settings.Secure.ACCESSIBILITY_BUTTON_TARGET_COMPONENT,
+ userState.mUserId, buttonTargets, str -> str);
+ scheduleNotifyClientsOfServicesStateChangeLocked(userState);
+ }
+
private void updateRecommendedUiTimeoutLocked(AccessibilityUserState userState) {
int newNonInteractiveUiTimeout = userState.getUserNonInteractiveUiTimeoutLocked();
int newInteractiveUiTimeout = userState.getUserInteractiveUiTimeoutLocked();
@@ -2218,9 +2283,13 @@
* AIDL-exposed method to be called when the accessibility shortcut key is enabled. Requires
* permission to write secure settings, since someone with that permission can enable
* accessibility services themselves.
+ *
+ * @param targetName The flattened {@link ComponentName} string or the class name of a system
+ * class implementing a supported accessibility feature, or {@code null} if there's no
+ * specified target.
*/
@Override
- public void performAccessibilityShortcut() {
+ public void performAccessibilityShortcut(String targetName) {
if ((UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID)
&& (mContext.checkCallingPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
!= PackageManager.PERMISSION_GRANTED)) {
@@ -2229,7 +2298,7 @@
}
mMainHandler.sendMessage(obtainMessage(
AccessibilityManagerService::performAccessibilityShortcutInternal, this,
- Display.DEFAULT_DISPLAY, ACCESSIBILITY_SHORTCUT_KEY));
+ Display.DEFAULT_DISPLAY, ACCESSIBILITY_SHORTCUT_KEY, targetName));
}
/**
@@ -2237,20 +2306,31 @@
*
* @param shortcutType The shortcut type.
* @param displayId The display id of the accessibility button.
+ * @param targetName The flattened {@link ComponentName} string or the class name of a system
+ * class implementing a supported accessibility feature, or {@code null} if there's no
+ * specified target.
*/
private void performAccessibilityShortcutInternal(int displayId,
- @ShortcutType int shortcutType) {
+ @ShortcutType int shortcutType, @Nullable String targetName) {
final List<String> shortcutTargets = getAccessibilityShortcutTargetsInternal(shortcutType);
if (shortcutTargets.isEmpty()) {
Slog.d(LOG_TAG, "No target to perform shortcut, shortcutType=" + shortcutType);
return;
}
- // In case there are many targets assigned to the given shortcut.
- if (shortcutTargets.size() > 1) {
- showAccessibilityTargetsSelection(displayId, shortcutType);
- return;
+ // In case the caller specified a target name
+ if (targetName != null) {
+ if (!shortcutTargets.contains(targetName)) {
+ Slog.d(LOG_TAG, "Perform shortcut failed, invalid target name:" + targetName);
+ return;
+ }
+ } else {
+ // In case there are many targets assigned to the given shortcut.
+ if (shortcutTargets.size() > 1) {
+ showAccessibilityTargetsSelection(displayId, shortcutType);
+ return;
+ }
+ targetName = shortcutTargets.get(0);
}
- final String targetName = shortcutTargets.get(0);
// In case user assigned magnification to the given shortcut.
if (targetName.equals(MAGNIFICATION_CONTROLLER_NAME)) {
sendAccessibilityButtonToInputFilter(displayId);
@@ -2793,11 +2873,6 @@
private final Uri mDisplayMagnificationEnabledUri = Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED);
- // TODO(a11y shortcut): Remove this setting key, and have a migrate function in
- // Setting provider after new shortcut UI merged.
- private final Uri mNavBarMagnificationEnabledUri = Settings.Secure.getUriFor(
- Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED);
-
private final Uri mAutoclickEnabledUri = Settings.Secure.getUriFor(
Settings.Secure.ACCESSIBILITY_AUTOCLICK_ENABLED);
@@ -2837,8 +2912,6 @@
false, this, UserHandle.USER_ALL);
contentResolver.registerContentObserver(mDisplayMagnificationEnabledUri,
false, this, UserHandle.USER_ALL);
- contentResolver.registerContentObserver(mNavBarMagnificationEnabledUri,
- false, this, UserHandle.USER_ALL);
contentResolver.registerContentObserver(mAutoclickEnabledUri,
false, this, UserHandle.USER_ALL);
contentResolver.registerContentObserver(mEnabledAccessibilityServicesUri,
@@ -2873,8 +2946,7 @@
if (readTouchExplorationEnabledSettingLocked(userState)) {
onUserStateChangedLocked(userState);
}
- } else if (mDisplayMagnificationEnabledUri.equals(uri)
- || mNavBarMagnificationEnabledUri.equals(uri)) {
+ } else if (mDisplayMagnificationEnabledUri.equals(uri)) {
if (readMagnificationEnabledSettingsLocked(userState)) {
onUserStateChangedLocked(userState);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
index 2032109..7dbec7c 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilitySecurityPolicy.java
@@ -326,6 +326,19 @@
}
/**
+ * Checks if a service can take screenshot.
+ *
+ * @param service The service requesting access
+ *
+ * @return Whether ot not the service may take screenshot
+ */
+ public boolean canTakeScreenshotLocked(
+ @NonNull AbstractAccessibilityServiceConnection service) {
+ return (service.getCapabilities()
+ & AccessibilityServiceInfo.CAPABILITY_CAN_TAKE_SCREENSHOT) != 0;
+ }
+
+ /**
* Returns the parent userId of the profile according to the specified userId.
*
* @param userId The userId to check
@@ -426,6 +439,7 @@
return false;
}
}
+ // TODO: Check parent windowId if the giving windowId is from embedded view hierarchy.
if (windowId == mAccessibilityWindowManager.getActiveWindowId(userId)) {
return true;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index cbff6bd..25911a7 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -16,6 +16,8 @@
package com.android.server.accessibility;
+import static android.accessibilityservice.AccessibilityService.KEY_ACCESSIBILITY_SCREENSHOT;
+
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import android.Manifest;
@@ -25,16 +27,21 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.ParceledListSlice;
+import android.graphics.Bitmap;
import android.os.Binder;
+import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Process;
+import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Slog;
import android.view.Display;
+import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.server.inputmethod.InputMethodManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
@@ -264,6 +271,24 @@
}
@Override
+ public boolean switchToInputMethod(String imeId) {
+ synchronized (mLock) {
+ if (!hasRightsToCurrentUserLocked()) {
+ return false;
+ }
+ }
+ final boolean result;
+ final int callingUserId = UserHandle.getCallingUserId();
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ result = InputMethodManagerInternal.get().switchToInputMethod(imeId, callingUserId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ return result;
+ }
+
+ @Override
public boolean isAccessibilityButtonAvailable() {
synchronized (mLock) {
if (!hasRightsToCurrentUserLocked()) {
@@ -294,48 +319,16 @@
}
}
- // TODO(a11y shortcut): Refactoring the logic here, after the new Settings shortcut Ui merged.
public boolean isAccessibilityButtonAvailableLocked(AccessibilityUserState userState) {
// If the service does not request the accessibility button, it isn't available
if (!mRequestAccessibilityButton) {
return false;
}
-
// If the accessibility button isn't currently shown, it cannot be available to services
if (!mSystemSupport.isAccessibilityButtonShown()) {
return false;
}
-
- // If magnification is on and assigned to the accessibility button, services cannot be
- if (userState.isNavBarMagnificationEnabledLocked()
- && userState.isNavBarMagnificationAssignedToAccessibilityButtonLocked()) {
- return false;
- }
-
- int requestingServices = 0;
- for (int i = userState.mBoundServices.size() - 1; i >= 0; i--) {
- final AccessibilityServiceConnection service = userState.mBoundServices.get(i);
- if (service.mRequestAccessibilityButton) {
- requestingServices++;
- }
- }
-
- if (requestingServices == 1) {
- // If only a single service is requesting, it must be this service, and the
- // accessibility button is available to it
- return true;
- } else {
- // With more than one active service, we derive the target from the user's settings
- if (userState.getServiceAssignedToAccessibilityButtonLocked() == null) {
- // If the user has not made an assignment, we treat the button as available to
- // all services until the user interacts with the button to make an assignment
- return true;
- } else {
- // If an assignment was made, it defines availability
- return mComponentName.equals(
- userState.getServiceAssignedToAccessibilityButtonLocked());
- }
- }
+ return true;
}
@Override
@@ -400,4 +393,15 @@
}
}
}
+
+ @Override
+ public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) {
+ mMainHandler.post(PooledLambda.obtainRunnable((nonArg) -> {
+ final Bitmap screenshot = super.takeScreenshot(displayId);
+ // Send back the result.
+ final Bundle payload = new Bundle();
+ payload.putParcelable(KEY_ACCESSIBILITY_SCREENSHOT, screenshot);
+ callback.sendResult(payload);
+ }, null).recycleOnUse());
+ }
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
index a163f74..ebe2af6 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityUserState.java
@@ -100,7 +100,6 @@
private boolean mIsAutoclickEnabled;
private boolean mIsDisplayMagnificationEnabled;
private boolean mIsFilterKeyEventsEnabled;
- private boolean mIsNavBarMagnificationEnabled;
private boolean mIsPerformGesturesEnabled;
private boolean mIsTextHighContrastEnabled;
private boolean mIsTouchExplorationEnabled;
@@ -153,7 +152,6 @@
mAccessibilityButtonTargets.clear();
mIsTouchExplorationEnabled = false;
mIsDisplayMagnificationEnabled = false;
- mIsNavBarMagnificationEnabled = false;
mIsAutoclickEnabled = false;
mUserNonInteractiveUiTimeout = 0;
mUserInteractiveUiTimeout = 0;
@@ -435,8 +433,6 @@
pw.append(", touchExplorationEnabled=").append(String.valueOf(mIsTouchExplorationEnabled));
pw.append(", displayMagnificationEnabled=").append(String.valueOf(
mIsDisplayMagnificationEnabled));
- pw.append(", navBarMagnificationEnabled=").append(String.valueOf(
- mIsNavBarMagnificationEnabled));
pw.append(", autoclickEnabled=").append(String.valueOf(mIsAutoclickEnabled));
pw.append(", nonInteractiveUiTimeout=").append(String.valueOf(mNonInteractiveUiTimeout));
pw.append(", interactiveUiTimeout=").append(String.valueOf(mInteractiveUiTimeout));
@@ -553,8 +549,12 @@
mLastSentClientState = state;
}
- public boolean isShortcutKeyMagnificationEnabledLocked() {
- return mAccessibilityShortcutKeyTargets.contains(MAGNIFICATION_CONTROLLER_NAME);
+ /**
+ * Returns true if navibar magnification or shortcut key magnification is enabled.
+ */
+ public boolean isShortcutMagnificationEnabledLocked() {
+ return mAccessibilityShortcutKeyTargets.contains(MAGNIFICATION_CONTROLLER_NAME)
+ || mAccessibilityButtonTargets.contains(MAGNIFICATION_CONTROLLER_NAME);
}
/**
@@ -690,28 +690,4 @@
public void setUserNonInteractiveUiTimeoutLocked(int timeout) {
mUserNonInteractiveUiTimeout = timeout;
}
-
- // TODO(a11y shortcut): These functions aren't necessary, after the new Settings shortcut Ui
- // is merged.
- boolean isNavBarMagnificationEnabledLocked() {
- return mIsNavBarMagnificationEnabled;
- }
-
- void setNavBarMagnificationEnabledLocked(boolean enabled) {
- mIsNavBarMagnificationEnabled = enabled;
- }
-
- boolean isNavBarMagnificationAssignedToAccessibilityButtonLocked() {
- return mAccessibilityButtonTargets.contains(MAGNIFICATION_CONTROLLER_NAME);
- }
-
- ComponentName getServiceAssignedToAccessibilityButtonLocked() {
- final String targetName = mAccessibilityButtonTargets.isEmpty() ? null
- : mAccessibilityButtonTargets.valueAt(0);
- if (targetName == null) {
- return null;
- }
- return ComponentName.unflattenFromString(targetName);
- }
- // TODO(a11y shortcut): End
}
diff --git a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
index 1754926..11dcfef 100644
--- a/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
+++ b/services/accessibility/java/com/android/server/accessibility/SystemActionPerformer.java
@@ -157,8 +157,13 @@
/**
* This method is called to register a system action. If a system action is already registered
* with the given id, the existing system action will be overwritten.
+ *
+ * This method is supposed to be package internal since this class is meant to be used by
+ * AccessibilityManagerService only. But Mockito has a bug which requiring this to be public
+ * to be mocked.
*/
- void registerSystemAction(int id, RemoteAction action) {
+ @VisibleForTesting
+ public void registerSystemAction(int id, RemoteAction action) {
synchronized (mSystemActionLock) {
mRegisteredSystemActions.put(id, action);
}
@@ -170,8 +175,13 @@
/**
* This method is called to unregister a system action previously registered through
* registerSystemAction.
+ *
+ * This method is supposed to be package internal since this class is meant to be used by
+ * AccessibilityManagerService only. But Mockito has a bug which requiring this to be public
+ * to be mocked.
*/
- void unregisterSystemAction(int id) {
+ @VisibleForTesting
+ public void unregisterSystemAction(int id) {
synchronized (mSystemActionLock) {
mRegisteredSystemActions.remove(id);
}
diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
index 7dd4a70..5d9af26 100644
--- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
@@ -25,6 +25,7 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
+import android.os.RemoteCallback;
import android.os.RemoteException;
import android.util.Slog;
import android.view.Display;
@@ -297,6 +298,11 @@
}
@Override
+ public boolean switchToInputMethod(String imeId) {
+ return false;
+ }
+
+ @Override
public boolean isAccessibilityButtonAvailable() {
return false;
}
@@ -320,5 +326,8 @@
@Override
public void onFingerprintGesture(int gesture) {}
+
+ @Override
+ public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) {}
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java
index 2891c6c..4c9e590 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/MultiTap.java
@@ -82,7 +82,7 @@
mCurrentTaps++;
if (mCurrentTaps == mTargetTaps) {
// Done.
- completeAfterTapTimeout(event, rawEvent, policyFlags);
+ completeGesture(event, rawEvent, policyFlags);
return;
}
// Needs more taps.
diff --git a/services/api/current.txt b/services/api/current.txt
index d802177..18e38b1 100644
--- a/services/api/current.txt
+++ b/services/api/current.txt
@@ -1 +1,31 @@
// Signature format: 2.0
+package com.android.server {
+
+ public abstract class SystemService {
+ ctor public SystemService(@NonNull android.content.Context);
+ method @NonNull public final android.content.Context getContext();
+ method public boolean isSupportedUser(@NonNull com.android.server.SystemService.TargetUser);
+ method public void onBootPhase(int);
+ method public void onCleanupUser(@NonNull com.android.server.SystemService.TargetUser);
+ method public abstract void onStart();
+ method public void onStartUser(@NonNull com.android.server.SystemService.TargetUser);
+ method public void onStopUser(@NonNull com.android.server.SystemService.TargetUser);
+ method public void onSwitchUser(@Nullable com.android.server.SystemService.TargetUser, @NonNull com.android.server.SystemService.TargetUser);
+ method public void onUnlockUser(@NonNull com.android.server.SystemService.TargetUser);
+ method protected final void publishBinderService(@NonNull String, @NonNull android.os.IBinder);
+ method protected final void publishBinderService(@NonNull String, @NonNull android.os.IBinder, boolean);
+ field public static final int PHASE_ACTIVITY_MANAGER_READY = 550; // 0x226
+ field public static final int PHASE_BOOT_COMPLETED = 1000; // 0x3e8
+ field public static final int PHASE_DEVICE_SPECIFIC_SERVICES_READY = 520; // 0x208
+ field public static final int PHASE_LOCK_SETTINGS_READY = 480; // 0x1e0
+ field public static final int PHASE_SYSTEM_SERVICES_READY = 500; // 0x1f4
+ field public static final int PHASE_THIRD_PARTY_APPS_CAN_START = 600; // 0x258
+ field public static final int PHASE_WAIT_FOR_DEFAULT_DISPLAY = 100; // 0x64
+ }
+
+ public static final class SystemService.TargetUser {
+ method @NonNull public android.os.UserHandle getUserHandle();
+ }
+
+}
+
diff --git a/services/art-profile b/services/art-profile
index a0338d5..4e113c8 100644
--- a/services/art-profile
+++ b/services/art-profile
@@ -3066,18 +3066,18 @@
HSPLcom/android/server/am/AppBindRecord;->dumpInIntentBind(Ljava/io/PrintWriter;Ljava/lang/String;)V
PLcom/android/server/am/AppBindRecord;->toString()Ljava/lang/String;
PLcom/android/server/am/AppBindRecord;->writeToProto(Landroid/util/proto/ProtoOutputStream;J)V
-PLcom/android/server/am/AppCompactor$1;->onPropertyChanged(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
-HSPLcom/android/server/am/AppCompactor$MemCompactionHandler;->handleMessage(Landroid/os/Message;)V
-HSPLcom/android/server/am/AppCompactor;-><init>(Lcom/android/server/am/ActivityManagerService;)V
-HSPLcom/android/server/am/AppCompactor;->access$1000(Lcom/android/server/am/AppCompactor;)V
-HSPLcom/android/server/am/AppCompactor;->access$700(Lcom/android/server/am/AppCompactor;)Lcom/android/server/am/ActivityManagerService;
-HSPLcom/android/server/am/AppCompactor;->access$800(Lcom/android/server/am/AppCompactor;)Ljava/util/ArrayList;
-HSPLcom/android/server/am/AppCompactor;->access$900(Lcom/android/server/am/AppCompactor;)Ljava/util/Random;
-PLcom/android/server/am/AppCompactor;->dump(Ljava/io/PrintWriter;)V
-HSPLcom/android/server/am/AppCompactor;->init()V
-HSPLcom/android/server/am/AppCompactor;->updateCompactionThrottles()V
-HSPLcom/android/server/am/AppCompactor;->updateUseCompaction()V
-HSPLcom/android/server/am/AppCompactor;->useCompaction()Z
+PLcom/android/server/am/CachedAppOptimizer$1;->onPropertyChanged(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+HSPLcom/android/server/am/CachedAppOptimizer$MemCompactionHandler;->handleMessage(Landroid/os/Message;)V
+HSPLcom/android/server/am/CachedAppOptimizer;-><init>(Lcom/android/server/am/ActivityManagerService;)V
+HSPLcom/android/server/am/CachedAppOptimizer;->access$1000(Lcom/android/server/am/CachedAppOptimizer;)V
+HSPLcom/android/server/am/CachedAppOptimizer;->access$700(Lcom/android/server/am/CachedAppOptimizer;)Lcom/android/server/am/ActivityManagerService;
+HSPLcom/android/server/am/CachedAppOptimizer;->access$800(Lcom/android/server/am/CachedAppOptimizer;)Ljava/util/ArrayList;
+HSPLcom/android/server/am/CachedAppOptimizer;->access$900(Lcom/android/server/am/CachedAppOptimizer;)Ljava/util/Random;
+PLcom/android/server/am/CachedAppOptimizer;->dump(Ljava/io/PrintWriter;)V
+HSPLcom/android/server/am/CachedAppOptimizer;->init()V
+HSPLcom/android/server/am/CachedAppOptimizer;->updateCompactionThrottles()V
+HSPLcom/android/server/am/CachedAppOptimizer;->updateUseCompaction()V
+HSPLcom/android/server/am/CachedAppOptimizer;->useCompaction()Z
PLcom/android/server/am/AppErrorDialog$1;->handleMessage(Landroid/os/Message;)V
PLcom/android/server/am/AppErrorDialog;-><init>(Landroid/content/Context;Lcom/android/server/am/ActivityManagerService;Lcom/android/server/am/AppErrorDialog$Data;)V
PLcom/android/server/am/AppErrorDialog;->onClick(Landroid/view/View;)V
@@ -18632,9 +18632,9 @@
Lcom/android/server/am/ActivityManagerService$UidObserverRegistration;
Lcom/android/server/am/ActivityManagerService;
Lcom/android/server/am/AppBindRecord;
-Lcom/android/server/am/AppCompactor$1;
-Lcom/android/server/am/AppCompactor$MemCompactionHandler;
-Lcom/android/server/am/AppCompactor;
+Lcom/android/server/am/CachedAppOptimizer$1;
+Lcom/android/server/am/CachedAppOptimizer$MemCompactionHandler;
+Lcom/android/server/am/CachedAppOptimizer;
Lcom/android/server/am/AppErrorDialog$Data;
Lcom/android/server/am/AppErrorResult;
Lcom/android/server/am/AppErrors$BadProcessInfo;
diff --git a/services/art-profile-boot b/services/art-profile-boot
index e09424b..fe4178a 100644
--- a/services/art-profile-boot
+++ b/services/art-profile-boot
@@ -538,7 +538,7 @@
Lcom/android/server/am/ActivityManagerService;->updateLowMemStateLocked(III)Z
Lcom/android/server/wm/ConfigurationContainer;->getWindowConfiguration()Landroid/app/WindowConfiguration;
Lcom/android/server/am/OomAdjuster;->applyOomAdjLocked(Lcom/android/server/am/ProcessRecord;ZJJ)Z
-Lcom/android/server/am/AppCompactor;->useCompaction()Z
+Lcom/android/server/am/CachedAppOptimizer;->useCompaction()Z
Lcom/android/server/am/ProcessList;->procStatesDifferForMem(II)Z
Lcom/android/server/am/ActivityManagerService;->dispatchUidsChanged()V
Lcom/android/server/audio/AudioService$VolumeStreamState;->setIndex(IILjava/lang/String;)Z
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 03d9626..5405fc7 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -331,8 +331,8 @@
}
@Override // from SystemService
- public boolean isSupported(UserInfo userInfo) {
- return userInfo.isFull() || userInfo.isManagedProfile();
+ public boolean isSupportedUser(TargetUser user) {
+ return user.getUserInfo().isFull() || user.getUserInfo().isManagedProfile();
}
@Override // from SystemService
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 3b51329..cdd7510 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -19,6 +19,7 @@
import static android.service.autofill.AutofillFieldClassificationService.EXTRA_SCORES;
import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
+import static android.view.autofill.AutofillManager.ACTION_RESPONSE_EXPIRED;
import static android.view.autofill.AutofillManager.ACTION_START_SESSION;
import static android.view.autofill.AutofillManager.ACTION_VALUE_CHANGED;
import static android.view.autofill.AutofillManager.ACTION_VIEW_ENTERED;
@@ -42,8 +43,6 @@
import android.app.assist.AssistStructure;
import android.app.assist.AssistStructure.AutofillOverlay;
import android.app.assist.AssistStructure.ViewNode;
-import android.app.slice.Slice;
-import android.app.slice.SliceItem;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -72,6 +71,7 @@
import android.service.autofill.FillContext;
import android.service.autofill.FillRequest;
import android.service.autofill.FillResponse;
+import android.service.autofill.InlinePresentation;
import android.service.autofill.InternalSanitizer;
import android.service.autofill.InternalValidator;
import android.service.autofill.SaveInfo;
@@ -83,7 +83,6 @@
import android.util.ArraySet;
import android.util.LocalLog;
import android.util.Log;
-import android.util.Size;
import android.util.Slog;
import android.util.SparseArray;
import android.util.TimeUtils;
@@ -114,7 +113,6 @@
import com.android.server.inputmethod.InputMethodManagerInternal;
import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -165,6 +163,9 @@
/** uid the session is for */
public final int uid;
+ /** user id the session is for */
+ public final int userId;
+
/** ID of the task associated with this session's activity */
public final int taskId;
@@ -269,6 +270,9 @@
@GuardedBy("mLock")
private final LocalLog mWtfHistory;
+ @GuardedBy("mLock")
+ private boolean mExpiredResponse;
+
/**
* Map of {@link MetricsEvent#AUTOFILL_REQUEST} metrics, keyed by fill request id.
*/
@@ -313,20 +317,9 @@
@NonNull
private final InputMethodManagerInternal mInputMethodManagerInternal;
- @GuardedBy("mLock")
- @Nullable
- private CompletableFuture<InlineSuggestionsRequest> mSuggestionsRequestFuture;
-
- @GuardedBy("mLock")
- @Nullable
- private CompletableFuture<IInlineSuggestionsResponseCallback>
- mInlineSuggestionsResponseCallbackFuture;
-
@Nullable
private InlineSuggestionsRequestCallbackImpl mInlineSuggestionsRequestCallback;
- private static final int INLINE_REQUEST_TIMEOUT_MS = 1000;
-
/**
* Receiver of assist data from the app's {@link Activity}.
*/
@@ -338,7 +331,9 @@
+ "mForAugmentedAutofillOnly: %s", mForAugmentedAutofillOnly);
return;
}
- if (mCurrentViewId == null) {
+ // Keeps to prevent it is cleared on multiple threads.
+ final AutofillId currentViewId = mCurrentViewId;
+ if (currentViewId == null) {
Slog.w(TAG, "No current view id - session might have finished");
return;
}
@@ -412,7 +407,7 @@
if (mContexts == null) {
mContexts = new ArrayList<>(1);
}
- mContexts.add(new FillContext(requestId, structure, mCurrentViewId));
+ mContexts.add(new FillContext(requestId, structure, currentViewId));
cancelCurrentRequestLocked();
@@ -424,7 +419,9 @@
final ArrayList<FillContext> contexts =
mergePreviousSessionLocked(/* forSave= */ false);
- final InlineSuggestionsRequest suggestionsRequest = getInlineSuggestionsRequest();
+ final InlineSuggestionsRequest suggestionsRequest =
+ mInlineSuggestionsRequestCallback != null
+ ? mInlineSuggestionsRequestCallback.getRequest() : null;
request = new FillRequest(requestId, contexts, mClientState, flags,
suggestionsRequest);
@@ -622,14 +619,8 @@
private void maybeRequestInlineSuggestionsRequestThenFillLocked(@NonNull ViewState viewState,
int newState, int flags) {
if (isInlineSuggestionsEnabled()) {
- mSuggestionsRequestFuture = new CompletableFuture<>();
- mInlineSuggestionsResponseCallbackFuture = new CompletableFuture<>();
-
- if (mInlineSuggestionsRequestCallback == null) {
- mInlineSuggestionsRequestCallback = new InlineSuggestionsRequestCallbackImpl(this);
- }
-
- mInputMethodManagerInternal.onCreateInlineSuggestionsRequest(
+ mInlineSuggestionsRequestCallback = new InlineSuggestionsRequestCallbackImpl();
+ mInputMethodManagerInternal.onCreateInlineSuggestionsRequest(userId,
mComponentName, mCurrentViewId, mInlineSuggestionsRequestCallback);
}
@@ -638,10 +629,14 @@
private static final class InlineSuggestionsRequestCallbackImpl
extends IInlineSuggestionsRequestCallback.Stub {
- private final WeakReference<Session> mSession;
+ private static final int INLINE_REQUEST_TIMEOUT_MS = 1000;
- private InlineSuggestionsRequestCallbackImpl(Session session) {
- mSession = new WeakReference<>(session);
+ private final CompletableFuture<InlineSuggestionsRequest> mRequest;
+ private final CompletableFuture<IInlineSuggestionsResponseCallback> mResponseCallback;
+
+ private InlineSuggestionsRequestCallbackImpl() {
+ mRequest = new CompletableFuture<>();
+ mResponseCallback = new CompletableFuture<>();
}
@Override
@@ -650,13 +645,8 @@
Log.d(TAG, "inline suggestions request unsupported, "
+ "falling back to regular autofill");
}
- final Session session = mSession.get();
- if (session != null) {
- synchronized (session.mLock) {
- session.mSuggestionsRequestFuture.cancel(true);
- session.mInlineSuggestionsResponseCallbackFuture.cancel(true);
- }
- }
+ mRequest.cancel(true);
+ mResponseCallback.cancel(true);
}
@Override
@@ -665,13 +655,36 @@
if (sDebug) {
Log.d(TAG, "onInlineSuggestionsRequest() received: " + request);
}
- final Session session = mSession.get();
- if (session != null) {
- synchronized (session.mLock) {
- session.mSuggestionsRequestFuture.complete(request);
- session.mInlineSuggestionsResponseCallbackFuture.complete(callback);
- }
+ mRequest.complete(request);
+ mResponseCallback.complete(callback);
+ }
+
+ @Nullable
+ private InlineSuggestionsRequest getRequest() {
+ try {
+ return mRequest.get(INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ } catch (TimeoutException e) {
+ Log.w(TAG, "Exception getting inline suggestions request in time: " + e);
+ } catch (CancellationException e) {
+ Log.w(TAG, "Inline suggestions request cancelled");
+ } catch (InterruptedException | ExecutionException e) {
+ throw new RuntimeException(e);
}
+ return null;
+ }
+
+ @Nullable
+ private IInlineSuggestionsResponseCallback getResponseCallback() {
+ try {
+ return mResponseCallback.get(INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ } catch (TimeoutException e) {
+ Log.w(TAG, "Exception getting inline suggestions callback in time: " + e);
+ } catch (CancellationException e) {
+ Log.w(TAG, "Inline suggestions callback cancelled");
+ } catch (InterruptedException | ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ return null;
}
}
@@ -681,6 +694,7 @@
@GuardedBy("mLock")
private void requestNewFillResponseLocked(@NonNull ViewState viewState, int newState,
int flags) {
+ mExpiredResponse = false;
if (mForAugmentedAutofillOnly || mRemoteFillService == null) {
if (sVerbose) {
Slog.v(TAG, "requestNewFillResponse(): triggering augmented autofill instead "
@@ -753,6 +767,7 @@
mFlags = flags;
this.taskId = taskId;
this.uid = uid;
+ this.userId = userId;
mStartTime = SystemClock.elapsedRealtime();
mService = service;
mLock = lock;
@@ -1297,6 +1312,9 @@
}
}
+ // The client becomes invisible for the authentication, the response is effective.
+ mExpiredResponse = false;
+
final Parcelable result = data.getParcelable(AutofillManager.EXTRA_AUTHENTICATION_RESULT);
final Bundle newClientState = data.getBundle(AutofillManager.EXTRA_CLIENT_STATE);
if (sDebug) {
@@ -2312,16 +2330,18 @@
* @param id The id of the view that is entered.
* @param viewState The view that is entered.
* @param flags The flag that was passed by the AutofillManager.
+ *
+ * @return {@code true} if a new fill response is requested.
*/
@GuardedBy("mLock")
- private void requestNewFillResponseOnViewEnteredIfNecessaryLocked(@NonNull AutofillId id,
+ private boolean requestNewFillResponseOnViewEnteredIfNecessaryLocked(@NonNull AutofillId id,
@NonNull ViewState viewState, int flags) {
if ((flags & FLAG_MANUAL_REQUEST) != 0) {
mForAugmentedAutofillOnly = false;
if (sDebug) Slog.d(TAG, "Re-starting session on view " + id + " and flags " + flags);
maybeRequestInlineSuggestionsRequestThenFillLocked(viewState,
ViewState.STATE_RESTARTED_SESSION, flags);
- return;
+ return true;
}
// If it's not, then check if it it should start a partition.
@@ -2332,12 +2352,14 @@
}
maybeRequestInlineSuggestionsRequestThenFillLocked(viewState,
ViewState.STATE_STARTED_PARTITION, flags);
+ return true;
} else {
if (sVerbose) {
Slog.v(TAG, "Not starting new partition for view " + id + ": "
+ viewState.getStateAsString());
}
}
+ return false;
}
/**
@@ -2345,7 +2367,7 @@
*
* @param id The id of the view that is entered
*
- * @return {@code true} iff a new partition should be started
+ * @return {@code true} if a new partition should be started
*/
@GuardedBy("mLock")
private boolean shouldStartNewPartitionLocked(@NonNull AutofillId id) {
@@ -2353,6 +2375,13 @@
return true;
}
+ if (mExpiredResponse) {
+ if (sDebug) {
+ Slog.d(TAG, "Starting a new partition because the response has expired.");
+ }
+ return true;
+ }
+
final int numResponses = mResponses.size();
if (numResponses >= AutofillManagerService.getPartitionMaxCount()) {
Slog.e(TAG, "Not starting a new partition on " + id + " because session " + this.id
@@ -2404,6 +2433,14 @@
+ id + " destroyed");
return;
}
+ if (action == ACTION_RESPONSE_EXPIRED) {
+ mExpiredResponse = true;
+ if (sDebug) {
+ Slog.d(TAG, "Set the response has expired.");
+ }
+ return;
+ }
+
id.setSessionId(this.id);
if (sVerbose) {
Slog.v(TAG, "updateLocked(" + this.id + "): id=" + id + ", action="
@@ -2567,7 +2604,9 @@
return;
}
- requestNewFillResponseOnViewEnteredIfNecessaryLocked(id, viewState, flags);
+ if (requestNewFillResponseOnViewEnteredIfNecessaryLocked(id, viewState, flags)) {
+ return;
+ }
if (isSameViewEntered) {
return;
@@ -2637,9 +2676,8 @@
return;
}
- final List<Slice> inlineSuggestionSlices = response.getInlineSuggestionSlices();
- if (inlineSuggestionSlices != null) {
- if (requestShowInlineSuggestions(inlineSuggestionSlices, response)) {
+ if (response.supportsInlineSuggestions()) {
+ if (requestShowInlineSuggestions(response)) {
//TODO(b/137800469): Add logging instead of bypassing below logic.
return;
}
@@ -2680,11 +2718,10 @@
/**
* Returns whether we made a request to show inline suggestions.
*/
- private boolean requestShowInlineSuggestions(List<Slice> inlineSuggestionSlices,
- FillResponse response) {
+ private boolean requestShowInlineSuggestions(FillResponse response) {
final IInlineSuggestionsResponseCallback inlineContentCallback =
- getInlineSuggestionsResponseCallback();
-
+ mInlineSuggestionsRequestCallback != null
+ ? mInlineSuggestionsRequestCallback.getResponseCallback() : null;
if (inlineContentCallback == null) {
Log.w(TAG, "Session input method callback is not set yet");
return false;
@@ -2696,54 +2733,20 @@
return false;
}
+ final int size = datasets.size();
final ArrayList<InlineSuggestion> inlineSuggestions = new ArrayList<>();
- final int slicesSize = inlineSuggestionSlices.size();
- if (datasets.size() < slicesSize) {
- Log.w(TAG, "Too many slices provided, not enough corresponding datasets");
- return false;
- }
- for (int sliceIndex = 0; sliceIndex < slicesSize; sliceIndex++) {
- Log.i(TAG, "Reading slice-" + sliceIndex + " at requestshowinlinesuggestions");
- final Slice inlineSuggestionSlice = inlineSuggestionSlices.get(sliceIndex);
- final List<SliceItem> sliceItems = inlineSuggestionSlice.getItems();
-
- final int itemsSize = sliceItems.size();
- int minWidth = -1;
- int maxWidth = -1;
- int minHeight = -1;
- int maxHeight = -1;
- for (int itemIndex = 0; itemIndex < itemsSize; itemIndex++) {
- final SliceItem item = sliceItems.get(itemIndex);
- final String subtype = item.getSubType();
- switch (item.getSubType()) {
- case "SUBTYPE_MIN_WIDTH":
- minWidth = item.getInt();
- break;
- case "SUBTYPE_MAX_WIDTH":
- maxWidth = item.getInt();
- break;
- case "SUBTYPE_MIN_HEIGHT":
- minHeight = item.getInt();
- break;
- case "SUBTYPE_MAX_HEIGHT":
- maxHeight = item.getInt();
- break;
- default:
- Log.i(TAG, "unrecognized inline suggestions subtype: " + subtype);
- }
+ for (int index = 0; index < size; index++) {
+ final Dataset dataset = datasets.get(index);
+ //TODO(b/146453536): Use the proper presentation/spec for currently focused view.
+ final InlinePresentation inlinePresentation = dataset.getFieldInlinePresentation(0);
+ if (inlinePresentation == null) {
+ if (sDebug) Log.d(TAG, "Missing InlinePresentation on dataset=" + dataset);
+ continue;
}
-
- if (minWidth < 0 || maxWidth < 0 || minHeight < 0 || maxHeight < 0) {
- Log.w(TAG, "missing inline suggestion requirements");
- return false;
- }
-
- final InlinePresentationSpec spec = new InlinePresentationSpec.Builder(
- new Size(minWidth, minHeight), new Size(maxWidth, maxHeight)).build();
+ final InlinePresentationSpec spec = inlinePresentation.getInlinePresentationSpec();
final InlineSuggestionInfo inlineSuggestionInfo = new InlineSuggestionInfo(
spec, InlineSuggestionInfo.SOURCE_AUTOFILL, new String[] { "" });
- final Dataset dataset = datasets.get(sliceIndex);
inlineSuggestions.add(new InlineSuggestion(inlineSuggestionInfo,
new IInlineContentProvider.Stub() {
@@ -3040,10 +3043,12 @@
final AutofillId focusedId = AutofillId.withoutSession(mCurrentViewId);
- final InlineSuggestionsRequest inlineSuggestionsRequest = getInlineSuggestionsRequest();
+ final InlineSuggestionsRequest inlineSuggestionsRequest =
+ mInlineSuggestionsRequestCallback != null
+ ? mInlineSuggestionsRequestCallback.getRequest() : null;
final IInlineSuggestionsResponseCallback inlineSuggestionsResponseCallback =
- getInlineSuggestionsResponseCallback();
-
+ mInlineSuggestionsRequestCallback != null
+ ? mInlineSuggestionsRequestCallback.getResponseCallback() : null;
remoteService.onRequestAutofillLocked(id, mClient, taskId, mComponentName, focusedId,
currentValue, inlineSuggestionsRequest, inlineSuggestionsResponseCallback);
@@ -3053,40 +3058,6 @@
return mAugmentedAutofillDestroyer;
}
- @Nullable
- private InlineSuggestionsRequest getInlineSuggestionsRequest() {
- if (mSuggestionsRequestFuture != null) {
- try {
- return mSuggestionsRequestFuture.get(
- INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
- } catch (TimeoutException e) {
- Log.w(TAG, "Exception getting inline suggestions request in time: " + e);
- } catch (CancellationException e) {
- Log.w(TAG, "Inline suggestions request cancelled");
- } catch (InterruptedException | ExecutionException e) {
- throw new RuntimeException(e);
- }
- }
- return null;
- }
-
- @Nullable
- private IInlineSuggestionsResponseCallback getInlineSuggestionsResponseCallback() {
- if (mInlineSuggestionsResponseCallbackFuture != null) {
- try {
- return mInlineSuggestionsResponseCallbackFuture.get(
- INLINE_REQUEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
- } catch (TimeoutException e) {
- Log.w(TAG, "Exception getting inline suggestions callback in time: " + e);
- } catch (CancellationException e) {
- Log.w(TAG, "Inline suggestions callback cancelled");
- } catch (InterruptedException | ExecutionException e) {
- throw new RuntimeException(e);
- }
- }
- return null;
- }
-
@GuardedBy("mLock")
private void cancelAugmentedAutofillLocked() {
final RemoteAugmentedAutofillService remoteService = mService
@@ -3736,6 +3707,8 @@
return "VIEW_EXITED";
case ACTION_VALUE_CHANGED:
return "VALUE_CHANGED";
+ case ACTION_RESPONSE_EXPIRED:
+ return "RESPONSE_EXPIRED";
default:
return "UNKNOWN_" + action;
}
diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java
index 17cb739..1e3ee88 100644
--- a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java
@@ -16,24 +16,36 @@
package com.android.server.autofill.ui;
+import static android.app.slice.SliceItem.FORMAT_IMAGE;
+import static android.app.slice.SliceItem.FORMAT_TEXT;
+
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.slice.Slice;
+import android.app.slice.SliceItem;
import android.content.Context;
-import android.graphics.Color;
import android.graphics.PixelFormat;
+import android.graphics.drawable.Icon;
import android.os.IBinder;
import android.service.autofill.Dataset;
import android.util.Log;
import android.util.Slog;
+import android.view.LayoutInflater;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
import android.view.View;
+import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillValue;
+import android.widget.ImageView;
import android.widget.TextView;
+import com.android.internal.R;
+
+import java.util.List;
+
/**
* This is a temporary inline suggestion UI inflater which will be replaced by the ExtServices
* implementation.
@@ -72,18 +84,66 @@
mContext.getDisplay(), (IBinder) null);
final SurfaceControl sc = wvr.getSurfacePackage().getSurfaceControl();
- TextView textView = new TextView(mContext);
- textView.setText(datasetValue.getTextValue());
- textView.setBackgroundColor(Color.WHITE);
- textView.setTextColor(Color.BLACK);
- if (onClickListener != null) {
- textView.setOnClickListener(onClickListener);
- }
+ final ViewGroup suggestionView =
+ (ViewGroup) renderSlice(dataset.getFieldInlinePresentation(index).getSlice());
WindowManager.LayoutParams lp =
new WindowManager.LayoutParams(width, height,
WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.TRANSPARENT);
- wvr.addView(textView, lp);
+ wvr.addView(suggestionView, lp);
return sc;
}
+
+ private View renderSlice(Slice slice) {
+ final LayoutInflater inflater = LayoutInflater.from(mContext);
+ final ViewGroup suggestionView =
+ (ViewGroup) inflater.inflate(R.layout.autofill_inline_suggestion, null);
+
+ final ImageView startIconView =
+ suggestionView.findViewById(R.id.autofill_inline_suggestion_start_icon);
+ final TextView titleView =
+ suggestionView.findViewById(R.id.autofill_inline_suggestion_title);
+ final TextView subtitleView =
+ suggestionView.findViewById(R.id.autofill_inline_suggestion_subtitle);
+ final ImageView endIconView =
+ suggestionView.findViewById(R.id.autofill_inline_suggestion_end_icon);
+
+ boolean hasStartIcon = false;
+ boolean hasEndIcon = false;
+ boolean hasSubtitle = false;
+ final List<SliceItem> sliceItems = slice.getItems();
+ for (int i = 0; i < sliceItems.size(); i++) {
+ final SliceItem sliceItem = sliceItems.get(i);
+ if (sliceItem.getFormat().equals(FORMAT_IMAGE)) {
+ final Icon sliceIcon = sliceItem.getIcon();
+ if (i == 0) { // start icon
+ startIconView.setImageIcon(sliceIcon);
+ hasStartIcon = true;
+ } else { // end icon
+ endIconView.setImageIcon(sliceIcon);
+ hasEndIcon = true;
+ }
+ } else if (sliceItem.getFormat().equals(FORMAT_TEXT)) {
+ final List<String> sliceHints = sliceItem.getHints();
+ final String sliceText = sliceItem.getText().toString();
+ if (sliceHints.contains("inline_title")) { // title
+ titleView.setText(sliceText);
+ } else { // subtitle
+ subtitleView.setText(sliceText);
+ hasSubtitle = true;
+ }
+ }
+ }
+ if (!hasStartIcon) {
+ startIconView.setVisibility(View.GONE);
+ }
+ if (!hasEndIcon) {
+ endIconView.setVisibility(View.GONE);
+ }
+ if (!hasSubtitle) {
+ subtitleView.setVisibility(View.GONE);
+ }
+
+ return suggestionView;
+ }
}
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 3bce322..b13bef2 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -1406,10 +1406,7 @@
long oldId = Binder.clearCallingIdentity();
final int[] userIds;
try {
- userIds =
- mContext
- .getSystemService(UserManager.class)
- .getProfileIds(callingUserId, false);
+ userIds = getUserManager().getProfileIds(callingUserId, false);
} finally {
Binder.restoreCallingIdentity(oldId);
}
@@ -1452,6 +1449,11 @@
if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) {
return;
}
+ dumpWithoutCheckingPermission(fd, pw, args);
+ }
+
+ @VisibleForTesting
+ void dumpWithoutCheckingPermission(FileDescriptor fd, PrintWriter pw, String[] args) {
int userId = binderGetCallingUserId();
if (!isUserReadyForBackup(userId)) {
pw.println("Inactive");
@@ -1460,7 +1462,16 @@
if (args != null) {
for (String arg : args) {
- if ("users".equals(arg.toLowerCase())) {
+ if ("-h".equals(arg)) {
+ pw.println("'dumpsys backup' optional arguments:");
+ pw.println(" -h : this help text");
+ pw.println(" a[gents] : dump information about defined backup agents");
+ pw.println(" transportclients : dump information about transport clients");
+ pw.println(" transportstats : dump transport statts");
+ pw.println(" users : dump the list of users for which backup service "
+ + "is running");
+ return;
+ } else if ("users".equals(arg.toLowerCase())) {
pw.print(DUMP_RUNNING_USERS_MESSAGE);
for (int i = 0; i < mUserServices.size(); i++) {
pw.print(" " + mUserServices.keyAt(i));
@@ -1471,11 +1482,12 @@
}
}
- UserBackupManagerService userBackupManagerService =
- getServiceForUserIfCallerHasPermission(UserHandle.USER_SYSTEM, "dump()");
-
- if (userBackupManagerService != null) {
- userBackupManagerService.dump(fd, pw, args);
+ for (int i = 0; i < mUserServices.size(); i++) {
+ UserBackupManagerService userBackupManagerService =
+ getServiceForUserIfCallerHasPermission(mUserServices.keyAt(i), "dump()");
+ if (userBackupManagerService != null) {
+ userBackupManagerService.dump(fd, pw, args);
+ }
}
}
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index 064cd06..2c229b4 100644
--- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
@@ -2538,7 +2538,6 @@
KeyValueBackupJob.schedule(mUserId, mContext, mConstants);
} else {
if (DEBUG) Slog.v(TAG, "Scheduling immediate backup pass");
- synchronized (mQueueLock) {
// Fire the intent that kicks off the whole shebang...
try {
mRunBackupIntent.send();
@@ -2546,10 +2545,8 @@
// should never happen
Slog.e(TAG, "run-backup intent cancelled!");
}
-
// ...and cancel any pending scheduled job, because we've just superseded it
KeyValueBackupJob.cancel(mUserId, mContext);
- }
}
} finally {
Binder.restoreCallingIdentity(oldId);
@@ -3545,14 +3542,7 @@
try {
if (args != null) {
for (String arg : args) {
- if ("-h".equals(arg)) {
- pw.println("'dumpsys backup' optional arguments:");
- pw.println(" -h : this help text");
- pw.println(" a[gents] : dump information about defined backup agents");
- pw.println(" users : dump the list of users for which backup service "
- + "is running");
- return;
- } else if ("agents".startsWith(arg)) {
+ if ("agents".startsWith(arg)) {
dumpAgents(pw);
return;
} else if ("transportclients".equals(arg.toLowerCase())) {
@@ -3583,8 +3573,10 @@
}
private void dumpInternal(PrintWriter pw) {
+ // Add prefix for only non-system users so that system user dumpsys is the same as before
+ String userPrefix = mUserId == UserHandle.USER_SYSTEM ? "" : "User " + mUserId + ":";
synchronized (mQueueLock) {
- pw.println("Backup Manager is " + (mEnabled ? "enabled" : "disabled")
+ pw.println(userPrefix + "Backup Manager is " + (mEnabled ? "enabled" : "disabled")
+ " / " + (!mSetupComplete ? "not " : "") + "setup complete / "
+ (this.mPendingInits.size() == 0 ? "not " : "") + "pending init");
pw.println("Auto-restore is " + (mAutoRestore ? "enabled" : "disabled"));
@@ -3594,13 +3586,13 @@
+ " (now = " + System.currentTimeMillis() + ')');
pw.println(" next scheduled: " + KeyValueBackupJob.nextScheduled(mUserId));
- pw.println("Transport whitelist:");
+ pw.println(userPrefix + "Transport whitelist:");
for (ComponentName transport : mTransportManager.getTransportWhitelist()) {
pw.print(" ");
pw.println(transport.flattenToShortString());
}
- pw.println("Available transports:");
+ pw.println(userPrefix + "Available transports:");
final String[] transports = listAllTransports();
if (transports != null) {
for (String t : transports) {
@@ -3626,18 +3618,18 @@
mTransportManager.dumpTransportClients(pw);
- pw.println("Pending init: " + mPendingInits.size());
+ pw.println(userPrefix + "Pending init: " + mPendingInits.size());
for (String s : mPendingInits) {
pw.println(" " + s);
}
- pw.print("Ancestral: ");
+ pw.print(userPrefix + "Ancestral: ");
pw.println(Long.toHexString(mAncestralToken));
- pw.print("Current: ");
+ pw.print(userPrefix + "Current: ");
pw.println(Long.toHexString(mCurrentToken));
int numPackages = mBackupParticipants.size();
- pw.println("Participants:");
+ pw.println(userPrefix + "Participants:");
for (int i = 0; i < numPackages; i++) {
int uid = mBackupParticipants.keyAt(i);
pw.print(" uid: ");
@@ -3648,7 +3640,7 @@
}
}
- pw.println("Ancestral packages: "
+ pw.println(userPrefix + "Ancestral packages: "
+ (mAncestralPackages == null ? "none" : mAncestralPackages.size()));
if (mAncestralPackages != null) {
for (String pkg : mAncestralPackages) {
@@ -3657,17 +3649,17 @@
}
Set<String> processedPackages = mProcessedPackagesJournal.getPackagesCopy();
- pw.println("Ever backed up: " + processedPackages.size());
+ pw.println(userPrefix + "Ever backed up: " + processedPackages.size());
for (String pkg : processedPackages) {
pw.println(" " + pkg);
}
- pw.println("Pending key/value backup: " + mPendingBackups.size());
+ pw.println(userPrefix + "Pending key/value backup: " + mPendingBackups.size());
for (BackupRequest req : mPendingBackups.values()) {
pw.println(" " + req);
}
- pw.println("Full backup queue:" + mFullBackupQueue.size());
+ pw.println(userPrefix + "Full backup queue:" + mFullBackupQueue.size());
for (FullBackupEntry entry : mFullBackupQueue) {
pw.print(" ");
pw.print(entry.lastBackup);
diff --git a/services/backup/java/com/android/server/backup/internal/BackupHandler.java b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
index eb62620..b06fc52 100644
--- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java
+++ b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
@@ -21,12 +21,10 @@
import static com.android.server.backup.BackupManagerService.TAG;
import android.app.backup.RestoreSet;
-import android.content.Intent;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.RemoteException;
-import android.os.UserHandle;
import android.util.EventLog;
import android.util.Pair;
import android.util.Slog;
@@ -40,7 +38,6 @@
import com.android.server.backup.TransportManager;
import com.android.server.backup.UserBackupManagerService;
import com.android.server.backup.fullbackup.PerformAdbBackupTask;
-import com.android.server.backup.fullbackup.PerformFullTransportBackupTask;
import com.android.server.backup.keyvalue.BackupRequest;
import com.android.server.backup.keyvalue.KeyValueBackupTask;
import com.android.server.backup.params.AdbBackupParams;
@@ -73,10 +70,7 @@
public static final int MSG_RESTORE_SESSION_TIMEOUT = 8;
public static final int MSG_FULL_CONFIRMATION_TIMEOUT = 9;
public static final int MSG_RUN_ADB_RESTORE = 10;
- public static final int MSG_RETRY_INIT = 11;
public static final int MSG_RETRY_CLEAR = 12;
- public static final int MSG_WIDGET_BROADCAST = 13;
- public static final int MSG_RUN_FULL_TRANSPORT_BACKUP = 14;
public static final int MSG_REQUEST_BACKUP = 15;
public static final int MSG_SCHEDULE_BACKUP_PACKAGE = 16;
public static final int MSG_BACKUP_OPERATION_TIMEOUT = 17;
@@ -279,12 +273,6 @@
break;
}
- case MSG_RUN_FULL_TRANSPORT_BACKUP: {
- PerformFullTransportBackupTask task = (PerformFullTransportBackupTask) msg.obj;
- (new Thread(task, "transport-backup")).start();
- break;
- }
-
case MSG_RUN_RESTORE: {
RestoreParams params = (RestoreParams) msg.obj;
Slog.d(TAG, "MSG_RUN_RESTORE observer=" + params.observer);
@@ -445,12 +433,6 @@
break;
}
- case MSG_WIDGET_BROADCAST: {
- final Intent intent = (Intent) msg.obj;
- backupManagerService.getContext().sendBroadcastAsUser(intent, UserHandle.SYSTEM);
- break;
- }
-
case MSG_REQUEST_BACKUP: {
BackupParams params = (BackupParams) msg.obj;
if (MORE_DEBUG) {
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 27824af..8eca62a 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -175,8 +175,8 @@
}
@Override // from SystemService
- public boolean isSupported(UserInfo userInfo) {
- return userInfo.isFull() || userInfo.isManagedProfile();
+ public boolean isSupportedUser(TargetUser user) {
+ return user.getUserInfo().isFull() || user.getUserInfo().isManagedProfile();
}
@Override // from SystemService
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 5b98f06..b2fba73 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -94,8 +94,8 @@
"android.hardware.light-V2.0-java",
"android.hardware.power-V1.0-java",
"android.hardware.tv.cec-V1.0-java",
+ "android.hardware.vibrator-java",
"app-compat-annotations",
- "vintf-vibrator-java",
"framework-tethering",
],
@@ -117,6 +117,7 @@
"android.hardware.oemlock-V1.0-java",
"android.hardware.configstore-V1.0-java",
"android.hardware.contexthub-V1.0-java",
+ "android.hardware.rebootescrow-java",
"android.hardware.soundtrigger-V2.3-java",
"android.hidl.manager-V1.2-java",
"dnsresolver_aidl_interface-V2-java",
@@ -164,9 +165,3 @@
name: "protolog.conf.json.gz",
src: ":services.core.json.gz",
}
-
-platform_compat_config {
- name: "services-core-platform-compat-config",
- src: ":services.core.unboosted",
-}
-
diff --git a/services/core/java/android/app/usage/UsageStatsManagerInternal.java b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
index 2f8c506..f3647602 100644
--- a/services/core/java/android/app/usage/UsageStatsManagerInternal.java
+++ b/services/core/java/android/app/usage/UsageStatsManagerInternal.java
@@ -23,8 +23,6 @@
import android.os.UserHandle;
import android.os.UserManager;
-import com.android.server.usage.AppStandbyInternal.AppIdleStateChangeListener;
-
import java.util.List;
import java.util.Set;
@@ -198,6 +196,12 @@
long beginTime, long endTime, boolean obfuscateInstantApps);
/**
+ * Returns the events for the user in the given time period.
+ */
+ public abstract UsageEvents queryEventsForUser(@UserIdInt int userId, long beginTime,
+ long endTime, boolean shouldObfuscateInstantApps);
+
+ /**
* Used to persist the last time a job was run for this app, in order to make decisions later
* whether a job should be deferred until later. The time passed in should be in elapsed
* realtime since boot.
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 40a7dfc..368416b 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -41,6 +41,7 @@
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Collection;
import java.util.List;
import java.util.function.Consumer;
@@ -319,6 +320,12 @@
int deviceOwnerUserId, String deviceOwner, SparseArray<String> profileOwners);
/**
+ * Called by DevicePolicyManagerService to set the package names protected by the device
+ * owner.
+ */
+ public abstract void setDeviceOwnerProtectedPackages(List<String> packageNames);
+
+ /**
* Returns {@code true} if a given package can't be wiped. Otherwise, returns {@code false}.
*/
public abstract boolean isPackageDataProtected(int userId, String packageName);
@@ -472,10 +479,12 @@
* will be disabled. Pass in null or an empty list to disable
* all overlays. The order of the items is significant if several
* overlays modify the same resource.
+ * @param outUpdatedPackageNames An output list that contains the package names of packages
+ * affected by the update of enabled overlays.
* @return true if all packages names were known by the package manager, false otherwise
*/
public abstract boolean setEnabledOverlayPackages(int userId, String targetPackageName,
- List<String> overlayPackageNames);
+ List<String> overlayPackageNames, Collection<String> outUpdatedPackageNames);
/**
* Resolves an activity intent, allowing instant apps to be resolved.
@@ -805,6 +814,12 @@
public abstract boolean isApexPackage(String packageName);
/**
+ * Returns list of {@code packageName} of apks inside the given apex.
+ * @param apexPackageName Package name of the apk container of apex
+ */
+ public abstract List<String> getApksInApex(String apexPackageName);
+
+ /**
* Uninstalls given {@code packageName}.
*
* @param packageName apex package to uninstall.
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index 73b6c7a..0f2fb92 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -208,6 +208,7 @@
AppWakeupHistory mAppWakeupHistory;
ClockReceiver mClockReceiver;
final DeliveryTracker mDeliveryTracker = new DeliveryTracker();
+ IBinder.DeathRecipient mListenerDeathRecipient;
Intent mTimeTickIntent;
IAlarmListener mTimeTickTrigger;
PendingIntent mDateChangeSender;
@@ -1447,6 +1448,18 @@
public void onStart() {
mInjector.init();
+ mListenerDeathRecipient = new IBinder.DeathRecipient() {
+ @Override
+ public void binderDied() {
+ }
+
+ @Override
+ public void binderDied(IBinder who) {
+ final IAlarmListener listener = IAlarmListener.Stub.asInterface(who);
+ removeImpl(null, listener);
+ }
+ };
+
synchronized (mLock) {
mHandler = new AlarmHandler();
mConstants = new Constants(mHandler);
@@ -1653,6 +1666,15 @@
return;
}
+ if (directReceiver != null) {
+ try {
+ directReceiver.asBinder().linkToDeath(mListenerDeathRecipient, 0);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Dropping unreachable alarm listener " + listenerTag);
+ return;
+ }
+ }
+
// Sanity check the window length. This will catch people mistakenly
// trying to pass an end-of-window timestamp rather than a duration.
if (windowLength > AlarmManager.INTERVAL_HALF_DAY) {
@@ -2079,8 +2101,9 @@
@Override
public long currentNetworkTimeMillis() {
final NtpTrustedTime time = NtpTrustedTime.getInstance(getContext());
- if (time.hasCache()) {
- return time.currentTimeMillis();
+ NtpTrustedTime.TimeResult ntpResult = time.getCachedTimeResult();
+ if (ntpResult != null) {
+ return ntpResult.currentTimeMillis();
} else {
throw new ParcelableException(new DateTimeException("Missing NTP fix"));
}
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index bd8a361..c1e23e4 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -79,19 +79,18 @@
import android.net.IpMemoryStore;
import android.net.IpPrefix;
import android.net.LinkProperties;
-import android.net.LinkProperties.CompareResult;
import android.net.MatchAllNetworkSpecifier;
import android.net.NattSocketKeepalive;
import android.net.Network;
import android.net.NetworkAgent;
+import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkConfig;
-import android.net.NetworkFactory;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
-import android.net.NetworkMisc;
import android.net.NetworkMonitorManager;
import android.net.NetworkPolicyManager;
+import android.net.NetworkProvider;
import android.net.NetworkQuotaInfo;
import android.net.NetworkRequest;
import android.net.NetworkScore;
@@ -113,6 +112,7 @@
import android.net.metrics.NetworkEvent;
import android.net.netlink.InetDiagMessage;
import android.net.shared.PrivateDnsConfig;
+import android.net.util.LinkPropertiesUtils.CompareResult;
import android.net.util.MultinetworkPolicyTracker;
import android.net.util.NetdService;
import android.os.Binder;
@@ -219,6 +219,7 @@
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* @hide
@@ -362,10 +363,10 @@
private static final int EVENT_PROXY_HAS_CHANGED = 16;
/**
- * used internally when registering NetworkFactories
- * obj = NetworkFactoryInfo
+ * used internally when registering NetworkProviders
+ * obj = NetworkProviderInfo
*/
- private static final int EVENT_REGISTER_NETWORK_FACTORY = 17;
+ private static final int EVENT_REGISTER_NETWORK_PROVIDER = 17;
/**
* used internally when registering NetworkAgents
@@ -401,10 +402,10 @@
private static final int EVENT_RELEASE_NETWORK_REQUEST = 22;
/**
- * used internally when registering NetworkFactories
+ * used internally when registering NetworkProviders
* obj = Messenger
*/
- private static final int EVENT_UNREGISTER_NETWORK_FACTORY = 23;
+ private static final int EVENT_UNREGISTER_NETWORK_PROVIDER = 23;
/**
* used internally to expire a wakelock when transitioning
@@ -595,6 +596,10 @@
// sequence number of NetworkRequests
private int mNextNetworkRequestId = 1;
+ // Sequence number for NetworkProvider IDs.
+ private final AtomicInteger mNextNetworkProviderId = new AtomicInteger(
+ NetworkProvider.FIRST_PROVIDER_ID);
+
// NetworkRequest activity String log entries.
private static final int MAX_NETWORK_REQUEST_LOGS = 20;
private final LocalLog mNetworkRequestInfoLogs = new LocalLog(MAX_NETWORK_REQUEST_LOGS);
@@ -2388,9 +2393,9 @@
return;
}
- pw.print("NetworkFactories for:");
- for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
- pw.print(" " + nfi.name);
+ pw.print("NetworkProviders for:");
+ for (NetworkProviderInfo npi : mNetworkProviderInfos.values()) {
+ pw.print(" " + npi.name);
}
pw.println();
pw.println();
@@ -2625,8 +2630,8 @@
if (nai.everConnected) {
loge("ERROR: cannot call explicitlySelected on already-connected network");
}
- nai.networkMisc.explicitlySelected = toBool(msg.arg1);
- nai.networkMisc.acceptUnvalidated = toBool(msg.arg1) && toBool(msg.arg2);
+ nai.networkAgentConfig.explicitlySelected = toBool(msg.arg1);
+ nai.networkAgentConfig.acceptUnvalidated = toBool(msg.arg1) && toBool(msg.arg2);
// Mark the network as temporarily accepting partial connectivity so that it
// will be validated (and possibly become default) even if it only provides
// partial internet access. Note that if user connects to partial connectivity
@@ -2634,7 +2639,7 @@
// out of wifi coverage) and if the same wifi is available again, the device
// will auto connect to this wifi even though the wifi has "no internet".
// TODO: Evaluate using a separate setting in IpMemoryStore.
- nai.networkMisc.acceptPartialConnectivity = toBool(msg.arg2);
+ nai.networkAgentConfig.acceptPartialConnectivity = toBool(msg.arg2);
break;
}
case NetworkAgent.EVENT_SOCKET_KEEPALIVE: {
@@ -2666,10 +2671,10 @@
}
// Only show the notification when the private DNS is broken and the
// PRIVATE_DNS_BROKEN notification hasn't shown since last valid.
- if (privateDnsBroken && !nai.networkMisc.hasShownBroken) {
+ if (privateDnsBroken && !nai.networkAgentConfig.hasShownBroken) {
showNetworkNotification(nai, NotificationType.PRIVATE_DNS_BROKEN);
}
- nai.networkMisc.hasShownBroken = privateDnsBroken;
+ nai.networkAgentConfig.hasShownBroken = privateDnsBroken;
} else if (nai.networkCapabilities.isPrivateDnsBroken()) {
// If probePrivateDnsCompleted is false but nai.networkCapabilities says
// private DNS is broken, it means this network is being reevaluated.
@@ -2679,7 +2684,7 @@
nai.networkCapabilities.setPrivateDnsBroken(false);
final int oldScore = nai.getCurrentScore();
updateCapabilities(oldScore, nai, nai.networkCapabilities);
- nai.networkMisc.hasShownBroken = false;
+ nai.networkAgentConfig.hasShownBroken = false;
}
break;
}
@@ -2721,7 +2726,7 @@
nai.lastValidated = valid;
nai.everValidated |= valid;
updateCapabilities(oldScore, nai, nai.networkCapabilities);
- // If score has changed, rebroadcast to NetworkFactories. b/17726566
+ // If score has changed, rebroadcast to NetworkProviders. b/17726566
if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai);
if (valid) {
handleFreshlyValidatedNetwork(nai);
@@ -2738,7 +2743,7 @@
// If network becomes valid, the hasShownBroken should be reset for
// that network so that the notification will be fired when the private
// DNS is broken again.
- nai.networkMisc.hasShownBroken = false;
+ nai.networkAgentConfig.hasShownBroken = false;
}
} else if (partialConnectivityChanged) {
updateCapabilities(nai.getCurrentScore(), nai, nai.networkCapabilities);
@@ -2797,9 +2802,10 @@
loge("EVENT_PROVISIONING_NOTIFICATION from unknown NetworkMonitor");
break;
}
- if (!nai.networkMisc.provisioningNotificationDisabled) {
+ if (!nai.networkAgentConfig.provisioningNotificationDisabled) {
mNotifier.showNotification(netId, NotificationType.SIGN_IN, nai, null,
- (PendingIntent) msg.obj, nai.networkMisc.explicitlySelected);
+ (PendingIntent) msg.obj,
+ nai.networkAgentConfig.explicitlySelected);
}
}
break;
@@ -2836,25 +2842,11 @@
return true;
}
- private boolean maybeHandleNetworkFactoryMessage(Message msg) {
- switch (msg.what) {
- default:
- return false;
- case NetworkFactory.EVENT_UNFULFILLABLE_REQUEST: {
- handleReleaseNetworkRequest((NetworkRequest) msg.obj, msg.sendingUid,
- /* callOnUnavailable */ true);
- break;
- }
- }
- return true;
- }
-
@Override
public void handleMessage(Message msg) {
if (!maybeHandleAsyncChannelMessage(msg)
&& !maybeHandleNetworkMonitorMessage(msg)
- && !maybeHandleNetworkAgentInfoMessage(msg)
- && !maybeHandleNetworkFactoryMessage(msg)) {
+ && !maybeHandleNetworkAgentInfoMessage(msg)) {
maybeHandleNetworkAgentMessage(msg);
}
}
@@ -3025,32 +3017,16 @@
private void handleAsyncChannelHalfConnect(Message msg) {
ensureRunningOnConnectivityServiceThread();
final AsyncChannel ac = (AsyncChannel) msg.obj;
- if (mNetworkFactoryInfos.containsKey(msg.replyTo)) {
+ if (mNetworkProviderInfos.containsKey(msg.replyTo)) {
if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
if (VDBG) log("NetworkFactory connected");
// Finish setting up the full connection
- mNetworkFactoryInfos.get(msg.replyTo).asyncChannel.sendMessage(
- AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
- // A network factory has connected. Send it all current NetworkRequests.
- for (NetworkRequestInfo nri : mNetworkRequests.values()) {
- if (nri.request.isListen()) continue;
- ensureRunningOnConnectivityServiceThread();
- NetworkAgentInfo nai = nri.mSatisfier;
- final int score;
- final int serial;
- if (nai != null) {
- score = nai.getCurrentScore();
- serial = nai.factorySerialNumber;
- } else {
- score = 0;
- serial = NetworkFactory.SerialNumber.NONE;
- }
- ac.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK, score, serial,
- nri.request);
- }
+ NetworkProviderInfo npi = mNetworkProviderInfos.get(msg.replyTo);
+ npi.completeConnection();
+ sendAllRequestsToProvider(npi);
} else {
loge("Error connecting NetworkFactory");
- mNetworkFactoryInfos.remove(msg.obj);
+ mNetworkProviderInfos.remove(msg.obj);
}
} else if (mNetworkAgentInfos.containsKey(msg.replyTo)) {
if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
@@ -3082,8 +3058,8 @@
if (nai != null) {
disconnectAndDestroyNetwork(nai);
} else {
- NetworkFactoryInfo nfi = mNetworkFactoryInfos.remove(msg.replyTo);
- if (DBG && nfi != null) log("unregisterNetworkFactory for " + nfi.name);
+ NetworkProviderInfo npi = mNetworkProviderInfos.remove(msg.replyTo);
+ if (DBG && npi != null) log("unregisterNetworkFactory for " + npi.name);
}
}
@@ -3166,7 +3142,7 @@
// ip[6]tables to flush routes and remove the incoming packet mark rule, so do it
// after we've rematched networks with requests which should make a potential
// fallback network the default or requested a new network from the
- // NetworkFactories, so network traffic isn't interrupted for an unnecessarily
+ // NetworkProviders, so network traffic isn't interrupted for an unnecessarily
// long time.
destroyNativeNetwork(nai);
mDnsManager.removeNetwork(nai.network);
@@ -3179,8 +3155,8 @@
// This should never fail. Specifying an already in use NetID will cause failure.
if (networkAgent.isVPN()) {
mNetd.networkCreateVpn(networkAgent.network.netId,
- (networkAgent.networkMisc == null
- || !networkAgent.networkMisc.allowBypass));
+ (networkAgent.networkAgentConfig == null
+ || !networkAgent.networkAgentConfig.allowBypass));
} else {
mNetd.networkCreatePhysical(networkAgent.network.netId,
getNetworkPermission(networkAgent.networkCapabilities));
@@ -3429,9 +3405,8 @@
}
}
- for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
- nfi.asyncChannel.sendMessage(android.net.NetworkFactory.CMD_CANCEL_REQUEST,
- nri.request);
+ for (NetworkProviderInfo npi : mNetworkProviderInfos.values()) {
+ npi.cancelRequest(nri.request);
}
} else {
// listens don't have a singular affectedNetwork. Check all networks to see
@@ -3481,16 +3456,16 @@
return;
}
- if (!nai.networkMisc.explicitlySelected) {
+ if (!nai.networkAgentConfig.explicitlySelected) {
Slog.wtf(TAG, "BUG: setAcceptUnvalidated non non-explicitly selected network");
}
- if (accept != nai.networkMisc.acceptUnvalidated) {
- nai.networkMisc.acceptUnvalidated = accept;
+ if (accept != nai.networkAgentConfig.acceptUnvalidated) {
+ nai.networkAgentConfig.acceptUnvalidated = accept;
// If network becomes partial connectivity and user already accepted to use this
// network, we should respect the user's option and don't need to popup the
// PARTIAL_CONNECTIVITY notification to user again.
- nai.networkMisc.acceptPartialConnectivity = accept;
+ nai.networkAgentConfig.acceptPartialConnectivity = accept;
rematchAllNetworksAndRequests();
sendUpdatedScoreToFactories(nai);
}
@@ -3527,8 +3502,8 @@
return;
}
- if (accept != nai.networkMisc.acceptPartialConnectivity) {
- nai.networkMisc.acceptPartialConnectivity = accept;
+ if (accept != nai.networkAgentConfig.acceptPartialConnectivity) {
+ nai.networkAgentConfig.acceptPartialConnectivity = accept;
}
// TODO: Use the current design or save the user choice into IpMemoryStore.
@@ -3626,17 +3601,32 @@
enforceSettingsPermission();
}
- // getNetworkAgentInfoForNetwork is thread-safe
- final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(mNetwork);
- if (nai == null) return;
-
- // nai.networkMonitor() is thread-safe
- final NetworkMonitorManager nm = nai.networkMonitor();
+ final NetworkMonitorManager nm = getNetworkMonitorManager(mNetwork);
if (nm == null) return;
nm.notifyCaptivePortalAppFinished(response);
}
@Override
+ public void appRequest(final int request) {
+ final NetworkMonitorManager nm = getNetworkMonitorManager(mNetwork);
+ if (nm == null) return;
+
+ if (request == CaptivePortal.APP_REQUEST_REEVALUATION_REQUIRED) {
+ nm.forceReevaluation(Binder.getCallingUid());
+ }
+ }
+
+ @Nullable
+ private NetworkMonitorManager getNetworkMonitorManager(final Network network) {
+ // getNetworkAgentInfoForNetwork is thread-safe
+ final NetworkAgentInfo nai = getNetworkAgentInfoForNetwork(network);
+ if (nai == null) return null;
+
+ // nai.networkMonitor() is thread-safe
+ return nai.networkMonitor();
+ }
+
+ @Override
public void logEvent(int eventId, String packageName) {
enforceSettingsPermission();
@@ -3738,7 +3728,7 @@
action = ConnectivityManager.ACTION_PROMPT_PARTIAL_CONNECTIVITY;
// Don't bother the user with a high-priority notification if the network was not
// explicitly selected by the user.
- highPriority = nai.networkMisc.explicitlySelected;
+ highPriority = nai.networkAgentConfig.explicitlySelected;
break;
default:
Slog.wtf(TAG, "Unknown notification type " + type);
@@ -3771,14 +3761,15 @@
// automatically connects to a network that has partial Internet access, the user will
// always be able to use it, either because they've already chosen "don't ask again" or
// because we have prompt them.
- if (nai.partialConnectivity && !nai.networkMisc.acceptPartialConnectivity) {
+ if (nai.partialConnectivity && !nai.networkAgentConfig.acceptPartialConnectivity) {
return true;
}
// If a network has no Internet access, only prompt if the network was explicitly selected
// and if the user has not already told us to use the network regardless of whether it
// validated or not.
- if (nai.networkMisc.explicitlySelected && !nai.networkMisc.acceptUnvalidated) {
+ if (nai.networkAgentConfig.explicitlySelected
+ && !nai.networkAgentConfig.acceptUnvalidated) {
return true;
}
@@ -3866,12 +3857,12 @@
handleApplyDefaultProxy((ProxyInfo)msg.obj);
break;
}
- case EVENT_REGISTER_NETWORK_FACTORY: {
- handleRegisterNetworkFactory((NetworkFactoryInfo)msg.obj);
+ case EVENT_REGISTER_NETWORK_PROVIDER: {
+ handleRegisterNetworkProvider((NetworkProviderInfo) msg.obj);
break;
}
- case EVENT_UNREGISTER_NETWORK_FACTORY: {
- handleUnregisterNetworkFactory((Messenger)msg.obj);
+ case EVENT_UNREGISTER_NETWORK_PROVIDER: {
+ handleUnregisterNetworkProvider((Messenger) msg.obj);
break;
}
case EVENT_REGISTER_NETWORK_AGENT: {
@@ -4916,7 +4907,7 @@
}
};
- private final HashMap<Messenger, NetworkFactoryInfo> mNetworkFactoryInfos = new HashMap<>();
+ private final HashMap<Messenger, NetworkProviderInfo> mNetworkProviderInfos = new HashMap<>();
private final HashMap<NetworkRequest, NetworkRequestInfo> mNetworkRequests = new HashMap<>();
private static final int MAX_NETWORK_REQUESTS_PER_UID = 100;
@@ -4924,18 +4915,73 @@
@GuardedBy("mUidToNetworkRequestCount")
private final SparseIntArray mUidToNetworkRequestCount = new SparseIntArray();
- private static class NetworkFactoryInfo {
+ private static class NetworkProviderInfo {
public final String name;
public final Messenger messenger;
- public final AsyncChannel asyncChannel;
- public final int factorySerialNumber;
+ private final AsyncChannel mAsyncChannel;
+ private final IBinder.DeathRecipient mDeathRecipient;
+ public final int providerId;
- NetworkFactoryInfo(String name, Messenger messenger, AsyncChannel asyncChannel,
- int factorySerialNumber) {
+ NetworkProviderInfo(String name, Messenger messenger, AsyncChannel asyncChannel,
+ int providerId, IBinder.DeathRecipient deathRecipient) {
this.name = name;
this.messenger = messenger;
- this.asyncChannel = asyncChannel;
- this.factorySerialNumber = factorySerialNumber;
+ this.providerId = providerId;
+ mAsyncChannel = asyncChannel;
+ mDeathRecipient = deathRecipient;
+
+ if ((mAsyncChannel == null) == (mDeathRecipient == null)) {
+ throw new AssertionError("Must pass exactly one of asyncChannel or deathRecipient");
+ }
+ }
+
+ boolean isLegacyNetworkFactory() {
+ return mAsyncChannel != null;
+ }
+
+ void sendMessageToNetworkProvider(int what, int arg1, int arg2, Object obj) {
+ try {
+ messenger.send(Message.obtain(null /* handler */, what, arg1, arg2, obj));
+ } catch (RemoteException e) {
+ // Remote process died. Ignore; the death recipient will remove this
+ // NetworkProviderInfo from mNetworkProviderInfos.
+ }
+ }
+
+ void requestNetwork(NetworkRequest request, int score, int servingProviderId) {
+ if (isLegacyNetworkFactory()) {
+ mAsyncChannel.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK, score,
+ servingProviderId, request);
+ } else {
+ sendMessageToNetworkProvider(NetworkProvider.CMD_REQUEST_NETWORK, score,
+ servingProviderId, request);
+ }
+ }
+
+ void cancelRequest(NetworkRequest request) {
+ if (isLegacyNetworkFactory()) {
+ mAsyncChannel.sendMessage(android.net.NetworkFactory.CMD_CANCEL_REQUEST, request);
+ } else {
+ sendMessageToNetworkProvider(NetworkProvider.CMD_CANCEL_REQUEST, 0, 0, request);
+ }
+ }
+
+ void connect(Context context, Handler handler) {
+ if (isLegacyNetworkFactory()) {
+ mAsyncChannel.connect(context, handler, messenger);
+ } else {
+ try {
+ messenger.getBinder().linkToDeath(mDeathRecipient, 0);
+ } catch (RemoteException e) {
+ mDeathRecipient.binderDied();
+ }
+ }
+ }
+
+ void completeConnection() {
+ if (isLegacyNetworkFactory()) {
+ mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION);
+ }
}
}
@@ -5306,6 +5352,11 @@
mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_LISTENER, nri));
}
+ /** Returns the next Network provider ID. */
+ public final int nextNetworkProviderId() {
+ return mNextNetworkProviderId.getAndIncrement();
+ }
+
@Override
public void releaseNetworkRequest(NetworkRequest networkRequest) {
ensureNetworkRequestHasType(networkRequest);
@@ -5316,31 +5367,65 @@
@Override
public int registerNetworkFactory(Messenger messenger, String name) {
enforceNetworkFactoryPermission();
- NetworkFactoryInfo nfi = new NetworkFactoryInfo(name, messenger, new AsyncChannel(),
- NetworkFactory.SerialNumber.nextSerialNumber());
- mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_FACTORY, nfi));
- return nfi.factorySerialNumber;
+ NetworkProviderInfo npi = new NetworkProviderInfo(name, messenger, new AsyncChannel(),
+ nextNetworkProviderId(), null /* deathRecipient */);
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_PROVIDER, npi));
+ return npi.providerId;
}
- private void handleRegisterNetworkFactory(NetworkFactoryInfo nfi) {
- if (DBG) log("Got NetworkFactory Messenger for " + nfi.name);
- mNetworkFactoryInfos.put(nfi.messenger, nfi);
- nfi.asyncChannel.connect(mContext, mTrackerHandler, nfi.messenger);
+ private void handleRegisterNetworkProvider(NetworkProviderInfo npi) {
+ if (mNetworkProviderInfos.containsKey(npi.messenger)) {
+ // Avoid creating duplicates. even if an app makes a direct AIDL call.
+ // This will never happen if an app calls ConnectivityManager#registerNetworkProvider,
+ // as that will throw if a duplicate provider is registered.
+ Slog.e(TAG, "Attempt to register existing NetworkProviderInfo "
+ + mNetworkProviderInfos.get(npi.messenger).name);
+ return;
+ }
+
+ if (DBG) log("Got NetworkProvider Messenger for " + npi.name);
+ mNetworkProviderInfos.put(npi.messenger, npi);
+ npi.connect(mContext, mTrackerHandler);
+ if (!npi.isLegacyNetworkFactory()) {
+ // Legacy NetworkFactories get their requests when their AsyncChannel connects.
+ sendAllRequestsToProvider(npi);
+ }
+ }
+
+ @Override
+ public int registerNetworkProvider(Messenger messenger, String name) {
+ enforceNetworkFactoryPermission();
+ NetworkProviderInfo npi = new NetworkProviderInfo(name, messenger,
+ null /* asyncChannel */, nextNetworkProviderId(),
+ () -> unregisterNetworkProvider(messenger));
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_REGISTER_NETWORK_PROVIDER, npi));
+ return npi.providerId;
+ }
+
+ @Override
+ public void unregisterNetworkProvider(Messenger messenger) {
+ enforceNetworkFactoryPermission();
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_NETWORK_PROVIDER, messenger));
}
@Override
public void unregisterNetworkFactory(Messenger messenger) {
- enforceNetworkFactoryPermission();
- mHandler.sendMessage(mHandler.obtainMessage(EVENT_UNREGISTER_NETWORK_FACTORY, messenger));
+ unregisterNetworkProvider(messenger);
}
- private void handleUnregisterNetworkFactory(Messenger messenger) {
- NetworkFactoryInfo nfi = mNetworkFactoryInfos.remove(messenger);
- if (nfi == null) {
- loge("Failed to find Messenger in unregisterNetworkFactory");
+ private void handleUnregisterNetworkProvider(Messenger messenger) {
+ NetworkProviderInfo npi = mNetworkProviderInfos.remove(messenger);
+ if (npi == null) {
+ loge("Failed to find Messenger in unregisterNetworkProvider");
return;
}
- if (DBG) log("unregisterNetworkFactory for " + nfi.name);
+ if (DBG) log("unregisterNetworkProvider for " + npi.name);
+ }
+
+ @Override
+ public void declareNetworkRequestUnfulfillable(NetworkRequest request) {
+ enforceNetworkFactoryPermission();
+ mHandler.post(() -> handleReleaseNetworkRequest(request, Binder.getCallingUid(), true));
}
// NOTE: Accessed on multiple threads, must be synchronized on itself.
@@ -5404,11 +5489,14 @@
// changes that would conflict throughout the automerger graph. Having this method temporarily
// helps with the process of going through with all these dependent changes across the entire
// tree.
- public int registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo,
+ /**
+ * Register a new agent. {@see #registerNetworkAgent} below.
+ */
+ public Network registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo,
LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
- int currentScore, NetworkMisc networkMisc) {
+ int currentScore, NetworkAgentConfig networkAgentConfig) {
return registerNetworkAgent(messenger, networkInfo, linkProperties, networkCapabilities,
- currentScore, networkMisc, NetworkFactory.SerialNumber.NONE);
+ currentScore, networkAgentConfig, NetworkProvider.ID_NONE);
}
/**
@@ -5423,12 +5511,13 @@
* later : see {@link #updateCapabilities}.
* @param currentScore the initial score of the network. See
* {@link NetworkAgentInfo#getCurrentScore}.
- * @param networkMisc metadata about the network. This is never updated.
- * @param factorySerialNumber the serial number of the factory owning this NetworkAgent.
+ * @param networkAgentConfig metadata about the network. This is never updated.
+ * @param providerId the ID of the provider owning this NetworkAgent.
+ * @return the network created for this agent.
*/
- public int registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo,
+ public Network registerNetworkAgent(Messenger messenger, NetworkInfo networkInfo,
LinkProperties linkProperties, NetworkCapabilities networkCapabilities,
- int currentScore, NetworkMisc networkMisc, int factorySerialNumber) {
+ int currentScore, NetworkAgentConfig networkAgentConfig, int providerId) {
enforceNetworkFactoryPermission();
LinkProperties lp = new LinkProperties(linkProperties);
@@ -5440,8 +5529,8 @@
ns.putIntExtension(NetworkScore.LEGACY_SCORE, currentScore);
final NetworkAgentInfo nai = new NetworkAgentInfo(messenger, new AsyncChannel(),
new Network(mNetIdManager.reserveNetId()), new NetworkInfo(networkInfo), lp, nc,
- ns, mContext, mTrackerHandler, new NetworkMisc(networkMisc), this, mNetd,
- mDnsResolver, mNMS, factorySerialNumber);
+ ns, mContext, mTrackerHandler, new NetworkAgentConfig(networkAgentConfig), this,
+ mNetd, mDnsResolver, mNMS, providerId);
// Make sure the network capabilities reflect what the agent info says.
nai.getAndSetNetworkCapabilities(mixInCapabilities(nai, nc));
final String extraInfo = networkInfo.getExtraInfo();
@@ -5459,7 +5548,7 @@
// If the network disconnects or sends any other event before that, messages are deferred by
// NetworkAgent until nai.asyncChannel.connect(), which will be called when finalizing the
// registration.
- return nai.network.netId;
+ return nai.network;
}
private void handleRegisterNetworkAgent(NetworkAgentInfo nai, INetworkMonitor networkMonitor) {
@@ -5720,7 +5809,7 @@
// Once a NetworkAgent is connected, complain if some immutable capabilities are removed.
// Don't complain for VPNs since they're not driven by requests and there is no risk of
// causing a connect/teardown loop.
- // TODO: remove this altogether and make it the responsibility of the NetworkFactories to
+ // TODO: remove this altogether and make it the responsibility of the NetworkProviders to
// avoid connect/teardown loops.
if (nai.everConnected &&
!nai.isVPN() &&
@@ -5862,7 +5951,7 @@
LinkProperties lp) {
if (nc == null || lp == null) return false;
return nai.isVPN()
- && !nai.networkMisc.allowBypass
+ && !nai.networkAgentConfig.allowBypass
&& nc.getEstablishingVpnAppUid() != Process.SYSTEM_UID
&& lp.getInterfaceName() != null
&& (lp.hasIPv4DefaultRoute() || lp.hasIPv6DefaultRoute());
@@ -5960,9 +6049,27 @@
if (VDBG || DDBG){
log("sending new Min Network Score(" + score + "): " + networkRequest.toString());
}
- for (NetworkFactoryInfo nfi : mNetworkFactoryInfos.values()) {
- nfi.asyncChannel.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK, score,
- serial, networkRequest);
+ for (NetworkProviderInfo npi : mNetworkProviderInfos.values()) {
+ npi.requestNetwork(networkRequest, score, serial);
+ }
+ }
+
+ /** Sends all current NetworkRequests to the specified factory. */
+ private void sendAllRequestsToProvider(NetworkProviderInfo npi) {
+ ensureRunningOnConnectivityServiceThread();
+ for (NetworkRequestInfo nri : mNetworkRequests.values()) {
+ if (nri.request.isListen()) continue;
+ NetworkAgentInfo nai = nri.mSatisfier;
+ final int score;
+ final int serial;
+ if (nai != null) {
+ score = nai.getCurrentScore();
+ serial = nai.factorySerialNumber;
+ } else {
+ score = 0;
+ serial = NetworkProvider.ID_NONE;
+ }
+ npi.requestNetwork(nri.request, score, serial);
}
}
@@ -6243,11 +6350,11 @@
Slog.wtf(TAG, "BUG: " + newSatisfier.name() + " already has " + nri.request);
}
addedRequests.add(nri);
- // Tell NetworkFactories about the new score, so they can stop
+ // Tell NetworkProviders about the new score, so they can stop
// trying to connect if they know they cannot match it.
// TODO - this could get expensive if we have a lot of requests for this
// network. Think about if there is a way to reduce this. Push
- // netid->request mapping to each factory?
+ // netid->request mapping to each provider?
sendUpdatedScoreToFactories(nri.request, newSatisfier);
if (isDefaultRequest(nri)) {
isNewDefault = true;
@@ -6276,7 +6383,7 @@
} else {
Slog.wtf(TAG, "BUG: Removing request " + nri.request.requestId + " from " +
newNetwork.name() +
- " without updating mSatisfier or factories!");
+ " without updating mSatisfier or providers!");
}
// TODO: Technically, sending CALLBACK_LOST here is
// incorrect if there is a replacement network currently
@@ -6535,7 +6642,7 @@
// command must be sent after updating LinkProperties to maximize chances of
// NetworkMonitor seeing the correct LinkProperties when starting.
// TODO: pass LinkProperties to the NetworkMonitor in the notifyNetworkConnected call.
- if (networkAgent.networkMisc.acceptPartialConnectivity) {
+ if (networkAgent.networkAgentConfig.acceptPartialConnectivity) {
networkAgent.networkMonitor().setAcceptPartialConnectivity();
}
networkAgent.networkMonitor().notifyNetworkConnected(
diff --git a/services/core/java/com/android/server/CountryDetectorService.java b/services/core/java/com/android/server/CountryDetectorService.java
index 861c731..b0132d3 100644
--- a/services/core/java/com/android/server/CountryDetectorService.java
+++ b/services/core/java/com/android/server/CountryDetectorService.java
@@ -24,21 +24,29 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
+import android.text.TextUtils;
import android.util.PrintWriterPrinter;
import android.util.Printer;
import android.util.Slog;
+import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.DumpUtils;
import com.android.server.location.ComprehensiveCountryDetector;
+import com.android.server.location.CountryDetectorBase;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
/**
- * This class detects the country that the user is in through {@link ComprehensiveCountryDetector}.
+ * This class detects the country that the user is in. The default country detection is made through
+ * {@link com.android.server.location.ComprehensiveCountryDetector}. It is possible to overlay the
+ * detection algorithm by overlaying the attribute R.string.config_customCountryDetector with the
+ * custom class name to use instead. The custom class must extend
+ * {@link com.android.server.location.CountryDetectorBase}
*
* @hide
*/
@@ -88,7 +96,7 @@
private final HashMap<IBinder, Receiver> mReceivers;
private final Context mContext;
- private ComprehensiveCountryDetector mCountryDetector;
+ private CountryDetectorBase mCountryDetector;
private boolean mSystemReady;
private Handler mHandler;
private CountryListener mLocationBasedDetectorListener;
@@ -184,8 +192,17 @@
});
}
- private void initialize() {
- mCountryDetector = new ComprehensiveCountryDetector(mContext);
+ @VisibleForTesting
+ void initialize() {
+ final String customCountryClass = mContext.getString(R.string.config_customCountryDetector);
+ if (!TextUtils.isEmpty(customCountryClass)) {
+ mCountryDetector = loadCustomCountryDetectorIfAvailable(customCountryClass);
+ }
+
+ if (mCountryDetector == null) {
+ Slog.d(TAG, "Using default country detector");
+ mCountryDetector = new ComprehensiveCountryDetector(mContext);
+ }
mLocationBasedDetectorListener = country -> mHandler.post(() -> notifyReceivers(country));
}
@@ -194,10 +211,32 @@
}
@VisibleForTesting
+ CountryDetectorBase getCountryDetector() {
+ return mCountryDetector;
+ }
+
+ @VisibleForTesting
boolean isSystemReady() {
return mSystemReady;
}
+ private CountryDetectorBase loadCustomCountryDetectorIfAvailable(
+ final String customCountryClass) {
+ CountryDetectorBase customCountryDetector = null;
+
+ Slog.d(TAG, "Using custom country detector class: " + customCountryClass);
+ try {
+ customCountryDetector = Class.forName(customCountryClass).asSubclass(
+ CountryDetectorBase.class).getConstructor(Context.class).newInstance(
+ mContext);
+ } catch (ClassNotFoundException | InstantiationException | IllegalAccessException
+ | NoSuchMethodException | InvocationTargetException e) {
+ Slog.e(TAG, "Could not instantiate the custom country detector class");
+ }
+
+ return customCountryDetector;
+ }
+
@SuppressWarnings("unused")
@Override
protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
@@ -206,9 +245,10 @@
try {
final Printer p = new PrintWriterPrinter(fout);
p.println("CountryDetectorService state:");
+ p.println("Country detector class=" + mCountryDetector.getClass().getName());
p.println(" Number of listeners=" + mReceivers.keySet().size());
if (mCountryDetector == null) {
- p.println(" ComprehensiveCountryDetector not initialized");
+ p.println(" CountryDetector not initialized");
} else {
p.println(" " + mCountryDetector.toString());
}
diff --git a/services/core/java/com/android/server/DynamicSystemService.java b/services/core/java/com/android/server/DynamicSystemService.java
index 7909e30..c60460f 100644
--- a/services/core/java/com/android/server/DynamicSystemService.java
+++ b/services/core/java/com/android/server/DynamicSystemService.java
@@ -44,9 +44,9 @@
private static final String TAG = "DynamicSystemService";
private static final String NO_SERVICE_ERROR = "no gsiservice";
private static final int GSID_ROUGH_TIMEOUT_MS = 8192;
- private static final String PATH_DEFAULT = "/data/gsi";
+ private static final String PATH_DEFAULT = "/data/gsi/";
private Context mContext;
- private String mInstallPath;
+ private String mInstallPath, mDsuSlot;
private volatile IGsiService mGsiService;
DynamicSystemService(Context context) {
@@ -115,7 +115,7 @@
}
@Override
- public boolean startInstallation() throws RemoteException {
+ public boolean startInstallation(String dsuSlot) throws RemoteException {
IGsiService service = getGsiService();
// priority from high to low: sysprop -> sdcard -> /data
String path = SystemProperties.get("os.aot.path");
@@ -129,16 +129,17 @@
if (!Environment.MEDIA_MOUNTED.equals(volume.getState())) continue;
File sdCard = volume.getPathFile();
if (sdCard.isDirectory()) {
- path = sdCard.getPath();
+ path = new File(sdCard, dsuSlot).getPath();
break;
}
}
if (path.isEmpty()) {
- path = PATH_DEFAULT;
+ path = PATH_DEFAULT + dsuSlot;
}
Slog.i(TAG, "startInstallation -> " + path);
}
mInstallPath = path;
+ mDsuSlot = dsuSlot;
if (service.openInstall(path) != 0) {
Slog.i(TAG, "Failed to open " + path);
return false;
@@ -203,7 +204,7 @@
public boolean setEnable(boolean enable, boolean oneShot) throws RemoteException {
IGsiService gsiService = getGsiService();
if (enable) {
- return gsiService.enableGsi(oneShot) == 0;
+ return gsiService.enableGsi(oneShot, mDsuSlot) == 0;
} else {
return gsiService.disableGsi();
}
diff --git a/services/core/java/com/android/server/GnssManagerService.java b/services/core/java/com/android/server/GnssManagerService.java
index bbcfdc6..32cdc41 100644
--- a/services/core/java/com/android/server/GnssManagerService.java
+++ b/services/core/java/com/android/server/GnssManagerService.java
@@ -47,7 +47,6 @@
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocationManagerServiceUtils.LinkedListener;
import com.android.server.LocationManagerServiceUtils.LinkedListenerBase;
-import com.android.server.location.AbstractLocationProvider;
import com.android.server.location.CallerIdentity;
import com.android.server.location.GnssBatchingProvider;
import com.android.server.location.GnssCapabilitiesProvider;
@@ -116,11 +115,9 @@
private final Handler mHandler;
public GnssManagerService(LocationManagerService locationManagerService,
- Context context,
- AbstractLocationProvider.LocationProviderManager gnssProviderManager,
- LocationUsageLogger locationUsageLogger) {
- this(locationManagerService, context, new GnssLocationProvider(context, gnssProviderManager,
- FgThread.getHandler().getLooper()), locationUsageLogger);
+ Context context, LocationUsageLogger locationUsageLogger) {
+ this(locationManagerService, context,
+ new GnssLocationProvider(context, FgThread.getHandler()), locationUsageLogger);
}
// Can use this constructor to inject GnssLocationProvider for testing
diff --git a/services/core/java/com/android/server/GraphicsStatsService.java b/services/core/java/com/android/server/GraphicsStatsService.java
index 70569db..5179fa7 100644
--- a/services/core/java/com/android/server/GraphicsStatsService.java
+++ b/services/core/java/com/android/server/GraphicsStatsService.java
@@ -38,11 +38,13 @@
import android.view.IGraphicsStatsCallback;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FastPrintWriter;
import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
+import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
@@ -78,6 +80,8 @@
private static final int SAVE_BUFFER = 1;
private static final int DELETE_OLD = 2;
+ private static final int AID_STATSD = 1066; // Statsd uid is set to 1066 forever.
+
// This isn't static because we need this to happen after registerNativeMethods, however
// the class is loaded (and thus static ctor happens) before that occurs.
private final int ASHMEM_SIZE = nGetAshmemSize();
@@ -121,6 +125,7 @@
return true;
}
});
+ nativeInit();
}
/**
@@ -186,6 +191,86 @@
return pfd;
}
+ // If lastFullDay is true, pullGraphicsStats returns stats for the last complete day/24h period
+ // that does not include today. If lastFullDay is false, pullGraphicsStats returns stats for the
+ // current day.
+ // This method is invoked from native code only.
+ @SuppressWarnings({"UnusedDeclaration"})
+ private long pullGraphicsStats(boolean lastFullDay) throws RemoteException {
+ int uid = Binder.getCallingUid();
+
+ // DUMP and PACKAGE_USAGE_STATS permissions are required to invoke this method.
+ // TODO: remove exception for statsd daemon after required permissions are granted. statsd
+ // TODO: should have these permissions granted by data/etc/platform.xml, but it does not.
+ if (uid != AID_STATSD) {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new FastPrintWriter(sw);
+ if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) {
+ pw.flush();
+ throw new RemoteException(sw.toString());
+ }
+ }
+
+ long callingIdentity = Binder.clearCallingIdentity();
+ try {
+ return pullGraphicsStatsImpl(lastFullDay);
+ } finally {
+ Binder.restoreCallingIdentity(callingIdentity);
+ }
+ }
+
+ private long pullGraphicsStatsImpl(boolean lastFullDay) {
+ long targetDay;
+ if (lastFullDay) {
+ // Get stats from yesterday. Stats stay constant, because the day is over.
+ targetDay = normalizeDate(System.currentTimeMillis() - 86400000).getTimeInMillis();
+ } else {
+ // Get stats from today. Stats may change as more apps are run today.
+ targetDay = normalizeDate(System.currentTimeMillis()).getTimeInMillis();
+ }
+
+ // Find active buffers for targetDay.
+ ArrayList<HistoricalBuffer> buffers;
+ synchronized (mLock) {
+ buffers = new ArrayList<>(mActive.size());
+ for (int i = 0; i < mActive.size(); i++) {
+ ActiveBuffer buffer = mActive.get(i);
+ if (buffer.mInfo.startTime == targetDay) {
+ try {
+ buffers.add(new HistoricalBuffer(buffer));
+ } catch (IOException ex) {
+ // Ignore
+ }
+ }
+ }
+ }
+
+ // Dump active and historic buffers for targetDay in a serialized
+ // GraphicsStatsServiceDumpProto proto.
+ long dump = nCreateDump(-1, true);
+ try {
+ synchronized (mFileAccessLock) {
+ HashSet<File> skipList = dumpActiveLocked(dump, buffers);
+ buffers.clear();
+ String subPath = String.format("%d", targetDay);
+ File dateDir = new File(mGraphicsStatsDir, subPath);
+ if (dateDir.exists()) {
+ for (File pkg : dateDir.listFiles()) {
+ for (File version : pkg.listFiles()) {
+ File data = new File(version, "total");
+ if (skipList.contains(data)) {
+ continue;
+ }
+ nAddToDump(dump, data.getAbsolutePath());
+ }
+ }
+ }
+ }
+ } finally {
+ return nFinishDumpInMemory(dump);
+ }
+ }
+
private ParcelFileDescriptor getPfd(MemoryFile file) {
try {
if (!file.getFileDescriptor().valid()) {
@@ -379,12 +464,21 @@
}
}
+ @Override
+ protected void finalize() throws Throwable {
+ nativeDestructor();
+ }
+
+ private native void nativeInit();
+ private static native void nativeDestructor();
+
private static native int nGetAshmemSize();
private static native long nCreateDump(int outFd, boolean isProto);
private static native void nAddToDump(long dump, String path, String packageName,
long versionCode, long startTime, long endTime, byte[] data);
private static native void nAddToDump(long dump, String path);
private static native void nFinishDump(long dump);
+ private static native long nFinishDumpInMemory(long dump);
private static native void nSaveBuffer(String path, String packageName, long versionCode,
long startTime, long endTime, byte[] data);
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index c5f1923..dc393d1 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -23,8 +23,6 @@
import static android.location.LocationManager.PASSIVE_PROVIDER;
import static android.os.PowerManager.locationPowerSaveModeToString;
-import static com.android.internal.util.Preconditions.checkState;
-
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -71,10 +69,8 @@
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
-import android.os.UserManager;
import android.os.WorkSource;
import android.os.WorkSource.WorkChain;
-import android.provider.Settings;
import android.stats.location.LocationStatsEnums;
import android.text.TextUtils;
import android.util.EventLog;
@@ -87,11 +83,11 @@
import com.android.internal.content.PackageMonitor;
import com.android.internal.location.ProviderProperties;
import com.android.internal.location.ProviderRequest;
-import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.server.location.AbstractLocationProvider;
+import com.android.server.location.AbstractLocationProvider.State;
import com.android.server.location.ActivityRecognitionProxy;
import com.android.server.location.CallerIdentity;
import com.android.server.location.GeocoderProxy;
@@ -105,7 +101,9 @@
import com.android.server.location.LocationSettingsStore;
import com.android.server.location.LocationUsageLogger;
import com.android.server.location.MockProvider;
+import com.android.server.location.MockableLocationProvider;
import com.android.server.location.PassiveProvider;
+import com.android.server.location.UserInfoStore;
import com.android.server.pm.permission.PermissionManagerServiceInternal;
import java.io.ByteArrayOutputStream;
@@ -121,6 +119,8 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
/**
@@ -193,33 +193,31 @@
private final Object mLock = new Object();
private final Context mContext;
private final Handler mHandler;
+ private final UserInfoStore mUserInfoStore;
private final LocationSettingsStore mSettingsStore;
private final LocationUsageLogger mLocationUsageLogger;
+ private final PassiveLocationProviderManager mPassiveManager;
+
private AppOpsManager mAppOps;
private PackageManager mPackageManager;
private PowerManager mPowerManager;
private ActivityManager mActivityManager;
- private UserManager mUserManager;
private GeofenceManager mGeofenceManager;
private LocationFudger mLocationFudger;
private GeocoderProxy mGeocodeProvider;
- @Nullable
- private GnssManagerService mGnssManagerService;
- private PassiveProvider mPassiveProvider; // track passive provider for special cases
+ @Nullable private GnssManagerService mGnssManagerService;
+
@GuardedBy("mLock")
private String mExtraLocationControllerPackage;
+ @GuardedBy("mLock")
private boolean mExtraLocationControllerPackageEnabled;
- // list of currently active providers
- @GuardedBy("mLock")
- private final ArrayList<LocationProviderManager> mProviders = new ArrayList<>();
-
- // list of non-mock providers, so that when mock providers replace real providers, they can be
- // later re-replaced
- @GuardedBy("mLock")
- private final ArrayList<LocationProviderManager> mRealProviders = new ArrayList<>();
+ // @GuardedBy("mLock")
+ // hold lock for write or to prevent write, no lock for read
+ private final CopyOnWriteArrayList<LocationProviderManager> mProviderManagers =
+ new CopyOnWriteArrayList<>();
@GuardedBy("mLock")
private final HashMap<Object, Receiver> mReceivers = new HashMap<>();
@@ -238,10 +236,6 @@
private final HashMap<String, Location> mLastLocationCoarseInterval =
new HashMap<>();
- // current active user on the device - other users are denied location data
- private int mCurrentUserId = UserHandle.USER_SYSTEM;
- private int[] mCurrentUserProfiles = new int[]{UserHandle.USER_SYSTEM};
-
@GuardedBy("mLock")
@PowerManager.LocationPowerSaveMode
private int mBatterySaverMode;
@@ -249,9 +243,18 @@
private LocationManagerService(Context context) {
mContext = context;
mHandler = FgThread.getHandler();
+ mUserInfoStore = new UserInfoStore(mContext);
mSettingsStore = new LocationSettingsStore(mContext, mHandler);
mLocationUsageLogger = new LocationUsageLogger();
+ // set up passive provider - we do this early because it has no dependencies on system
+ // services or external code that isn't ready yet, and because this allows the variable to
+ // be final. other more complex providers are initialized later, when system services are
+ // ready
+ mPassiveManager = new PassiveLocationProviderManager();
+ mProviderManagers.add(mPassiveManager);
+ mPassiveManager.setRealProvider(new PassiveProvider(mContext));
+
// Let the package manager query which are the default location
// providers as they get certain permissions granted by default.
PermissionManagerServiceInternal permissionManagerInternal = LocalServices.getService(
@@ -267,6 +270,7 @@
}
private void onSystemReady() {
+ mUserInfoStore.onSystemReady();
mSettingsStore.onSystemReady();
synchronized (mLock) {
@@ -274,7 +278,6 @@
mAppOps = mContext.getSystemService(AppOpsManager.class);
mPowerManager = mContext.getSystemService(PowerManager.class);
mActivityManager = mContext.getSystemService(ActivityManager.class);
- mUserManager = mContext.getSystemService(UserManager.class);
mLocationFudger = new LocationFudger(mContext, mHandler);
mGeofenceManager = new GeofenceManager(mContext, mSettingsStore);
@@ -362,10 +365,13 @@
}
}.register(mContext, mHandler.getLooper(), true);
+ mUserInfoStore.addListener((oldUserId, newUserId) -> {
+ synchronized (mLock) {
+ onUserChangedLocked(oldUserId, newUserId);
+ }
+ });
+
IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
- intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
- intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
intentFilter.addAction(Intent.ACTION_SCREEN_ON);
@@ -378,14 +384,6 @@
}
synchronized (mLock) {
switch (action) {
- case Intent.ACTION_USER_SWITCHED:
- onUserChangedLocked(
- intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
- break;
- case Intent.ACTION_MANAGED_PROFILE_ADDED:
- case Intent.ACTION_MANAGED_PROFILE_REMOVED:
- onUserProfilesChangedLocked();
- break;
case Intent.ACTION_SCREEN_ON:
case Intent.ACTION_SCREEN_OFF:
onScreenStateChangedLocked();
@@ -395,11 +393,10 @@
}
}, UserHandle.ALL, intentFilter, null, mHandler);
- // switching the user from null to system here performs the bulk of the initialization
+ // switching the user from null to current here performs the bulk of the initialization
// work. the user being changed will cause a reload of all user specific settings, which
// causes initialization, and propagates changes until a steady state is reached
- mCurrentUserId = UserHandle.USER_NULL;
- onUserChangedLocked(ActivityManager.getCurrentUser());
+ onUserChangedLocked(UserHandle.USER_NULL, mUserInfoStore.getCurrentUserId());
}
}
@@ -415,15 +412,15 @@
for (Receiver receiver : mReceivers.values()) {
receiver.updateMonitoring(true);
}
- for (LocationProviderManager p : mProviders) {
- applyRequirementsLocked(p);
+ for (LocationProviderManager manager : mProviderManagers) {
+ applyRequirementsLocked(manager);
}
}
@GuardedBy("mLock")
private void onPermissionsChangedLocked() {
- for (LocationProviderManager p : mProviders) {
- applyRequirementsLocked(p);
+ for (LocationProviderManager manager : mProviderManagers) {
+ applyRequirementsLocked(manager);
}
}
@@ -442,16 +439,16 @@
mBatterySaverMode = newLocationMode;
- for (LocationProviderManager p : mProviders) {
- applyRequirementsLocked(p);
+ for (LocationProviderManager manager : mProviderManagers) {
+ applyRequirementsLocked(manager);
}
}
@GuardedBy("mLock")
private void onScreenStateChangedLocked() {
if (mBatterySaverMode == PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF) {
- for (LocationProviderManager p : mProviders) {
- applyRequirementsLocked(p);
+ for (LocationProviderManager manager : mProviderManagers) {
+ applyRequirementsLocked(manager);
}
}
}
@@ -466,8 +463,8 @@
intent.putExtra(LocationManager.EXTRA_LOCATION_ENABLED, isLocationEnabledForUser(userId));
mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
- for (LocationProviderManager p : mProviders) {
- p.onUseableChangedLocked(userId);
+ for (LocationProviderManager manager : mProviderManagers) {
+ manager.onUseableChangedLocked(userId);
}
}
@@ -521,36 +518,26 @@
@GuardedBy("mLock")
private void onBackgroundThrottleIntervalChangedLocked() {
- for (LocationProviderManager provider : mProviders) {
- applyRequirementsLocked(provider);
+ for (LocationProviderManager manager : mProviderManagers) {
+ applyRequirementsLocked(manager);
}
}
@GuardedBy("mLock")
private void onBackgroundThrottleWhitelistChangedLocked() {
- for (LocationProviderManager p : mProviders) {
- applyRequirementsLocked(p);
+ for (LocationProviderManager manager : mProviderManagers) {
+ applyRequirementsLocked(manager);
}
}
@GuardedBy("lock")
private void onIgnoreSettingsWhitelistChangedLocked() {
- for (LocationProviderManager p : mProviders) {
- applyRequirementsLocked(p);
+ for (LocationProviderManager manager : mProviderManagers) {
+ applyRequirementsLocked(manager);
}
}
@GuardedBy("mLock")
- private void onUserProfilesChangedLocked() {
- mCurrentUserProfiles = mUserManager.getProfileIdsWithDisabled(mCurrentUserId);
- }
-
- @GuardedBy("mLock")
- private boolean isCurrentProfileLocked(int userId) {
- return ArrayUtils.contains(mCurrentUserProfiles, userId);
- }
-
- @GuardedBy("mLock")
private void ensureFallbackFusedProviderPresentLocked(String[] pkgs) {
PackageManager pm = mContext.getPackageManager();
String systemPackageName = mContext.getPackageName();
@@ -558,7 +545,7 @@
List<ResolveInfo> rInfos = pm.queryIntentServicesAsUser(
new Intent(FUSED_LOCATION_SERVICE_ACTION),
- PackageManager.GET_META_DATA, mCurrentUserId);
+ PackageManager.GET_META_DATA, mUserInfoStore.getCurrentUserId());
for (ResolveInfo rInfo : rInfos) {
String packageName = rInfo.serviceInfo.packageName;
@@ -623,22 +610,11 @@
@GuardedBy("mLock")
private void initializeProvidersLocked() {
- // create a passive location provider, which is always enabled
- LocationProviderManager passiveProviderManager = new LocationProviderManager(
- PASSIVE_PROVIDER);
- addProviderLocked(passiveProviderManager);
- mPassiveProvider = new PassiveProvider(mContext, passiveProviderManager);
- passiveProviderManager.attachLocked(mPassiveProvider);
-
if (GnssManagerService.isGnssSupported()) {
- // Create a gps location provider manager
- LocationProviderManager gnssProviderManager = new LocationProviderManager(GPS_PROVIDER);
- mRealProviders.add(gnssProviderManager);
- addProviderLocked(gnssProviderManager);
-
- mGnssManagerService = new GnssManagerService(this, mContext, gnssProviderManager,
- mLocationUsageLogger);
- gnssProviderManager.attachLocked(mGnssManagerService.getGnssLocationProvider());
+ mGnssManagerService = new GnssManagerService(this, mContext, mLocationUsageLogger);
+ LocationProviderManager gnssManager = new LocationProviderManager(GPS_PROVIDER);
+ mProviderManagers.add(gnssManager);
+ gnssManager.setRealProvider(mGnssManagerService.getGnssLocationProvider());
}
/*
@@ -662,37 +638,31 @@
ensureFallbackFusedProviderPresentLocked(pkgs);
- // bind to network provider
- LocationProviderManager networkProviderManager = new LocationProviderManager(
- NETWORK_PROVIDER);
LocationProviderProxy networkProvider = LocationProviderProxy.createAndBind(
mContext,
- networkProviderManager,
NETWORK_LOCATION_SERVICE_ACTION,
com.android.internal.R.bool.config_enableNetworkLocationOverlay,
com.android.internal.R.string.config_networkLocationProviderPackageName,
com.android.internal.R.array.config_locationProviderPackageNames);
if (networkProvider != null) {
- mRealProviders.add(networkProviderManager);
- addProviderLocked(networkProviderManager);
- networkProviderManager.attachLocked(networkProvider);
+ LocationProviderManager networkManager = new LocationProviderManager(NETWORK_PROVIDER);
+ mProviderManagers.add(networkManager);
+ networkManager.setRealProvider(networkProvider);
} else {
Slog.w(TAG, "no network location provider found");
}
// bind to fused provider
- LocationProviderManager fusedProviderManager = new LocationProviderManager(FUSED_PROVIDER);
LocationProviderProxy fusedProvider = LocationProviderProxy.createAndBind(
mContext,
- fusedProviderManager,
FUSED_LOCATION_SERVICE_ACTION,
com.android.internal.R.bool.config_enableFusedLocationOverlay,
com.android.internal.R.string.config_fusedLocationProviderPackageName,
com.android.internal.R.array.config_locationProviderPackageNames);
if (fusedProvider != null) {
- mRealProviders.add(fusedProviderManager);
- addProviderLocked(fusedProviderManager);
- fusedProviderManager.attachLocked(fusedProvider);
+ LocationProviderManager fusedManager = new LocationProviderManager(FUSED_PROVIDER);
+ mProviderManagers.add(fusedManager);
+ fusedManager.setRealProvider(fusedProvider);
} else {
Slog.e(TAG, "no fused location provider found",
new IllegalStateException("Location service needs a fused location provider"));
@@ -754,250 +724,200 @@
Boolean.parseBoolean(fragments[7]) /* supportsBearing */,
Integer.parseInt(fragments[8]) /* powerRequirement */,
Integer.parseInt(fragments[9]) /* accuracy */);
- LocationProviderManager testProviderManager = new LocationProviderManager(name);
- addProviderLocked(testProviderManager);
- testProviderManager.attachLocked(
- new MockProvider(mContext, testProviderManager, properties));
+ addTestProvider(name, properties, mContext.getOpPackageName());
}
}
@GuardedBy("mLock")
- private void onUserChangedLocked(int userId) {
- if (mCurrentUserId == userId) {
- return;
- }
-
+ private void onUserChangedLocked(int oldUserId, int newUserId) {
if (D) {
- Log.d(TAG, "foreground user is changing to " + userId);
+ Log.d(TAG, "foreground user is changing to " + newUserId);
}
- int oldUserId = userId;
- mCurrentUserId = userId;
- onUserProfilesChangedLocked();
+ for (LocationProviderManager manager : mProviderManagers) {
+ // update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility
+ mSettingsStore.setLocationProviderAllowed(manager.getName(),
+ manager.isUseable(newUserId), newUserId);
- // let providers know the current user has changed
- for (LocationProviderManager p : mProviders) {
- p.onUseableChangedLocked(oldUserId);
- p.onUseableChangedLocked(mCurrentUserId);
+ manager.onUseableChangedLocked(oldUserId);
+ manager.onUseableChangedLocked(newUserId);
}
}
/**
* Location provider manager, manages a LocationProvider.
*/
- class LocationProviderManager implements AbstractLocationProvider.LocationProviderManager {
+ class LocationProviderManager implements MockableLocationProvider.Listener {
private final String mName;
- // remember to clear binder identity before invoking any provider operation
- @GuardedBy("mLock")
- @Nullable
- protected AbstractLocationProvider mProvider;
+ // acquiring mLock makes operations on mProvider atomic, but is otherwise unnecessary
+ protected final MockableLocationProvider mProvider;
+ // useable state for parent user ids, no entry implies false. location state is only kept
+ // for parent user ids, the location state for a profile user id is assumed to be the same
+ // as for the parent. if querying this structure, ensure that the user id being used is a
+ // parent id or the results may be incorrect.
@GuardedBy("mLock")
- private SparseArray<Boolean> mUseable; // combined state for each user id
- @GuardedBy("mLock")
- private boolean mEnabled; // state of provider
-
- @GuardedBy("mLock")
- @Nullable
- private ProviderProperties mProperties;
+ private final SparseArray<Boolean> mUseable;
private LocationProviderManager(String name) {
mName = name;
-
- mProvider = null;
mUseable = new SparseArray<>(1);
- mEnabled = false;
- mProperties = null;
- // update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility
- Settings.Secure.putStringForUser(
- mContext.getContentResolver(),
- Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
- "-" + mName,
- mCurrentUserId);
- }
+ // initialize last since this lets our reference escape
+ mProvider = new MockableLocationProvider(mContext, mLock, this);
- @GuardedBy("mLock")
- public void attachLocked(AbstractLocationProvider provider) {
- Objects.requireNonNull(provider);
- checkState(mProvider == null);
-
- if (D) {
- Log.d(TAG, mName + " provider attached");
- }
-
- mProvider = provider;
-
- // it would be more correct to call this for all users, but we know this can only
- // affect the current user since providers are disabled for non-current users
- onUseableChangedLocked(mCurrentUserId);
+ // we can assume all users start with unuseable location state since the initial state
+ // of all providers is disabled. no need to initialize mUseable further.
}
public String getName() {
return mName;
}
- @GuardedBy("mLock")
- public List<String> getPackagesLocked() {
- if (mProvider == null) {
- return Collections.emptyList();
- } else {
- // safe to not clear binder context since this doesn't call into the real provider
- return mProvider.getProviderPackages();
- }
+ public boolean hasProvider() {
+ return mProvider.getProvider() != null;
}
- public boolean isMock() {
- return false;
+ public void setRealProvider(AbstractLocationProvider provider) {
+ mProvider.setRealProvider(provider);
}
- @GuardedBy("mLock")
- public boolean isPassiveLocked() {
- return mProvider == mPassiveProvider;
+ public void setMockProvider(@Nullable MockProvider provider) {
+ mProvider.setMockProvider(provider);
}
- @GuardedBy("mLock")
+ public Set<String> getPackages() {
+ return mProvider.getState().providerPackageNames;
+ }
+
@Nullable
- public ProviderProperties getPropertiesLocked() {
- return mProperties;
+ public ProviderProperties getProperties() {
+ return mProvider.getState().properties;
}
- public void setRequest(ProviderRequest request, WorkSource workSource) {
- // move calls going to providers onto a different thread to avoid deadlock
- mHandler.post(() -> {
- synchronized (mLock) {
- if (mProvider != null) {
- mProvider.onSetRequest(request, workSource);
- }
+ public void setMockProviderEnabled(boolean enabled) {
+ synchronized (mLock) {
+ if (!mProvider.isMock()) {
+ throw new IllegalArgumentException(mName + " provider is not a test provider");
}
- });
+
+ mProvider.setMockProviderEnabled(enabled);
+ }
}
- public void sendExtraCommand(String command, Bundle extras) {
- int uid = Binder.getCallingUid();
- int pid = Binder.getCallingPid();
-
- // move calls going to providers onto a different thread to avoid deadlock
- mHandler.post(() -> {
- synchronized (mLock) {
- if (mProvider != null) {
- mProvider.onSendExtraCommand(uid, pid, command, extras);
- }
+ public void setMockProviderLocation(Location location) {
+ synchronized (mLock) {
+ if (!mProvider.isMock()) {
+ throw new IllegalArgumentException(mName + " provider is not a test provider");
}
- });
+
+ String locationProvider = location.getProvider();
+ if (!TextUtils.isEmpty(locationProvider) && !mName.equals(locationProvider)) {
+ // The location has an explicit provider that is different from the mock
+ // provider name. The caller may be trying to fool us via b/33091107.
+ EventLog.writeEvent(0x534e4554, "33091107", Binder.getCallingUid(),
+ mName + "!=" + locationProvider);
+ }
+
+ mProvider.setMockProviderLocation(location);
+ }
+ }
+
+ public List<LocationRequest> getMockProviderRequests() {
+ synchronized (mLock) {
+ if (!mProvider.isMock()) {
+ throw new IllegalArgumentException(mName + " provider is not a test provider");
+ }
+
+ return mProvider.getCurrentRequest().locationRequests;
+ }
+ }
+
+ public void setRequest(ProviderRequest request) {
+ mProvider.setRequest(request);
+ }
+
+ public void sendExtraCommand(int uid, int pid, String command, Bundle extras) {
+ mProvider.sendExtraCommand(uid, pid, command, extras);
}
@GuardedBy("mLock")
- public void dumpLocked(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
- pw.print(mName + " provider");
- if (isMock()) {
- pw.print(" [mock]");
- }
- pw.println(":");
-
- pw.increaseIndent();
-
- pw.println("useable=" + isUseableLocked(mCurrentUserId));
- if (!isUseableLocked(mCurrentUserId)) {
- pw.println("attached=" + (mProvider != null));
- pw.println("enabled=" + mEnabled);
- }
-
- pw.println("properties=" + mProperties);
-
- if (mProvider != null) {
- // in order to be consistent with other provider APIs, this should be run on the
- // location thread... but this likely isn't worth it just for dumping info.
- long identity = Binder.clearCallingIdentity();
- try {
- mProvider.dump(fd, pw, args);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
- pw.decreaseIndent();
- }
-
@Override
public void onReportLocation(Location location) {
- // likelihood of a 0,0 bug is far greater than this being a valid location
- if (!isMock() && location.getLatitude() == 0 && location.getLongitude() == 0) {
- Slog.w(TAG, "blocking 0,0 location from " + mName + " provider");
- return;
+ // don't validate mock locations
+ if (!location.isFromMockProvider()) {
+ if (location.getLatitude() == 0 && location.getLongitude() == 0) {
+ Slog.w(TAG, "blocking 0,0 location from " + mName + " provider");
+ return;
+ }
}
- synchronized (mLock) {
- handleLocationChangedLocked(location, this);
- }
+ handleLocationChangedLocked(location, this);
}
+ @GuardedBy("mLock")
@Override
public void onReportLocation(List<Location> locations) {
if (mGnssManagerService == null) {
return;
}
- synchronized (mLock) {
- LocationProviderManager gpsProvider = getLocationProviderLocked(GPS_PROVIDER);
- if (gpsProvider == null || !gpsProvider.isUseableLocked()) {
- Slog.w(TAG, "reportLocationBatch() called without user permission");
- return;
- }
- mGnssManagerService.onReportLocation(locations);
+ if (!GPS_PROVIDER.equals(mName) || !isUseable()) {
+ Slog.w(TAG, "reportLocationBatch() called without user permission");
+ return;
}
- }
- @Override
- public void onSetEnabled(boolean enabled) {
- synchronized (mLock) {
- if (enabled == mEnabled) {
- return;
- }
-
- if (D) {
- Log.d(TAG, mName + " provider enabled is now " + mEnabled);
- }
-
- mEnabled = enabled;
-
- // it would be more correct to call this for all users, but we know this can only
- // affect the current user since providers are disabled for non-current users
- onUseableChangedLocked(mCurrentUserId);
- }
- }
-
- @Override
- public void onSetProperties(ProviderProperties properties) {
- synchronized (mLock) {
- mProperties = properties;
- }
+ mGnssManagerService.onReportLocation(locations);
}
@GuardedBy("mLock")
- public boolean isUseableLocked() {
- return isUseableLocked(mCurrentUserId);
+ @Override
+ public void onStateChanged(State oldState, State newState) {
+ if (oldState.enabled != newState.enabled) {
+ // it would be more correct to call this for all users, but we know this can
+ // only affect the current user since providers are disabled for non-current
+ // users
+ onUseableChangedLocked(mUserInfoStore.getCurrentUserId());
+ }
}
- @GuardedBy("mLock")
- public boolean isUseableLocked(int userId) {
- return mUseable.get(userId, Boolean.FALSE);
+ public boolean isUseable() {
+ return isUseable(mUserInfoStore.getCurrentUserId());
+ }
+
+ public boolean isUseable(int userId) {
+ synchronized (mLock) {
+ // normalize user id to always refer to parent since profile state is always the
+ // same as parent state
+ userId = mUserInfoStore.getParentUserId(userId);
+
+ return mUseable.get(userId, Boolean.FALSE);
+ }
}
@GuardedBy("mLock")
public void onUseableChangedLocked(int userId) {
+ if (userId == UserHandle.USER_NULL) {
+ // only used during initialization - we don't care about the null user
+ return;
+ }
+
+ // normalize user id to always refer to parent since profile state is always the same
+ // as parent state
+ userId = mUserInfoStore.getParentUserId(userId);
+
// if any property that contributes to "useability" here changes state, it MUST result
// in a direct or indrect call to onUseableChangedLocked. this allows the provider to
// guarantee that it will always eventually reach the correct state.
- boolean useable = mProvider != null && mProviders.contains(this)
- && isCurrentProfileLocked(userId) && isLocationEnabledForUser(userId)
- && mEnabled;
+ boolean useable = (userId == mUserInfoStore.getCurrentUserId())
+ && mSettingsStore.isLocationEnabled(userId) && mProvider.getState().enabled;
- if (useable == isUseableLocked(userId)) {
+ if (useable == isUseable(userId)) {
return;
}
+
mUseable.put(userId, useable);
if (D) {
@@ -1007,11 +927,7 @@
// fused and passive provider never get public updates for legacy reasons
if (!FUSED_PROVIDER.equals(mName) && !PASSIVE_PROVIDER.equals(mName)) {
// update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility
- Settings.Secure.putStringForUser(
- mContext.getContentResolver(),
- Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
- (useable ? "+" : "-") + mName,
- userId);
+ mSettingsStore.setLocationProviderAllowed(mName, useable, userId);
Intent intent = new Intent(LocationManager.PROVIDERS_CHANGED_ACTION);
intent.putExtra(LocationManager.EXTRA_PROVIDER_NAME, mName);
@@ -1029,55 +945,64 @@
updateProviderUseableLocked(this);
}
+
+ public void dump(FileDescriptor fd, IndentingPrintWriter pw, String[] args) {
+ synchronized (mLock) {
+ pw.print(mName + " provider");
+ if (mProvider.isMock()) {
+ pw.print(" [mock]");
+ }
+ pw.println(":");
+
+ pw.increaseIndent();
+
+ boolean useable = isUseable();
+ pw.println("useable=" + useable);
+ if (!useable) {
+ pw.println("enabled=" + mProvider.getState().enabled);
+ }
+
+ pw.println("properties=" + mProvider.getState().properties);
+ }
+
+ mProvider.dump(fd, pw, args);
+
+ pw.decreaseIndent();
+ }
}
- private class MockLocationProvider extends LocationProviderManager {
+ class PassiveLocationProviderManager extends LocationProviderManager {
- private ProviderRequest mCurrentRequest;
-
- private MockLocationProvider(String name) {
- super(name);
+ private PassiveLocationProviderManager() {
+ super(PASSIVE_PROVIDER);
}
@Override
- public void attachLocked(AbstractLocationProvider provider) {
- checkState(provider instanceof MockProvider);
- super.attachLocked(provider);
+ public void setRealProvider(AbstractLocationProvider provider) {
+ Preconditions.checkArgument(provider instanceof PassiveProvider);
+ super.setRealProvider(provider);
}
- public boolean isMock() {
- return true;
+ @Override
+ public void setMockProvider(@Nullable MockProvider provider) {
+ if (provider != null) {
+ throw new IllegalArgumentException("Cannot mock the passive provider");
+ }
}
- @GuardedBy("mLock")
- public void setEnabledLocked(boolean enabled) {
- if (mProvider != null) {
+ public void updateLocation(Location location) {
+ synchronized (mLock) {
+ PassiveProvider passiveProvider = (PassiveProvider) mProvider.getProvider();
+ Preconditions.checkState(passiveProvider != null);
+
long identity = Binder.clearCallingIdentity();
try {
- ((MockProvider) mProvider).setEnabled(enabled);
+ passiveProvider.updateLocation(location);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
}
-
- @GuardedBy("mLock")
- public void setLocationLocked(Location location) {
- if (mProvider != null) {
- long identity = Binder.clearCallingIdentity();
- try {
- ((MockProvider) mProvider).setLocation(location);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
- }
-
- @Override
- public void setRequest(ProviderRequest request, WorkSource workSource) {
- super.setRequest(request, workSource);
- mCurrentRequest = request;
- }
}
/**
@@ -1181,17 +1106,17 @@
// See if receiver has any enabled update records. Also note if any update records
// are high power (has a high power provider with an interval under a threshold).
for (UpdateRecord updateRecord : mUpdateRecords.values()) {
- LocationProviderManager provider = getLocationProviderLocked(
+ LocationProviderManager manager = getLocationProviderManager(
updateRecord.mProvider);
- if (provider == null) {
+ if (manager == null) {
continue;
}
- if (!provider.isUseableLocked() && !isSettingsExemptLocked(updateRecord)) {
+ if (!manager.isUseable() && !isSettingsExemptLocked(updateRecord)) {
continue;
}
requestingLocation = true;
- ProviderProperties properties = provider.getPropertiesLocked();
+ ProviderProperties properties = manager.getProperties();
if (properties != null
&& properties.mPowerRequirement == Criteria.POWER_HIGH
&& updateRecord.mRequest.getInterval() < HIGH_POWER_INTERVAL_MS) {
@@ -1432,7 +1357,7 @@
String featureId, String listenerIdentifier) {
Objects.requireNonNull(listenerIdentifier);
- return mGnssManagerService == null ? false : mGnssManagerService.addGnssBatchingCallback(
+ return mGnssManagerService != null && mGnssManagerService.addGnssBatchingCallback(
callback, packageName, featureId, listenerIdentifier);
}
@@ -1443,7 +1368,7 @@
@Override
public boolean startGnssBatch(long periodNanos, boolean wakeOnFifoFull, String packageName) {
- return mGnssManagerService == null ? false : mGnssManagerService.startGnssBatch(periodNanos,
+ return mGnssManagerService != null && mGnssManagerService.startGnssBatch(periodNanos,
wakeOnFifoFull, packageName);
}
@@ -1454,35 +1379,14 @@
@Override
public boolean stopGnssBatch() {
- return mGnssManagerService == null ? false : mGnssManagerService.stopGnssBatch();
+ return mGnssManagerService != null && mGnssManagerService.stopGnssBatch();
}
- @GuardedBy("mLock")
- private void addProviderLocked(LocationProviderManager provider) {
- Preconditions.checkState(getLocationProviderLocked(provider.getName()) == null);
-
- mProviders.add(provider);
-
- // it would be more correct to call this for all users, but we know this can only
- // affect the current user since providers are disabled for non-current users
- provider.onUseableChangedLocked(mCurrentUserId);
- }
-
- @GuardedBy("mLock")
- private void removeProviderLocked(LocationProviderManager provider) {
- if (mProviders.remove(provider)) {
- // it would be more correct to call this for all users, but we know this can only
- // affect the current user since providers are disabled for non-current users
- provider.onUseableChangedLocked(mCurrentUserId);
- }
- }
-
- @GuardedBy("mLock")
@Nullable
- private LocationProviderManager getLocationProviderLocked(String providerName) {
- for (LocationProviderManager provider : mProviders) {
- if (providerName.equals(provider.getName())) {
- return provider;
+ private LocationProviderManager getLocationProviderManager(String providerName) {
+ for (LocationProviderManager manager : mProviderManagers) {
+ if (providerName.equals(manager.getName())) {
+ return manager;
}
}
@@ -1531,12 +1435,12 @@
// network and fused providers are ok with COARSE or FINE
return RESOLUTION_LEVEL_COARSE;
} else {
- for (LocationProviderManager lp : mProviders) {
+ for (LocationProviderManager lp : mProviderManagers) {
if (!lp.getName().equals(provider)) {
continue;
}
- ProviderProperties properties = lp.getPropertiesLocked();
+ ProviderProperties properties = lp.getProperties();
if (properties != null) {
if (properties.mRequiresSatellite) {
// provider requiring satellites require FINE permission
@@ -1587,11 +1491,9 @@
case RESOLUTION_LEVEL_COARSE:
return AppOpsManager.OPSTR_COARSE_LOCATION;
case RESOLUTION_LEVEL_FINE:
- return AppOpsManager.OPSTR_FINE_LOCATION;
+ // fall through
case RESOLUTION_LEVEL_NONE:
- // The client is not allowed to get any location, so both FINE and COARSE ops will
- // be denied. Pick the most restrictive one to be safe.
- return AppOpsManager.OPSTR_FINE_LOCATION;
+ // fall through
default:
// Use the most restrictive ops if not sure.
return AppOpsManager.OPSTR_FINE_LOCATION;
@@ -1629,17 +1531,14 @@
*/
@Override
public List<String> getAllProviders() {
- synchronized (mLock) {
- ArrayList<String> providers = new ArrayList<>(mProviders.size());
- for (LocationProviderManager provider : mProviders) {
- String name = provider.getName();
- if (FUSED_PROVIDER.equals(name)) {
- continue;
- }
- providers.add(name);
+ ArrayList<String> providers = new ArrayList<>(mProviderManagers.size());
+ for (LocationProviderManager manager : mProviderManagers) {
+ if (FUSED_PROVIDER.equals(manager.getName())) {
+ continue;
}
- return providers;
+ providers.add(manager.getName());
}
+ return providers;
}
/**
@@ -1651,21 +1550,21 @@
public List<String> getProviders(Criteria criteria, boolean enabledOnly) {
int allowedResolutionLevel = getCallerAllowedResolutionLevel();
synchronized (mLock) {
- ArrayList<String> providers = new ArrayList<>(mProviders.size());
- for (LocationProviderManager provider : mProviders) {
- String name = provider.getName();
+ ArrayList<String> providers = new ArrayList<>(mProviderManagers.size());
+ for (LocationProviderManager manager : mProviderManagers) {
+ String name = manager.getName();
if (FUSED_PROVIDER.equals(name)) {
continue;
}
if (allowedResolutionLevel < getMinimumResolutionLevelForProviderUseLocked(name)) {
continue;
}
- if (enabledOnly && !provider.isUseableLocked()) {
+ if (enabledOnly && !manager.isUseable()) {
continue;
}
if (criteria != null
&& !android.location.LocationProvider.propertiesMeetCriteria(
- name, provider.getPropertiesLocked(), criteria)) {
+ name, manager.getProperties(), criteria)) {
continue;
}
providers.add(name);
@@ -1702,15 +1601,15 @@
}
@GuardedBy("mLock")
- private void updateProviderUseableLocked(LocationProviderManager provider) {
- boolean useable = provider.isUseableLocked();
+ private void updateProviderUseableLocked(LocationProviderManager manager) {
+ boolean useable = manager.isUseable();
ArrayList<Receiver> deadReceivers = null;
- ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName());
+ ArrayList<UpdateRecord> records = mRecordsByProvider.get(manager.getName());
if (records != null) {
for (UpdateRecord record : records) {
- if (!isCurrentProfileLocked(
+ if (!mUserInfoStore.isCurrentUserOrProfile(
UserHandle.getUserId(record.mReceiver.mCallerIdentity.mUid))) {
continue;
}
@@ -1721,7 +1620,7 @@
}
// Sends a notification message to the receiver
- if (!record.mReceiver.callProviderEnabledLocked(provider.getName(), useable)) {
+ if (!record.mReceiver.callProviderEnabledLocked(manager.getName(), useable)) {
if (deadReceivers == null) {
deadReceivers = new ArrayList<>();
}
@@ -1736,26 +1635,25 @@
}
}
- applyRequirementsLocked(provider);
+ applyRequirementsLocked(manager);
}
@GuardedBy("mLock")
private void applyRequirementsLocked(String providerName) {
- LocationProviderManager provider = getLocationProviderLocked(providerName);
- if (provider != null) {
- applyRequirementsLocked(provider);
+ LocationProviderManager manager = getLocationProviderManager(providerName);
+ if (manager != null) {
+ applyRequirementsLocked(manager);
}
}
@GuardedBy("mLock")
- private void applyRequirementsLocked(LocationProviderManager provider) {
- ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName());
- WorkSource worksource = new WorkSource();
- ProviderRequest providerRequest = new ProviderRequest();
+ private void applyRequirementsLocked(LocationProviderManager manager) {
+ ArrayList<UpdateRecord> records = mRecordsByProvider.get(manager.getName());
+ ProviderRequest.Builder providerRequest = new ProviderRequest.Builder();
// if provider is not active, it should not respond to requests
- if (mProviders.contains(provider) && records != null && !records.isEmpty()) {
+ if (mProviderManagers.contains(manager) && records != null && !records.isEmpty()) {
long backgroundThrottleInterval;
long identity = Binder.clearCallingIdentity();
@@ -1765,6 +1663,8 @@
Binder.restoreCallingIdentity(identity);
}
+ ArrayList<LocationRequest> requests = new ArrayList<>(records.size());
+
final boolean isForegroundOnlyMode =
mBatterySaverMode == PowerManager.LOCATION_MODE_FOREGROUND_ONLY;
final boolean shouldThrottleRequests =
@@ -1772,9 +1672,9 @@
== PowerManager.LOCATION_MODE_THROTTLE_REQUESTS_WHEN_SCREEN_OFF
&& !mPowerManager.isInteractive();
// initialize the low power mode to true and set to false if any of the records requires
- providerRequest.lowPowerMode = true;
+ providerRequest.setLowPowerMode(true);
for (UpdateRecord record : records) {
- if (!isCurrentProfileLocked(
+ if (!mUserInfoStore.isCurrentUserOrProfile(
UserHandle.getUserId(record.mReceiver.mCallerIdentity.mUid))) {
continue;
}
@@ -1787,10 +1687,10 @@
}
final boolean isBatterySaverDisablingLocation = shouldThrottleRequests
|| (isForegroundOnlyMode && !record.mIsForegroundUid);
- if (!provider.isUseableLocked() || isBatterySaverDisablingLocation) {
+ if (!manager.isUseable() || isBatterySaverDisablingLocation) {
if (isSettingsExemptLocked(record)) {
- providerRequest.locationSettingsIgnored = true;
- providerRequest.lowPowerMode = false;
+ providerRequest.setLocationSettingsIgnored(true);
+ providerRequest.setLowPowerMode(false);
} else {
continue;
}
@@ -1801,7 +1701,7 @@
// if we're forcing location, don't apply any throttling
- if (!providerRequest.locationSettingsIgnored && !isThrottlingExemptLocked(
+ if (!providerRequest.isLocationSettingsIgnored() && !isThrottlingExemptLocked(
record.mReceiver.mCallerIdentity)) {
if (!record.mIsForegroundUid) {
interval = Math.max(interval, backgroundThrottleInterval);
@@ -1813,42 +1713,44 @@
}
record.mRequest = locationRequest;
- providerRequest.locationRequests.add(locationRequest);
+ requests.add(locationRequest);
if (!locationRequest.isLowPowerMode()) {
- providerRequest.lowPowerMode = false;
+ providerRequest.setLowPowerMode(false);
}
- if (interval < providerRequest.interval) {
- providerRequest.reportLocation = true;
- providerRequest.interval = interval;
+ if (interval < providerRequest.getInterval()) {
+ providerRequest.setInterval(interval);
}
}
- if (providerRequest.reportLocation) {
+ providerRequest.setLocationRequests(requests);
+
+ if (providerRequest.getInterval() < Long.MAX_VALUE) {
// calculate who to blame for power
// This is somewhat arbitrary. We pick a threshold interval
// that is slightly higher that the minimum interval, and
// spread the blame across all applications with a request
// under that threshold.
- long thresholdInterval = (providerRequest.interval + 1000) * 3 / 2;
+ // TODO: overflow
+ long thresholdInterval = (providerRequest.getInterval() + 1000) * 3 / 2;
for (UpdateRecord record : records) {
- if (isCurrentProfileLocked(
+ if (mUserInfoStore.isCurrentUserOrProfile(
UserHandle.getUserId(record.mReceiver.mCallerIdentity.mUid))) {
LocationRequest locationRequest = record.mRequest;
// Don't assign battery blame for update records whose
// client has no permission to receive location data.
- if (!providerRequest.locationRequests.contains(locationRequest)) {
+ if (!providerRequest.getLocationRequests().contains(locationRequest)) {
continue;
}
if (locationRequest.getInterval() <= thresholdInterval) {
if (record.mReceiver.mWorkSource != null
&& isValidWorkSource(record.mReceiver.mWorkSource)) {
- worksource.add(record.mReceiver.mWorkSource);
+ providerRequest.getWorkSource().add(record.mReceiver.mWorkSource);
} else {
// Assign blame to caller if there's no WorkSource associated with
// the request or if it's invalid.
- worksource.add(
+ providerRequest.getWorkSource().add(
record.mReceiver.mCallerIdentity.mUid,
record.mReceiver.mCallerIdentity.mPackageName);
}
@@ -1858,7 +1760,7 @@
}
}
- provider.setRequest(providerRequest, worksource);
+ manager.setRequest(providerRequest.build());
}
/**
@@ -2198,8 +2100,8 @@
throw new IllegalArgumentException("provider name must not be null");
}
- LocationProviderManager provider = getLocationProviderLocked(name);
- if (provider == null) {
+ LocationProviderManager manager = getLocationProviderManager(name);
+ if (manager == null) {
throw new IllegalArgumentException("provider doesn't exist: " + name);
}
@@ -2217,7 +2119,7 @@
oldRecord.disposeLocked(false);
}
- if (!provider.isUseableLocked() && !isSettingsExemptLocked(record)) {
+ if (!manager.isUseable() && !isSettingsExemptLocked(record)) {
// Notify the listener that updates are currently disabled - but only if the request
// does not ignore location settings
receiver.callProviderEnabledLocked(name, false);
@@ -2320,16 +2222,16 @@
// or use the fused provider
String name = request.getProvider();
if (name == null) name = LocationManager.FUSED_PROVIDER;
- LocationProviderManager provider = getLocationProviderLocked(name);
- if (provider == null) return null;
+ LocationProviderManager manager = getLocationProviderManager(name);
+ if (manager == null) return null;
// only the current user or location providers may get location this way
- if (!isCurrentProfileLocked(UserHandle.getUserId(uid)) && !isProviderPackage(
- packageName)) {
+ if (!mUserInfoStore.isCurrentUserOrProfile(UserHandle.getUserId(uid))
+ && !isProviderPackage(packageName)) {
return null;
}
- if (!provider.isUseableLocked()) {
+ if (!manager.isUseable()) {
return null;
}
@@ -2450,19 +2352,19 @@
"Access Fine Location permission not granted to inject Location");
synchronized (mLock) {
- LocationProviderManager provider = getLocationProviderLocked(location.getProvider());
- if (provider == null || !provider.isUseableLocked()) {
+ LocationProviderManager manager = getLocationProviderManager(location.getProvider());
+ if (manager == null || !manager.isUseable()) {
return false;
}
// NOTE: If last location is already available, location is not injected. If
// provider's normal source (like a GPS chipset) have already provided an output
// there is no need to inject this location.
- if (mLastLocation.get(provider.getName()) != null) {
+ if (mLastLocation.get(manager.getName()) != null) {
return false;
}
- updateLastLocationLocked(location, provider.getName());
+ updateLastLocationLocked(location, manager.getName());
return true;
}
}
@@ -2511,7 +2413,7 @@
packageName,
request,
/* hasListener= */ false,
- intent != null,
+ true,
geofence,
mActivityManager.getPackageImportance(packageName));
}
@@ -2542,7 +2444,7 @@
packageName,
/* LocationRequest= */ null,
/* hasListener= */ false,
- intent != null,
+ true,
geofence,
mActivityManager.getPackageImportance(packageName));
}
@@ -2555,7 +2457,7 @@
@Override
public boolean registerGnssStatusCallback(IGnssStatusListener listener, String packageName,
String featureId) {
- return mGnssManagerService == null ? false : mGnssManagerService.registerGnssStatusCallback(
+ return mGnssManagerService != null && mGnssManagerService.registerGnssStatusCallback(
listener, packageName, featureId);
}
@@ -2569,9 +2471,8 @@
String packageName, String featureId, String listenerIdentifier) {
Objects.requireNonNull(listenerIdentifier);
- return mGnssManagerService == null ? false
- : mGnssManagerService.addGnssMeasurementsListener(listener, packageName, featureId,
- listenerIdentifier);
+ return mGnssManagerService != null && mGnssManagerService.addGnssMeasurementsListener(
+ listener, packageName, featureId, listenerIdentifier);
}
@Override
@@ -2586,8 +2487,8 @@
public void injectGnssMeasurementCorrections(
GnssMeasurementCorrections measurementCorrections, String packageName) {
if (mGnssManagerService != null) {
- mGnssManagerService.injectGnssMeasurementCorrections(
- measurementCorrections, packageName);
+ mGnssManagerService.injectGnssMeasurementCorrections(measurementCorrections,
+ packageName);
}
}
@@ -2602,9 +2503,8 @@
String packageName, String featureId, String listenerIdentifier) {
Objects.requireNonNull(listenerIdentifier);
- return mGnssManagerService == null ? false
- : mGnssManagerService.addGnssNavigationMessageListener(listener, packageName,
- featureId, listenerIdentifier);
+ return mGnssManagerService != null && mGnssManagerService.addGnssNavigationMessageListener(
+ listener, packageName, featureId, listenerIdentifier);
}
@Override
@@ -2634,9 +2534,10 @@
LocationStatsEnums.API_SEND_EXTRA_COMMAND,
providerName);
- LocationProviderManager provider = getLocationProviderLocked(providerName);
- if (provider != null) {
- provider.sendExtraCommand(command, extras);
+ LocationProviderManager manager = getLocationProviderManager(providerName);
+ if (manager != null) {
+ manager.sendExtraCommand(Binder.getCallingUid(), Binder.getCallingPid(), command,
+ extras);
}
mLocationUsageLogger.logLocationApiUsage(
@@ -2650,43 +2551,37 @@
@Override
public boolean sendNiResponse(int notifId, int userResponse) {
- return mGnssManagerService == null ? false : mGnssManagerService.sendNiResponse(notifId,
+ return mGnssManagerService != null && mGnssManagerService.sendNiResponse(notifId,
userResponse);
}
@Override
public ProviderProperties getProviderProperties(String providerName) {
- synchronized (mLock) {
- LocationProviderManager provider = getLocationProviderLocked(providerName);
- if (provider == null) {
- return null;
- }
- return provider.getPropertiesLocked();
+ LocationProviderManager manager = getLocationProviderManager(providerName);
+ if (manager == null) {
+ return null;
}
+ return manager.getProperties();
}
@Override
public boolean isProviderPackage(String packageName) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_DEVICE_CONFIG,
Manifest.permission.READ_DEVICE_CONFIG + " permission required");
- synchronized (mLock) {
- for (LocationProviderManager provider : mProviders) {
- if (provider.getPackagesLocked().contains(packageName)) {
- return true;
- }
+ for (LocationProviderManager manager : mProviderManagers) {
+ if (manager.getPackages().contains(packageName)) {
+ return true;
}
- return false;
}
+ return false;
}
@Override
public List<String> getProviderPackages(String providerName) {
mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_DEVICE_CONFIG,
Manifest.permission.READ_DEVICE_CONFIG + " permission required");
- synchronized (mLock) {
- LocationProviderManager provider = getLocationProviderLocked(providerName);
- return provider == null ? Collections.emptyList() : provider.getPackagesLocked();
- }
+ LocationProviderManager manager = getLocationProviderManager(providerName);
+ return manager == null ? Collections.emptyList() : new ArrayList<>(manager.getPackages());
}
@Override
@@ -2753,8 +2648,8 @@
if (FUSED_PROVIDER.equals(providerName)) return false;
synchronized (mLock) {
- LocationProviderManager provider = getLocationProviderLocked(providerName);
- return provider != null && provider.isUseableLocked(userId);
+ LocationProviderManager manager = getLocationProviderManager(providerName);
+ return manager != null && manager.isUseable(userId);
}
}
@@ -2792,37 +2687,39 @@
}
@GuardedBy("mLock")
- private void handleLocationChangedLocked(Location location, LocationProviderManager provider) {
- if (!mProviders.contains(provider)) {
+ private void handleLocationChangedLocked(Location location, LocationProviderManager manager) {
+ if (!mProviderManagers.contains(manager)) {
+ Log.w(TAG, "received location from unknown provider: " + manager.getName());
return;
}
if (!location.isComplete()) {
- Log.w(TAG, "Dropping incomplete location: " + location);
+ Log.w(TAG, "dropping incomplete location from " + manager.getName() + " provider: "
+ + location);
return;
}
- // only notify passive provider and update last location for locations that come from
- // useable providers
- if (provider.isUseableLocked()) {
- if (!provider.isPassiveLocked()) {
- mPassiveProvider.updateLocation(location);
- }
+ // notify passive provider
+ if (manager != mPassiveManager) {
+ mPassiveManager.updateLocation(new Location(location));
}
if (D) Log.d(TAG, "incoming location: " + location);
long now = SystemClock.elapsedRealtime();
- if (provider.isUseableLocked()) {
- updateLastLocationLocked(location, provider.getName());
+
+
+ // only update last location for locations that come from useable providers
+ if (manager.isUseable()) {
+ updateLastLocationLocked(location, manager.getName());
}
// Update last known coarse interval location if enough time has passed.
Location lastLocationCoarseInterval = mLastLocationCoarseInterval.get(
- provider.getName());
+ manager.getName());
if (lastLocationCoarseInterval == null) {
lastLocationCoarseInterval = new Location(location);
- if (provider.isUseableLocked()) {
- mLastLocationCoarseInterval.put(provider.getName(), lastLocationCoarseInterval);
+ if (manager.isUseable()) {
+ mLastLocationCoarseInterval.put(manager.getName(), lastLocationCoarseInterval);
}
}
long timeDeltaMs = TimeUnit.NANOSECONDS.toMillis(location.getElapsedRealtimeNanos()
@@ -2837,7 +2734,7 @@
lastLocationCoarseInterval.getExtraLocation(Location.EXTRA_NO_GPS_LOCATION);
// Skip if there are no UpdateRecords for this provider.
- ArrayList<UpdateRecord> records = mRecordsByProvider.get(provider.getName());
+ ArrayList<UpdateRecord> records = mRecordsByProvider.get(manager.getName());
if (records == null || records.size() == 0) return;
// Fetch coarse location
@@ -2854,17 +2751,16 @@
Receiver receiver = r.mReceiver;
boolean receiverDead = false;
- if (!provider.isUseableLocked() && !isSettingsExemptLocked(r)) {
+ if (!manager.isUseable() && !isSettingsExemptLocked(r)) {
continue;
}
int receiverUserId = UserHandle.getUserId(receiver.mCallerIdentity.mUid);
- if (!isCurrentProfileLocked(receiverUserId)
+ if (!mUserInfoStore.isCurrentUserOrProfile(receiverUserId)
&& !isProviderPackage(receiver.mCallerIdentity.mPackageName)) {
if (D) {
Log.d(TAG, "skipping loc update for background user " + receiverUserId +
- " (current user: " + mCurrentUserId + ", app: " +
- receiver.mCallerIdentity.mPackageName + ")");
+ " (app: " + receiver.mCallerIdentity.mPackageName + ")");
}
continue;
}
@@ -2949,7 +2845,7 @@
for (UpdateRecord r : deadUpdateRecords) {
r.disposeLocked(true);
}
- applyRequirementsLocked(provider);
+ applyRequirementsLocked(manager);
}
}
@@ -3006,143 +2902,99 @@
// Mock Providers
- private boolean canCallerAccessMockLocation(String opPackageName) {
- return mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(),
- opPackageName) == AppOpsManager.MODE_ALLOWED;
- }
-
@Override
- public void addTestProvider(String name, ProviderProperties properties, String opPackageName) {
- if (!canCallerAccessMockLocation(opPackageName)) {
+ public void addTestProvider(String provider, ProviderProperties properties,
+ String packageName) {
+ if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName)
+ != AppOpsManager.MODE_ALLOWED) {
return;
}
- if (PASSIVE_PROVIDER.equals(name)) {
- throw new IllegalArgumentException("Cannot mock the passive location provider");
+ synchronized (mLock) {
+ LocationProviderManager manager = getLocationProviderManager(provider);
+ if (manager == null) {
+ manager = new LocationProviderManager(provider);
+ mProviderManagers.add(manager);
+ }
+
+ manager.setMockProvider(new MockProvider(mContext, properties));
+ }
+ }
+
+ @Override
+ public void removeTestProvider(String provider, String packageName) {
+ if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName)
+ != AppOpsManager.MODE_ALLOWED) {
+ return;
}
synchronized (mLock) {
- long identity = Binder.clearCallingIdentity();
- try {
- LocationProviderManager oldProvider = getLocationProviderLocked(name);
- if (oldProvider != null) {
- removeProviderLocked(oldProvider);
- }
+ LocationProviderManager manager = getLocationProviderManager(provider);
+ if (manager == null) {
+ return;
+ }
- MockLocationProvider mockProviderManager = new MockLocationProvider(name);
- addProviderLocked(mockProviderManager);
- mockProviderManager.attachLocked(
- new MockProvider(mContext, mockProviderManager, properties));
- } finally {
- Binder.restoreCallingIdentity(identity);
+ manager.setMockProvider(null);
+ if (!manager.hasProvider()) {
+ mProviderManagers.remove(manager);
+ mLastLocation.remove(manager.getName());
+ mLastLocationCoarseInterval.remove(manager.getName());
}
}
}
@Override
- public void removeTestProvider(String name, String opPackageName) {
- if (!canCallerAccessMockLocation(opPackageName)) {
+ public void setTestProviderLocation(String provider, Location location, String packageName) {
+ if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName)
+ != AppOpsManager.MODE_ALLOWED) {
return;
}
- synchronized (mLock) {
- long identity = Binder.clearCallingIdentity();
- try {
- LocationProviderManager testProvider = getLocationProviderLocked(name);
- if (testProvider == null || !testProvider.isMock()) {
- return;
- }
-
- removeProviderLocked(testProvider);
-
- // reinstate real provider if available
- LocationProviderManager realProvider = null;
- for (LocationProviderManager provider : mRealProviders) {
- if (name.equals(provider.getName())) {
- realProvider = provider;
- break;
- }
- }
-
- if (realProvider != null) {
- addProviderLocked(realProvider);
- }
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
+ LocationProviderManager manager = getLocationProviderManager(provider);
+ if (manager == null) {
+ throw new IllegalArgumentException("provider doesn't exist: " + provider);
}
+
+ manager.setMockProviderLocation(location);
}
@Override
- public void setTestProviderLocation(String providerName, Location location,
- String opPackageName) {
- if (!canCallerAccessMockLocation(opPackageName)) {
+ public void setTestProviderEnabled(String provider, boolean enabled, String packageName) {
+ if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName)
+ != AppOpsManager.MODE_ALLOWED) {
return;
}
- synchronized (mLock) {
- LocationProviderManager testProvider = getLocationProviderLocked(providerName);
- if (testProvider == null || !testProvider.isMock()) {
- throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown");
- }
-
- String locationProvider = location.getProvider();
- if (!TextUtils.isEmpty(locationProvider) && !providerName.equals(locationProvider)) {
- // The location has an explicit provider that is different from the mock
- // provider name. The caller may be trying to fool us via b/33091107.
- EventLog.writeEvent(0x534e4554, "33091107", Binder.getCallingUid(),
- providerName + "!=" + location.getProvider());
- }
-
- ((MockLocationProvider) testProvider).setLocationLocked(location);
- }
- }
-
- @Override
- public void setTestProviderEnabled(String providerName, boolean enabled, String opPackageName) {
- if (!canCallerAccessMockLocation(opPackageName)) {
- return;
+ LocationProviderManager manager = getLocationProviderManager(provider);
+ if (manager == null) {
+ throw new IllegalArgumentException("provider doesn't exist: " + provider);
}
- synchronized (mLock) {
- LocationProviderManager testProvider = getLocationProviderLocked(providerName);
- if (testProvider == null || !testProvider.isMock()) {
- throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown");
- }
-
- ((MockLocationProvider) testProvider).setEnabledLocked(enabled);
- }
+ manager.setMockProviderEnabled(enabled);
}
@Override
@NonNull
- public List<LocationRequest> getTestProviderCurrentRequests(String providerName,
- String opPackageName) {
- if (!canCallerAccessMockLocation(opPackageName)) {
+ public List<LocationRequest> getTestProviderCurrentRequests(String provider,
+ String packageName) {
+ if (mAppOps.checkOp(AppOpsManager.OP_MOCK_LOCATION, Binder.getCallingUid(), packageName)
+ != AppOpsManager.MODE_ALLOWED) {
return Collections.emptyList();
}
- synchronized (mLock) {
- LocationProviderManager testProvider = getLocationProviderLocked(providerName);
- if (testProvider == null || !testProvider.isMock()) {
- throw new IllegalArgumentException("Provider \"" + providerName + "\" unknown");
- }
-
- MockLocationProvider provider = (MockLocationProvider) testProvider;
- if (provider.mCurrentRequest == null) {
- return Collections.emptyList();
- }
- List<LocationRequest> requests = new ArrayList<>();
- for (LocationRequest request : provider.mCurrentRequest.locationRequests) {
- requests.add(new LocationRequest(request));
- }
- return requests;
+ LocationProviderManager manager = getLocationProviderManager(provider);
+ if (manager == null) {
+ throw new IllegalArgumentException("provider doesn't exist: " + provider);
}
+
+ return manager.getMockProviderRequests();
}
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+ if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) {
+ return;
+ }
IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
@@ -3158,9 +3010,17 @@
+ TimeUtils.logTimeOfDay(System.currentTimeMillis()));
ipw.println(", Current Elapsed Time: "
+ TimeUtils.formatDuration(SystemClock.elapsedRealtime()));
- ipw.println("Current user: " + mCurrentUserId + " " + Arrays.toString(
- mCurrentUserProfiles));
- ipw.println("Location Mode: " + isLocationEnabledForUser(mCurrentUserId));
+
+ ipw.println("User Info:");
+ ipw.increaseIndent();
+ mUserInfoStore.dump(fd, ipw, args);
+ ipw.decreaseIndent();
+
+ ipw.println("Location Settings:");
+ ipw.increaseIndent();
+ mSettingsStore.dump(fd, ipw, args);
+ ipw.decreaseIndent();
+
ipw.println("Battery Saver Location Mode: "
+ locationPowerSaveModeToString(mBatterySaverMode));
@@ -3192,6 +3052,8 @@
}
ipw.decreaseIndent();
+ mRequestStatistics.history.dump(ipw);
+
ipw.println("Last Known Locations:");
ipw.increaseIndent();
for (Map.Entry<String, Location> entry : mLastLocation.entrySet()) {
@@ -3225,24 +3087,19 @@
ipw.decreaseIndent();
}
- ipw.println("Location Settings:");
- ipw.increaseIndent();
- mSettingsStore.dump(fd, ipw, args);
- ipw.decreaseIndent();
-
ipw.println("Location Providers:");
ipw.increaseIndent();
- for (LocationProviderManager provider : mProviders) {
- provider.dumpLocked(fd, ipw, args);
+ for (LocationProviderManager manager : mProviderManagers) {
+ manager.dump(fd, ipw, args);
}
ipw.decreaseIndent();
- }
- if (mGnssManagerService != null) {
- ipw.println("GNSS:");
- ipw.increaseIndent();
- mGnssManagerService.dump(fd, ipw, args);
- ipw.decreaseIndent();
+ if (mGnssManagerService != null) {
+ ipw.println("GNSS:");
+ ipw.increaseIndent();
+ mGnssManagerService.dump(fd, ipw, args);
+ ipw.decreaseIndent();
+ }
}
}
}
diff --git a/services/core/java/com/android/server/MmsServiceBroker.java b/services/core/java/com/android/server/MmsServiceBroker.java
index 92d1da5..9b1326b 100644
--- a/services/core/java/com/android/server/MmsServiceBroker.java
+++ b/services/core/java/com/android/server/MmsServiceBroker.java
@@ -140,11 +140,6 @@
}
@Override
- public Bundle getCarrierConfigValues(int subId) throws RemoteException {
- return null;
- }
-
- @Override
public Uri importTextMessage(String callingPkg, String address, int type, String text,
long timestampMillis, boolean seen, boolean read) throws RemoteException {
return null;
@@ -373,12 +368,6 @@
}
@Override
- public Bundle getCarrierConfigValues(int subId) throws RemoteException {
- Slog.d(TAG, "getCarrierConfigValues() by " + getCallingPackageName());
- return getServiceGuarded().getCarrierConfigValues(subId);
- }
-
- @Override
public Uri importTextMessage(String callingPkg, String address, int type, String text,
long timestampMillis, boolean seen, boolean read) throws RemoteException {
if (getAppOpsManager().noteOp(AppOpsManager.OP_WRITE_SMS, Binder.getCallingUid(),
diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java
index 1f73650..05d8360 100644
--- a/services/core/java/com/android/server/NetworkManagementService.java
+++ b/services/core/java/com/android/server/NetworkManagementService.java
@@ -58,10 +58,12 @@
import android.net.NetworkStats;
import android.net.NetworkUtils;
import android.net.RouteInfo;
-import android.net.TetherConfigParcel;
import android.net.TetherStatsParcel;
import android.net.UidRange;
import android.net.UidRangeParcel;
+import android.net.shared.NetdUtils;
+import android.net.shared.RouteUtils;
+import android.net.shared.RouteUtils.ModifyOperation;
import android.net.util.NetdService;
import android.os.BatteryStats;
import android.os.Binder;
@@ -898,48 +900,14 @@
@Override
public void addRoute(int netId, RouteInfo route) {
- modifyRoute(MODIFY_OPERATION_ADD, netId, route);
+ NetworkStack.checkNetworkStackPermission(mContext);
+ RouteUtils.modifyRoute(mNetdService, ModifyOperation.ADD, netId, route);
}
@Override
public void removeRoute(int netId, RouteInfo route) {
- modifyRoute(MODIFY_OPERATION_REMOVE, netId, route);
- }
-
- private void modifyRoute(boolean add, int netId, RouteInfo route) {
NetworkStack.checkNetworkStackPermission(mContext);
-
- final String ifName = route.getInterface();
- final String dst = route.getDestination().toString();
- final String nextHop;
-
- switch (route.getType()) {
- case RouteInfo.RTN_UNICAST:
- if (route.hasGateway()) {
- nextHop = route.getGateway().getHostAddress();
- } else {
- nextHop = INetd.NEXTHOP_NONE;
- }
- break;
- case RouteInfo.RTN_UNREACHABLE:
- nextHop = INetd.NEXTHOP_UNREACHABLE;
- break;
- case RouteInfo.RTN_THROW:
- nextHop = INetd.NEXTHOP_THROW;
- break;
- default:
- nextHop = INetd.NEXTHOP_NONE;
- break;
- }
- try {
- if (add) {
- mNetdService.networkAddRoute(netId, ifName, dst, nextHop);
- } else {
- mNetdService.networkRemoveRoute(netId, ifName, dst, nextHop);
- }
- } catch (RemoteException | ServiceSpecificException e) {
- throw new IllegalStateException(e);
- }
+ RouteUtils.modifyRoute(mNetdService, ModifyOperation.REMOVE, netId, route);
}
private ArrayList<String> readRouteList(String filename) {
@@ -1023,12 +991,8 @@
@Override
public void startTetheringWithConfiguration(boolean usingLegacyDnsProxy, String[] dhcpRange) {
NetworkStack.checkNetworkStackPermission(mContext);
- // an odd number of addrs will fail
try {
- final TetherConfigParcel config = new TetherConfigParcel();
- config.usingLegacyDnsProxy = usingLegacyDnsProxy;
- config.dhcpRanges = dhcpRange;
- mNetdService.tetherStartWithConfiguration(config);
+ NetdUtils.tetherStart(mNetdService, usingLegacyDnsProxy, dhcpRange);
} catch (RemoteException | ServiceSpecificException e) {
throw new IllegalStateException(e);
}
@@ -1060,26 +1024,21 @@
public void tetherInterface(String iface) {
NetworkStack.checkNetworkStackPermission(mContext);
try {
- mNetdService.tetherInterfaceAdd(iface);
+ final LinkAddress addr = getInterfaceConfig(iface).getLinkAddress();
+ final IpPrefix dest = new IpPrefix(addr.getAddress(), addr.getPrefixLength());
+ NetdUtils.tetherInterface(mNetdService, iface, dest);
} catch (RemoteException | ServiceSpecificException e) {
throw new IllegalStateException(e);
}
- List<RouteInfo> routes = new ArrayList<>();
- // The RouteInfo constructor truncates the LinkAddress to a network prefix, thus making it
- // suitable to use as a route destination.
- routes.add(new RouteInfo(getInterfaceConfig(iface).getLinkAddress(), null, iface));
- addInterfaceToLocalNetwork(iface, routes);
}
@Override
public void untetherInterface(String iface) {
NetworkStack.checkNetworkStackPermission(mContext);
try {
- mNetdService.tetherInterfaceRemove(iface);
+ NetdUtils.untetherInterface(mNetdService, iface);
} catch (RemoteException | ServiceSpecificException e) {
throw new IllegalStateException(e);
- } finally {
- removeInterfaceFromLocalNetwork(iface);
}
}
@@ -2122,16 +2081,8 @@
@Override
public void addInterfaceToLocalNetwork(String iface, List<RouteInfo> routes) {
modifyInterfaceInNetwork(MODIFY_OPERATION_ADD, INetd.LOCAL_NET_ID, iface);
-
- for (RouteInfo route : routes) {
- if (!route.isDefaultRoute()) {
- modifyRoute(MODIFY_OPERATION_ADD, INetd.LOCAL_NET_ID, route);
- }
- }
-
- // IPv6 link local should be activated always.
- modifyRoute(MODIFY_OPERATION_ADD, INetd.LOCAL_NET_ID,
- new RouteInfo(new IpPrefix("fe80::/64"), null, iface));
+ // modifyInterfaceInNetwork already check calling permission.
+ RouteUtils.addRoutesToLocalNetwork(mNetdService, iface, routes);
}
@Override
@@ -2141,17 +2092,8 @@
@Override
public int removeRoutesFromLocalNetwork(List<RouteInfo> routes) {
- int failures = 0;
-
- for (RouteInfo route : routes) {
- try {
- modifyRoute(MODIFY_OPERATION_REMOVE, INetd.LOCAL_NET_ID, route);
- } catch (IllegalStateException e) {
- failures++;
- }
- }
-
- return failures;
+ NetworkStack.checkNetworkStackPermission(mContext);
+ return RouteUtils.removeRoutesFromLocalNetwork(mNetdService, routes);
}
@Override
diff --git a/services/core/java/com/android/server/NetworkScoreService.java b/services/core/java/com/android/server/NetworkScoreService.java
index b26ef92..d1d1cb3 100644
--- a/services/core/java/com/android/server/NetworkScoreService.java
+++ b/services/core/java/com/android/server/NetworkScoreService.java
@@ -523,7 +523,7 @@
@Override
public void accept(INetworkScoreCache networkScoreCache, Object cookie) {
- int filterType = NetworkScoreManager.CACHE_FILTER_NONE;
+ int filterType = NetworkScoreManager.SCORE_FILTER_NONE;
if (cookie instanceof Integer) {
filterType = (Integer) cookie;
}
@@ -547,17 +547,17 @@
private List<ScoredNetwork> filterScores(List<ScoredNetwork> scoredNetworkList,
int filterType) {
switch (filterType) {
- case NetworkScoreManager.CACHE_FILTER_NONE:
+ case NetworkScoreManager.SCORE_FILTER_NONE:
return scoredNetworkList;
- case NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK:
+ case NetworkScoreManager.SCORE_FILTER_CURRENT_NETWORK:
if (mCurrentNetworkFilter == null) {
mCurrentNetworkFilter =
new CurrentNetworkScoreCacheFilter(new WifiInfoSupplier(mContext));
}
return mCurrentNetworkFilter.apply(scoredNetworkList);
- case NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS:
+ case NetworkScoreManager.SCORE_FILTER_SCAN_RESULTS:
if (mScanResultsFilter == null) {
mScanResultsFilter = new ScanResultsScoreCacheFilter(
new ScanResultsSupplier(mContext));
diff --git a/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java b/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java
index 39be311..7894788 100644
--- a/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/NetworkTimeUpdateServiceImpl.java
@@ -18,6 +18,8 @@
import android.app.AlarmManager;
import android.app.PendingIntent;
+import android.app.timedetector.NetworkTimeSuggestion;
+import android.app.timedetector.TimeDetector;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
@@ -34,8 +36,8 @@
import android.os.Message;
import android.os.PowerManager;
import android.os.SystemClock;
+import android.os.TimestampedValue;
import android.provider.Settings;
-import android.telephony.TelephonyManager;
import android.util.Log;
import android.util.NtpTrustedTime;
import android.util.TimeUtils;
@@ -46,21 +48,19 @@
import java.io.PrintWriter;
/**
- * Monitors the network time and updates the system time if it is out of sync
- * and there hasn't been any NITZ update from the carrier recently.
- * If looking up the network time fails for some reason, it tries a few times with a short
- * interval and then resets to checking on longer intervals.
- * <p>
- * If the user enables AUTO_TIME, it will check immediately for the network time, if NITZ wasn't
- * available.
- * </p>
+ * Monitors the network time. If looking up the network time fails for some reason, it tries a few
+ * times with a short interval and then resets to checking on longer intervals.
+ *
+ * <p>When available, the time is always suggested to the {@link
+ * com.android.server.timedetector.TimeDetectorService} where it may be used to set the device
+ * system clock, depending on user settings and what other signals are available.
*/
public class NetworkTimeUpdateServiceImpl extends Binder implements NetworkTimeUpdateService {
private static final String TAG = "NetworkTimeUpdateService";
private static final boolean DBG = false;
- private static final int EVENT_AUTO_TIME_CHANGED = 1;
+ private static final int EVENT_AUTO_TIME_ENABLED = 1;
private static final int EVENT_POLL_NETWORK_TIME = 2;
private static final int EVENT_NETWORK_CHANGED = 3;
@@ -69,20 +69,19 @@
private static final int POLL_REQUEST = 0;
- private static final long NOT_SET = -1;
- private long mNitzTimeSetTime = NOT_SET;
private Network mDefaultNetwork = null;
private final Context mContext;
private final NtpTrustedTime mTime;
private final AlarmManager mAlarmManager;
+ private final TimeDetector mTimeDetector;
private final ConnectivityManager mCM;
private final PendingIntent mPendingPollIntent;
private final PowerManager.WakeLock mWakeLock;
// NTP lookup is done on this thread and handler
private Handler mHandler;
- private SettingsObserver mSettingsObserver;
+ private AutoTimeSettingObserver mAutoTimeSettingObserver;
private NetworkTimeUpdateCallback mNetworkTimeUpdateCallback;
// Normal polling frequency
@@ -91,8 +90,6 @@
private final long mPollingIntervalShorterMs;
// Number of times to try again
private final int mTryAgainTimesMax;
- // If the time difference is greater than this threshold, then update the time.
- private final int mTimeErrorThresholdMs;
// Keeps track of how many quick attempts were made to fetch NTP time.
// During bootup, the network may not have been up yet, or it's taking time for the
// connection to happen.
@@ -102,6 +99,7 @@
mContext = context;
mTime = NtpTrustedTime.getInstance(context);
mAlarmManager = mContext.getSystemService(AlarmManager.class);
+ mTimeDetector = mContext.getSystemService(TimeDetector.class);
mCM = mContext.getSystemService(ConnectivityManager.class);
Intent pollIntent = new Intent(ACTION_POLL, null);
@@ -113,8 +111,6 @@
com.android.internal.R.integer.config_ntpPollingIntervalShorter);
mTryAgainTimesMax = mContext.getResources().getInteger(
com.android.internal.R.integer.config_ntpRetry);
- mTimeErrorThresholdMs = mContext.getResources().getInteger(
- com.android.internal.R.integer.config_ntpThreshold);
mWakeLock = context.getSystemService(PowerManager.class).newWakeLock(
PowerManager.PARTIAL_WAKE_LOCK, TAG);
@@ -122,7 +118,6 @@
@Override
public void systemRunning() {
- registerForTelephonyIntents();
registerForAlarms();
HandlerThread thread = new HandlerThread(TAG);
@@ -131,14 +126,9 @@
mNetworkTimeUpdateCallback = new NetworkTimeUpdateCallback();
mCM.registerDefaultNetworkCallback(mNetworkTimeUpdateCallback, mHandler);
- mSettingsObserver = new SettingsObserver(mHandler, EVENT_AUTO_TIME_CHANGED);
- mSettingsObserver.observe(mContext);
- }
-
- private void registerForTelephonyIntents() {
- IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(TelephonyManager.ACTION_NETWORK_SET_TIME);
- mContext.registerReceiver(mNitzReceiver, intentFilter);
+ mAutoTimeSettingObserver = new AutoTimeSettingObserver(mContext, mHandler,
+ EVENT_AUTO_TIME_ENABLED);
+ mAutoTimeSettingObserver.observe();
}
private void registerForAlarms() {
@@ -152,8 +142,7 @@
}
private void onPollNetworkTime(int event) {
- // If Automatic time is not set, don't bother. Similarly, if we don't
- // have any default network, don't bother.
+ // If we don't have any default network, don't bother.
if (mDefaultNetwork == null) return;
mWakeLock.acquire();
try {
@@ -165,18 +154,23 @@
private void onPollNetworkTimeUnderWakeLock(int event) {
// Force an NTP fix when outdated
- if (mTime.getCacheAge() >= mPollingIntervalMs) {
+ NtpTrustedTime.TimeResult cachedNtpResult = mTime.getCachedTimeResult();
+ if (cachedNtpResult == null || cachedNtpResult.getAgeMillis() >= mPollingIntervalMs) {
if (DBG) Log.d(TAG, "Stale NTP fix; forcing refresh");
mTime.forceRefresh();
+ cachedNtpResult = mTime.getCachedTimeResult();
}
- if (mTime.getCacheAge() < mPollingIntervalMs) {
+ if (cachedNtpResult != null && cachedNtpResult.getAgeMillis() < mPollingIntervalMs) {
// Obtained fresh fix; schedule next normal update
resetAlarm(mPollingIntervalMs);
- if (isAutomaticTimeRequested()) {
- updateSystemClock(event);
- }
+ // Suggest the time to the time detector. It may choose use it to set the system clock.
+ TimestampedValue<Long> timeSignal = new TimestampedValue<>(
+ cachedNtpResult.getElapsedRealtimeMillis(), cachedNtpResult.getTimeMillis());
+ NetworkTimeSuggestion timeSuggestion = new NetworkTimeSuggestion(timeSignal);
+ timeSuggestion.addDebugInfo("Origin: NetworkTimeUpdateServiceImpl. event=" + event);
+ mTimeDetector.suggestNetworkTime(timeSuggestion);
} else {
// No fresh fix; schedule retry
mTryAgainCounter++;
@@ -190,36 +184,6 @@
}
}
- private long getNitzAge() {
- if (mNitzTimeSetTime == NOT_SET) {
- return Long.MAX_VALUE;
- } else {
- return SystemClock.elapsedRealtime() - mNitzTimeSetTime;
- }
- }
-
- /**
- * Consider updating system clock based on current NTP fix, if requested by
- * user, significant enough delta, and we don't have a recent NITZ.
- */
- private void updateSystemClock(int event) {
- final boolean forceUpdate = (event == EVENT_AUTO_TIME_CHANGED);
- if (!forceUpdate) {
- if (getNitzAge() < mPollingIntervalMs) {
- if (DBG) Log.d(TAG, "Ignoring NTP update due to recent NITZ");
- return;
- }
-
- final long skew = Math.abs(mTime.currentTimeMillis() - System.currentTimeMillis());
- if (skew < mTimeErrorThresholdMs) {
- if (DBG) Log.d(TAG, "Ignoring NTP update due to low skew");
- return;
- }
- }
-
- SystemClock.setCurrentTimeMillis(mTime.currentTimeMillis());
- }
-
/**
* Cancel old alarm and starts a new one for the specified interval.
*
@@ -232,27 +196,6 @@
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, next, mPendingPollIntent);
}
- /**
- * Checks if the user prefers to automatically set the time.
- */
- private boolean isAutomaticTimeRequested() {
- return Settings.Global.getInt(
- mContext.getContentResolver(), Settings.Global.AUTO_TIME, 0) != 0;
- }
-
- /** Receiver for Nitz time events */
- private BroadcastReceiver mNitzReceiver = new BroadcastReceiver() {
-
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (DBG) Log.d(TAG, "Received " + action);
- if (TelephonyManager.ACTION_NETWORK_SET_TIME.equals(action)) {
- mNitzTimeSetTime = SystemClock.elapsedRealtime();
- }
- }
- };
-
/** Handler to do the network accesses on */
private class MyHandler extends Handler {
@@ -263,7 +206,7 @@
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case EVENT_AUTO_TIME_CHANGED:
+ case EVENT_AUTO_TIME_ENABLED:
case EVENT_POLL_NETWORK_TIME:
case EVENT_NETWORK_CHANGED:
onPollNetworkTime(msg.what);
@@ -287,27 +230,42 @@
}
}
- /** Observer to watch for changes to the AUTO_TIME setting */
- private static class SettingsObserver extends ContentObserver {
+ /**
+ * Observer to watch for changes to the AUTO_TIME setting. It only triggers when the setting
+ * is enabled.
+ */
+ private static class AutoTimeSettingObserver extends ContentObserver {
- private int mMsg;
- private Handler mHandler;
+ private final Context mContext;
+ private final int mMsg;
+ private final Handler mHandler;
- SettingsObserver(Handler handler, int msg) {
+ AutoTimeSettingObserver(Context context, Handler handler, int msg) {
super(handler);
+ mContext = context;
mHandler = handler;
mMsg = msg;
}
- void observe(Context context) {
- ContentResolver resolver = context.getContentResolver();
+ void observe() {
+ ContentResolver resolver = mContext.getContentResolver();
resolver.registerContentObserver(Settings.Global.getUriFor(Settings.Global.AUTO_TIME),
false, this);
}
@Override
public void onChange(boolean selfChange) {
- mHandler.obtainMessage(mMsg).sendToTarget();
+ if (isAutomaticTimeEnabled()) {
+ mHandler.obtainMessage(mMsg).sendToTarget();
+ }
+ }
+
+ /**
+ * Checks if the user prefers to automatically set the time.
+ */
+ private boolean isAutomaticTimeEnabled() {
+ ContentResolver resolver = mContext.getContentResolver();
+ return Settings.Global.getInt(resolver, Settings.Global.AUTO_TIME, 0) != 0;
}
}
@@ -319,11 +277,12 @@
pw.print("\nPollingIntervalShorterMs: ");
TimeUtils.formatDuration(mPollingIntervalShorterMs, pw);
pw.println("\nTryAgainTimesMax: " + mTryAgainTimesMax);
- pw.print("TimeErrorThresholdMs: ");
- TimeUtils.formatDuration(mTimeErrorThresholdMs, pw);
pw.println("\nTryAgainCounter: " + mTryAgainCounter);
- pw.println("NTP cache age: " + mTime.getCacheAge());
- pw.println("NTP cache certainty: " + mTime.getCacheCertainty());
+ NtpTrustedTime.TimeResult ntpResult = mTime.getCachedTimeResult();
+ pw.println("NTP cache result: " + ntpResult);
+ if (ntpResult != null) {
+ pw.println("NTP result age: " + ntpResult.getAgeMillis());
+ }
pw.println();
}
}
diff --git a/services/core/java/com/android/server/NsdService.java b/services/core/java/com/android/server/NsdService.java
index b9b7bf7..4a1820a 100644
--- a/services/core/java/com/android/server/NsdService.java
+++ b/services/core/java/com/android/server/NsdService.java
@@ -22,10 +22,10 @@
import android.database.ContentObserver;
import android.net.NetworkStack;
import android.net.Uri;
-import android.net.nsd.DnsSdTxtRecord;
import android.net.nsd.INsdManager;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
+import android.net.util.nsd.DnsSdTxtRecord;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index deff440..b464422 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -29,6 +29,7 @@
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
+import android.os.Process;
import android.os.SystemProperties;
import android.provider.DeviceConfig;
import android.text.TextUtils;
@@ -36,6 +37,7 @@
import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.LongArrayQueue;
+import android.util.MathUtils;
import android.util.Slog;
import android.util.Xml;
@@ -117,6 +119,12 @@
// Whether explicit health checks are enabled or not
private static final boolean DEFAULT_EXPLICIT_HEALTH_CHECK_ENABLED = true;
+ @VisibleForTesting
+ static final int DEFAULT_BOOT_LOOP_TRIGGER_COUNT = 5;
+ static final long DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS = TimeUnit.MINUTES.toMillis(10);
+ private static final String PROP_RESCUE_BOOT_COUNT = "sys.rescue_boot_count";
+ private static final String PROP_RESCUE_BOOT_START = "sys.rescue_boot_start";
+
private long mNumberOfNativeCrashPollsRemaining;
private static final int DB_VERSION = 1;
@@ -152,6 +160,7 @@
private final Runnable mSyncStateWithScheduledReason = this::syncStateWithScheduledReason;
private final Runnable mSaveToFile = this::saveToFile;
private final SystemClock mSystemClock;
+ private final BootThreshold mBootThreshold;
@GuardedBy("mLock")
private boolean mIsPackagesReady;
// Flag to control whether explicit health checks are supported or not
@@ -169,6 +178,7 @@
@FunctionalInterface
@VisibleForTesting
interface SystemClock {
+ // TODO: Add elapsedRealtime to this interface
long uptimeMillis();
}
@@ -198,14 +208,17 @@
mConnectivityModuleConnector = connectivityModuleConnector;
mSystemClock = clock;
mNumberOfNativeCrashPollsRemaining = NUMBER_OF_NATIVE_CRASH_POLLS;
+ mBootThreshold = new BootThreshold(DEFAULT_BOOT_LOOP_TRIGGER_COUNT,
+ DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS);
loadFromFile();
+ sPackageWatchdog = this;
}
/** Creates or gets singleton instance of PackageWatchdog. */
public static PackageWatchdog getInstance(Context context) {
synchronized (PackageWatchdog.class) {
if (sPackageWatchdog == null) {
- sPackageWatchdog = new PackageWatchdog(context);
+ new PackageWatchdog(context);
}
return sPackageWatchdog;
}
@@ -410,6 +423,35 @@
}
}
+ /**
+ * Called when the system server boots. If the system server is detected to be in a boot loop,
+ * query each observer and perform the mitigation action with the lowest user impact.
+ */
+ public void noteBoot() {
+ synchronized (mLock) {
+ if (mBootThreshold.incrementAndTest()) {
+ mBootThreshold.reset();
+ PackageHealthObserver currentObserverToNotify = null;
+ int currentObserverImpact = Integer.MAX_VALUE;
+ for (int i = 0; i < mAllObservers.size(); i++) {
+ final ObserverInternal observer = mAllObservers.valueAt(i);
+ PackageHealthObserver registeredObserver = observer.registeredObserver;
+ if (registeredObserver != null) {
+ int impact = registeredObserver.onBootLoop();
+ if (impact != PackageHealthObserverImpact.USER_IMPACT_NONE
+ && impact < currentObserverImpact) {
+ currentObserverToNotify = registeredObserver;
+ currentObserverImpact = impact;
+ }
+ }
+ }
+ if (currentObserverToNotify != null) {
+ currentObserverToNotify.executeBootLoopMitigation();
+ }
+ }
+ }
+ }
+
// TODO(b/120598832): Optimize write? Maybe only write a separate smaller file? Also
// avoid holding lock?
// This currently adds about 7ms extra to shutdown thread
@@ -518,6 +560,22 @@
boolean execute(@Nullable VersionedPackage versionedPackage,
@FailureReasons int failureReason);
+
+ /**
+ * Called when the system server has booted several times within a window of time, defined
+ * by {@link #mBootThreshold}
+ */
+ default @PackageHealthObserverImpact int onBootLoop() {
+ return PackageHealthObserverImpact.USER_IMPACT_NONE;
+ }
+
+ /**
+ * Executes mitigation for {@link #onBootLoop}
+ */
+ default boolean executeBootLoopMitigation() {
+ return false;
+ }
+
// TODO(b/120598832): Ensure uniqueness?
/**
* Identifier for the observer, should not change across device updates otherwise the
@@ -1366,4 +1424,62 @@
return value > 0 ? value : Long.MAX_VALUE;
}
}
+
+ /**
+ * Handles the thresholding logic for system server boots.
+ */
+ static class BootThreshold {
+
+ private final int mBootTriggerCount;
+ private final long mTriggerWindow;
+
+ BootThreshold(int bootTriggerCount, long triggerWindow) {
+ this.mBootTriggerCount = bootTriggerCount;
+ this.mTriggerWindow = triggerWindow;
+ }
+
+ public void reset() {
+ setStart(0);
+ setCount(0);
+ }
+
+ private int getCount() {
+ return SystemProperties.getInt(PROP_RESCUE_BOOT_COUNT, 0);
+ }
+
+ private void setCount(int count) {
+ SystemProperties.set(PROP_RESCUE_BOOT_COUNT, Integer.toString(count));
+ }
+
+ public long getStart() {
+ return SystemProperties.getLong(PROP_RESCUE_BOOT_START, 0);
+ }
+
+ public void setStart(long start) {
+ final long now = android.os.SystemClock.elapsedRealtime();
+ final long newStart = MathUtils.constrain(start, 0, now);
+ SystemProperties.set(PROP_RESCUE_BOOT_START, Long.toString(newStart));
+ }
+
+ /** Increments the boot counter, and returns whether the device is bootlooping. */
+ public boolean incrementAndTest() {
+ final long now = android.os.SystemClock.elapsedRealtime();
+ if (now - getStart() < 0) {
+ Slog.e(TAG, "Window was less than zero. Resetting start to current time.");
+ setStart(now);
+ }
+ final long window = now - getStart();
+ if (window >= mTriggerWindow) {
+ setCount(1);
+ setStart(now);
+ return false;
+ } else {
+ int count = getCount() + 1;
+ setCount(count);
+ EventLogTags.writeRescueNote(Process.ROOT_UID, count, window);
+ return count >= mBootTriggerCount;
+ }
+ }
+
+ }
}
diff --git a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
index 190fff1..21fa9f9 100644
--- a/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
+++ b/services/core/java/com/android/server/PersistentDataBlockManagerInternal.java
@@ -46,4 +46,7 @@
/** Update the OEM unlock enabled bit, bypassing user restriction checks. */
void forceOemUnlockEnabled(boolean enabled);
+
+ /** Retrieves the UID that can access the persistent data partition. */
+ int getAllowedUid();
}
diff --git a/services/core/java/com/android/server/PersistentDataBlockService.java b/services/core/java/com/android/server/PersistentDataBlockService.java
index 73c8520..00d8b0f 100644
--- a/services/core/java/com/android/server/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/PersistentDataBlockService.java
@@ -680,6 +680,11 @@
writeDataBuffer(getTestHarnessModeDataOffset(), ByteBuffer.allocate(size));
}
+ @Override
+ public int getAllowedUid() {
+ return mAllowedUid;
+ }
+
private void writeInternal(byte[] data, long offset, int dataLength) {
checkArgument(data == null || data.length > 0, "data must be null or non-empty");
checkArgument(
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index fb1a962..e8e3b39d 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -18,26 +18,33 @@
import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
+import android.annotation.Nullable;
import android.content.ContentResolver;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.VersionedPackage;
import android.os.Build;
import android.os.Environment;
import android.os.FileUtils;
+import android.os.Process;
import android.os.RecoverySystem;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
-import android.text.format.DateUtils;
import android.util.ExceptionUtils;
import android.util.Log;
import android.util.MathUtils;
import android.util.Slog;
-import android.util.SparseArray;
import android.util.StatsLog;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
+import com.android.server.PackageWatchdog.FailureReasons;
+import com.android.server.PackageWatchdog.PackageHealthObserver;
+import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
import com.android.server.am.SettingsToPropertiesMapper;
import com.android.server.utils.FlagNamespaceUtils;
@@ -72,25 +79,23 @@
static final int LEVEL_FACTORY_RESET = 4;
@VisibleForTesting
static final String PROP_RESCUE_BOOT_COUNT = "sys.rescue_boot_count";
- /**
- * The boot trigger window size must always be greater than Watchdog's deadlock timeout
- * {@link Watchdog#DEFAULT_TIMEOUT}.
- */
- @VisibleForTesting
- static final long BOOT_TRIGGER_WINDOW_MILLIS = 600 * DateUtils.SECOND_IN_MILLIS;
- @VisibleForTesting
- static final long PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS = 30 * DateUtils.SECOND_IN_MILLIS;
@VisibleForTesting
static final String TAG = "RescueParty";
+ private static final String NAME = "rescue-party-observer";
+
+
private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue";
- private static final String PROP_RESCUE_BOOT_START = "sys.rescue_boot_start";
private static final String PROP_VIRTUAL_DEVICE = "ro.hardware.virtual_device";
- /** Threshold for boot loops */
- private static final Threshold sBoot = new BootThreshold();
- /** Threshold for app crash loops */
- private static SparseArray<Threshold> sApps = new SparseArray<>();
+ private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
+ | ApplicationInfo.FLAG_SYSTEM;
+
+ /** Register the Rescue Party observer as a Package Watchdog health observer */
+ public static void registerHealthObserver(Context context) {
+ PackageWatchdog.getInstance(context).registerHealthObserver(
+ RescuePartyObserver.getInstance(context));
+ }
private static boolean isDisabled() {
// Check if we're explicitly enabled for testing
@@ -122,37 +127,6 @@
}
/**
- * Take note of a boot event. If we notice too many of these events
- * happening in rapid succession, we'll send out a rescue party.
- */
- public static void noteBoot(Context context) {
- if (isDisabled()) return;
- if (sBoot.incrementAndTest()) {
- sBoot.reset();
- incrementRescueLevel(sBoot.uid);
- executeRescueLevel(context);
- }
- }
-
- /**
- * Take note of a persistent app or apex module crash. If we notice too many of these
- * events happening in rapid succession, we'll send out a rescue party.
- */
- public static void noteAppCrash(Context context, int uid) {
- if (isDisabled()) return;
- Threshold t = sApps.get(uid);
- if (t == null) {
- t = new AppThreshold(uid);
- sApps.put(uid, t);
- }
- if (t.incrementAndTest()) {
- t.reset();
- incrementRescueLevel(t.uid);
- executeRescueLevel(context);
- }
- }
-
- /**
* Check if we're currently attempting to reboot for a factory reset.
*/
public static boolean isAttemptingFactoryReset() {
@@ -169,16 +143,6 @@
}
@VisibleForTesting
- static void resetAllThresholds() {
- sBoot.reset();
-
- for (int i = 0; i < sApps.size(); i++) {
- Threshold appThreshold = sApps.get(sApps.keyAt(i));
- appThreshold.reset();
- }
- }
-
- @VisibleForTesting
static long getElapsedRealtime() {
return SystemClock.elapsedRealtime();
}
@@ -191,6 +155,14 @@
}
/**
+ * Get the current rescue level.
+ */
+ private static int getRescueLevel() {
+ return MathUtils.constrain(SystemProperties.getInt(PROP_RESCUE_LEVEL, LEVEL_NONE),
+ LEVEL_NONE, LEVEL_FACTORY_RESET);
+ }
+
+ /**
* Escalate to the next rescue level. After incrementing the level you'll
* probably want to call {@link #executeRescueLevel(Context)}.
*/
@@ -243,6 +215,28 @@
FlagNamespaceUtils.NAMESPACE_NO_PACKAGE);
}
+ private static int mapRescueLevelToUserImpact(int rescueLevel) {
+ switch(rescueLevel) {
+ case LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS:
+ case LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES:
+ return PackageHealthObserverImpact.USER_IMPACT_LOW;
+ case LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS:
+ case LEVEL_FACTORY_RESET:
+ return PackageHealthObserverImpact.USER_IMPACT_HIGH;
+ default:
+ return PackageHealthObserverImpact.USER_IMPACT_NONE;
+ }
+ }
+
+ private static int getPackageUid(Context context, String packageName) {
+ try {
+ return context.getPackageManager().getPackageUid(packageName, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ // Since UIDs are always >= 0, this value means the UID could not be determined.
+ return -1;
+ }
+ }
+
private static void resetAllSettings(Context context, int mode) throws Exception {
// Try our best to reset all settings possible, and once finished
// rethrow any exception that we encountered
@@ -271,103 +265,104 @@
}
/**
- * Threshold that can be triggered if a number of events occur within a
- * window of time.
+ * Handle mitigation action for package failures. This observer will be register to Package
+ * Watchdog and will receive calls about package failures. This observer is persistent so it
+ * may choose to mitigate failures for packages it has not explicitly asked to observe.
*/
- private abstract static class Threshold {
- public abstract int getCount();
- public abstract void setCount(int count);
- public abstract long getStart();
- public abstract void setStart(long start);
+ public static class RescuePartyObserver implements PackageHealthObserver {
- private final int uid;
- private final int triggerCount;
- private final long triggerWindow;
+ private Context mContext;
- public Threshold(int uid, int triggerCount, long triggerWindow) {
- this.uid = uid;
- this.triggerCount = triggerCount;
- this.triggerWindow = triggerWindow;
+ @GuardedBy("RescuePartyObserver.class")
+ static RescuePartyObserver sRescuePartyObserver;
+
+ private RescuePartyObserver(Context context) {
+ mContext = context;
}
- public void reset() {
- setCount(0);
- setStart(0);
- }
-
- /**
- * @return if this threshold has been triggered
- */
- public boolean incrementAndTest() {
- final long now = getElapsedRealtime();
- final long window = now - getStart();
- if (window > triggerWindow) {
- setCount(1);
- setStart(now);
- return false;
- } else {
- int count = getCount() + 1;
- setCount(count);
- EventLogTags.writeRescueNote(uid, count, window);
- Slog.w(TAG, "Noticed " + count + " events for UID " + uid + " in last "
- + (window / 1000) + " sec");
- return (count >= triggerCount);
+ /** Creates or gets singleton instance of RescueParty. */
+ public static RescuePartyObserver getInstance(Context context) {
+ synchronized (RescuePartyObserver.class) {
+ if (sRescuePartyObserver == null) {
+ sRescuePartyObserver = new RescuePartyObserver(context);
+ }
+ return sRescuePartyObserver;
}
}
- }
- /**
- * Specialization of {@link Threshold} for monitoring boot events. It stores
- * counters in system properties for robustness.
- */
- private static class BootThreshold extends Threshold {
- public BootThreshold() {
- // We're interested in TRIGGER_COUNT events in any
- // BOOT_TRIGGER_WINDOW_MILLIS second period; this window is super relaxed because
- // booting can take a long time if forced to dexopt things.
- super(android.os.Process.ROOT_UID, TRIGGER_COUNT, BOOT_TRIGGER_WINDOW_MILLIS);
+ @Override
+ public int onHealthCheckFailed(@Nullable VersionedPackage failedPackage,
+ @FailureReasons int failureReason) {
+ if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
+ || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) {
+ int rescueLevel = MathUtils.constrain(
+ SystemProperties.getInt(PROP_RESCUE_LEVEL, LEVEL_NONE) + 1,
+ LEVEL_NONE, LEVEL_FACTORY_RESET);
+ return mapRescueLevelToUserImpact(rescueLevel);
+ } else {
+ return PackageHealthObserverImpact.USER_IMPACT_NONE;
+ }
}
@Override
- public int getCount() {
- return SystemProperties.getInt(PROP_RESCUE_BOOT_COUNT, 0);
+ public boolean execute(@Nullable VersionedPackage failedPackage,
+ @FailureReasons int failureReason) {
+ if (isDisabled()) {
+ return false;
+ }
+ if (failureReason == PackageWatchdog.FAILURE_REASON_APP_CRASH
+ || failureReason == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING) {
+ int triggerUid = getPackageUid(mContext, failedPackage.getPackageName());
+ incrementRescueLevel(triggerUid);
+ executeRescueLevel(mContext);
+ return true;
+ } else {
+ return false;
+ }
}
@Override
- public void setCount(int count) {
- SystemProperties.set(PROP_RESCUE_BOOT_COUNT, Integer.toString(count));
+ public boolean isPersistent() {
+ return true;
}
@Override
- public long getStart() {
- return SystemProperties.getLong(PROP_RESCUE_BOOT_START, 0);
+ public boolean mayObservePackage(String packageName) {
+ PackageManager pm = mContext.getPackageManager();
+ try {
+ // A package is a Mainline module if this is non-null
+ if (pm.getModuleInfo(packageName, 0) != null) {
+ return true;
+ }
+ ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
+ return (info.flags & PERSISTENT_MASK) == PERSISTENT_MASK;
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
}
@Override
- public void setStart(long start) {
- SystemProperties.set(PROP_RESCUE_BOOT_START, Long.toString(start));
- }
- }
-
- /**
- * Specialization of {@link Threshold} for monitoring app crashes. It stores
- * counters in memory.
- */
- private static class AppThreshold extends Threshold {
- private int count;
- private long start;
-
- public AppThreshold(int uid) {
- // We're interested in TRIGGER_COUNT events in any
- // PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS second period; apps crash pretty quickly
- // so we can keep a tight leash on them.
- super(uid, TRIGGER_COUNT, PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS);
+ public int onBootLoop() {
+ if (isDisabled()) {
+ return PackageHealthObserverImpact.USER_IMPACT_NONE;
+ }
+ return mapRescueLevelToUserImpact(getRescueLevel());
}
- @Override public int getCount() { return count; }
- @Override public void setCount(int count) { this.count = count; }
- @Override public long getStart() { return start; }
- @Override public void setStart(long start) { this.start = start; }
+ @Override
+ public boolean executeBootLoopMitigation() {
+ if (isDisabled()) {
+ return false;
+ }
+ incrementRescueLevel(Process.ROOT_UID);
+ executeRescueLevel(mContext);
+ return true;
+ }
+
+ @Override
+ public String getName() {
+ return NAME;
+ }
}
private static int[] getAllUserIds() {
diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java
index 18ccd01..bcc3bdb 100644
--- a/services/core/java/com/android/server/StorageManagerService.java
+++ b/services/core/java/com/android/server/StorageManagerService.java
@@ -16,6 +16,7 @@
package com.android.server;
+import static android.Manifest.permission.ACCESS_MTP;
import static android.Manifest.permission.INSTALL_PACKAGES;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
@@ -43,6 +44,7 @@
import static com.android.internal.util.XmlUtils.writeIntAttribute;
import static com.android.internal.util.XmlUtils.writeLongAttribute;
import static com.android.internal.util.XmlUtils.writeStringAttribute;
+import static com.android.server.storage.StorageUserConnection.REMOTE_TIMEOUT_SECONDS;
import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
@@ -111,6 +113,7 @@
import android.os.storage.VolumeInfo;
import android.os.storage.VolumeRecord;
import android.provider.DeviceConfig;
+import android.provider.Downloads;
import android.provider.MediaStore;
import android.provider.Settings;
import android.sysprop.VoldProperties;
@@ -367,6 +370,8 @@
private volatile int mMediaStoreAuthorityAppId = -1;
+ private volatile int mDownloadsAuthorityAppId = -1;
+
private volatile int mCurrentUserId = UserHandle.USER_SYSTEM;
private final Installer mInstaller;
@@ -1119,7 +1124,7 @@
// Push down current secure keyguard status so that we ignore malicious
// USB devices while locked.
mSecureKeyguardShowing = isShowing
- && mContext.getSystemService(KeyguardManager.class).isDeviceSecure();
+ && mContext.getSystemService(KeyguardManager.class).isDeviceSecure(mCurrentUserId);
try {
mVold.onSecureKeyguardStateChanged(mSecureKeyguardShowing);
} catch (Exception e) {
@@ -1788,6 +1793,15 @@
mMediaStoreAuthorityAppId = UserHandle.getAppId(provider.applicationInfo.uid);
}
+ provider = mPmInternal.resolveContentProvider(
+ Downloads.Impl.AUTHORITY, PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
+ UserHandle.getUserId(UserHandle.USER_SYSTEM));
+
+ if (provider != null) {
+ mDownloadsAuthorityAppId = UserHandle.getAppId(provider.applicationInfo.uid);
+ }
+
try {
mIAppOpsService.startWatchingMode(OP_REQUEST_INSTALL_PACKAGES, null, mAppOpsCallback);
mIAppOpsService.startWatchingMode(OP_LEGACY_STORAGE, null, mAppOpsCallback);
@@ -1972,16 +1986,28 @@
Slog.i(TAG, "Mounting volume " + vol);
mVold.mount(vol.id, vol.mountFlags, vol.mountUserId, new IVoldMountCallback.Stub() {
@Override
- public boolean onVolumeChecking(FileDescriptor deviceFd, String path,
+ public boolean onVolumeChecking(FileDescriptor fd, String path,
String internalPath) {
vol.path = path;
vol.internalPath = internalPath;
+ ParcelFileDescriptor pfd = new ParcelFileDescriptor(fd);
try {
- mStorageSessionController.onVolumeMount(deviceFd, vol);
+ mStorageSessionController.onVolumeMount(pfd, vol);
return true;
} catch (ExternalStorageServiceException e) {
- Slog.i(TAG, "Failed to mount volume " + vol, e);
+ Slog.e(TAG, "Failed to mount volume " + vol, e);
+
+ Slog.i(TAG, "Scheduling reset in one minute");
+ mHandler.removeMessages(H_RESET);
+ mHandler.sendMessageDelayed(mHandler.obtainMessage(H_RESET),
+ TimeUnit.SECONDS.toMillis(REMOTE_TIMEOUT_SECONDS * 2));
return false;
+ } finally {
+ try {
+ pfd.close();
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to close FUSE device fd", e);
+ }
}
}
});
@@ -2842,8 +2868,10 @@
*/
@Override
public void startCheckpoint(int numTries) throws RemoteException {
- // Only the system process is permitted to start checkpoints
- if (Binder.getCallingUid() != android.os.Process.SYSTEM_UID) {
+ // Only the root, system_server and shell processes are permitted to start checkpoints
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid != Process.SYSTEM_UID && callingUid != Process.ROOT_UID
+ && callingUid != Process.SHELL_UID) {
throw new SecurityException("no permission to start filesystem checkpoint");
}
@@ -3881,6 +3909,19 @@
return Zygote.MOUNT_EXTERNAL_PASS_THROUGH;
}
+ if (mIsFuseEnabled && mDownloadsAuthorityAppId == UserHandle.getAppId(uid)) {
+ // DownloadManager can write in app-private directories on behalf of apps;
+ // give it write access to Android/
+ return Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE;
+ }
+
+ final boolean hasMtp = mIPackageManager.checkUidPermission(ACCESS_MTP, uid) ==
+ PERMISSION_GRANTED;
+ if (mIsFuseEnabled && hasMtp) {
+ // The process hosting the MTP server should be able to write in Android/
+ return Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE;
+ }
+
// Determine if caller is holding runtime permission
final boolean hasRead = StorageManager.checkPermissionAndCheckOp(mContext, false, 0,
uid, packageName, READ_EXTERNAL_STORAGE, OP_READ_EXTERNAL_STORAGE);
diff --git a/services/core/java/com/android/server/SystemService.java b/services/core/java/com/android/server/SystemService.java
index c5409f8..b1584fe 100644
--- a/services/core/java/com/android/server/SystemService.java
+++ b/services/core/java/com/android/server/SystemService.java
@@ -18,18 +18,26 @@
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_DEFAULT;
+import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.annotation.SystemApi.Client;
+import android.annotation.SystemApi.Process;
import android.annotation.UserIdInt;
import android.app.ActivityThread;
import android.content.Context;
import android.content.pm.UserInfo;
import android.os.IBinder;
import android.os.ServiceManager;
+import android.os.UserHandle;
import android.os.UserManager;
import com.android.server.pm.UserManagerService;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
@@ -57,15 +65,17 @@
*
* {@hide}
*/
+@SystemApi(client = Client.MODULE_LIBRARIES, process = Process.SYSTEM_SERVER)
public abstract class SystemService {
+ /** @hide */
// TODO(b/133242016) STOPSHIP: change to false before R ships
protected static final boolean DEBUG_USER = true;
/*
- * Boot Phases
+ * The earliest boot phase the system send to system services on boot.
*/
- public static final int PHASE_WAIT_FOR_DEFAULT_DISPLAY = 100; // maybe should be a dependency?
+ public static final int PHASE_WAIT_FOR_DEFAULT_DISPLAY = 100;
/**
* After receiving this boot phase, services can obtain lock settings data.
@@ -98,13 +108,70 @@
* After receiving this boot phase, services can allow user interaction with the device.
* This phase occurs when boot has completed and the home application has started.
* System services may prefer to listen to this phase rather than registering a
- * broadcast receiver for ACTION_BOOT_COMPLETED to reduce overall latency.
+ * broadcast receiver for {@link android.content.Intent#ACTION_LOCKED_BOOT_COMPLETED}
+ * to reduce overall latency.
*/
public static final int PHASE_BOOT_COMPLETED = 1000;
+ /** @hide */
+ @IntDef(flag = true, prefix = { "PHASE_" }, value = {
+ PHASE_WAIT_FOR_DEFAULT_DISPLAY,
+ PHASE_LOCK_SETTINGS_READY,
+ PHASE_SYSTEM_SERVICES_READY,
+ PHASE_DEVICE_SPECIFIC_SERVICES_READY,
+ PHASE_ACTIVITY_MANAGER_READY,
+ PHASE_THIRD_PARTY_APPS_CAN_START,
+ PHASE_BOOT_COMPLETED
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface BootPhase {}
+
private final Context mContext;
/**
+ * Class representing user in question in the lifecycle callbacks.
+ * @hide
+ */
+ @SystemApi(client = Client.MODULE_LIBRARIES, process = Process.SYSTEM_SERVER)
+ public static final class TargetUser {
+ @NonNull
+ private final UserInfo mUserInfo;
+
+ /** @hide */
+ public TargetUser(@NonNull UserInfo userInfo) {
+ mUserInfo = userInfo;
+ }
+
+ /**
+ * @return The information about the user. <b>NOTE: </b> this is a "live" object
+ * referenced by {@link UserManagerService} and hence should not be modified.
+ *
+ * @hide
+ */
+ @NonNull
+ public UserInfo getUserInfo() {
+ return mUserInfo;
+ }
+
+ /**
+ * @return the target {@link UserHandle}.
+ */
+ @NonNull
+ public UserHandle getUserHandle() {
+ return mUserInfo.getUserHandle();
+ }
+
+ /**
+ * @return the integer user id
+ *
+ * @hide
+ */
+ public int getUserIdentifier() {
+ return mUserInfo.id;
+ }
+ }
+
+ /**
* Initializes the system service.
* <p>
* Subclasses must define a single argument constructor that accepts the context
@@ -113,13 +180,14 @@
*
* @param context The system server context.
*/
- public SystemService(Context context) {
+ public SystemService(@NonNull Context context) {
mContext = context;
}
/**
* Gets the system context.
*/
+ @NonNull
public final Context getContext() {
return mContext;
}
@@ -128,6 +196,8 @@
* Get the system UI context. This context is to be used for displaying UI. It is themable,
* which means resources can be overridden at runtime. Do not use to retrieve properties that
* configure the behavior of the device that is not UX related.
+ *
+ * @hide
*/
public final Context getUiContext() {
// This has already been set up by the time any SystemServices are created.
@@ -137,15 +207,16 @@
/**
* Returns true if the system is running in safe mode.
* TODO: we should define in which phase this becomes valid
+ *
+ * @hide
*/
public final boolean isSafeMode() {
return getManager().isSafeMode();
}
/**
- * Called when the dependencies listed in the @Service class-annotation are available
- * and after the chosen start phase.
- * When this method returns, the service should be published.
+ * Called when the system service should publish a binder service using
+ * {@link #publishBinderService(String, IBinder).}
*/
public abstract void onStart();
@@ -155,7 +226,7 @@
*
* @param phase The current boot phase.
*/
- public void onBootPhase(int phase) {}
+ public void onBootPhase(@BootPhase int phase) {}
/**
* Checks if the service should be available for the given user.
@@ -163,12 +234,14 @@
* <p>By default returns {@code true}, but subclasses should extend for optimization, if they
* don't support some types (like headless system user).
*/
- public boolean isSupported(@NonNull UserInfo userInfo) {
+ public boolean isSupportedUser(@NonNull TargetUser user) {
return true;
}
/**
- * Helper method used to dump which users are {@link #onStartUser(UserInfo) supported}.
+ * Helper method used to dump which users are {@link #onStartUser(TargetUser) supported}.
+ *
+ * @hide
*/
protected void dumpSupportedUsers(@NonNull PrintWriter pw, @NonNull String prefix) {
final List<UserInfo> allUsers = UserManager.get(mContext).getUsers();
@@ -187,34 +260,59 @@
}
/**
- * @deprecated subclasses should extend {@link #onStartUser(UserInfo)} instead (which by default
- * calls this method).
+ * @deprecated subclasses should extend {@link #onStartUser(TargetUser)} instead
+ * (which by default calls this method).
+ *
+ * @hide
*/
@Deprecated
public void onStartUser(@UserIdInt int userId) {}
/**
- * Called when a new user is starting, for system services to initialize any per-user
- * state they maintain for running users.
+ * @deprecated subclasses should extend {@link #onStartUser(TargetUser)} instead
+ * (which by default calls this method).
*
- * <p>This method is only called when the service {@link #isSupported(UserInfo) supports} this
- * user.
- *
- * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object
- * referenced by {@link UserManagerService} and hence should not be modified.
+ * @hide
*/
+ @Deprecated
public void onStartUser(@NonNull UserInfo userInfo) {
onStartUser(userInfo.id);
}
/**
- * @deprecated subclasses should extend {@link #onUnlockUser(UserInfo)} instead (which by
+ * Called when a new user is starting, for system services to initialize any per-user
+ * state they maintain for running users.
+ *
+ * <p>This method is only called when the service {@link #isSupportedUser(TargetUser) supports}
+ * this user.
+ *
+ * @param user target user
+ */
+ public void onStartUser(@NonNull TargetUser user) {
+ onStartUser(user.getUserInfo());
+ }
+
+ /**
+ * @deprecated subclasses should extend {@link #onUnlockUser(TargetUser)} instead (which by
* default calls this method).
+ *
+ * @hide
*/
@Deprecated
public void onUnlockUser(@UserIdInt int userId) {}
/**
+ * @deprecated subclasses should extend {@link #onUnlockUser(TargetUser)} instead (which by
+ * default calls this method).
+ *
+ * @hide
+ */
+ @Deprecated
+ public void onUnlockUser(@NonNull UserInfo userInfo) {
+ onUnlockUser(userInfo.id);
+ }
+
+ /**
* Called when an existing user is in the process of being unlocked. This
* means the credential-encrypted storage for that user is now available,
* and encryption-aware component filtering is no longer in effect.
@@ -226,90 +324,127 @@
* {@link UserManager#isUserUnlockingOrUnlocked(int)} to handle both of
* these states.
* <p>
- * This method is only called when the service {@link #isSupported(UserInfo) supports} this
- * user.
+ * This method is only called when the service {@link #isSupportedUser(TargetUser) supports}
+ * this user.
*
- * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object
- * referenced by {@link UserManagerService} and hence should not be modified.
+ * @param user target user
*/
- public void onUnlockUser(@NonNull UserInfo userInfo) {
- onUnlockUser(userInfo.id);
+ public void onUnlockUser(@NonNull TargetUser user) {
+ onUnlockUser(user.getUserInfo());
}
/**
- * @deprecated subclasses should extend {@link #onSwitchUser(UserInfo, UserInfo)} instead
+ * @deprecated subclasses should extend {@link #onSwitchUser(TargetUser, TargetUser)} instead
* (which by default calls this method).
+ *
+ * @hide
*/
@Deprecated
- public void onSwitchUser(@UserIdInt int userId) {}
+ public void onSwitchUser(@UserIdInt int toUserId) {}
+
+ /**
+ * @deprecated subclasses should extend {@link #onSwitchUser(TargetUser, TargetUser)} instead
+ * (which by default calls this method).
+ *
+ * @hide
+ */
+ @Deprecated
+ public void onSwitchUser(@Nullable UserInfo from, @NonNull UserInfo to) {
+ onSwitchUser(to.id);
+ }
/**
* Called when switching to a different foreground user, for system services that have
* special behavior for whichever user is currently in the foreground. This is called
* before any application processes are aware of the new user.
*
- * <p>This method is only called when the service {@link #isSupported(UserInfo) supports} either
- * of the users ({@code from} or {@code to}).
+ * <p>This method is only called when the service {@link #isSupportedUser(TargetUser) supports}
+ * either of the users ({@code from} or {@code to}).
*
* <b>NOTE: </b> both {@code from} and {@code to} are "live" objects
* referenced by {@link UserManagerService} and hence should not be modified.
*
- * @param from The information about the user being switched from.
- * @param to The information about the user being switched from to.
+ * @param from the user switching from
+ * @param to the user switching to
*/
- public void onSwitchUser(@NonNull UserInfo from, @NonNull UserInfo to) {
- onSwitchUser(to.id);
+ public void onSwitchUser(@Nullable TargetUser from, @NonNull TargetUser to) {
+ onSwitchUser((from == null ? null : from.getUserInfo()), to.getUserInfo());
}
/**
- * @deprecated subclasses should extend {@link #onStopUser(UserInfo)} instead (which by default
- * calls this method).
+ * @deprecated subclasses should extend {@link #onStopUser(TargetUser)} instead
+ * (which by default calls this method).
+ *
+ * @hide
*/
@Deprecated
public void onStopUser(@UserIdInt int userId) {}
/**
+ * @deprecated subclasses should extend {@link #onStopUser(TargetUser)} instead
+ * (which by default calls this method).
+ *
+ * @hide
+ */
+ @Deprecated
+ public void onStopUser(@NonNull UserInfo user) {
+ onStopUser(user.id);
+
+ }
+
+ /**
* Called when an existing user is stopping, for system services to finalize any per-user
* state they maintain for running users. This is called prior to sending the SHUTDOWN
* broadcast to the user; it is a good place to stop making use of any resources of that
* user (such as binding to a service running in the user).
*
- * <p>This method is only called when the service {@link #isSupported(UserInfo) supports} this
- * user.
+ * <p>This method is only called when the service {@link #isSupportedUser(TargetUser) supports}
+ * this user.
*
* <p>NOTE: This is the last callback where the callee may access the target user's CE storage.
*
- * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object
- * referenced by {@link UserManagerService} and hence should not be modified.
+ * @param user target user
*/
- public void onStopUser(@NonNull UserInfo userInfo) {
- onStopUser(userInfo.id);
+ public void onStopUser(@NonNull TargetUser user) {
+ onStopUser(user.getUserInfo());
}
/**
- * @deprecated subclasses should extend {@link #onCleanupUser(UserInfo)} instead (which by
+ * @deprecated subclasses should extend {@link #onCleanupUser(TargetUser)} instead (which by
* default calls this method).
+ *
+ * @hide
*/
@Deprecated
public void onCleanupUser(@UserIdInt int userId) {}
/**
+ * @deprecated subclasses should extend {@link #onCleanupUser(TargetUser)} instead (which by
+ * default calls this method).
+ *
+ * @hide
+ */
+ @Deprecated
+ public void onCleanupUser(@NonNull UserInfo user) {
+ onCleanupUser(user.id);
+ }
+
+ /**
* Called when an existing user is stopping, for system services to finalize any per-user
* state they maintain for running users. This is called after all application process
* teardown of the user is complete.
*
- * <p>This method is only called when the service {@link #isSupported(UserInfo) supports} this
- * user.
+ * <p>This method is only called when the service {@link #isSupportedUser(TargetUser) supports}
+ * this user.
*
* <p>NOTE: When this callback is called, the CE storage for the target user may not be
- * accessible already. Use {@link #onStopUser(UserInfo)} instead if you need to access the CE
+ * accessible already. Use {@link #onStopUser(TargetUser)} instead if you need to access the CE
* storage.
*
- * @param userInfo The information about the user. <b>NOTE: </b> this is a "live" object
- * referenced by {@link UserManagerService} and hence should not be modified.
+ * @param user target user
*/
- public void onCleanupUser(@NonNull UserInfo userInfo) {
- onCleanupUser(userInfo.id);
+ public void onCleanupUser(@NonNull TargetUser user) {
+ onCleanupUser(user.getUserInfo());
}
/**
@@ -318,7 +453,7 @@
* @param name the name of the new service
* @param service the service object
*/
- protected final void publishBinderService(String name, IBinder service) {
+ protected final void publishBinderService(@NonNull String name, @NonNull IBinder service) {
publishBinderService(name, service, false);
}
@@ -330,7 +465,7 @@
* @param allowIsolated set to true to allow isolated sandboxed processes
* to access this service
*/
- protected final void publishBinderService(String name, IBinder service,
+ protected final void publishBinderService(@NonNull String name, @NonNull IBinder service,
boolean allowIsolated) {
publishBinderService(name, service, allowIsolated, DUMP_FLAG_PRIORITY_DEFAULT);
}
@@ -343,6 +478,8 @@
* @param allowIsolated set to true to allow isolated sandboxed processes
* to access this service
* @param dumpPriority supported dump priority levels as a bitmask
+ *
+ * @hide
*/
protected final void publishBinderService(String name, IBinder service,
boolean allowIsolated, int dumpPriority) {
@@ -351,6 +488,8 @@
/**
* Get a binder service by its name.
+ *
+ * @hide
*/
protected final IBinder getBinderService(String name) {
return ServiceManager.getService(name);
@@ -358,6 +497,8 @@
/**
* Publish the service so it is only accessible to the system process.
+ *
+ * @hide
*/
protected final <T> void publishLocalService(Class<T> type, T service) {
LocalServices.addService(type, service);
@@ -365,6 +506,8 @@
/**
* Get a local service by interface.
+ *
+ * @hide
*/
protected final <T> T getLocalService(Class<T> type) {
return LocalServices.getService(type);
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index c715798..e7f7846 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -25,10 +25,14 @@
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManagerInternal;
+import android.util.ArrayMap;
import android.util.Slog;
+import com.android.server.SystemService.TargetUser;
import com.android.server.utils.TimingsTraceAndSlog;
+import dalvik.system.PathClassLoader;
+
import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
@@ -62,6 +66,9 @@
// Services that should receive lifecycle events.
private final ArrayList<SystemService> mServices = new ArrayList<SystemService>();
+ // Map of paths to PathClassLoader, so we don't load the same path multiple times.
+ private final ArrayMap<String, PathClassLoader> mLoadedPaths = new ArrayMap<>();
+
private int mCurrentPhase = -1;
private UserManagerInternal mUserManagerInternal;
@@ -75,20 +82,46 @@
*
* @return The service instance.
*/
- @SuppressWarnings("unchecked")
public SystemService startService(String className) {
- final Class<SystemService> serviceClass;
+ final Class<SystemService> serviceClass = loadClassFromLoader(className,
+ this.getClass().getClassLoader());
+ return startService(serviceClass);
+ }
+
+ /**
+ * Starts a service by class name and a path that specifies the jar where the service lives.
+ *
+ * @return The service instance.
+ */
+ public SystemService startServiceFromJar(String className, String path) {
+ PathClassLoader pathClassLoader = mLoadedPaths.get(path);
+ if (pathClassLoader == null) {
+ // NB: the parent class loader should always be the system server class loader.
+ // Changing it has implications that require discussion with the mainline team.
+ pathClassLoader = new PathClassLoader(path, this.getClass().getClassLoader());
+ mLoadedPaths.put(path, pathClassLoader);
+ }
+ final Class<SystemService> serviceClass = loadClassFromLoader(className, pathClassLoader);
+ return startService(serviceClass);
+ }
+
+ /*
+ * Loads and initializes a class from the given classLoader. Returns the class.
+ */
+ @SuppressWarnings("unchecked")
+ private static Class<SystemService> loadClassFromLoader(String className,
+ ClassLoader classLoader) {
try {
- serviceClass = (Class<SystemService>)Class.forName(className);
+ return (Class<SystemService>) Class.forName(className, true, classLoader);
} catch (ClassNotFoundException ex) {
- Slog.i(TAG, "Starting " + className);
throw new RuntimeException("Failed to create service " + className
- + ": service class not found, usually indicates that the caller should "
+ + " from class loader " + classLoader.toString() + ": service class not "
+ + "found, usually indicates that the caller should "
+ "have called PackageManager.hasSystemFeature() to check whether the "
+ "feature is available on this device before trying to start the "
- + "services that implement it", ex);
+ + "services that implement it. Also ensure that the correct path for the "
+ + "classloader is supplied, if applicable.", ex);
}
- return startService(serviceClass);
}
/**
@@ -264,26 +297,26 @@
@UserIdInt int curUserId, @UserIdInt int prevUserId) {
t.traceBegin("ssm." + onWhat + "User-" + curUserId);
Slog.i(TAG, "Calling on" + onWhat + "User " + curUserId);
- final UserInfo curUserInfo = getUserInfo(curUserId);
- final UserInfo prevUserInfo = prevUserId == UserHandle.USER_NULL ? null
- : getUserInfo(prevUserId);
+ final TargetUser curUser = new TargetUser(getUserInfo(curUserId));
+ final TargetUser prevUser = prevUserId == UserHandle.USER_NULL ? null
+ : new TargetUser(getUserInfo(prevUserId));
final int serviceLen = mServices.size();
for (int i = 0; i < serviceLen; i++) {
final SystemService service = mServices.get(i);
final String serviceName = service.getClass().getName();
- boolean supported = service.isSupported(curUserInfo);
+ boolean supported = service.isSupportedUser(curUser);
// Must check if either curUser or prevUser is supported (for example, if switching from
// unsupported to supported, we still need to notify the services)
- if (!supported && prevUserInfo != null) {
- supported = service.isSupported(prevUserInfo);
+ if (!supported && prevUser != null) {
+ supported = service.isSupportedUser(prevUser);
}
if (!supported) {
if (DEBUG) {
Slog.d(TAG, "Skipping " + onWhat + "User-" + curUserId + " on service "
+ serviceName + " because it's not supported (curUser: "
- + curUserInfo + ", prevUser:" + prevUserInfo + ")");
+ + curUser + ", prevUser:" + prevUser + ")");
} else {
Slog.i(TAG, "Skipping " + onWhat + "User-" + curUserId + " on "
+ serviceName);
@@ -295,25 +328,25 @@
try {
switch (onWhat) {
case SWITCH:
- service.onSwitchUser(prevUserInfo, curUserInfo);
+ service.onSwitchUser(prevUser, curUser);
break;
case START:
- service.onStartUser(curUserInfo);
+ service.onStartUser(curUser);
break;
case UNLOCK:
- service.onUnlockUser(curUserInfo);
+ service.onUnlockUser(curUser);
break;
case STOP:
- service.onStopUser(curUserInfo);
+ service.onStopUser(curUser);
break;
case CLEANUP:
- service.onCleanupUser(curUserInfo);
+ service.onCleanupUser(curUser);
break;
default:
throw new IllegalArgumentException(onWhat + " what?");
}
} catch (Exception ex) {
- Slog.wtf(TAG, "Failure reporting " + onWhat + " of user " + curUserInfo
+ Slog.wtf(TAG, "Failure reporting " + onWhat + " of user " + curUser
+ " to service " + serviceName, ex);
}
warnIfTooLong(SystemClock.elapsedRealtime() - time, service,
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 783715c..4f03a8e 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -23,6 +23,7 @@
import static java.util.Arrays.copyOf;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.AppOpsManager;
@@ -41,14 +42,23 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.telephony.Annotation;
+import android.telephony.Annotation.ApnType;
import android.telephony.Annotation.DataFailureCause;
import android.telephony.Annotation.RadioPowerState;
import android.telephony.Annotation.SrvccState;
+import android.telephony.BarringInfo;
import android.telephony.CallAttributes;
import android.telephony.CallQuality;
import android.telephony.CellIdentity;
import android.telephony.CellInfo;
import android.telephony.CellLocation;
+import android.telephony.CellSignalStrength;
+import android.telephony.CellSignalStrengthCdma;
+import android.telephony.CellSignalStrengthGsm;
+import android.telephony.CellSignalStrengthLte;
+import android.telephony.CellSignalStrengthNr;
+import android.telephony.CellSignalStrengthTdscdma;
+import android.telephony.CellSignalStrengthWcdma;
import android.telephony.DataFailCause;
import android.telephony.DisconnectCause;
import android.telephony.LocationAccessPolicy;
@@ -74,8 +84,6 @@
import com.android.internal.telephony.IOnSubscriptionsChangedListener;
import com.android.internal.telephony.IPhoneStateListener;
import com.android.internal.telephony.ITelephonyRegistry;
-import com.android.internal.telephony.PhoneConstants;
-import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.telephony.TelephonyPermissions;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
@@ -247,6 +255,8 @@
private int[] mCallPreciseDisconnectCause;
+ private List<BarringInfo> mBarringInfo = null;
+
private boolean mCarrierNetworkChangeState = false;
private PhoneCapability mPhoneCapability = null;
@@ -261,8 +271,8 @@
private final LocalLog mListenLog = new LocalLog(100);
// Per-phoneMap of APN Type to DataConnectionState
- private List<Map<String, PreciseDataConnectionState>> mPreciseDataConnectionStates =
- new ArrayList<Map<String, PreciseDataConnectionState>>();
+ private List<Map<Integer, PreciseDataConnectionState>> mPreciseDataConnectionStates =
+ new ArrayList<Map<Integer, PreciseDataConnectionState>>();
// Nothing here yet, but putting it here in case we want to add more in the future.
static final int ENFORCE_COARSE_LOCATION_PERMISSION_MASK = 0;
@@ -274,11 +284,12 @@
static final int ENFORCE_PHONE_STATE_PERMISSION_MASK =
PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR
| PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR
- | PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST;
+ | PhoneStateListener.LISTEN_EMERGENCY_NUMBER_LIST
+ | PhoneStateListener.LISTEN_REGISTRATION_FAILURE;
static final int PRECISE_PHONE_STATE_PERMISSION_MASK =
- PhoneStateListener.LISTEN_PRECISE_CALL_STATE |
- PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE;
+ PhoneStateListener.LISTEN_PRECISE_CALL_STATE
+ | PhoneStateListener.LISTEN_PRECISE_DATA_CONNECTION_STATE;
static final int READ_ACTIVE_EMERGENCY_SESSION_PERMISSION_MASK =
PhoneStateListener.LISTEN_OUTGOING_EMERGENCY_CALL
@@ -402,7 +413,11 @@
mVoiceActivationState = copyOf(mVoiceActivationState, mNumPhones);
mDataActivationState = copyOf(mDataActivationState, mNumPhones);
mUserMobileDataState = copyOf(mUserMobileDataState, mNumPhones);
- mSignalStrength = copyOf(mSignalStrength, mNumPhones);
+ if (mSignalStrength != null) {
+ mSignalStrength = copyOf(mSignalStrength, mNumPhones);
+ } else {
+ mSignalStrength = new SignalStrength[mNumPhones];
+ }
mMessageWaiting = copyOf(mMessageWaiting, mNumPhones);
mCallForwarding = copyOf(mCallForwarding, mNumPhones);
mCellIdentity = copyOf(mCellIdentity, mNumPhones);
@@ -424,6 +439,7 @@
cutListToSize(mCellInfo, mNumPhones);
cutListToSize(mImsReasonInfo, mNumPhones);
cutListToSize(mPreciseDataConnectionStates, mNumPhones);
+ cutListToSize(mBarringInfo, mNumPhones);
return;
}
@@ -436,7 +452,7 @@
mDataActivationState[i] = TelephonyManager.SIM_ACTIVATION_STATE_UNKNOWN;
mCallIncomingNumber[i] = "";
mServiceState[i] = new ServiceState();
- mSignalStrength[i] = new SignalStrength();
+ mSignalStrength[i] = null;
mUserMobileDataState[i] = false;
mMessageWaiting[i] = false;
mCallForwarding[i] = false;
@@ -454,7 +470,8 @@
mRingingCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
mForegroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
- mPreciseDataConnectionStates.add(new HashMap<String, PreciseDataConnectionState>());
+ mPreciseDataConnectionStates.add(new HashMap<Integer, PreciseDataConnectionState>());
+ mBarringInfo.add(i, new BarringInfo());
}
}
@@ -512,6 +529,7 @@
mEmergencyNumberList = new HashMap<>();
mOutgoingCallEmergencyNumber = new EmergencyNumber[numPhones];
mOutgoingSmsEmergencyNumber = new EmergencyNumber[numPhones];
+ mBarringInfo = new ArrayList<>();
for (int i = 0; i < numPhones; i++) {
mCallState[i] = TelephonyManager.CALL_STATE_IDLE;
mDataActivity[i] = TelephonyManager.DATA_ACTIVITY_NONE;
@@ -520,7 +538,7 @@
mDataActivationState[i] = TelephonyManager.SIM_ACTIVATION_STATE_UNKNOWN;
mCallIncomingNumber[i] = "";
mServiceState[i] = new ServiceState();
- mSignalStrength[i] = new SignalStrength();
+ mSignalStrength[i] = null;
mUserMobileDataState[i] = false;
mMessageWaiting[i] = false;
mCallForwarding[i] = false;
@@ -538,7 +556,8 @@
mRingingCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
mForegroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
mBackgroundCallState[i] = PreciseCallState.PRECISE_CALL_STATE_IDLE;
- mPreciseDataConnectionStates.add(new HashMap<String, PreciseDataConnectionState>());
+ mPreciseDataConnectionStates.add(new HashMap<Integer, PreciseDataConnectionState>());
+ mBarringInfo.add(i, new BarringInfo());
}
mAppOps = mContext.getSystemService(AppOpsManager.class);
@@ -797,10 +816,12 @@
}
if ((events & PhoneStateListener.LISTEN_SIGNAL_STRENGTH) != 0) {
try {
- int gsmSignalStrength = mSignalStrength[phoneId]
- .getGsmSignalStrength();
- r.callback.onSignalStrengthChanged((gsmSignalStrength == 99 ? -1
- : gsmSignalStrength));
+ if (mSignalStrength[phoneId] != null) {
+ int gsmSignalStrength = mSignalStrength[phoneId]
+ .getGsmSignalStrength();
+ r.callback.onSignalStrengthChanged((gsmSignalStrength == 99 ? -1
+ : gsmSignalStrength));
+ }
} catch (RemoteException ex) {
remove(r.binder);
}
@@ -857,7 +878,9 @@
}
if ((events & PhoneStateListener.LISTEN_SIGNAL_STRENGTHS) != 0) {
try {
- r.callback.onSignalStrengthsChanged(mSignalStrength[phoneId]);
+ if (mSignalStrength[phoneId] != null) {
+ r.callback.onSignalStrengthsChanged(mSignalStrength[phoneId]);
+ }
} catch (RemoteException ex) {
remove(r.binder);
}
@@ -977,6 +1000,19 @@
remove(r.binder);
}
}
+ if ((events & PhoneStateListener.LISTEN_BARRING_INFO) != 0) {
+ BarringInfo barringInfo = mBarringInfo.get(phoneId);
+ BarringInfo biNoLocation = barringInfo != null
+ ? barringInfo.createLocationInfoSanitizedCopy() : null;
+ if (VDBG) log("listen: call onBarringInfoChanged=" + barringInfo);
+ try {
+ r.callback.onBarringInfoChanged(
+ checkFineLocationAccess(r, Build.VERSION_CODES.R)
+ ? barringInfo : biNoLocation);
+ } catch (RemoteException ex) {
+ remove(r.binder);
+ }
+ }
}
}
} else {
@@ -1290,7 +1326,7 @@
public void notifyCarrierNetworkChange(boolean active) {
// only CarrierService with carrier privilege rule should have the permission
int[] subIds = Arrays.stream(SubscriptionManager.from(mContext)
- .getActiveSubscriptionIdList(false))
+ .getActiveAndHiddenSubscriptionIdList())
.filter(i -> TelephonyPermissions.checkCarrierPrivilegeForSubId(mContext,
i)).toArray();
if (ArrayUtils.isEmpty(subIds)) {
@@ -1479,11 +1515,12 @@
*
* @param phoneId the phoneId carrying the data connection
* @param subId the subscriptionId for the data connection
- * @param apnType the APN type that triggered a change in the data connection
+ * @param apnType the apn type bitmask, defined with {@code ApnSetting#TYPE_*} flags.
* @param preciseState a PreciseDataConnectionState that has info about the data connection
*/
+ @Override
public void notifyDataConnectionForSubscriber(
- int phoneId, int subId, String apnType, PreciseDataConnectionState preciseState) {
+ int phoneId, int subId, @ApnType int apnType, PreciseDataConnectionState preciseState) {
if (!checkNotifyPermission("notifyDataConnection()" )) {
return;
}
@@ -1509,7 +1546,7 @@
synchronized (mRecords) {
if (validatePhoneId(phoneId)) {
// We only call the callback when the change is for default APN type.
- if (PhoneConstants.APN_TYPE_DEFAULT.equals(apnType)
+ if ((ApnSetting.TYPE_DEFAULT & apnType) != 0
&& (mDataConnectionState[phoneId] != state
|| mDataConnectionNetworkType[phoneId] != networkType)) {
String str = "onDataConnectionStateChanged("
@@ -1578,7 +1615,7 @@
loge("This function should not be invoked");
}
- private void notifyDataConnectionFailedForSubscriber(int phoneId, int subId, String apnType) {
+ private void notifyDataConnectionFailedForSubscriber(int phoneId, int subId, int apnType) {
if (!checkNotifyPermission("notifyDataConnectionFailed()")) {
return;
}
@@ -1593,7 +1630,7 @@
new PreciseDataConnectionState(
TelephonyManager.DATA_UNKNOWN,
TelephonyManager.NETWORK_TYPE_UNKNOWN,
- ApnSetting.getApnTypesBitmaskFromString(apnType), null, null,
+ apnType, null, null,
DataFailCause.NONE, null));
for (Record r : mRecords) {
if (r.matchPhoneStateListenerEvent(
@@ -1762,7 +1799,8 @@
}
}
- public void notifyPreciseDataConnectionFailed(int phoneId, int subId, String apnType,
+ @Override
+ public void notifyPreciseDataConnectionFailed(int phoneId, int subId, @ApnType int apnType,
String apn, @DataFailureCause int failCause) {
if (!checkNotifyPermission("notifyPreciseDataConnectionFailed()")) {
return;
@@ -1778,7 +1816,7 @@
new PreciseDataConnectionState(
TelephonyManager.DATA_UNKNOWN,
TelephonyManager.NETWORK_TYPE_UNKNOWN,
- ApnSetting.getApnTypesBitmaskFromString(apnType), null, null,
+ apnType, null, null,
failCause, null));
for (Record r : mRecords) {
if (r.matchPhoneStateListenerEvent(
@@ -2051,6 +2089,86 @@
}
@Override
+ public void notifyRegistrationFailed(int phoneId, int subId, @NonNull CellIdentity cellIdentity,
+ @NonNull String chosenPlmn, int domain, int causeCode, int additionalCauseCode) {
+ if (!checkNotifyPermission("notifyRegistrationFailed()")) {
+ return;
+ }
+
+ // In case callers don't have fine location access, pre-construct a location-free version
+ // of the CellIdentity. This will still have the PLMN ID, which should be sufficient for
+ // most purposes.
+ final CellIdentity noLocationCi = cellIdentity.sanitizeLocationInfo();
+
+ synchronized (mRecords) {
+ if (validatePhoneId(phoneId)) {
+ for (Record r : mRecords) {
+ if (r.matchPhoneStateListenerEvent(
+ PhoneStateListener.LISTEN_REGISTRATION_FAILURE)
+ && idMatch(r.subId, subId, phoneId)) {
+ try {
+ r.callback.onRegistrationFailed(
+ checkFineLocationAccess(r, Build.VERSION_CODES.R)
+ ? cellIdentity : noLocationCi,
+ chosenPlmn, domain, causeCode,
+ additionalCauseCode);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
+ }
+ }
+ }
+ handleRemoveListLocked();
+ }
+ }
+
+ /**
+ * Send a notification of changes to barring status to PhoneStateListener registrants.
+ *
+ * @param phoneId the phoneId
+ * @param subId the subId
+ * @param barringInfo a structure containing the complete updated barring info.
+ */
+ public void notifyBarringInfoChanged(int phoneId, int subId, @NonNull BarringInfo barringInfo) {
+ if (!checkNotifyPermission("notifyBarringInfo()")) {
+ return;
+ }
+ if (barringInfo == null) {
+ log("Received null BarringInfo for subId=" + subId + ", phoneId=" + phoneId);
+ mBarringInfo.set(phoneId, new BarringInfo());
+ return;
+ }
+
+ synchronized (mRecords) {
+ if (validatePhoneId(phoneId)) {
+ mBarringInfo.set(phoneId, barringInfo);
+ // Barring info is non-null
+ BarringInfo biNoLocation = barringInfo.createLocationInfoSanitizedCopy();
+ if (VDBG) log("listen: call onBarringInfoChanged=" + barringInfo);
+ for (Record r : mRecords) {
+ if (r.matchPhoneStateListenerEvent(
+ PhoneStateListener.LISTEN_BARRING_INFO)
+ && idMatch(r.subId, subId, phoneId)) {
+ try {
+ if (DBG_LOC) {
+ log("notifyBarringInfo: mBarringInfo="
+ + barringInfo + " r=" + r);
+ }
+ r.callback.onBarringInfoChanged(
+ checkFineLocationAccess(r, Build.VERSION_CODES.R)
+ ? barringInfo : biNoLocation);
+ } catch (RemoteException ex) {
+ mRemoveList.add(r.binder);
+ }
+ }
+ }
+ }
+ handleRemoveListLocked();
+ }
+ }
+
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
@@ -2090,6 +2208,7 @@
pw.println("mPreciseDataConnectionStates=" + mPreciseDataConnectionStates.get(i));
pw.println("mOutgoingCallEmergencyNumber=" + mOutgoingCallEmergencyNumber[i]);
pw.println("mOutgoingSmsEmergencyNumber=" + mOutgoingSmsEmergencyNumber[i]);
+ pw.println("mBarringInfo=" + mBarringInfo.get(i));
pw.decreaseIndent();
}
pw.println("mCarrierNetworkChangeState=" + mCarrierNetworkChangeState);
@@ -2126,12 +2245,32 @@
/** Fired when a subscription's phone state changes. */
private static final String ACTION_SUBSCRIPTION_PHONE_STATE_CHANGED =
"android.intent.action.SUBSCRIPTION_PHONE_STATE";
+ /**
+ * Broadcast Action: The data connection state has changed for any one of the
+ * phone's mobile data connections (eg, default, MMS or GPS specific connection).
+ */
+ private static final String ACTION_ANY_DATA_CONNECTION_STATE_CHANGED =
+ "android.intent.action.ANY_DATA_STATE";
// Legacy intent extra keys, copied from PhoneConstants.
// Used in legacy intents sent here, for backward compatibility.
+ private static final String PHONE_CONSTANTS_DATA_APN_TYPE_KEY = "apnType";
+ private static final String PHONE_CONSTANTS_DATA_APN_KEY = "apn";
private static final String PHONE_CONSTANTS_SLOT_KEY = "slot";
+ private static final String PHONE_CONSTANTS_STATE_KEY = "state";
private static final String PHONE_CONSTANTS_SUBSCRIPTION_KEY = "subscription";
+ /**
+ * Broadcast Action: The phone's signal strength has changed. The intent will have the
+ * following extra values:
+ * phoneName - A string version of the phone name.
+ * asu - A numeric value for the signal strength.
+ * An ASU is 0-31 or -1 if unknown (for GSM, dBm = -113 - 2 * asu).
+ * The following special values are defined:
+ * 0 means "-113 dBm or less".31 means "-51 dBm or greater".
+ */
+ public static final String ACTION_SIGNAL_STRENGTH_CHANGED = "android.intent.action.SIG_STR";
+
private void broadcastServiceStateChanged(ServiceState state, int phoneId, int subId) {
long ident = Binder.clearCallingIdentity();
try {
@@ -2142,7 +2281,7 @@
Binder.restoreCallingIdentity(ident);
}
- Intent intent = new Intent(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
+ Intent intent = new Intent(Intent.ACTION_SERVICE_STATE);
intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
Bundle data = new Bundle();
state.fillInNotifierBundle(data);
@@ -2166,15 +2305,34 @@
Binder.restoreCallingIdentity(ident);
}
- Intent intent = new Intent(TelephonyIntents.ACTION_SIGNAL_STRENGTH_CHANGED);
+ Intent intent = new Intent(ACTION_SIGNAL_STRENGTH_CHANGED);
Bundle data = new Bundle();
- signalStrength.fillInNotifierBundle(data);
+ fillInSignalStrengthNotifierBundle(signalStrength, data);
intent.putExtras(data);
intent.putExtra(PHONE_CONSTANTS_SUBSCRIPTION_KEY, subId);
intent.putExtra(PHONE_CONSTANTS_SLOT_KEY, phoneId);
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
+ private void fillInSignalStrengthNotifierBundle(SignalStrength signalStrength, Bundle bundle) {
+ List<CellSignalStrength> cellSignalStrengths = signalStrength.getCellSignalStrengths();
+ for (CellSignalStrength cellSignalStrength : cellSignalStrengths) {
+ if (cellSignalStrength instanceof CellSignalStrengthLte) {
+ bundle.putParcelable("Lte", (CellSignalStrengthLte) cellSignalStrength);
+ } else if (cellSignalStrength instanceof CellSignalStrengthCdma) {
+ bundle.putParcelable("Cdma", (CellSignalStrengthCdma) cellSignalStrength);
+ } else if (cellSignalStrength instanceof CellSignalStrengthGsm) {
+ bundle.putParcelable("Gsm", (CellSignalStrengthGsm) cellSignalStrength);
+ } else if (cellSignalStrength instanceof CellSignalStrengthWcdma) {
+ bundle.putParcelable("Wcdma", (CellSignalStrengthWcdma) cellSignalStrength);
+ } else if (cellSignalStrength instanceof CellSignalStrengthTdscdma) {
+ bundle.putParcelable("Tdscdma", (CellSignalStrengthTdscdma) cellSignalStrength);
+ } else if (cellSignalStrength instanceof CellSignalStrengthNr) {
+ bundle.putParcelable("Nr", (CellSignalStrengthNr) cellSignalStrength);
+ }
+ }
+ }
+
/**
* Broadcasts an intent notifying apps of a phone state change. {@code subId} can be
* a valid subId, in which case this function fires a subId-specific intent, or it
@@ -2248,19 +2406,50 @@
}
private void broadcastDataConnectionStateChanged(int state, String apn,
- String apnType, int subId) {
+ int apnType, int subId) {
// Note: not reporting to the battery stats service here, because the
// status bar takes care of that after taking into account all of the
// required info.
- Intent intent = new Intent(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
- intent.putExtra(TelephonyManager.EXTRA_STATE, dataStateToString(state));
-
- intent.putExtra(PhoneConstants.DATA_APN_KEY, apn);
- intent.putExtra(PhoneConstants.DATA_APN_TYPE_KEY, apnType);
+ Intent intent = new Intent(ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
+ intent.putExtra(PHONE_CONSTANTS_STATE_KEY, dataStateToString(state));
+ intent.putExtra(PHONE_CONSTANTS_DATA_APN_KEY, apn);
+ intent.putExtra(PHONE_CONSTANTS_DATA_APN_TYPE_KEY, getApnTypesStringFromBitmask(apnType));
intent.putExtra(PHONE_CONSTANTS_SUBSCRIPTION_KEY, subId);
mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
}
+ private static final Map<Integer, String> APN_TYPE_INT_MAP;
+ static {
+ APN_TYPE_INT_MAP = new android.util.ArrayMap<Integer, String>();
+ APN_TYPE_INT_MAP.put(ApnSetting.TYPE_DEFAULT, "default");
+ APN_TYPE_INT_MAP.put(ApnSetting.TYPE_MMS, "mms");
+ APN_TYPE_INT_MAP.put(ApnSetting.TYPE_SUPL, "supl");
+ APN_TYPE_INT_MAP.put(ApnSetting.TYPE_DUN, "dun");
+ APN_TYPE_INT_MAP.put(ApnSetting.TYPE_HIPRI, "hipri");
+ APN_TYPE_INT_MAP.put(ApnSetting.TYPE_FOTA, "fota");
+ APN_TYPE_INT_MAP.put(ApnSetting.TYPE_IMS, "ims");
+ APN_TYPE_INT_MAP.put(ApnSetting.TYPE_CBS, "cbs");
+ APN_TYPE_INT_MAP.put(ApnSetting.TYPE_IA, "ia");
+ APN_TYPE_INT_MAP.put(ApnSetting.TYPE_EMERGENCY, "emergency");
+ APN_TYPE_INT_MAP.put(ApnSetting.TYPE_MCX, "mcx");
+ APN_TYPE_INT_MAP.put(ApnSetting.TYPE_XCAP, "xcap");
+ }
+
+ /**
+ * Copy of ApnSetting#getApnTypesStringFromBitmask for legacy broadcast.
+ * @param apnTypeBitmask bitmask of APN types.
+ * @return comma delimited list of APN types.
+ */
+ private static String getApnTypesStringFromBitmask(int apnTypeBitmask) {
+ List<String> types = new ArrayList<>();
+ for (Integer type : APN_TYPE_INT_MAP.keySet()) {
+ if ((apnTypeBitmask & type) == type) {
+ types.add(APN_TYPE_INT_MAP.get(type));
+ }
+ }
+ return android.text.TextUtils.join(",", types);
+ }
+
private void enforceNotifyPermissionOrCarrierPrivilege(String method) {
if (checkNotifyPermission()) {
return;
@@ -2483,11 +2672,14 @@
if ((events & PhoneStateListener.LISTEN_SIGNAL_STRENGTHS) != 0) {
try {
- SignalStrength signalStrength = mSignalStrength[phoneId];
- if (DBG) {
- log("checkPossibleMissNotify: onSignalStrengthsChanged SS=" + signalStrength);
+ if (mSignalStrength[phoneId] != null) {
+ SignalStrength signalStrength = mSignalStrength[phoneId];
+ if (DBG) {
+ log("checkPossibleMissNotify: onSignalStrengthsChanged SS="
+ + signalStrength);
+ }
+ r.callback.onSignalStrengthsChanged(new SignalStrength(signalStrength));
}
- r.callback.onSignalStrengthsChanged(new SignalStrength(signalStrength));
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
@@ -2495,14 +2687,16 @@
if ((events & PhoneStateListener.LISTEN_SIGNAL_STRENGTH) != 0) {
try {
- int gsmSignalStrength = mSignalStrength[phoneId]
- .getGsmSignalStrength();
- if (DBG) {
- log("checkPossibleMissNotify: onSignalStrengthChanged SS=" +
- gsmSignalStrength);
+ if (mSignalStrength[phoneId] != null) {
+ int gsmSignalStrength = mSignalStrength[phoneId]
+ .getGsmSignalStrength();
+ if (DBG) {
+ log("checkPossibleMissNotify: onSignalStrengthChanged SS="
+ + gsmSignalStrength);
+ }
+ r.callback.onSignalStrengthChanged((gsmSignalStrength == 99 ? -1
+ : gsmSignalStrength));
}
- r.callback.onSignalStrengthChanged((gsmSignalStrength == 99 ? -1
- : gsmSignalStrength));
} catch (RemoteException ex) {
mRemoveList.add(r.binder);
}
@@ -2651,8 +2845,14 @@
return "TD_SCDMA";
case TelephonyManager.NETWORK_TYPE_IWLAN:
return "IWLAN";
- case TelephonyManager.NETWORK_TYPE_LTE_CA:
- return "LTE_CA";
+
+ //TODO: This network type is marked as hidden because it is not a
+ // true network type and we are looking to remove it completely from the available list
+ // of network types. Since this method is only used for logging, in the event that this
+ // network type is selected, the log will read as "Unknown."
+ //case TelephonyManager.NETWORK_TYPE_LTE_CA:
+ // return "LTE_CA";
+
case TelephonyManager.NETWORK_TYPE_NR:
return "NR";
default:
diff --git a/services/core/java/com/android/server/TestNetworkService.java b/services/core/java/com/android/server/TestNetworkService.java
index c27b0da..ed3bab9 100644
--- a/services/core/java/com/android/server/TestNetworkService.java
+++ b/services/core/java/com/android/server/TestNetworkService.java
@@ -218,7 +218,7 @@
// Has to be in TestNetworkAgent to ensure all teardown codepaths properly clean up
// resources, even for binder death or unwanted calls.
synchronized (mTestNetworkTracker) {
- mTestNetworkTracker.remove(netId);
+ mTestNetworkTracker.remove(network.netId);
}
}
}
@@ -337,7 +337,7 @@
callingUid,
binder);
- mTestNetworkTracker.put(agent.netId, agent);
+ mTestNetworkTracker.put(agent.network.netId, agent);
}
} catch (SocketException e) {
throw new UncheckedIOException(e);
diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java
index 2091c2a..9ffe89c 100644
--- a/services/core/java/com/android/server/UiModeManagerService.java
+++ b/services/core/java/com/android/server/UiModeManagerService.java
@@ -1059,8 +1059,8 @@
if (Sandman.shouldStartDockApp(getContext(), homeIntent)) {
try {
int result = ActivityTaskManager.getService().startActivityWithConfig(
- null, null, homeIntent, null, null, null, 0, 0,
- mConfiguration, null, UserHandle.USER_CURRENT);
+ null, getContext().getBasePackageName(), homeIntent, null, null, null,
+ 0, 0, mConfiguration, null, UserHandle.USER_CURRENT);
if (ActivityManager.isStartResultSuccessful(result)) {
dockAppStarted = true;
} else if (result != ActivityManager.START_INTENT_NOT_RESOLVED) {
diff --git a/services/core/java/com/android/server/VibratorService.java b/services/core/java/com/android/server/VibratorService.java
index 76a8f92..5a56a9f 100644
--- a/services/core/java/com/android/server/VibratorService.java
+++ b/services/core/java/com/android/server/VibratorService.java
@@ -32,7 +32,6 @@
import android.hardware.vibrator.IVibrator;
import android.hardware.vibrator.V1_0.EffectStrength;
import android.icu.text.DateFormat;
-import android.media.AudioAttributes;
import android.media.AudioManager;
import android.os.BatteryStats;
import android.os.Binder;
@@ -54,6 +53,7 @@
import android.os.SystemClock;
import android.os.Trace;
import android.os.UserHandle;
+import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.os.WorkSource;
@@ -61,7 +61,6 @@
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.util.DebugUtils;
-import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.util.StatsLog;
@@ -107,8 +106,9 @@
private static final int SCALE_VERY_LOW_MAX_AMPLITUDE = 168; // 2/3 * 255
private static final int SCALE_LOW_MAX_AMPLITUDE = 192; // 3/4 * 255
- // If a vibration is playing for longer than 5s, it's probably not haptic feedback.
- private static final long MAX_HAPTIC_FEEDBACK_DURATION = 5000;
+ // Default vibration attributes. Used when vibration is requested without attributes
+ private static final VibrationAttributes DEFAULT_ATTRIBUTES =
+ new VibrationAttributes.Builder().build();
// If HAL supports callbacks set the timeout to ASYNC_TIMEOUT_MULTIPLIER * duration.
private static final long ASYNC_TIMEOUT_MULTIPLIER = 2;
@@ -163,8 +163,7 @@
private int mHapticFeedbackIntensity;
private int mNotificationIntensity;
private int mRingIntensity;
- private SparseArray<Pair<VibrationEffect, AudioAttributes>> mAlwaysOnEffects =
- new SparseArray<>();
+ private SparseArray<Vibration> mAlwaysOnEffects = new SparseArray<>();
static native boolean vibratorExists();
static native void vibratorInit();
@@ -207,7 +206,7 @@
// with other system events, any duration calculations should be done use startTime so as
// not to be affected by discontinuities created by RTC adjustments.
public final long startTimeDebug;
- public final AudioAttributes attrs;
+ public final VibrationAttributes attrs;
public final int uid;
public final String opPkg;
public final String reason;
@@ -220,7 +219,7 @@
public VibrationEffect originalEffect;
private Vibration(IBinder token, VibrationEffect effect,
- AudioAttributes attrs, int uid, String opPkg, String reason) {
+ VibrationAttributes attrs, int uid, String opPkg, String reason) {
this.token = token;
this.effect = effect;
this.startTime = SystemClock.elapsedRealtime();
@@ -253,28 +252,7 @@
}
public boolean isHapticFeedback() {
- if (VibratorService.this.isHapticFeedback(attrs.getUsage())) {
- return true;
- }
- if (effect instanceof VibrationEffect.Prebaked) {
- VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
- switch (prebaked.getId()) {
- case VibrationEffect.EFFECT_CLICK:
- case VibrationEffect.EFFECT_DOUBLE_CLICK:
- case VibrationEffect.EFFECT_HEAVY_CLICK:
- case VibrationEffect.EFFECT_TEXTURE_TICK:
- case VibrationEffect.EFFECT_TICK:
- case VibrationEffect.EFFECT_POP:
- case VibrationEffect.EFFECT_THUD:
- return true;
- default:
- Slog.w(TAG, "Unknown prebaked vibration effect, "
- + "assuming it isn't haptic feedback.");
- return false;
- }
- }
- final long duration = effect.getDuration();
- return duration >= 0 && duration < MAX_HAPTIC_FEEDBACK_DURATION;
+ return VibratorService.this.isHapticFeedback(attrs.getUsage());
}
public boolean isNotification() {
@@ -303,13 +281,13 @@
private final long mStartTimeDebug;
private final VibrationEffect mEffect;
private final VibrationEffect mOriginalEffect;
- private final AudioAttributes mAttrs;
+ private final VibrationAttributes mAttrs;
private final int mUid;
private final String mOpPkg;
private final String mReason;
- public VibrationInfo(long startTimeDebug, VibrationEffect effect,
- VibrationEffect originalEffect, AudioAttributes attrs, int uid,
+ VibrationInfo(long startTimeDebug, VibrationEffect effect,
+ VibrationEffect originalEffect, VibrationAttributes attrs, int uid,
String opPkg, String reason) {
mStartTimeDebug = startTimeDebug;
mEffect = effect;
@@ -481,6 +459,10 @@
Settings.System.getUriFor(Settings.System.RING_VIBRATION_INTENSITY),
true, mSettingObserver, UserHandle.USER_ALL);
+ mContext.getContentResolver().registerContentObserver(
+ Settings.Global.getUriFor(Settings.Global.ZEN_MODE),
+ true, mSettingObserver, UserHandle.USER_ALL);
+
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
@@ -528,7 +510,8 @@
}
@Override // Binder call
- public boolean setAlwaysOnEffect(int id, VibrationEffect effect, AudioAttributes attrs) {
+ public boolean setAlwaysOnEffect(int uid, String opPkg, int alwaysOnId, VibrationEffect effect,
+ VibrationAttributes attrs) {
if (!hasPermission(android.Manifest.permission.VIBRATE_ALWAYS_ON)) {
throw new SecurityException("Requires VIBRATE_ALWAYS_ON permission");
}
@@ -538,8 +521,8 @@
}
if (effect == null) {
synchronized (mLock) {
- mAlwaysOnEffects.delete(id);
- vibratorAlwaysOnDisable(id);
+ mAlwaysOnEffects.delete(alwaysOnId);
+ vibratorAlwaysOnDisable(alwaysOnId);
}
} else {
if (!verifyVibrationEffect(effect)) {
@@ -549,14 +532,11 @@
Slog.e(TAG, "Only prebaked effects supported for always-on.");
return false;
}
- if (attrs == null) {
- attrs = new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_UNKNOWN)
- .build();
- }
+ attrs = fixupVibrationAttributes(attrs);
synchronized (mLock) {
- mAlwaysOnEffects.put(id, Pair.create(effect, attrs));
- updateAlwaysOnLocked(id, effect, attrs);
+ Vibration vib = new Vibration(null, effect, attrs, uid, opPkg, null);
+ mAlwaysOnEffects.put(alwaysOnId, vib);
+ updateAlwaysOnLocked(alwaysOnId, vib);
}
}
return true;
@@ -596,6 +576,23 @@
return true;
}
+ private VibrationAttributes fixupVibrationAttributes(VibrationAttributes attrs) {
+ if (attrs == null) {
+ attrs = DEFAULT_ATTRIBUTES;
+ }
+ if (shouldBypassDnd(attrs)) {
+ if (!(hasPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
+ || hasPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ || hasPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING))) {
+ final int flags = attrs.getFlags()
+ & ~VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
+ attrs = new VibrationAttributes.Builder(attrs).replaceFlags(flags).build();
+ }
+ }
+
+ return attrs;
+ }
+
private static long[] getLongIntArray(Resources r, int resid) {
int[] ar = r.getIntArray(resid);
if (ar == null) {
@@ -610,7 +607,7 @@
@Override // Binder call
public void vibrate(int uid, String opPkg, VibrationEffect effect,
- @Nullable AudioAttributes attrs, String reason, IBinder token) {
+ @Nullable VibrationAttributes attrs, String reason, IBinder token) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "vibrate, reason = " + reason);
try {
if (!hasPermission(android.Manifest.permission.VIBRATE)) {
@@ -625,21 +622,7 @@
return;
}
- if (attrs == null) {
- attrs = new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_UNKNOWN)
- .build();
- }
-
- if (shouldBypassDnd(attrs)) {
- if (!(hasPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS)
- || hasPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- || hasPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING))) {
- final int flags = attrs.getAllFlags()
- & ~AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
- attrs = new AudioAttributes.Builder(attrs).replaceFlags(flags).build();
- }
- }
+ attrs = fixupVibrationAttributes(attrs);
// If our current vibration is longer than the new vibration and is the same amplitude,
// then just let the current one finish.
@@ -800,29 +783,8 @@
private void startVibrationLocked(final Vibration vib) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "startVibrationLocked");
try {
- if (!isAllowedToVibrateLocked(vib)) {
- return;
- }
-
final int intensity = getCurrentIntensityLocked(vib);
- if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) {
- return;
- }
-
- if (vib.isRingtone() && !shouldVibrateForRingtone()) {
- if (DEBUG) {
- Slog.e(TAG, "Vibrate ignored, not vibrating for ringtones");
- }
- return;
- }
-
- final int mode = getAppOpMode(vib);
- if (mode != AppOpsManager.MODE_ALLOWED) {
- if (mode == AppOpsManager.MODE_ERRORED) {
- // We might be getting calls from within system_server, so we don't actually
- // want to throw a SecurityException here.
- Slog.w(TAG, "Would be an error: vibrate from uid " + vib.uid);
- }
+ if (!shouldVibrate(vib, intensity)) {
return;
}
applyVibrationIntensityScalingLocked(vib, intensity);
@@ -868,18 +830,10 @@
return true;
}
- if (vib.attrs.getUsage() == AudioAttributes.USAGE_NOTIFICATION_RINGTONE) {
- return true;
- }
-
- if (vib.attrs.getUsage() == AudioAttributes.USAGE_ALARM
- || vib.attrs.getUsage() == AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY
- || vib.attrs.getUsage()
- == AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST) {
- return true;
- }
-
- return false;
+ int usage = vib.attrs.getUsage();
+ return usage == VibrationAttributes.USAGE_RINGTONE
+ || usage == VibrationAttributes.USAGE_ALARM
+ || usage == VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
}
private int getCurrentIntensityLocked(Vibration vib) {
@@ -968,13 +922,13 @@
}
}
- private static boolean shouldBypassDnd(AudioAttributes attrs) {
- return (attrs.getAllFlags() & AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0;
+ private static boolean shouldBypassDnd(VibrationAttributes attrs) {
+ return attrs.isFlagSet(VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY);
}
private int getAppOpMode(Vibration vib) {
int mode = mAppOps.checkAudioOpNoThrow(AppOpsManager.OP_VIBRATE,
- vib.attrs.getUsage(), vib.uid, vib.opPkg);
+ vib.attrs.getAudioAttributes().getUsage(), vib.uid, vib.opPkg);
if (mode == AppOpsManager.MODE_ALLOWED) {
mode = mAppOps.startOpNoThrow(AppOpsManager.OP_VIBRATE, vib.uid, vib.opPkg);
}
@@ -989,6 +943,35 @@
return mode;
}
+ private boolean shouldVibrate(Vibration vib, int intensity) {
+ if (!isAllowedToVibrateLocked(vib)) {
+ return false;
+ }
+
+ if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) {
+ return false;
+ }
+
+ if (vib.isRingtone() && !shouldVibrateForRingtone()) {
+ if (DEBUG) {
+ Slog.e(TAG, "Vibrate ignored, not vibrating for ringtones");
+ }
+ return false;
+ }
+
+ final int mode = getAppOpMode(vib);
+ if (mode != AppOpsManager.MODE_ALLOWED) {
+ if (mode == AppOpsManager.MODE_ERRORED) {
+ // We might be getting calls from within system_server, so we don't actually
+ // want to throw a SecurityException here.
+ Slog.w(TAG, "Would be an error: vibrate from uid " + vib.uid);
+ }
+ return false;
+ }
+
+ return true;
+ }
+
@GuardedBy("mLock")
private void reportFinishVibrationLocked() {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "reportFinishVibrationLocked");
@@ -1100,14 +1083,12 @@
mVibrator.getDefaultRingVibrationIntensity(), UserHandle.USER_CURRENT);
}
- private void updateAlwaysOnLocked(int id, VibrationEffect effect, AudioAttributes attrs) {
- // TODO: Check DND and LowPower settings
- final Vibration vib = new Vibration(null, effect, attrs, 0, null, null);
+ private void updateAlwaysOnLocked(int id, Vibration vib) {
final int intensity = getCurrentIntensityLocked(vib);
- if (intensity == Vibrator.VIBRATION_INTENSITY_OFF) {
+ if (!shouldVibrate(vib, intensity)) {
vibratorAlwaysOnDisable(id);
} else {
- final VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) effect;
+ final VibrationEffect.Prebaked prebaked = (VibrationEffect.Prebaked) vib.effect;
final int strength = intensityToEffectStrength(intensity);
vibratorAlwaysOnEnable(id, prebaked.getId(), strength);
}
@@ -1116,8 +1097,8 @@
private void updateAlwaysOnLocked() {
for (int i = 0; i < mAlwaysOnEffects.size(); i++) {
int id = mAlwaysOnEffects.keyAt(i);
- Pair<VibrationEffect, AudioAttributes> pair = mAlwaysOnEffects.valueAt(i);
- updateAlwaysOnLocked(id, pair.first, pair.second);
+ Vibration vib = mAlwaysOnEffects.valueAt(i);
+ updateAlwaysOnLocked(id, vib);
}
}
@@ -1148,7 +1129,7 @@
return vibratorExists();
}
- private void doVibratorOn(long millis, int amplitude, int uid, AudioAttributes attrs) {
+ private void doVibratorOn(long millis, int amplitude, int uid, VibrationAttributes attrs) {
Trace.traceBegin(Trace.TRACE_TAG_VIBRATOR, "doVibratorOn");
try {
synchronized (mInputDeviceVibrators) {
@@ -1163,7 +1144,7 @@
final int vibratorCount = mInputDeviceVibrators.size();
if (vibratorCount != 0) {
for (int i = 0; i < vibratorCount; i++) {
- mInputDeviceVibrators.get(i).vibrate(millis, attrs);
+ mInputDeviceVibrators.get(i).vibrate(millis, attrs.getAudioAttributes());
}
} else {
// Note: ordering is important here! Many haptic drivers will reset their
@@ -1272,28 +1253,19 @@
}
private static boolean isNotification(int usageHint) {
- switch (usageHint) {
- case AudioAttributes.USAGE_NOTIFICATION:
- case AudioAttributes.USAGE_NOTIFICATION_EVENT:
- case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
- case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
- case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
- return true;
- default:
- return false;
- }
+ return usageHint == VibrationAttributes.USAGE_NOTIFICATION;
}
private static boolean isRingtone(int usageHint) {
- return usageHint == AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
+ return usageHint == VibrationAttributes.USAGE_RINGTONE;
}
private static boolean isHapticFeedback(int usageHint) {
- return usageHint == AudioAttributes.USAGE_ASSISTANCE_SONIFICATION;
+ return usageHint == VibrationAttributes.USAGE_TOUCH;
}
private static boolean isAlarm(int usageHint) {
- return usageHint == AudioAttributes.USAGE_ALARM;
+ return usageHint == VibrationAttributes.USAGE_ALARM;
}
private void noteVibratorOnLocked(int uid, long millis) {
@@ -1332,11 +1304,11 @@
private class VibrateThread extends Thread {
private final VibrationEffect.Waveform mWaveform;
private final int mUid;
- private final AudioAttributes mAttrs;
+ private final VibrationAttributes mAttrs;
private boolean mForceStop;
- VibrateThread(VibrationEffect.Waveform waveform, int uid, AudioAttributes attrs) {
+ VibrateThread(VibrationEffect.Waveform waveform, int uid, VibrationAttributes attrs) {
mWaveform = waveform;
mUid = uid;
mAttrs = attrs;
@@ -1600,7 +1572,7 @@
Slog.e(TAG, "Playing external vibration: " + vib);
}
}
- final int usage = vib.getAudioAttributes().getUsage();
+ final int usage = vib.getVibrationAttributes().getUsage();
final int defaultIntensity;
final int currentIntensity;
if (isRingtone(usage)) {
@@ -1731,7 +1703,7 @@
VibrationEffect effect =
VibrationEffect.createOneShot(duration, VibrationEffect.DEFAULT_AMPLITUDE);
- AudioAttributes attrs = createAudioAttributes(commonOptions);
+ VibrationAttributes attrs = createVibrationAttributes(commonOptions);
vibrate(Binder.getCallingUid(), description, effect, attrs, "Shell Command",
mToken);
return 0;
@@ -1792,7 +1764,7 @@
amplitudesList.stream().mapToInt(Integer::intValue).toArray();
effect = VibrationEffect.createWaveform(timings, amplitudes, repeat);
}
- AudioAttributes attrs = createAudioAttributes(commonOptions);
+ VibrationAttributes attrs = createVibrationAttributes(commonOptions);
vibrate(Binder.getCallingUid(), description, effect, attrs, "Shell Command",
mToken);
return 0;
@@ -1824,7 +1796,7 @@
VibrationEffect effect =
VibrationEffect.get(id, false);
- AudioAttributes attrs = createAudioAttributes(commonOptions);
+ VibrationAttributes attrs = createVibrationAttributes(commonOptions);
vibrate(Binder.getCallingUid(), description, effect, attrs, "Shell Command",
mToken);
return 0;
@@ -1833,13 +1805,13 @@
}
}
- private AudioAttributes createAudioAttributes(CommonOptions commonOptions) {
+ private VibrationAttributes createVibrationAttributes(CommonOptions commonOptions) {
final int flags = commonOptions.force
- ? AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY
+ ? VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY
: 0;
- return new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_UNKNOWN)
- .setFlags(flags)
+ return new VibrationAttributes.Builder()
+ .setUsage(VibrationAttributes.USAGE_UNKNOWN)
+ .replaceFlags(flags)
.build();
}
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index 454941c..a60b09f 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -39,8 +39,10 @@
import android.util.EventLog;
import android.util.Log;
import android.util.Slog;
+import android.util.SparseArray;
import android.util.StatsLog;
+import com.android.internal.os.ProcessCpuTracker;
import com.android.internal.os.ZygoteConnectionConstants;
import com.android.server.am.ActivityManagerService;
import com.android.server.wm.SurfaceAnimationThread;
@@ -606,13 +608,18 @@
pids.add(Process.myPid());
if (mPhonePid > 0) pids.add(mPhonePid);
+ long anrTime = SystemClock.uptimeMillis();
+ ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(false);
final File stack = ActivityManagerService.dumpStackTraces(
- pids, null, null, getInterestingNativePids());
+ pids, processCpuTracker, new SparseArray<>(), getInterestingNativePids());
// Give some extra time to make sure the stack traces get written.
// The system's been hanging for a minute, another second or two won't hurt much.
SystemClock.sleep(5000);
+ processCpuTracker.update();
+ String cpuInfo = processCpuTracker.printCurrentState(anrTime);
+
// Trigger the kernel to dump all blocked threads, and backtraces on all CPUs to the kernel log
doSysRq('w');
doSysRq('l');
@@ -627,7 +634,7 @@
if (mActivity != null) {
mActivity.addErrorToDropBox(
"watchdog", null, "system_server", null, null, null,
- subject, null, stack, null);
+ subject, cpuInfo, stack, null);
}
StatsLog.write(StatsLog.SYSTEM_SERVER_WATCHDOG_OCCURRED, subject);
}
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index a372fca0..debc2a1 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -2129,16 +2129,6 @@
}
@Override
- public void removeAccount(IAccountManagerResponse response, Account account,
- boolean expectActivityLaunch) {
- removeAccountAsUser(
- response,
- account,
- expectActivityLaunch,
- UserHandle.getCallingUserId());
- }
-
- @Override
public void removeAccountAsUser(IAccountManagerResponse response, Account account,
boolean expectActivityLaunch, int userId) {
final int callingUid = Binder.getCallingUid();
@@ -4454,12 +4444,6 @@
@Override
@NonNull
- public Account[] getAccounts(String type, String opPackageName) {
- return getAccountsAsUser(type, UserHandle.getCallingUserId(), opPackageName);
- }
-
- @Override
- @NonNull
public Account[] getAccountsForPackage(String packageName, int uid, String opPackageName) {
int callingUid = Binder.getCallingUid();
if (!UserHandle.isSameApp(callingUid, Process.SYSTEM_UID)) {
diff --git a/services/core/java/com/android/server/adb/AdbDebuggingManager.java b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
index 4b48ef9..143474b 100644
--- a/services/core/java/com/android/server/adb/AdbDebuggingManager.java
+++ b/services/core/java/com/android/server/adb/AdbDebuggingManager.java
@@ -413,6 +413,11 @@
case MESSAGE_ADB_CLEAR: {
Slog.d(TAG, "Received a request to clear the adb authorizations");
mConnectedKeys.clear();
+ // If the key store has not yet been instantiated then do so now; this avoids
+ // the unnecessary creation of the key store when adb is not enabled.
+ if (mAdbKeyStore == null) {
+ mAdbKeyStore = new AdbKeyStore();
+ }
mAdbKeyStore.deleteKeyStore();
cancelJobToUpdateAdbKeyStore();
break;
diff --git a/services/core/java/com/android/server/am/ActiveInstrumentation.java b/services/core/java/com/android/server/am/ActiveInstrumentation.java
index b2c82f0..db63638 100644
--- a/services/core/java/com/android/server/am/ActiveInstrumentation.java
+++ b/services/core/java/com/android/server/am/ActiveInstrumentation.java
@@ -67,6 +67,9 @@
// Set to true when we have told the watcher the instrumentation is finished.
boolean mFinished;
+ // The uid of the process who started this instrumentation.
+ int mSourceUid;
+
ActiveInstrumentation(ActivityManagerService service) {
mService = service;
}
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index a58bd9b..e2a036a 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -2068,7 +2068,7 @@
+ " type=" + resolvedType + " callingUid=" + callingUid);
userId = mAm.mUserController.handleIncomingUser(callingPid, callingUid, userId, false,
- ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE, "service",
+ ActivityManagerInternal.ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE, "service",
callingPackage);
ServiceMap smap = getServiceMapLocked(userId);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index dd1f370..883e7c6 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -275,6 +275,7 @@
import android.provider.Settings;
import android.server.ServerProtoEnums;
import android.sysprop.VoldProperties;
+import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.style.SuggestionSpan;
@@ -318,7 +319,6 @@
import com.android.internal.os.ProcessCpuTracker;
import com.android.internal.os.TransferPipe;
import com.android.internal.os.Zygote;
-import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.FastPrintWriter;
@@ -2176,10 +2176,13 @@
@Override
public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args,
boolean asProto) {
- if (asProto) return;
if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext,
"cpuinfo", pw)) return;
synchronized (mActivityManagerService.mProcessCpuTracker) {
+ if (asProto) {
+ mActivityManagerService.mProcessCpuTracker.dumpProto(fd);
+ return;
+ }
pw.print(mActivityManagerService.mProcessCpuTracker.printCurrentLoad());
pw.print(mActivityManagerService.mProcessCpuTracker.printCurrentState(
SystemClock.uptimeMillis()));
@@ -2552,7 +2555,7 @@
Process.setThreadGroupAndCpuset(BackgroundThread.get().getThreadId(),
Process.THREAD_GROUP_SYSTEM);
Process.setThreadGroupAndCpuset(
- mOomAdjuster.mAppCompact.mCompactionThread.getThreadId(),
+ mOomAdjuster.mCachedAppOptimizer.mCachedAppOptimizerThread.getThreadId(),
Process.THREAD_GROUP_SYSTEM);
} catch (Exception e) {
Slog.w(TAG, "Setting background thread cpuset failed");
@@ -4338,6 +4341,18 @@
final boolean allUids = mAtmInternal.isGetTasksAllowed(
"getProcessMemoryInfo", callingPid, callingUid);
+ // Check if the caller is actually instrumented and from shell, if it's true, we may lift
+ // the throttle of PSS info sampling.
+ boolean isCallerInstrumentedFromShell = false;
+ synchronized (mPidsSelfLocked) {
+ ProcessRecord caller = mPidsSelfLocked.get(callingPid);
+ if (caller != null) {
+ final ActiveInstrumentation instr = caller.getActiveInstrumentation();
+ isCallerInstrumentedFromShell = instr != null
+ && (instr.mSourceUid == SHELL_UID || instr.mSourceUid == ROOT_UID);
+ }
+ }
+
Debug.MemoryInfo[] infos = new Debug.MemoryInfo[pids.length];
for (int i=pids.length-1; i>=0; i--) {
infos[i] = new Debug.MemoryInfo();
@@ -4361,7 +4376,8 @@
continue; // Not allowed to see other users.
}
}
- if (proc != null && proc.lastMemInfoTime >= lastNow && proc.lastMemInfo != null) {
+ if (proc != null && proc.lastMemInfoTime >= lastNow && proc.lastMemInfo != null
+ && !isCallerInstrumentedFromShell) {
// It hasn't been long enough that we want to take another sample; return
// the last one.
infos[i].set(proc.lastMemInfo);
@@ -5288,7 +5304,7 @@
String data, Bundle extras, boolean ordered,
boolean sticky, int sendingUser) {
synchronized (ActivityManagerService.this) {
- mOomAdjuster.mAppCompact.compactAllSystem();
+ mOomAdjuster.mCachedAppOptimizer.compactAllSystem();
requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false);
}
}
@@ -8984,7 +9000,7 @@
final long timeSinceLastIdle = now - mLastIdleTime;
// Compact all non-zygote processes to freshen up the page cache.
- mOomAdjuster.mAppCompact.compactAllSystem();
+ mOomAdjuster.mCachedAppOptimizer.compactAllSystem();
final long lowRamSinceLastIdle = getLowRamTimeSinceIdle(now);
mLastIdleTime = now;
@@ -9092,13 +9108,14 @@
final Resources res = mContext.getResources();
mAppErrors.loadAppsNotReportingCrashesFromConfigLocked(res.getString(
com.android.internal.R.string.config_appsNotReportingCrashes));
- mUserController.mUserSwitchUiEnabled = !res.getBoolean(
+ final boolean userSwitchUiEnabled = !res.getBoolean(
com.android.internal.R.bool.config_customUserSwitchUi);
- mUserController.mMaxRunningUsers = res.getInteger(
+ final int maxRunningUsers = res.getInteger(
com.android.internal.R.integer.config_multiuserMaxRunningUsers);
- mUserController.mDelayUserDataLocking = res.getBoolean(
+ final boolean delayUserDataLocking = res.getBoolean(
com.android.internal.R.bool.config_multiuserDelayUserDataLocking);
-
+ mUserController.setInitialConfig(userSwitchUiEnabled, maxRunningUsers,
+ delayUserDataLocking);
mWaitForNetworkTimeoutMs = waitForNetworkTimeoutMs;
mPssDeferralTime = pssDeferralMs;
}
@@ -10004,7 +10021,7 @@
synchronized(this) {
mConstants.dump(pw);
- mOomAdjuster.dumpAppCompactorSettings(pw);
+ mOomAdjuster.dumpCachedAppOptimizerSettings(pw);
pw.println();
if (dumpAll) {
pw.println("-------------------------------------------------------------------------------");
@@ -10409,7 +10426,7 @@
} else if ("settings".equals(cmd)) {
synchronized (this) {
mConstants.dump(pw);
- mOomAdjuster.dumpAppCompactorSettings(pw);
+ mOomAdjuster.dumpCachedAppOptimizerSettings(pw);
}
} else if ("services".equals(cmd) || "s".equals(cmd)) {
if (dumpClient) {
@@ -12594,7 +12611,7 @@
ArrayList<ProcessRecord> procs, PrintWriter categoryPw) {
long uptime = SystemClock.uptimeMillis();
long realtime = SystemClock.elapsedRealtime();
- final long[] tmpLong = new long[1];
+ final long[] tmpLong = new long[3];
if (procs == null) {
// No Java processes. Maybe they want to print a native process.
@@ -12627,17 +12644,25 @@
for (int i = nativeProcs.size() - 1 ; i >= 0 ; i--) {
final ProcessCpuTracker.Stats r = nativeProcs.get(i);
final int pid = r.pid;
- if (!opts.isCheckinRequest && opts.dumpDetails) {
- pw.println("\n** MEMINFO in pid " + pid + " [" + r.baseName + "] **");
- }
if (mi == null) {
mi = new Debug.MemoryInfo();
}
if (opts.dumpDetails || (!brief && !opts.oomOnly)) {
- Debug.getMemoryInfo(pid, mi);
+ if (!Debug.getMemoryInfo(pid, mi)) {
+ continue;
+ }
} else {
- mi.dalvikPss = (int)Debug.getPss(pid, tmpLong, null);
- mi.dalvikPrivateDirty = (int)tmpLong[0];
+ long pss = Debug.getPss(pid, tmpLong, null);
+ if (pss == 0) {
+ continue;
+ }
+ mi.nativePss = (int) pss;
+ mi.nativePrivateDirty = (int) tmpLong[0];
+ mi.nativeRss = (int) tmpLong[2];
+ }
+ if (!opts.isCheckinRequest && opts.dumpDetails) {
+ pw.println("\n** MEMINFO in pid " + pid + " ["
+ + r.baseName + "] **");
}
ActivityThread.dumpMemInfoTable(pw, mi, opts.isCheckinRequest,
opts.dumpFullDetails, opts.dumpDalvik, opts.dumpSummaryOnly,
@@ -12715,9 +12740,6 @@
hasActivities = r.hasActivities();
}
if (thread != null) {
- if (!opts.isCheckinRequest && opts.dumpDetails) {
- pw.println("\n** MEMINFO in pid " + pid + " [" + r.processName + "] **");
- }
if (mi == null) {
mi = new Debug.MemoryInfo();
}
@@ -12727,17 +12749,26 @@
if (opts.dumpDetails || (!brief && !opts.oomOnly)) {
reportType = ProcessStats.ADD_PSS_EXTERNAL_SLOW;
startTime = SystemClock.currentThreadTimeMillis();
- Debug.getMemoryInfo(pid, mi);
+ if (!Debug.getMemoryInfo(pid, mi)) {
+ continue;
+ }
endTime = SystemClock.currentThreadTimeMillis();
hasSwapPss = mi.hasSwappedOutPss;
} else {
reportType = ProcessStats.ADD_PSS_EXTERNAL;
startTime = SystemClock.currentThreadTimeMillis();
- mi.dalvikPss = (int)Debug.getPss(pid, tmpLong, null);
+ long pss = Debug.getPss(pid, tmpLong, null);
+ if (pss == 0) {
+ continue;
+ }
+ mi.dalvikPss = (int) pss;
endTime = SystemClock.currentThreadTimeMillis();
- mi.dalvikPrivateDirty = (int)tmpLong[0];
+ mi.dalvikPrivateDirty = (int) tmpLong[0];
mi.dalvikRss = (int) tmpLong[2];
}
+ if (!opts.isCheckinRequest && opts.dumpDetails) {
+ pw.println("\n** MEMINFO in pid " + pid + " [" + r.processName + "] **");
+ }
if (opts.dumpDetails) {
if (opts.localOnly) {
ActivityThread.dumpMemInfoTable(pw, mi, opts.isCheckinRequest, opts.dumpFullDetails,
@@ -12867,10 +12898,17 @@
mi = new Debug.MemoryInfo();
}
if (!brief && !opts.oomOnly) {
- Debug.getMemoryInfo(st.pid, mi);
+ if (!Debug.getMemoryInfo(st.pid, mi)) {
+ continue;
+ }
} else {
- mi.nativePss = (int)Debug.getPss(st.pid, tmpLong, null);
- mi.nativePrivateDirty = (int)tmpLong[0];
+ long pss = Debug.getPss(st.pid, tmpLong, null);
+ if (pss == 0) {
+ continue;
+ }
+ mi.nativePss = (int) pss;
+ mi.nativePrivateDirty = (int) tmpLong[0];
+ mi.nativeRss = (int) tmpLong[2];
}
final long myTotalPss = mi.getTotalPss();
@@ -13174,7 +13212,7 @@
ArrayList<ProcessRecord> procs) {
final long uptimeMs = SystemClock.uptimeMillis();
final long realtimeMs = SystemClock.elapsedRealtime();
- final long[] tmpLong = new long[1];
+ final long[] tmpLong = new long[3];
if (procs == null) {
// No Java processes. Maybe they want to print a native process.
@@ -13209,20 +13247,29 @@
for (int i = nativeProcs.size() - 1 ; i >= 0 ; i--) {
final ProcessCpuTracker.Stats r = nativeProcs.get(i);
final int pid = r.pid;
- final long nToken = proto.start(MemInfoDumpProto.NATIVE_PROCESSES);
-
- proto.write(MemInfoDumpProto.ProcessMemory.PID, pid);
- proto.write(MemInfoDumpProto.ProcessMemory.PROCESS_NAME, r.baseName);
if (mi == null) {
mi = new Debug.MemoryInfo();
}
if (opts.dumpDetails || (!brief && !opts.oomOnly)) {
- Debug.getMemoryInfo(pid, mi);
+ if (!Debug.getMemoryInfo(pid, mi)) {
+ continue;
+ }
} else {
- mi.dalvikPss = (int)Debug.getPss(pid, tmpLong, null);
- mi.dalvikPrivateDirty = (int)tmpLong[0];
+ long pss = Debug.getPss(pid, tmpLong, null);
+ if (pss == 0) {
+ continue;
+ }
+ mi.nativePss = (int) pss;
+ mi.nativePrivateDirty = (int) tmpLong[0];
+ mi.nativeRss = (int) tmpLong[2];
}
+
+ final long nToken = proto.start(MemInfoDumpProto.NATIVE_PROCESSES);
+
+ proto.write(MemInfoDumpProto.ProcessMemory.PID, pid);
+ proto.write(MemInfoDumpProto.ProcessMemory.PROCESS_NAME, r.baseName);
+
ActivityThread.dumpMemInfoTable(proto, mi, opts.dumpDalvik,
opts.dumpSummaryOnly, 0, 0, 0, 0, 0, 0);
@@ -13313,13 +13360,19 @@
if (opts.dumpDetails || (!brief && !opts.oomOnly)) {
reportType = ProcessStats.ADD_PSS_EXTERNAL_SLOW;
startTime = SystemClock.currentThreadTimeMillis();
- Debug.getMemoryInfo(pid, mi);
+ if (!Debug.getMemoryInfo(pid, mi)) {
+ continue;
+ }
endTime = SystemClock.currentThreadTimeMillis();
hasSwapPss = mi.hasSwappedOutPss;
} else {
reportType = ProcessStats.ADD_PSS_EXTERNAL;
startTime = SystemClock.currentThreadTimeMillis();
- mi.dalvikPss = (int) Debug.getPss(pid, tmpLong, null);
+ long pss = Debug.getPss(pid, tmpLong, null);
+ if (pss == 0) {
+ continue;
+ }
+ mi.dalvikPss = (int) pss;
endTime = SystemClock.currentThreadTimeMillis();
mi.dalvikPrivateDirty = (int) tmpLong[0];
mi.dalvikRss = (int) tmpLong[2];
@@ -13447,10 +13500,17 @@
mi = new Debug.MemoryInfo();
}
if (!brief && !opts.oomOnly) {
- Debug.getMemoryInfo(st.pid, mi);
+ if (!Debug.getMemoryInfo(st.pid, mi)) {
+ continue;
+ }
} else {
- mi.nativePss = (int)Debug.getPss(st.pid, tmpLong, null);
- mi.nativePrivateDirty = (int)tmpLong[0];
+ long pss = Debug.getPss(st.pid, tmpLong, null);
+ if (pss == 0) {
+ continue;
+ }
+ mi.nativePss = (int) pss;
+ mi.nativePrivateDirty = (int) tmpLong[0];
+ mi.nativeRss = (int) tmpLong[2];
}
final long myTotalPss = mi.getTotalPss();
@@ -15098,7 +15158,7 @@
|| AppWidgetManager.ACTION_APPWIDGET_CONFIGURE.equals(action)
|| AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)
|| LocationManager.HIGH_POWER_REQUEST_CHANGE_ACTION.equals(action)
- || TelephonyIntents.ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE.equals(action)
+ || TelephonyManager.ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE.equals(action)
|| SuggestionSpan.ACTION_SUGGESTION_PICKED.equals(action)
|| AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION.equals(action)
|| AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION.equals(action)) {
@@ -16166,6 +16226,7 @@
disableTestApiChecks, mountExtStorageFull, abiOverride);
app.setActiveInstrumentation(activeInstr);
activeInstr.mFinished = false;
+ activeInstr.mSourceUid = callingUid;
activeInstr.mRunningProcesses.add(app);
if (!mActiveInstrumentation.contains(activeInstr)) {
mActiveInstrumentation.add(activeInstr);
@@ -16348,6 +16409,22 @@
}
@Override
+ public boolean updateMccMncConfiguration(String mcc, String mnc) {
+ int mccInt, mncInt;
+ try {
+ mccInt = Integer.parseInt(mcc);
+ mncInt = Integer.parseInt(mnc);
+ } catch (NumberFormatException | StringIndexOutOfBoundsException ex) {
+ Slog.e(TAG, "Error parsing mcc: " + mcc + " mnc: " + mnc + ". ex=" + ex);
+ return false;
+ }
+ Configuration config = new Configuration();
+ config.mcc = mccInt;
+ config.mnc = mncInt == 0 ? Configuration.MNC_ZERO : mncInt;
+ return mActivityTaskManager.updateConfiguration(config);
+ }
+
+ @Override
public int getLaunchedFromUid(IBinder activityToken) {
return mActivityTaskManager.getLaunchedFromUid(activityToken);
}
@@ -17866,7 +17943,31 @@
@Override
public int stopUser(final int userId, boolean force, final IStopUserCallback callback) {
- return mUserController.stopUser(userId, force, callback, null /* keyEvictedCallback */);
+ return mUserController.stopUser(userId, force, /* allowDelayedLocking= */ false,
+ /* callback= */ callback, /* keyEvictedCallback= */ null);
+ }
+
+ /**
+ * Stops user but allow delayed locking. Delayed locking keeps user unlocked even after
+ * stopping only if {@code config_multiuserDelayUserDataLocking} overlay is set true.
+ *
+ * <p>When delayed locking is not enabled through the overlay, this call becomes the same
+ * with {@link #stopUser(int, boolean, IStopUserCallback)} call.
+ *
+ * @param userId User id to stop.
+ * @param force Force stop the user even if the user is related with system user or current
+ * user.
+ * @param callback Callback called when user has stopped.
+ *
+ * @return {@link ActivityManager#USER_OP_SUCCESS} when user is stopped successfully. Returns
+ * other {@code ActivityManager#USER_OP_*} codes for failure.
+ *
+ */
+ @Override
+ public int stopUserWithDelayedLocking(final int userId, boolean force,
+ final IStopUserCallback callback) {
+ return mUserController.stopUser(userId, force, /* allowDelayedLocking= */ true,
+ /* callback= */ callback, /* keyEvictedCallback= */ null);
}
@Override
@@ -18272,7 +18373,7 @@
@Override
public int getMaxRunningUsers() {
- return mUserController.mMaxRunningUsers;
+ return mUserController.getMaxRunningUsers();
}
@Override
@@ -18613,6 +18714,11 @@
}
@Override
+ public void monitor() {
+ ActivityManagerService.this.monitor();
+ }
+
+ @Override
public long inputDispatchingTimedOut(int pid, boolean aboveSystem, String reason) {
synchronized (ActivityManagerService.this) {
return ActivityManagerService.this.inputDispatchingTimedOut(
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index a4c44fa..d7ad1c2 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -2934,33 +2934,37 @@
}
ArraySet<Long> enabled = new ArraySet<>();
ArraySet<Long> disabled = new ArraySet<>();
- switch (toggleValue) {
- case "enable":
- enabled.add(changeId);
- pw.println("Enabled change " + changeId + " for " + packageName + ".");
- CompatibilityChangeConfig overrides =
- new CompatibilityChangeConfig(
- new Compatibility.ChangeConfig(enabled, disabled));
- platformCompat.setOverrides(overrides, packageName);
- return 0;
- case "disable":
- disabled.add(changeId);
- pw.println("Disabled change " + changeId + " for " + packageName + ".");
- overrides =
- new CompatibilityChangeConfig(
- new Compatibility.ChangeConfig(enabled, disabled));
- platformCompat.setOverrides(overrides, packageName);
- return 0;
- case "reset":
- if (platformCompat.clearOverride(changeId, packageName)) {
- pw.println("Reset change " + changeId + " for " + packageName
- + " to default value.");
- } else {
- pw.println("No override exists for changeId " + changeId + ".");
- }
- return 0;
- default:
- pw.println("Invalid toggle value: '" + toggleValue + "'.");
+ try {
+ switch (toggleValue) {
+ case "enable":
+ enabled.add(changeId);
+ CompatibilityChangeConfig overrides =
+ new CompatibilityChangeConfig(
+ new Compatibility.ChangeConfig(enabled, disabled));
+ platformCompat.setOverrides(overrides, packageName);
+ pw.println("Enabled change " + changeId + " for " + packageName + ".");
+ return 0;
+ case "disable":
+ disabled.add(changeId);
+ overrides =
+ new CompatibilityChangeConfig(
+ new Compatibility.ChangeConfig(enabled, disabled));
+ platformCompat.setOverrides(overrides, packageName);
+ pw.println("Disabled change " + changeId + " for " + packageName + ".");
+ return 0;
+ case "reset":
+ if (platformCompat.clearOverride(changeId, packageName)) {
+ pw.println("Reset change " + changeId + " for " + packageName
+ + " to default value.");
+ } else {
+ pw.println("No override exists for changeId " + changeId + ".");
+ }
+ return 0;
+ default:
+ pw.println("Invalid toggle value: '" + toggleValue + "'.");
+ }
+ } catch (SecurityException e) {
+ pw.println(e.getMessage());
}
return -1;
}
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index c380726..8071f52 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -33,8 +33,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
-import android.content.pm.ModuleInfo;
-import android.content.pm.PackageManager;
import android.content.pm.VersionedPackage;
import android.net.Uri;
import android.os.Binder;
@@ -56,7 +54,6 @@
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
import com.android.server.PackageWatchdog;
-import com.android.server.RescueParty;
import com.android.server.wm.WindowProcessController;
import java.io.FileDescriptor;
@@ -88,6 +85,11 @@
private final ProcessMap<Long> mProcessCrashTimesPersistent = new ProcessMap<>();
/**
+ * The last time that various processes have crashed and shown an error dialog.
+ */
+ private final ProcessMap<Long> mProcessCrashShowDialogTimes = new ProcessMap<>();
+
+ /**
* Set of applications that we consider to be bad, and will reject
* incoming broadcasts from (which the user has no control over).
* Processes are added to this set when they have crashed twice within
@@ -418,28 +420,6 @@
}
if (r != null) {
- boolean isApexModule = false;
- try {
- for (String androidPackage : r.getPackageList()) {
- ModuleInfo moduleInfo = mContext.getPackageManager().getModuleInfo(
- androidPackage, /*flags=*/ 0);
- if (moduleInfo != null) {
- isApexModule = true;
- break;
- }
- }
- } catch (IllegalStateException | PackageManager.NameNotFoundException e) {
- // Call to PackageManager#getModuleInfo() can result in NameNotFoundException or
- // IllegalStateException. In case they are thrown, there isn't much we can do
- // other than proceed with app crash handling.
- }
-
- if (r.isPersistent() || isApexModule) {
- // If a persistent app or apex module is stuck in a crash loop, the device isn't
- // very usable, so we want to consider sending out a rescue party.
- RescueParty.noteAppCrash(mContext, r.uid);
- }
-
mPackageWatchdog.onPackageFailure(r.getPackageListWithVersionCode(),
PackageWatchdog.FAILURE_REASON_APP_CRASH);
}
@@ -820,6 +800,11 @@
}
return;
}
+ Long crashShowErrorTime = null;
+ if (!proc.isolated) {
+ crashShowErrorTime = mProcessCrashShowDialogTimes.get(proc.info.processName,
+ proc.uid);
+ }
final boolean showFirstCrash = Settings.Global.getInt(
mContext.getContentResolver(),
Settings.Global.SHOW_FIRST_CRASH_DIALOG, 0) != 0;
@@ -830,10 +815,16 @@
mService.mUserController.getCurrentUserId()) != 0;
final boolean crashSilenced = mAppsNotReportingCrashes != null &&
mAppsNotReportingCrashes.contains(proc.info.packageName);
+ final long now = SystemClock.uptimeMillis();
+ final boolean shouldThottle = crashShowErrorTime != null
+ && now < crashShowErrorTime + ProcessList.MIN_CRASH_INTERVAL;
if ((mService.mAtmInternal.canShowErrorDialogs() || showBackground)
- && !crashSilenced
+ && !crashSilenced && !shouldThottle
&& (showFirstCrash || showFirstCrashDevOption || data.repeating)) {
proc.getDialogController().showCrashDialogs(data);
+ if (!proc.isolated) {
+ mProcessCrashShowDialogTimes.put(proc.info.processName, proc.uid, now);
+ }
} else {
// The device is asleep, so just pretend that the user
// saw a crash dialog and hit "force quit".
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 37026fd..a98b83b 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -402,7 +402,7 @@
}
public ParcelFileDescriptor getStatisticsStream() {
- mContext.enforceCallingPermission(
+ mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.BATTERY_STATS, null);
//Slog.i("foo", "SENDING BATTERY INFO:");
//mStats.dumpLocked(new LogPrinter(Log.INFO, "foo", Log.LOG_ID_SYSTEM));
diff --git a/services/core/java/com/android/server/am/AppCompactor.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
similarity index 97%
rename from services/core/java/com/android/server/am/AppCompactor.java
rename to services/core/java/com/android/server/am/CachedAppOptimizer.java
index b7e2065..3ca5ebc 100644
--- a/services/core/java/com/android/server/am/AppCompactor.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -51,7 +51,7 @@
import java.util.Random;
import java.util.Set;
-public final class AppCompactor {
+public final class CachedAppOptimizer {
// Flags stored in the DeviceConfig API.
@VisibleForTesting static final String KEY_USE_COMPACTION = "use_compaction";
@@ -122,7 +122,7 @@
* that will wipe out the cpuset assignment for system_server threads.
* Accordingly, this is in the AMS constructor.
*/
- final ServiceThread mCompactionThread;
+ final ServiceThread mCachedAppOptimizerThread;
private final ArrayList<ProcessRecord> mPendingCompactionProcesses =
new ArrayList<ProcessRecord>();
@@ -214,15 +214,15 @@
private int mPersistentCompactionCount;
private int mBfgsCompactionCount;
- public AppCompactor(ActivityManagerService am) {
+ public CachedAppOptimizer(ActivityManagerService am) {
mAm = am;
- mCompactionThread = new ServiceThread("CompactionThread",
+ mCachedAppOptimizerThread = new ServiceThread("CachedAppOptimizerThread",
THREAD_PRIORITY_FOREGROUND, true);
mProcStateThrottle = new HashSet<>();
}
@VisibleForTesting
- AppCompactor(ActivityManagerService am, PropertyChangedCallbackForTest callback) {
+ CachedAppOptimizer(ActivityManagerService am, PropertyChangedCallbackForTest callback) {
this(am);
mTestCallback = callback;
}
@@ -243,7 +243,7 @@
updateFullDeltaRssThrottle();
updateProcStateThrottle();
}
- Process.setThreadGroupAndCpuset(mCompactionThread.getThreadId(),
+ Process.setThreadGroupAndCpuset(mCachedAppOptimizerThread.getThreadId(),
Process.THREAD_GROUP_SYSTEM);
}
@@ -258,7 +258,7 @@
@GuardedBy("mAm")
void dump(PrintWriter pw) {
- pw.println("AppCompactor settings");
+ pw.println("CachedAppOptimizer settings");
synchronized (mPhenotypeFlagLock) {
pw.println(" " + KEY_USE_COMPACTION + "=" + mUseCompaction);
pw.println(" " + KEY_COMPACT_ACTION_1 + "=" + mCompactActionSome);
@@ -300,7 +300,7 @@
app.reqCompactAction = COMPACT_PROCESS_SOME;
mPendingCompactionProcesses.add(app);
mCompactionHandler.sendMessage(
- mCompactionHandler.obtainMessage(
+ mCompactionHandler.obtainMessage(
COMPACT_PROCESS_MSG, app.setAdj, app.setProcState));
}
@@ -309,7 +309,7 @@
app.reqCompactAction = COMPACT_PROCESS_FULL;
mPendingCompactionProcesses.add(app);
mCompactionHandler.sendMessage(
- mCompactionHandler.obtainMessage(
+ mCompactionHandler.obtainMessage(
COMPACT_PROCESS_MSG, app.setAdj, app.setProcState));
}
@@ -362,8 +362,8 @@
private void updateUseCompaction() {
mUseCompaction = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
KEY_USE_COMPACTION, DEFAULT_USE_COMPACTION);
- if (mUseCompaction && !mCompactionThread.isAlive()) {
- mCompactionThread.start();
+ if (mUseCompaction && !mCachedAppOptimizerThread.isAlive()) {
+ mCachedAppOptimizerThread.start();
mCompactionHandler = new MemCompactionHandler();
}
}
@@ -521,7 +521,7 @@
private final class MemCompactionHandler extends Handler {
private MemCompactionHandler() {
- super(mCompactionThread.getLooper());
+ super(mCachedAppOptimizerThread.getLooper());
}
@Override
diff --git a/services/core/java/com/android/server/am/CarUserSwitchingDialog.java b/services/core/java/com/android/server/am/CarUserSwitchingDialog.java
index ebfc2a0..549051d 100644
--- a/services/core/java/com/android/server/am/CarUserSwitchingDialog.java
+++ b/services/core/java/com/android/server/am/CarUserSwitchingDialog.java
@@ -32,6 +32,7 @@
import android.graphics.RectF;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.os.Build;
import android.os.UserManager;
import android.view.LayoutInflater;
import android.view.View;
@@ -81,8 +82,15 @@
.setImageDrawable(drawable);
}
- ((TextView) view.findViewById(R.id.user_loading))
- .setText(res.getString(R.string.car_loading_profile));
+ TextView msgView = view.findViewById(R.id.user_loading);
+ // TODO: use developer settings instead
+ if (Build.IS_DEBUGGABLE) {
+ // TODO: use specific string
+ msgView.setText(res.getString(R.string.car_loading_profile) + " user\n(from "
+ + mOldUser.id + " to " + mNewUser.id + ")");
+ } else {
+ msgView.setText(res.getString(R.string.car_loading_profile));
+ }
setView(view);
}
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 12f4656..f86d6a7 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -122,9 +122,9 @@
PowerManagerInternal mLocalPowerManager;
/**
- * Service for compacting background apps.
+ * Service for optimizing resource usage from background apps.
*/
- AppCompactor mAppCompact;
+ CachedAppOptimizer mCachedAppOptimizer;
ActivityManagerConstants mConstants;
@@ -197,7 +197,7 @@
mLocalPowerManager = LocalServices.getService(PowerManagerInternal.class);
mConstants = mService.mConstants;
- mAppCompact = new AppCompactor(mService);
+ mCachedAppOptimizer = new CachedAppOptimizer(mService);
mProcessGroupHandler = new Handler(adjusterThread.getLooper(), msg -> {
final int pid = msg.arg1;
@@ -224,7 +224,7 @@
}
void initSettings() {
- mAppCompact.init();
+ mCachedAppOptimizer.init();
}
/**
@@ -1541,12 +1541,12 @@
trackedProcState = true;
}
} else if ((cr.flags & Context.BIND_NOT_PERCEPTIBLE) != 0
- && clientAdj < ProcessList.PERCEPTIBLE_APP_ADJ
- && adj > ProcessList.PERCEPTIBLE_LOW_APP_ADJ) {
+ && clientAdj <= ProcessList.PERCEPTIBLE_APP_ADJ
+ && adj >= ProcessList.PERCEPTIBLE_LOW_APP_ADJ) {
newAdj = ProcessList.PERCEPTIBLE_LOW_APP_ADJ;
} else if ((cr.flags&Context.BIND_NOT_VISIBLE) != 0
&& clientAdj < ProcessList.PERCEPTIBLE_APP_ADJ
- && adj > ProcessList.PERCEPTIBLE_APP_ADJ) {
+ && adj >= ProcessList.PERCEPTIBLE_APP_ADJ) {
newAdj = ProcessList.PERCEPTIBLE_APP_ADJ;
} else if (clientAdj >= ProcessList.PERCEPTIBLE_APP_ADJ) {
newAdj = clientAdj;
@@ -1978,7 +1978,7 @@
int changes = 0;
// don't compact during bootup
- if (mAppCompact.useCompaction() && mService.mBooted) {
+ if (mCachedAppOptimizer.useCompaction() && mService.mBooted) {
// Cached and prev/home compaction
if (app.curAdj != app.setAdj) {
// Perform a minor compaction when a perceptible app becomes the prev/home app
@@ -1987,26 +1987,26 @@
if (app.setAdj <= ProcessList.PERCEPTIBLE_APP_ADJ &&
(app.curAdj == ProcessList.PREVIOUS_APP_ADJ ||
app.curAdj == ProcessList.HOME_APP_ADJ)) {
- mAppCompact.compactAppSome(app);
+ mCachedAppOptimizer.compactAppSome(app);
} else if ((app.setAdj < ProcessList.CACHED_APP_MIN_ADJ
|| app.setAdj > ProcessList.CACHED_APP_MAX_ADJ)
&& app.curAdj >= ProcessList.CACHED_APP_MIN_ADJ
&& app.curAdj <= ProcessList.CACHED_APP_MAX_ADJ) {
- mAppCompact.compactAppFull(app);
+ mCachedAppOptimizer.compactAppFull(app);
}
} else if (mService.mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE
&& app.setAdj < ProcessList.FOREGROUND_APP_ADJ
// Because these can fire independent of oom_adj/procstate changes, we need
// to throttle the actual dispatch of these requests in addition to the
// processing of the requests. As a result, there is throttling both here
- // and in AppCompactor.
- && mAppCompact.shouldCompactPersistent(app, now)) {
- mAppCompact.compactAppPersistent(app);
+ // and in CachedAppOptimizer.
+ && mCachedAppOptimizer.shouldCompactPersistent(app, now)) {
+ mCachedAppOptimizer.compactAppPersistent(app);
} else if (mService.mWakefulness != PowerManagerInternal.WAKEFULNESS_AWAKE
&& app.getCurProcState()
== ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
- && mAppCompact.shouldCompactBFGS(app, now)) {
- mAppCompact.compactAppBfgs(app);
+ && mCachedAppOptimizer.shouldCompactBFGS(app, now)) {
+ mCachedAppOptimizer.compactAppBfgs(app);
}
}
@@ -2439,7 +2439,7 @@
}
@GuardedBy("mService")
- void dumpAppCompactorSettings(PrintWriter pw) {
- mAppCompact.dump(pw);
+ void dumpCachedAppOptimizerSettings(PrintWriter pw) {
+ mCachedAppOptimizer.dump(pw);
}
}
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index a1e1f29..b7f867d 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -103,6 +103,7 @@
import com.android.internal.util.MemInfoReader;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
+import com.android.server.SystemConfig;
import com.android.server.Watchdog;
import com.android.server.compat.PlatformCompat;
import com.android.server.pm.dex.DexManager;
@@ -357,6 +358,8 @@
private boolean mAppDataIsolationEnabled = false;
+ private ArrayList<String> mAppDataIsolationWhitelistedApps;
+
/**
* Temporary to avoid allocations. Protected by main lock.
*/
@@ -644,7 +647,9 @@
// Get this after boot, and won't be changed until it's rebooted, as we don't
// want some apps enabled while some apps disabled
mAppDataIsolationEnabled =
- SystemProperties.getBoolean(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, false);
+ SystemProperties.getBoolean(ANDROID_APP_DATA_ISOLATION_ENABLED_PROPERTY, true);
+ mAppDataIsolationWhitelistedApps = new ArrayList<>(
+ SystemConfig.getInstance().getAppDataIsolationWhitelistedApps());
if (sKillHandler == null) {
sKillThread = new ServiceThread(TAG + ":kill",
@@ -1555,20 +1560,26 @@
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
}
-
+ int numGids = 3;
+ if (mountExternal == Zygote.MOUNT_EXTERNAL_ANDROID_WRITABLE) {
+ numGids++;
+ }
/*
* Add shared application and profile GIDs so applications can share some
* resources like shared libraries and access user-wide resources
*/
if (ArrayUtils.isEmpty(permGids)) {
- gids = new int[3];
+ gids = new int[numGids];
} else {
- gids = new int[permGids.length + 3];
- System.arraycopy(permGids, 0, gids, 3, permGids.length);
+ gids = new int[permGids.length + numGids];
+ System.arraycopy(permGids, 0, gids, numGids, permGids.length);
}
gids[0] = UserHandle.getSharedAppGid(UserHandle.getAppId(uid));
gids[1] = UserHandle.getCacheAppGid(UserHandle.getAppId(uid));
gids[2] = UserHandle.getUserGid(UserHandle.getUserId(uid));
+ if (numGids > 3) {
+ gids[3] = Process.SDCARD_RW_GID;
+ }
// Replace any invalid GIDs
if (gids[0] == UserHandle.ERR_GID) gids[0] = gids[2];
@@ -1905,6 +1916,16 @@
result.put(packageName, Pair.create(volumeUuid, inode));
}
}
+ if (mAppDataIsolationWhitelistedApps != null) {
+ for (String packageName : mAppDataIsolationWhitelistedApps) {
+ String volumeUuid = pmInt.getPackage(packageName).getVolumeUuid();
+ long inode = pmInt.getCeDataInode(packageName, userId);
+ if (inode != 0) {
+ result.put(packageName, Pair.create(volumeUuid, inode));
+ }
+ }
+ }
+
return result;
}
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index eb1ab38..f3a2e70 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -16,12 +16,14 @@
package com.android.server.am;
+import static android.Manifest.permission.INTERACT_ACROSS_PROFILES;
import static android.Manifest.permission.INTERACT_ACROSS_USERS;
import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
import static android.app.ActivityManager.USER_OP_ERROR_IS_SYSTEM;
import static android.app.ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
import static android.app.ActivityManager.USER_OP_IS_CURRENT;
import static android.app.ActivityManager.USER_OP_SUCCESS;
+import static android.app.ActivityManagerInternal.ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE;
import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE;
@@ -91,7 +93,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.Preconditions;
import com.android.internal.widget.LockPatternUtils;
import com.android.server.FgThread;
import com.android.server.LocalServices;
@@ -159,7 +160,8 @@
* <p>Note: Current and system user (and their related profiles) are never stopped when
* switching users. Due to that, the actual number of running users can exceed mMaxRunningUsers
*/
- int mMaxRunningUsers;
+ @GuardedBy("mLock")
+ private int mMaxRunningUsers;
// Lock for internal state.
private final Object mLock = new Object();
@@ -211,7 +213,8 @@
private final RemoteCallbackList<IUserSwitchObserver> mUserSwitchObservers
= new RemoteCallbackList<>();
- boolean mUserSwitchUiEnabled = true;
+ @GuardedBy("mLock")
+ private boolean mUserSwitchUiEnabled = true;
/**
* Currently active user switch callbacks.
@@ -244,10 +247,11 @@
/**
* In this mode, user is always stopped when switched out but locking of user data is
* postponed until total number of unlocked users in the system reaches mMaxRunningUsers.
- * Once total number of unlocked users reach mMaxRunningUsers, least recentely used user
+ * Once total number of unlocked users reach mMaxRunningUsers, least recently used user
* will be locked.
*/
- boolean mDelayUserDataLocking;
+ @GuardedBy("mLock")
+ private boolean mDelayUserDataLocking;
/**
* Keep track of last active users for mDelayUserDataLocking.
* The latest stopped user is placed in front while the least recently stopped user in back.
@@ -273,6 +277,33 @@
updateStartedUserArrayLU();
}
+ void setInitialConfig(boolean userSwitchUiEnabled, int maxRunningUsers,
+ boolean delayUserDataLocking) {
+ synchronized (mLock) {
+ mUserSwitchUiEnabled = userSwitchUiEnabled;
+ mMaxRunningUsers = maxRunningUsers;
+ mDelayUserDataLocking = delayUserDataLocking;
+ }
+ }
+
+ private boolean isUserSwitchUiEnabled() {
+ synchronized (mLock) {
+ return mUserSwitchUiEnabled;
+ }
+ }
+
+ int getMaxRunningUsers() {
+ synchronized (mLock) {
+ return mMaxRunningUsers;
+ }
+ }
+
+ private boolean isDelayUserDataLockingEnabled() {
+ synchronized (mLock) {
+ return mDelayUserDataLocking;
+ }
+ }
+
void finishUserSwitch(UserState uss) {
// This call holds the AM lock so we post to the handler.
mHandler.post(() -> {
@@ -319,7 +350,11 @@
// Owner/System user and current user can't be stopped
continue;
}
- if (stopUsersLU(userId, false, null, null) == USER_OP_SUCCESS) {
+ // allowDelayedLocking set here as stopping user is done without any explicit request
+ // from outside.
+ if (stopUsersLU(userId, /* force= */ false, /* allowDelayedLocking= */ true,
+ /* stopUserCallback= */ null, /* keyEvictedCallback= */ null)
+ == USER_OP_SUCCESS) {
iterator.remove();
}
}
@@ -565,8 +600,8 @@
// intialize it; it should be stopped right away as it's not really a "real" user.
// TODO(b/143092698): in the long-term, it might be better to add a onCreateUser()
// callback on SystemService instead.
- stopUser(userInfo.id, /* force= */ true, /* stopUserCallback= */ null,
- /* keyEvictedCallback= */ null);
+ stopUser(userInfo.id, /* force= */ true, /* allowDelayedLocking= */ false,
+ /* stopUserCallback= */ null, /* keyEvictedCallback= */ null);
return;
}
@@ -609,7 +644,8 @@
}
int restartUser(final int userId, final boolean foreground) {
- return stopUser(userId, /* force */ true, null, new KeyEvictedCallback() {
+ return stopUser(userId, /* force= */ true, /* allowDelayedLocking= */ false,
+ /* stopUserCallback= */ null, new KeyEvictedCallback() {
@Override
public void keyEvicted(@UserIdInt int userId) {
// Post to the same handler that this callback is called from to ensure the user
@@ -619,15 +655,16 @@
});
}
- int stopUser(final int userId, final boolean force, final IStopUserCallback stopUserCallback,
- KeyEvictedCallback keyEvictedCallback) {
+ int stopUser(final int userId, final boolean force, boolean allowDelayedLocking,
+ final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) {
checkCallingPermission(INTERACT_ACROSS_USERS_FULL, "stopUser");
if (userId < 0 || userId == UserHandle.USER_SYSTEM) {
throw new IllegalArgumentException("Can't stop system user " + userId);
}
enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
synchronized (mLock) {
- return stopUsersLU(userId, force, stopUserCallback, keyEvictedCallback);
+ return stopUsersLU(userId, force, allowDelayedLocking, stopUserCallback,
+ keyEvictedCallback);
}
}
@@ -636,7 +673,7 @@
* {@link #getUsersToStopLU(int)} to determine the list of users that should be stopped.
*/
@GuardedBy("mLock")
- private int stopUsersLU(final int userId, boolean force,
+ private int stopUsersLU(final int userId, boolean force, boolean allowDelayedLocking,
final IStopUserCallback stopUserCallback, KeyEvictedCallback keyEvictedCallback) {
if (userId == UserHandle.USER_SYSTEM) {
return USER_OP_ERROR_IS_SYSTEM;
@@ -655,7 +692,8 @@
if (force) {
Slog.i(TAG,
"Force stop user " + userId + ". Related users will not be stopped");
- stopSingleUserLU(userId, stopUserCallback, keyEvictedCallback);
+ stopSingleUserLU(userId, allowDelayedLocking, stopUserCallback,
+ keyEvictedCallback);
return USER_OP_SUCCESS;
}
return USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
@@ -663,21 +701,64 @@
}
if (DEBUG_MU) Slog.i(TAG, "stopUsersLocked usersToStop=" + Arrays.toString(usersToStop));
for (int userIdToStop : usersToStop) {
- stopSingleUserLU(userIdToStop,
+ stopSingleUserLU(userIdToStop, allowDelayedLocking,
userIdToStop == userId ? stopUserCallback : null,
userIdToStop == userId ? keyEvictedCallback : null);
}
return USER_OP_SUCCESS;
}
+ /**
+ * Stops a single User. This can also trigger locking user data out depending on device's
+ * config ({@code mDelayUserDataLocking}) and arguments.
+ * User will be unlocked when
+ * - {@code mDelayUserDataLocking} is not set.
+ * - {@code mDelayUserDataLocking} is set and {@code keyEvictedCallback} is non-null.
+ * -
+ *
+ * @param userId User Id to stop and lock the data.
+ * @param allowDelayedLocking When set, do not lock user after stopping. Locking can happen
+ * later when number of unlocked users reaches
+ * {@code mMaxRunnngUsers}. Note that this is respected only when
+ * {@code mDelayUserDataLocking} is set and {@keyEvictedCallback} is
+ * null. Otherwise the user will be locked.
+ * @param stopUserCallback Callback to notify that user has stopped.
+ * @param keyEvictedCallback Callback to notify that user has been unlocked.
+ */
@GuardedBy("mLock")
- private void stopSingleUserLU(final int userId, final IStopUserCallback stopUserCallback,
+ private void stopSingleUserLU(final int userId, boolean allowDelayedLocking,
+ final IStopUserCallback stopUserCallback,
KeyEvictedCallback keyEvictedCallback) {
if (DEBUG_MU) Slog.i(TAG, "stopSingleUserLocked userId=" + userId);
final UserState uss = mStartedUsers.get(userId);
- if (uss == null) {
- // User is not started, nothing to do... but we do need to
- // callback if requested.
+ if (uss == null) { // User is not started
+ // If mDelayUserDataLocking is set and allowDelayedLocking is not set, we need to lock
+ // the requested user as the client wants to stop and lock the user. On the other hand,
+ // having keyEvictedCallback set will lead into locking user if mDelayUserDataLocking
+ // is set as that means client wants to lock the user immediately.
+ // If mDelayUserDataLocking is not set, the user was already locked when it was stopped
+ // and no further action is necessary.
+ if (mDelayUserDataLocking) {
+ if (allowDelayedLocking && keyEvictedCallback != null) {
+ Slog.wtf(TAG, "allowDelayedLocking set with KeyEvictedCallback, ignore it"
+ + " and lock user:" + userId, new RuntimeException());
+ allowDelayedLocking = false;
+ }
+ if (!allowDelayedLocking) {
+ if (mLastActiveUsers.remove(Integer.valueOf(userId))) {
+ // should lock the user, user is already gone
+ final ArrayList<KeyEvictedCallback> keyEvictedCallbacks;
+ if (keyEvictedCallback != null) {
+ keyEvictedCallbacks = new ArrayList<>(1);
+ keyEvictedCallbacks.add(keyEvictedCallback);
+ } else {
+ keyEvictedCallbacks = null;
+ }
+ dispatchUserLocking(userId, keyEvictedCallbacks);
+ }
+ }
+ }
+ // We do need to post the stopped callback even though user is already stopped.
if (stopUserCallback != null) {
mHandler.post(() -> {
try {
@@ -702,6 +783,7 @@
mInjector.getUserManagerInternal().setUserState(userId, uss.state);
updateStartedUserArrayLU();
+ final boolean allowDelayyLockingCopied = allowDelayedLocking;
// Post to handler to obtain amLock
mHandler.post(() -> {
// We are going to broadcast ACTION_USER_STOPPING and then
@@ -716,7 +798,8 @@
@Override
public void performReceive(Intent intent, int resultCode, String data,
Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
- mHandler.post(() -> finishUserStopping(userId, uss));
+ mHandler.post(() -> finishUserStopping(userId, uss,
+ allowDelayyLockingCopied));
}
};
@@ -732,7 +815,8 @@
}
}
- void finishUserStopping(final int userId, final UserState uss) {
+ void finishUserStopping(final int userId, final UserState uss,
+ final boolean allowDelayedLocking) {
Slog.d(TAG, "UserController event: finishUserStopping(" + userId + ")");
// On to the next.
final Intent shutdownIntent = new Intent(Intent.ACTION_SHUTDOWN);
@@ -744,7 +828,7 @@
mHandler.post(new Runnable() {
@Override
public void run() {
- finishUserStopped(uss);
+ finishUserStopped(uss, allowDelayedLocking);
}
});
}
@@ -771,7 +855,7 @@
Binder.getCallingPid(), userId);
}
- void finishUserStopped(UserState uss) {
+ void finishUserStopped(UserState uss, boolean allowDelayedLocking) {
final int userId = uss.mHandle.getIdentifier();
Slog.d(TAG, "UserController event: finishUserStopped(" + userId + ")");
final boolean stopped;
@@ -790,7 +874,13 @@
mStartedUsers.remove(userId);
mUserLru.remove(Integer.valueOf(userId));
updateStartedUserArrayLU();
- userIdToLock = updateUserToLockLU(userId);
+ if (allowDelayedLocking && !keyEvictedCallbacks.isEmpty()) {
+ Slog.wtf(TAG,
+ "Delayed locking enabled while KeyEvictedCallbacks not empty, userId:"
+ + userId + " callbacks:" + keyEvictedCallbacks);
+ allowDelayedLocking = false;
+ }
+ userIdToLock = updateUserToLockLU(userId, allowDelayedLocking);
if (userIdToLock == UserHandle.USER_NULL) {
lockUser = false;
}
@@ -824,31 +914,36 @@
if (!lockUser) {
return;
}
- final int userIdToLockF = userIdToLock;
- // Evict the user's credential encryption key. Performed on FgThread to make it
- // serialized with call to UserManagerService.onBeforeUnlockUser in finishUserUnlocking
- // to prevent data corruption.
- FgThread.getHandler().post(() -> {
- synchronized (mLock) {
- if (mStartedUsers.get(userIdToLockF) != null) {
- Slog.w(TAG, "User was restarted, skipping key eviction");
- return;
- }
- }
- try {
- mInjector.getStorageManager().lockUserKey(userIdToLockF);
- } catch (RemoteException re) {
- throw re.rethrowAsRuntimeException();
- }
- if (userIdToLockF == userId) {
- for (final KeyEvictedCallback callback : keyEvictedCallbacks) {
- callback.keyEvicted(userId);
- }
- }
- });
+ dispatchUserLocking(userIdToLock, keyEvictedCallbacks);
}
}
+ private void dispatchUserLocking(@UserIdInt int userId,
+ @Nullable List<KeyEvictedCallback> keyEvictedCallbacks) {
+ // Evict the user's credential encryption key. Performed on FgThread to make it
+ // serialized with call to UserManagerService.onBeforeUnlockUser in finishUserUnlocking
+ // to prevent data corruption.
+ FgThread.getHandler().post(() -> {
+ synchronized (mLock) {
+ if (mStartedUsers.get(userId) != null) {
+ Slog.w(TAG, "User was restarted, skipping key eviction");
+ return;
+ }
+ }
+ try {
+ mInjector.getStorageManager().lockUserKey(userId);
+ } catch (RemoteException re) {
+ throw re.rethrowAsRuntimeException();
+ }
+ if (keyEvictedCallbacks == null) {
+ return;
+ }
+ for (int i = 0; i < keyEvictedCallbacks.size(); i++) {
+ keyEvictedCallbacks.get(i).keyEvicted(userId);
+ }
+ });
+ }
+
/**
* For mDelayUserDataLocking mode, storage once unlocked is kept unlocked.
* Total number of unlocked user storage is limited by mMaxRunningUsers.
@@ -859,9 +954,9 @@
* @return user id to lock. UserHandler.USER_NULL will be returned if no user should be locked.
*/
@GuardedBy("mLock")
- private int updateUserToLockLU(@UserIdInt int userId) {
+ private int updateUserToLockLU(@UserIdInt int userId, boolean allowDelayedLocking) {
int userIdToLock = userId;
- if (mDelayUserDataLocking && !getUserInfo(userId).isEphemeral()
+ if (mDelayUserDataLocking && allowDelayedLocking && !getUserInfo(userId).isEphemeral()
&& !hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, userId)) {
mLastActiveUsers.remove((Integer) userId); // arg should be object, not index
mLastActiveUsers.add(0, userId);
@@ -943,7 +1038,8 @@
if (userInfo.isGuest() || userInfo.isEphemeral()) {
// This is a user to be stopped.
synchronized (mLock) {
- stopUsersLU(oldUserId, true, null, null);
+ stopUsersLU(oldUserId, /* force= */ true, /* allowDelayedLocking= */ false,
+ null, null);
}
}
}
@@ -975,7 +1071,7 @@
}
final int profilesToStartSize = profilesToStart.size();
int i = 0;
- for (; i < profilesToStartSize && i < (mMaxRunningUsers - 1); ++i) {
+ for (; i < profilesToStartSize && i < (getMaxRunningUsers() - 1); ++i) {
startUser(profilesToStart.get(i).id, /* foreground= */ false);
}
if (i < profilesToStartSize) {
@@ -1092,7 +1188,7 @@
return false;
}
- if (foreground && mUserSwitchUiEnabled) {
+ if (foreground && isUserSwitchUiEnabled()) {
t.traceBegin("startFreezingScreen");
mInjector.getWindowManager().startFreezingScreen(
R.anim.screen_user_exit, R.anim.screen_user_enter);
@@ -1140,9 +1236,11 @@
if (foreground) {
// Make sure the old user is no longer considering the display to be on.
mInjector.reportGlobalUsageEventLocked(UsageEvents.Event.SCREEN_NON_INTERACTIVE);
+ boolean userSwitchUiEnabled;
synchronized (mLock) {
mCurrentUserId = userId;
mTargetUserId = UserHandle.USER_NULL; // reset, mCurrentUserId has caught up
+ userSwitchUiEnabled = mUserSwitchUiEnabled;
}
mInjector.updateUserConfiguration();
updateCurrentProfileIds();
@@ -1150,7 +1248,7 @@
mInjector.reportCurWakefulnessUsageEvent();
// Once the internal notion of the active user has switched, we lock the device
// with the option to show the user switcher on the keyguard.
- if (mUserSwitchUiEnabled) {
+ if (userSwitchUiEnabled) {
mInjector.getWindowManager().setSwitchingUser(true);
mInjector.getWindowManager().lockNow(null);
}
@@ -1389,10 +1487,12 @@
Slog.w(TAG, "Cannot switch to User #" + targetUserId + ": not a full user");
return false;
}
+ boolean userSwitchUiEnabled;
synchronized (mLock) {
mTargetUserId = targetUserId;
+ userSwitchUiEnabled = mUserSwitchUiEnabled;
}
- if (mUserSwitchUiEnabled) {
+ if (userSwitchUiEnabled) {
UserInfo currentUserInfo = getUserInfo(currentUserId);
Pair<UserInfo, UserInfo> userNames = new Pair<>(currentUserInfo, targetUserInfo);
mUiHandler.removeMessages(START_USER_SWITCH_UI_MSG);
@@ -1456,14 +1556,15 @@
}
// If running in background is disabled or mDelayUserDataLocking mode, stop the user.
boolean disallowRunInBg = hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND,
- oldUserId) || mDelayUserDataLocking;
+ oldUserId) || isDelayUserDataLockingEnabled();
if (!disallowRunInBg) {
return;
}
synchronized (mLock) {
if (DEBUG_MU) Slog.i(TAG, "stopBackgroundUsersIfEnforced stopping " + oldUserId
+ " and related users");
- stopUsersLU(oldUserId, false, null, null);
+ stopUsersLU(oldUserId, /* force= */ false, /* allowDelayedLocking= */ true,
+ null, null);
}
}
@@ -1549,7 +1650,7 @@
void continueUserSwitch(UserState uss, int oldUserId, int newUserId) {
Slog.d(TAG, "Continue user switch oldUser #" + oldUserId + ", newUser #" + newUserId);
- if (mUserSwitchUiEnabled) {
+ if (isUserSwitchUiEnabled()) {
mInjector.getWindowManager().stopFreezingScreen();
}
uss.switching = false;
@@ -1641,11 +1742,11 @@
if (callingUid != 0 && callingUid != SYSTEM_UID) {
final boolean allow;
+ final boolean isSameProfileGroup = isSameProfileGroup(callingUserId, targetUserId);
if (mInjector.isCallerRecents(callingUid)
- && callingUserId == getCurrentUserId()
&& isSameProfileGroup(callingUserId, targetUserId)) {
- // If the caller is Recents and it is running in the current user, we then allow it
- // to access its profiles.
+ // If the caller is Recents and the caller has ownership of the profile group,
+ // we then allow it to access its profiles.
allow = true;
} else if (mInjector.checkComponentPermission(INTERACT_ACROSS_USERS_FULL, callingPid,
callingUid, -1, true) == PackageManager.PERMISSION_GRANTED) {
@@ -1654,6 +1755,9 @@
} else if (allowMode == ALLOW_FULL_ONLY) {
// We require full access, sucks to be you.
allow = false;
+ } else if (canInteractWithAcrossProfilesPermission(
+ allowMode, isSameProfileGroup, callingPid, callingUid)) {
+ allow = true;
} else if (mInjector.checkComponentPermission(INTERACT_ACROSS_USERS, callingPid,
callingUid, -1, true) != PackageManager.PERMISSION_GRANTED) {
// If the caller does not have either permission, they are always doomed.
@@ -1661,10 +1765,11 @@
} else if (allowMode == ALLOW_NON_FULL) {
// We are blanket allowing non-full access, you lucky caller!
allow = true;
- } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE) {
+ } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE
+ || allowMode == ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) {
// We may or may not allow this depending on whether the two users are
// in the same profile.
- allow = isSameProfileGroup(callingUserId, targetUserId);
+ allow = isSameProfileGroup;
} else {
throw new IllegalArgumentException("Unknown mode: " + allowMode);
}
@@ -1690,6 +1795,11 @@
if (allowMode != ALLOW_FULL_ONLY) {
builder.append(" or ");
builder.append(INTERACT_ACROSS_USERS);
+ if (isSameProfileGroup
+ && allowMode == ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) {
+ builder.append(" or ");
+ builder.append(INTERACT_ACROSS_PROFILES);
+ }
}
String msg = builder.toString();
Slog.w(TAG, msg);
@@ -1710,6 +1820,19 @@
return targetUserId;
}
+ private boolean canInteractWithAcrossProfilesPermission(
+ int allowMode, boolean isSameProfileGroup, int callingPid, int callingUid) {
+ if (allowMode != ALLOW_ALL_PROFILE_PERMISSIONS_IN_PROFILE) {
+ return false;
+ }
+ if (!isSameProfileGroup) {
+ return false;
+ }
+ return mInjector.checkComponentPermission(
+ INTERACT_ACROSS_PROFILES, callingPid, callingUid, /*owningUid= */-1,
+ /*exported= */true) == PackageManager.PERMISSION_GRANTED;
+ }
+
int unsafeConvertIncomingUser(@UserIdInt int userId) {
return (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF)
? getCurrentUserId(): userId;
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 5bbb517..e3b761e 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -17,6 +17,11 @@
package com.android.server.appop;
import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION;
+import static android.app.AppOpsManager.FILTER_BY_FEATURE_ID;
+import static android.app.AppOpsManager.FILTER_BY_OP_NAMES;
+import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME;
+import static android.app.AppOpsManager.FILTER_BY_UID;
+import static android.app.AppOpsManager.HistoricalOpsRequestFilter;
import static android.app.AppOpsManager.NoteOpEvent;
import static android.app.AppOpsManager.OP_CAMERA;
import static android.app.AppOpsManager.OP_COARSE_LOCATION;
@@ -53,7 +58,6 @@
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.AppOpsManager.HistoricalOps;
-import android.app.AppOpsManager.HistoricalOpsRequest;
import android.app.AppOpsManager.Mode;
import android.app.AppOpsManager.OpEntry;
import android.app.AppOpsManager.OpFeatureEntry;
@@ -632,6 +636,7 @@
}
private final class FeatureOp {
+ public final @Nullable String featureId;
public final @NonNull Op parent;
/**
@@ -658,7 +663,8 @@
@GuardedBy("AppOpsService.this")
private @Nullable ArrayMap<IBinder, InProgressStartOpEvent> mInProgressEvents;
- FeatureOp(@NonNull Op parent) {
+ FeatureOp(@Nullable String featureId, @NonNull Op parent) {
+ this.featureId = featureId;
this.parent = parent;
}
@@ -676,6 +682,9 @@
@OpFlags int flags) {
accessed(System.currentTimeMillis(), -1, proxyUid, proxyPackageName,
proxyFeatureId, uidState, flags);
+
+ mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName,
+ featureId, uidState, flags);
}
/**
@@ -720,6 +729,9 @@
*/
public void rejected(@AppOpsManager.UidState int uidState, @OpFlags int flags) {
rejected(System.currentTimeMillis(), uidState, flags);
+
+ mHistoricalRegistry.incrementOpRejected(parent.op, parent.uid, parent.packageName,
+ featureId, uidState, flags);
}
/**
@@ -780,7 +792,7 @@
// startOp events don't support proxy, hence use flags==SELF
mHistoricalRegistry.incrementOpAccessedCount(parent.op, parent.uid, parent.packageName,
- uidState, OP_FLAG_SELF);
+ featureId, uidState, OP_FLAG_SELF);
}
/**
@@ -820,8 +832,8 @@
mAccessEvents.put(makeKey(event.getUidState(), OP_FLAG_SELF), finishedEvent);
mHistoricalRegistry.increaseOpAccessDuration(parent.op, parent.uid,
- parent.packageName, event.getUidState(), AppOpsManager.OP_FLAG_SELF,
- finishedEvent.getDuration());
+ parent.packageName, featureId, event.getUidState(),
+ AppOpsManager.OP_FLAG_SELF, finishedEvent.getDuration());
mInProgressStartOpEventPool.release(event);
@@ -1031,7 +1043,7 @@
featureOp = mFeatures.get(featureId);
if (featureOp == null) {
- featureOp = new FeatureOp(parent);
+ featureOp = new FeatureOp(featureId, parent);
mFeatures.put(featureId, featureOp);
}
@@ -1697,18 +1709,47 @@
}
}
+ /**
+ * Verify that historical appop request arguments are valid.
+ */
+ private void ensureHistoricalOpRequestIsValid(int uid, String packageName, String featureId,
+ List<String> opNames, int filter, long beginTimeMillis, long endTimeMillis,
+ int flags) {
+ if ((filter & FILTER_BY_UID) != 0) {
+ Preconditions.checkArgument(uid != Process.INVALID_UID);
+ } else {
+ Preconditions.checkArgument(uid == Process.INVALID_UID);
+ }
+
+ if ((filter & FILTER_BY_PACKAGE_NAME) != 0) {
+ Objects.requireNonNull(packageName);
+ } else {
+ Preconditions.checkArgument(packageName == null);
+ }
+
+ if ((filter & FILTER_BY_FEATURE_ID) == 0) {
+ Preconditions.checkArgument(featureId == null);
+ }
+
+ if ((filter & FILTER_BY_OP_NAMES) != 0) {
+ Objects.requireNonNull(opNames);
+ } else {
+ Preconditions.checkArgument(opNames == null);
+ }
+
+ Preconditions.checkFlagsArgument(filter,
+ FILTER_BY_UID | FILTER_BY_PACKAGE_NAME | FILTER_BY_FEATURE_ID | FILTER_BY_OP_NAMES);
+ Preconditions.checkArgumentNonnegative(beginTimeMillis);
+ Preconditions.checkArgument(endTimeMillis > beginTimeMillis);
+ Preconditions.checkFlagsArgument(flags, OP_FLAGS_ALL);
+ }
+
@Override
- public void getHistoricalOps(int uid, @NonNull String packageName,
- @Nullable List<String> opNames, long beginTimeMillis, long endTimeMillis,
- @OpFlags int flags, @NonNull RemoteCallback callback) {
- // Use the builder to validate arguments.
- new HistoricalOpsRequest.Builder(
- beginTimeMillis, endTimeMillis)
- .setUid(uid)
- .setPackageName(packageName)
- .setOpNames(opNames)
- .setFlags(flags)
- .build();
+ public void getHistoricalOps(int uid, String packageName, String featureId,
+ List<String> opNames, int filter, long beginTimeMillis, long endTimeMillis,
+ int flags, RemoteCallback callback) {
+ ensureHistoricalOpRequestIsValid(uid, packageName, featureId, opNames, filter,
+ beginTimeMillis, endTimeMillis, flags);
Objects.requireNonNull(callback, "callback cannot be null");
mContext.enforcePermission(android.Manifest.permission.GET_APP_OPS_STATS,
@@ -1718,22 +1759,17 @@
? opNames.toArray(new String[opNames.size()]) : null;
// Must not hold the appops lock
- mHistoricalRegistry.getHistoricalOps(uid, packageName, opNamesArray,
- beginTimeMillis, endTimeMillis, flags, callback);
+ mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOps,
+ mHistoricalRegistry, uid, packageName, featureId, opNamesArray, filter,
+ beginTimeMillis, endTimeMillis, flags, callback).recycleOnUse());
}
@Override
- public void getHistoricalOpsFromDiskRaw(int uid, @NonNull String packageName,
- @Nullable List<String> opNames, long beginTimeMillis, long endTimeMillis,
- @OpFlags int flags, @NonNull RemoteCallback callback) {
- // Use the builder to validate arguments.
- new HistoricalOpsRequest.Builder(
- beginTimeMillis, endTimeMillis)
- .setUid(uid)
- .setPackageName(packageName)
- .setOpNames(opNames)
- .setFlags(flags)
- .build();
+ public void getHistoricalOpsFromDiskRaw(int uid, String packageName, String featureId,
+ List<String> opNames, int filter, long beginTimeMillis, long endTimeMillis,
+ int flags, RemoteCallback callback) {
+ ensureHistoricalOpRequestIsValid(uid, packageName, featureId, opNames, filter,
+ beginTimeMillis, endTimeMillis, flags);
Objects.requireNonNull(callback, "callback cannot be null");
mContext.enforcePermission(Manifest.permission.MANAGE_APPOPS,
@@ -1743,8 +1779,9 @@
? opNames.toArray(new String[opNames.size()]) : null;
// Must not hold the appops lock
- mHistoricalRegistry.getHistoricalOpsFromDiskRaw(uid, packageName, opNamesArray,
- beginTimeMillis, endTimeMillis, flags, callback);
+ mHandler.post(PooledLambda.obtainRunnable(HistoricalRegistry::getHistoricalOpsFromDiskRaw,
+ mHistoricalRegistry, uid, packageName, featureId, opNamesArray,
+ filter, beginTimeMillis, endTimeMillis, flags, callback).recycleOnUse());
}
@Override
@@ -2631,8 +2668,6 @@
+ switchCode + " (" + code + ") uid " + uid + " package "
+ packageName);
featureOp.rejected(uidState.state, flags);
- mHistoricalRegistry.incrementOpRejected(code, uid, packageName,
- uidState.state, flags);
scheduleOpNotedIfNeededLocked(code, uid, packageName, uidMode);
return uidMode;
}
@@ -2645,8 +2680,6 @@
+ switchCode + " (" + code + ") uid " + uid + " package "
+ packageName);
featureOp.rejected(uidState.state, flags);
- mHistoricalRegistry.incrementOpRejected(code, uid, packageName,
- uidState.state, flags);
scheduleOpNotedIfNeededLocked(code, uid, packageName, mode);
return mode;
}
@@ -2654,9 +2687,6 @@
if (DEBUG) Slog.d(TAG, "noteOperation: allowing code " + code + " uid " + uid
+ " package " + packageName + (featureId == null ? "" : "." + featureId));
featureOp.accessed(proxyUid, proxyPackageName, proxyFeatureId, uidState.state, flags);
- // TODO moltmann: Add features to historical app-ops
- mHistoricalRegistry.incrementOpAccessedCount(op.op, uid, packageName,
- uidState.state, flags);
scheduleOpNotedIfNeededLocked(code, uid, packageName,
AppOpsManager.MODE_ALLOWED);
@@ -2938,8 +2968,6 @@
+ switchCode + " (" + code + ") uid " + uid + " package "
+ resolvedPackageName);
featureOp.rejected(uidState.state, AppOpsManager.OP_FLAG_SELF);
- mHistoricalRegistry.incrementOpRejected(opCode, uid, packageName,
- uidState.state, AppOpsManager.OP_FLAG_SELF);
return uidMode;
}
} else {
@@ -2952,8 +2980,6 @@
+ switchCode + " (" + code + ") uid " + uid + " package "
+ resolvedPackageName);
featureOp.rejected(uidState.state, AppOpsManager.OP_FLAG_SELF);
- mHistoricalRegistry.incrementOpRejected(opCode, uid, packageName,
- uidState.state, AppOpsManager.OP_FLAG_SELF);
return mode;
}
}
@@ -4437,16 +4463,24 @@
pw.println(" Limit output to data associated with the given app op mode.");
pw.println(" --package [PACKAGE]");
pw.println(" Limit output to data associated with the given package name.");
+ pw.println(" --featureId [featureId]");
+ pw.println(" Limit output to data associated with the given feature id.");
pw.println(" --watchers");
pw.println(" Only output the watcher sections.");
pw.println(" --history");
pw.println(" Output the historical data.");
}
- private void dumpStatesLocked(@NonNull PrintWriter pw, long nowElapsed, @NonNull Op op,
- long now, @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix) {
+ private void dumpStatesLocked(@NonNull PrintWriter pw, @Nullable String filterFeatureId,
+ @HistoricalOpsRequestFilter int filter, long nowElapsed, @NonNull Op op, long now,
+ @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix) {
final int numFeatures = op.mFeatures.size();
for (int i = 0; i < numFeatures; i++) {
+ if ((filter & FILTER_BY_FEATURE_ID) != 0 && !Objects.equals(op.mFeatures.keyAt(i),
+ filterFeatureId)) {
+ continue;
+ }
+
pw.print(prefix + op.mFeatures.keyAt(i) + "=[\n");
dumpStatesLocked(pw, nowElapsed, op, op.mFeatures.keyAt(i), now, sdf, date,
prefix + " ");
@@ -4563,10 +4597,12 @@
int dumpOp = OP_NONE;
String dumpPackage = null;
+ String dumpFeatureId = null;
int dumpUid = Process.INVALID_UID;
int dumpMode = -1;
boolean dumpWatchers = false;
boolean dumpHistory = false;
+ @HistoricalOpsRequestFilter int dumpFilter = 0;
if (args != null) {
for (int i=0; i<args.length; i++) {
@@ -4583,6 +4619,7 @@
return;
}
dumpOp = Shell.strOpToOp(args[i], pw);
+ dumpFilter |= FILTER_BY_OP_NAMES;
if (dumpOp < 0) {
return;
}
@@ -4593,6 +4630,7 @@
return;
}
dumpPackage = args[i];
+ dumpFilter |= FILTER_BY_PACKAGE_NAME;
try {
dumpUid = AppGlobals.getPackageManager().getPackageUid(dumpPackage,
PackageManager.MATCH_KNOWN_PACKAGES | PackageManager.MATCH_INSTANT,
@@ -4604,6 +4642,15 @@
return;
}
dumpUid = UserHandle.getAppId(dumpUid);
+ dumpFilter |= FILTER_BY_UID;
+ } else if ("--featureId".equals(arg)) {
+ i++;
+ if (i >= args.length) {
+ pw.println("No argument for --featureId option");
+ return;
+ }
+ dumpFeatureId = args[i];
+ dumpFilter |= FILTER_BY_FEATURE_ID;
} else if ("--mode".equals(arg)) {
i++;
if (i >= args.length) {
@@ -4640,8 +4687,8 @@
final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
final Date date = new Date();
boolean needSep = false;
- if (dumpOp < 0 && dumpMode < 0 && dumpPackage == null && mProfileOwners != null
- && !dumpWatchers && !dumpHistory) {
+ if (dumpFilter == 0 && dumpMode < 0 && mProfileOwners != null && !dumpWatchers
+ && !dumpHistory) {
pw.println(" Profile owners:");
for (int poi = 0; poi < mProfileOwners.size(); poi++) {
pw.print(" User #");
@@ -4944,7 +4991,8 @@
pw.print("="); pw.print(AppOpsManager.modeToName(mode));
}
pw.println("): ");
- dumpStatesLocked(pw, nowElapsed, op, now, sdf, date, " ");
+ dumpStatesLocked(pw, dumpFeatureId, dumpFilter, nowElapsed, op, now, sdf,
+ date, " ");
}
}
}
@@ -5043,7 +5091,8 @@
// Must not hold the appops lock
if (dumpHistory && !dumpWatchers) {
- mHistoricalRegistry.dump(" ", pw, dumpUid, dumpPackage, dumpOp);
+ mHistoricalRegistry.dump(" ", pw, dumpUid, dumpPackage, dumpFeatureId, dumpOp,
+ dumpFilter);
}
}
diff --git a/services/core/java/com/android/server/appop/HistoricalRegistry.java b/services/core/java/com/android/server/appop/HistoricalRegistry.java
index 2175ca0..cd450d4 100644
--- a/services/core/java/com/android/server/appop/HistoricalRegistry.java
+++ b/services/core/java/com/android/server/appop/HistoricalRegistry.java
@@ -15,12 +15,19 @@
*/
package com.android.server.appop;
+import static android.app.AppOpsManager.FILTER_BY_FEATURE_ID;
+import static android.app.AppOpsManager.FILTER_BY_OP_NAMES;
+import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME;
+import static android.app.AppOpsManager.FILTER_BY_UID;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.AppOpsManager;
+import android.app.AppOpsManager.HistoricalFeatureOps;
import android.app.AppOpsManager.HistoricalMode;
import android.app.AppOpsManager.HistoricalOp;
import android.app.AppOpsManager.HistoricalOps;
+import android.app.AppOpsManager.HistoricalOpsRequestFilter;
import android.app.AppOpsManager.HistoricalPackageOps;
import android.app.AppOpsManager.HistoricalUidOps;
import android.app.AppOpsManager.OpFlags;
@@ -72,6 +79,7 @@
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@@ -273,8 +281,9 @@
+ "=" + setting + " resetting!");
}
- void dump(String prefix, PrintWriter pw, int filterUid,
- String filterPackage, int filterOp) {
+ void dump(String prefix, PrintWriter pw, int filterUid, @Nullable String filterPackage,
+ @Nullable String filterFeatureId, int filterOp,
+ @HistoricalOpsRequestFilter int filter) {
if (!isApiEnabled()) {
return;
}
@@ -289,7 +298,7 @@
pw.println(AppOpsManager.historicalModeToString(mMode));
final StringDumpVisitor visitor = new StringDumpVisitor(prefix + " ",
- pw, filterUid, filterPackage, filterOp);
+ pw, filterUid, filterPackage, filterFeatureId, filterOp, filter);
final long nowMillis = System.currentTimeMillis();
// Dump in memory state first
@@ -329,7 +338,8 @@
}
void getHistoricalOpsFromDiskRaw(int uid, @NonNull String packageName,
- @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis,
+ @Nullable String featureId, @Nullable String[] opNames,
+ @HistoricalOpsRequestFilter int filter, long beginTimeMillis, long endTimeMillis,
@OpFlags int flags, @NonNull RemoteCallback callback) {
if (!isApiEnabled()) {
callback.sendResult(new Bundle());
@@ -344,8 +354,8 @@
return;
}
final HistoricalOps result = new HistoricalOps(beginTimeMillis, endTimeMillis);
- mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, opNames,
- beginTimeMillis, endTimeMillis, flags);
+ mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, featureId,
+ opNames, filter, beginTimeMillis, endTimeMillis, flags);
final Bundle payload = new Bundle();
payload.putParcelable(AppOpsManager.KEY_HISTORICAL_OPS, result);
callback.sendResult(payload);
@@ -353,9 +363,10 @@
}
}
- void getHistoricalOps(int uid, @NonNull String packageName,
- @Nullable String[] opNames, long beginTimeMillis, long endTimeMillis,
- @OpFlags int flags, @NonNull RemoteCallback callback) {
+ void getHistoricalOps(int uid, @NonNull String packageName, @Nullable String featureId,
+ @Nullable String[] opNames, @HistoricalOpsRequestFilter int filter,
+ long beginTimeMillis, long endTimeMillis, @OpFlags int flags,
+ @NonNull RemoteCallback callback) {
if (!isApiEnabled()) {
callback.sendResult(new Bundle());
return;
@@ -390,8 +401,8 @@
|| inMemoryAdjEndTimeMillis <= currentOps.getBeginTimeMillis())) {
// Some of the current batch falls into the query, so extract that.
final HistoricalOps currentOpsCopy = new HistoricalOps(currentOps);
- currentOpsCopy.filter(uid, packageName, opNames, inMemoryAdjBeginTimeMillis,
- inMemoryAdjEndTimeMillis);
+ currentOpsCopy.filter(uid, packageName, featureId, opNames, filter,
+ inMemoryAdjBeginTimeMillis, inMemoryAdjEndTimeMillis);
result.merge(currentOpsCopy);
}
pendingWrites = new ArrayList<>(mPendingWrites);
@@ -410,8 +421,8 @@
- onDiskAndInMemoryOffsetMillis, 0);
final long onDiskAdjEndTimeMillis = Math.max(inMemoryAdjEndTimeMillis
- onDiskAndInMemoryOffsetMillis, 0);
- mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, opNames,
- onDiskAdjBeginTimeMillis, onDiskAdjEndTimeMillis, flags);
+ mPersistence.collectHistoricalOpsDLocked(result, uid, packageName, featureId,
+ opNames, filter, onDiskAdjBeginTimeMillis, onDiskAdjEndTimeMillis, flags);
}
// Rebase the result time to be since epoch.
@@ -425,43 +436,47 @@
}
void incrementOpAccessedCount(int op, int uid, @NonNull String packageName,
- @UidState int uidState, @OpFlags int flags) {
+ @Nullable String featureId, @UidState int uidState, @OpFlags int flags) {
synchronized (mInMemoryLock) {
if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
if (!isPersistenceInitializedMLocked()) {
Slog.e(LOG_TAG, "Interaction before persistence initialized");
return;
}
- getUpdatedPendingHistoricalOpsMLocked(System.currentTimeMillis())
- .increaseAccessCount(op, uid, packageName, uidState, flags, 1);
+ getUpdatedPendingHistoricalOpsMLocked(
+ System.currentTimeMillis()).increaseAccessCount(op, uid, packageName,
+ featureId, uidState, flags, 1);
}
}
}
void incrementOpRejected(int op, int uid, @NonNull String packageName,
- @UidState int uidState, @OpFlags int flags) {
+ @Nullable String featureId, @UidState int uidState, @OpFlags int flags) {
synchronized (mInMemoryLock) {
if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
if (!isPersistenceInitializedMLocked()) {
Slog.e(LOG_TAG, "Interaction before persistence initialized");
return;
}
- getUpdatedPendingHistoricalOpsMLocked(System.currentTimeMillis())
- .increaseRejectCount(op, uid, packageName, uidState, flags, 1);
+ getUpdatedPendingHistoricalOpsMLocked(
+ System.currentTimeMillis()).increaseRejectCount(op, uid, packageName,
+ featureId, uidState, flags, 1);
}
}
}
void increaseOpAccessDuration(int op, int uid, @NonNull String packageName,
- @UidState int uidState, @OpFlags int flags, long increment) {
+ @Nullable String featureId, @UidState int uidState, @OpFlags int flags,
+ long increment) {
synchronized (mInMemoryLock) {
if (mMode == AppOpsManager.HISTORICAL_MODE_ENABLED_ACTIVE) {
if (!isPersistenceInitializedMLocked()) {
Slog.e(LOG_TAG, "Interaction before persistence initialized");
return;
}
- getUpdatedPendingHistoricalOpsMLocked(System.currentTimeMillis())
- .increaseAccessDuration(op, uid, packageName, uidState, flags, increment);
+ getUpdatedPendingHistoricalOpsMLocked(
+ System.currentTimeMillis()).increaseAccessDuration(op, uid, packageName,
+ featureId, uidState, flags, increment);
}
}
}
@@ -713,6 +728,7 @@
private static final String TAG_OPS = "ops";
private static final String TAG_UID = "uid";
private static final String TAG_PACKAGE = "pkg";
+ private static final String TAG_FEATURE = "ftr";
private static final String TAG_OP = "op";
private static final String TAG_STATE = "st";
@@ -791,8 +807,8 @@
@Nullable List<HistoricalOps> readHistoryRawDLocked() {
return collectHistoricalOpsBaseDLocked(Process.INVALID_UID /*filterUid*/,
- null /*filterPackageName*/, null /*filterOpNames*/,
- 0 /*filterBeginTimeMills*/, Long.MAX_VALUE /*filterEndTimeMills*/,
+ null /*filterPackageName*/, null /*filterFeatureId*/, null /*filterOpNames*/,
+ 0 /*filter*/, 0 /*filterBeginTimeMills*/, Long.MAX_VALUE /*filterEndTimeMills*/,
AppOpsManager.OP_FLAGS_ALL);
}
@@ -846,11 +862,12 @@
}
private void collectHistoricalOpsDLocked(@NonNull HistoricalOps currentOps,
- int filterUid, @NonNull String filterPackageName, @Nullable String[] filterOpNames,
+ int filterUid, @Nullable String filterPackageName, @Nullable String filterFeatureId,
+ @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter,
long filterBeingMillis, long filterEndMillis, @OpFlags int filterFlags) {
final List<HistoricalOps> readOps = collectHistoricalOpsBaseDLocked(filterUid,
- filterPackageName, filterOpNames, filterBeingMillis, filterEndMillis,
- filterFlags);
+ filterPackageName, filterFeatureId, filterOpNames, filter, filterBeingMillis,
+ filterEndMillis, filterFlags);
if (readOps != null) {
final int readCount = readOps.size();
for (int i = 0; i < readCount; i++) {
@@ -861,7 +878,8 @@
}
private @Nullable LinkedList<HistoricalOps> collectHistoricalOpsBaseDLocked(
- int filterUid, @NonNull String filterPackageName, @Nullable String[] filterOpNames,
+ int filterUid, @Nullable String filterPackageName, @Nullable String filterFeatureId,
+ @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter,
long filterBeginTimeMillis, long filterEndTimeMillis, @OpFlags int filterFlags) {
File baseDir = null;
try {
@@ -874,9 +892,9 @@
final Set<String> historyFiles = getHistoricalFileNames(baseDir);
final long[] globalContentOffsetMillis = {0};
final LinkedList<HistoricalOps> ops = collectHistoricalOpsRecursiveDLocked(
- baseDir, filterUid, filterPackageName, filterOpNames, filterBeginTimeMillis,
- filterEndTimeMillis, filterFlags, globalContentOffsetMillis,
- null /*outOps*/, 0 /*depth*/, historyFiles);
+ baseDir, filterUid, filterPackageName, filterFeatureId, filterOpNames,
+ filter, filterBeginTimeMillis, filterEndTimeMillis, filterFlags,
+ globalContentOffsetMillis, null /*outOps*/, 0 /*depth*/, historyFiles);
if (DEBUG) {
filesInvariant.stopTracking(baseDir);
}
@@ -890,8 +908,9 @@
}
private @Nullable LinkedList<HistoricalOps> collectHistoricalOpsRecursiveDLocked(
- @NonNull File baseDir, int filterUid, @NonNull String filterPackageName,
- @Nullable String[] filterOpNames, long filterBeginTimeMillis,
+ @NonNull File baseDir, int filterUid, @Nullable String filterPackageName,
+ @Nullable String filterFeatureId, @Nullable String[] filterOpNames,
+ @HistoricalOpsRequestFilter int filter, long filterBeginTimeMillis,
long filterEndTimeMillis, @OpFlags int filterFlags,
@NonNull long[] globalContentOffsetMillis,
@Nullable LinkedList<HistoricalOps> outOps, int depth,
@@ -908,17 +927,17 @@
// Read historical data at this level
final List<HistoricalOps> readOps = readHistoricalOpsLocked(baseDir,
previousIntervalEndMillis, currentIntervalEndMillis, filterUid,
- filterPackageName, filterOpNames, filterBeginTimeMillis, filterEndTimeMillis,
- filterFlags, globalContentOffsetMillis, depth, historyFiles);
-
+ filterPackageName, filterFeatureId, filterOpNames, filter,
+ filterBeginTimeMillis, filterEndTimeMillis, filterFlags,
+ globalContentOffsetMillis, depth, historyFiles);
// Empty is a special signal to stop diving
if (readOps != null && readOps.isEmpty()) {
return outOps;
}
// Collect older historical data from subsequent levels
- outOps = collectHistoricalOpsRecursiveDLocked(
- baseDir, filterUid, filterPackageName, filterOpNames, filterBeginTimeMillis,
+ outOps = collectHistoricalOpsRecursiveDLocked(baseDir, filterUid, filterPackageName,
+ filterFeatureId, filterOpNames, filter, filterBeginTimeMillis,
filterEndTimeMillis, filterFlags, globalContentOffsetMillis, outOps, depth + 1,
historyFiles);
@@ -987,10 +1006,10 @@
final List<HistoricalOps> existingOps = readHistoricalOpsLocked(oldBaseDir,
previousIntervalEndMillis, currentIntervalEndMillis,
Process.INVALID_UID /*filterUid*/, null /*filterPackageName*/,
- null /*filterOpNames*/, Long.MIN_VALUE /*filterBeginTimeMillis*/,
- Long.MAX_VALUE /*filterEndTimeMillis*/, AppOpsManager.OP_FLAGS_ALL,
- null, depth, null /*historyFiles*/);
-
+ null /*filterFeatureId*/, null /*filterOpNames*/, 0 /*filter*/,
+ Long.MIN_VALUE /*filterBeginTimeMillis*/,
+ Long.MAX_VALUE /*filterEndTimeMillis*/, AppOpsManager.OP_FLAGS_ALL, null, depth,
+ null /*historyFiles*/);
if (DEBUG) {
enforceOpsWellFormed(existingOps);
}
@@ -1100,8 +1119,9 @@
}
private @Nullable List<HistoricalOps> readHistoricalOpsLocked(File baseDir,
- long intervalBeginMillis, long intervalEndMillis,
- int filterUid, @Nullable String filterPackageName, @Nullable String[] filterOpNames,
+ long intervalBeginMillis, long intervalEndMillis, int filterUid,
+ @Nullable String filterPackageName, @Nullable String filterFeatureId,
+ @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter,
long filterBeginTimeMillis, long filterEndTimeMillis, @OpFlags int filterFlags,
@Nullable long[] cumulativeOverflowMillis, int depth,
@NonNull Set<String> historyFiles)
@@ -1127,13 +1147,14 @@
return null;
}
}
- return readHistoricalOpsLocked(file, filterUid, filterPackageName, filterOpNames,
- filterBeginTimeMillis, filterEndTimeMillis, filterFlags,
+ return readHistoricalOpsLocked(file, filterUid, filterPackageName, filterFeatureId,
+ filterOpNames, filter, filterBeginTimeMillis, filterEndTimeMillis, filterFlags,
cumulativeOverflowMillis);
}
private @Nullable List<HistoricalOps> readHistoricalOpsLocked(@NonNull File file,
- int filterUid, @Nullable String filterPackageName, @Nullable String[] filterOpNames,
+ int filterUid, @Nullable String filterPackageName, @Nullable String filterFeatureId,
+ @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter,
long filterBeginTimeMillis, long filterEndTimeMillis, @OpFlags int filterFlags,
@Nullable long[] cumulativeOverflowMillis)
throws IOException, XmlPullParserException {
@@ -1158,9 +1179,10 @@
final int depth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, depth)) {
if (TAG_OPS.equals(parser.getName())) {
- final HistoricalOps ops = readeHistoricalOpsDLocked(parser,
- filterUid, filterPackageName, filterOpNames, filterBeginTimeMillis,
- filterEndTimeMillis, filterFlags, cumulativeOverflowMillis);
+ final HistoricalOps ops = readeHistoricalOpsDLocked(parser, filterUid,
+ filterPackageName, filterFeatureId, filterOpNames, filter,
+ filterBeginTimeMillis, filterEndTimeMillis, filterFlags,
+ cumulativeOverflowMillis);
if (ops == null) {
continue;
}
@@ -1193,7 +1215,8 @@
private @Nullable HistoricalOps readeHistoricalOpsDLocked(
@NonNull XmlPullParser parser, int filterUid, @Nullable String filterPackageName,
- @Nullable String[] filterOpNames, long filterBeginTimeMillis,
+ @Nullable String filterFeatureId, @Nullable String[] filterOpNames,
+ @HistoricalOpsRequestFilter int filter, long filterBeginTimeMillis,
long filterEndTimeMillis, @OpFlags int filterFlags,
@Nullable long[] cumulativeOverflowMillis)
throws IOException, XmlPullParserException {
@@ -1222,7 +1245,8 @@
while (XmlUtils.nextElementWithin(parser, depth)) {
if (TAG_UID.equals(parser.getName())) {
final HistoricalOps returnedOps = readHistoricalUidOpsDLocked(ops, parser,
- filterUid, filterPackageName, filterOpNames, filterFlags, filterScale);
+ filterUid, filterPackageName, filterFeatureId, filterOpNames, filter,
+ filterFlags, filterScale);
if (ops == null) {
ops = returnedOps;
}
@@ -1236,11 +1260,12 @@
private @Nullable HistoricalOps readHistoricalUidOpsDLocked(
@Nullable HistoricalOps ops, @NonNull XmlPullParser parser, int filterUid,
- @Nullable String filterPackageName, @Nullable String[] filterOpNames,
+ @Nullable String filterPackageName, @Nullable String filterFeatureId,
+ @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter,
@OpFlags int filterFlags, double filterScale)
throws IOException, XmlPullParserException {
final int uid = XmlUtils.readIntAttribute(parser, ATTR_NAME);
- if (filterUid != Process.INVALID_UID && filterUid != uid) {
+ if ((filter & FILTER_BY_UID) != 0 && filterUid != uid) {
XmlUtils.skipCurrentTag(parser);
return null;
}
@@ -1248,8 +1273,8 @@
while (XmlUtils.nextElementWithin(parser, depth)) {
if (TAG_PACKAGE.equals(parser.getName())) {
final HistoricalOps returnedOps = readHistoricalPackageOpsDLocked(ops,
- uid, parser, filterPackageName, filterOpNames, filterFlags,
- filterScale);
+ uid, parser, filterPackageName, filterFeatureId, filterOpNames, filter,
+ filterFlags, filterScale);
if (ops == null) {
ops = returnedOps;
}
@@ -1260,19 +1285,46 @@
private @Nullable HistoricalOps readHistoricalPackageOpsDLocked(
@Nullable HistoricalOps ops, int uid, @NonNull XmlPullParser parser,
- @Nullable String filterPackageName, @Nullable String[] filterOpNames,
+ @Nullable String filterPackageName, @Nullable String filterFeatureId,
+ @Nullable String[] filterOpNames, @HistoricalOpsRequestFilter int filter,
@OpFlags int filterFlags, double filterScale)
throws IOException, XmlPullParserException {
final String packageName = XmlUtils.readStringAttribute(parser, ATTR_NAME);
- if (filterPackageName != null && !filterPackageName.equals(packageName)) {
+ if ((filter & FILTER_BY_PACKAGE_NAME) != 0 && !filterPackageName.equals(packageName)) {
+ XmlUtils.skipCurrentTag(parser);
+ return null;
+ }
+ final int depth = parser.getDepth();
+ while (XmlUtils.nextElementWithin(parser, depth)) {
+ if (TAG_FEATURE.equals(parser.getName())) {
+ final HistoricalOps returnedOps = readHistoricalFeatureOpsDLocked(ops, uid,
+ packageName, parser, filterFeatureId, filterOpNames, filter,
+ filterFlags, filterScale);
+ if (ops == null) {
+ ops = returnedOps;
+ }
+ }
+ }
+ return ops;
+ }
+
+ private @Nullable HistoricalOps readHistoricalFeatureOpsDLocked(@Nullable HistoricalOps ops,
+ int uid, String packageName, @NonNull XmlPullParser parser,
+ @Nullable String filterFeatureId, @Nullable String[] filterOpNames,
+ @HistoricalOpsRequestFilter int filter, @OpFlags int filterFlags,
+ double filterScale)
+ throws IOException, XmlPullParserException {
+ final String featureId = XmlUtils.readStringAttribute(parser, ATTR_NAME);
+ if ((filter & FILTER_BY_FEATURE_ID) != 0 && !Objects.equals(filterFeatureId,
+ featureId)) {
XmlUtils.skipCurrentTag(parser);
return null;
}
final int depth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, depth)) {
if (TAG_OP.equals(parser.getName())) {
- final HistoricalOps returnedOps = readHistoricalOpDLocked(ops, uid,
- packageName, parser, filterOpNames, filterFlags, filterScale);
+ final HistoricalOps returnedOps = readHistoricalOpDLocked(ops, uid, packageName,
+ featureId, parser, filterOpNames, filter, filterFlags, filterScale);
if (ops == null) {
ops = returnedOps;
}
@@ -1282,11 +1334,13 @@
}
private @Nullable HistoricalOps readHistoricalOpDLocked(@Nullable HistoricalOps ops,
- int uid, String packageName, @NonNull XmlPullParser parser,
- @Nullable String[] filterOpNames, @OpFlags int filterFlags, double filterScale)
+ int uid, @NonNull String packageName, @Nullable String featureId,
+ @NonNull XmlPullParser parser, @Nullable String[] filterOpNames,
+ @HistoricalOpsRequestFilter int filter, @OpFlags int filterFlags,
+ double filterScale)
throws IOException, XmlPullParserException {
final int op = XmlUtils.readIntAttribute(parser, ATTR_NAME);
- if (filterOpNames != null && !ArrayUtils.contains(filterOpNames,
+ if ((filter & FILTER_BY_OP_NAMES) != 0 && !ArrayUtils.contains(filterOpNames,
AppOpsManager.opToPublicName(op))) {
XmlUtils.skipCurrentTag(parser);
return null;
@@ -1295,7 +1349,7 @@
while (XmlUtils.nextElementWithin(parser, depth)) {
if (TAG_STATE.equals(parser.getName())) {
final HistoricalOps returnedOps = readStateDLocked(ops, uid,
- packageName, op, parser, filterFlags, filterScale);
+ packageName, featureId, op, parser, filterFlags, filterScale);
if (ops == null) {
ops = returnedOps;
}
@@ -1305,8 +1359,9 @@
}
private @Nullable HistoricalOps readStateDLocked(@Nullable HistoricalOps ops,
- int uid, String packageName, int op, @NonNull XmlPullParser parser,
- @OpFlags int filterFlags, double filterScale) throws IOException {
+ int uid, @NonNull String packageName, @Nullable String featureId, int op,
+ @NonNull XmlPullParser parser, @OpFlags int filterFlags, double filterScale)
+ throws IOException {
final long key = XmlUtils.readLongAttribute(parser, ATTR_NAME);
final int flags = AppOpsManager.extractFlagsFromKey(key) & filterFlags;
if (flags == 0) {
@@ -1322,7 +1377,8 @@
if (ops == null) {
ops = new HistoricalOps(0, 0);
}
- ops.increaseAccessCount(op, uid, packageName, uidState, flags, accessCount);
+ ops.increaseAccessCount(op, uid, packageName, featureId, uidState, flags,
+ accessCount);
}
long rejectCount = XmlUtils.readLongAttribute(parser, ATTR_REJECT_COUNT, 0);
if (rejectCount > 0) {
@@ -1333,7 +1389,8 @@
if (ops == null) {
ops = new HistoricalOps(0, 0);
}
- ops.increaseRejectCount(op, uid, packageName, uidState, flags, rejectCount);
+ ops.increaseRejectCount(op, uid, packageName, featureId, uidState, flags,
+ rejectCount);
}
long accessDuration = XmlUtils.readLongAttribute(parser, ATTR_ACCESS_DURATION, 0);
if (accessDuration > 0) {
@@ -1344,7 +1401,8 @@
if (ops == null) {
ops = new HistoricalOps(0, 0);
}
- ops.increaseAccessDuration(op, uid, packageName, uidState, flags, accessDuration);
+ ops.increaseAccessDuration(op, uid, packageName, featureId, uidState, flags,
+ accessDuration);
}
return ops;
}
@@ -1409,14 +1467,26 @@
@NonNull XmlSerializer serializer) throws IOException {
serializer.startTag(null, TAG_PACKAGE);
serializer.attribute(null, ATTR_NAME, packageOps.getPackageName());
- final int opCount = packageOps.getOpCount();
- for (int i = 0; i < opCount; i++) {
- final HistoricalOp op = packageOps.getOpAt(i);
- writeHistoricalOpDLocked(op, serializer);
+ final int numFeatures = packageOps.getFeatureCount();
+ for (int i = 0; i < numFeatures; i++) {
+ final HistoricalFeatureOps op = packageOps.getFeatureOpsAt(i);
+ writeHistoricalFeatureOpsDLocked(op, serializer);
}
serializer.endTag(null, TAG_PACKAGE);
}
+ private void writeHistoricalFeatureOpsDLocked(@NonNull HistoricalFeatureOps featureOps,
+ @NonNull XmlSerializer serializer) throws IOException {
+ serializer.startTag(null, TAG_FEATURE);
+ XmlUtils.writeStringAttribute(serializer, ATTR_NAME, featureOps.getFeatureId());
+ final int opCount = featureOps.getOpCount();
+ for (int i = 0; i < opCount; i++) {
+ final HistoricalOp op = featureOps.getOpAt(i);
+ writeHistoricalOpDLocked(op, serializer);
+ }
+ serializer.endTag(null, TAG_FEATURE);
+ }
+
private void writeHistoricalOpDLocked(@NonNull HistoricalOp op,
@NonNull XmlSerializer serializer) throws IOException {
final LongSparseArray keys = op.collectKeys();
@@ -1648,24 +1718,31 @@
private final @NonNull String mOpsPrefix;
private final @NonNull String mUidPrefix;
private final @NonNull String mPackagePrefix;
+ private final @NonNull String mFeaturePrefix;
private final @NonNull String mEntryPrefix;
private final @NonNull String mUidStatePrefix;
private final @NonNull PrintWriter mWriter;
private final int mFilterUid;
private final String mFilterPackage;
+ private final String mFilterFeatureId;
private final int mFilterOp;
+ private final @HistoricalOpsRequestFilter int mFilter;
- StringDumpVisitor(@NonNull String prefix, @NonNull PrintWriter writer,
- int filterUid, String filterPackage, int filterOp) {
+ StringDumpVisitor(@NonNull String prefix, @NonNull PrintWriter writer, int filterUid,
+ @Nullable String filterPackage, @Nullable String filterFeatureId, int filterOp,
+ @HistoricalOpsRequestFilter int filter) {
mOpsPrefix = prefix + " ";
mUidPrefix = mOpsPrefix + " ";
mPackagePrefix = mUidPrefix + " ";
- mEntryPrefix = mPackagePrefix + " ";
+ mFeaturePrefix = mPackagePrefix + " ";
+ mEntryPrefix = mFeaturePrefix + " ";
mUidStatePrefix = mEntryPrefix + " ";
mWriter = writer;
mFilterUid = filterUid;
mFilterPackage = filterPackage;
+ mFilterFeatureId = filterFeatureId;
mFilterOp = filterOp;
+ mFilter = filter;
}
@Override
@@ -1691,7 +1768,7 @@
@Override
public void visitHistoricalUidOps(HistoricalUidOps ops) {
- if (mFilterUid != Process.INVALID_UID && mFilterUid != ops.getUid()) {
+ if ((mFilter & FILTER_BY_UID) != 0 && mFilterUid != ops.getUid()) {
return;
}
mWriter.println();
@@ -1703,7 +1780,8 @@
@Override
public void visitHistoricalPackageOps(HistoricalPackageOps ops) {
- if (mFilterPackage != null && !mFilterPackage.equals(ops.getPackageName())) {
+ if ((mFilter & FILTER_BY_PACKAGE_NAME) != 0 && !mFilterPackage.equals(
+ ops.getPackageName())) {
return;
}
mWriter.print(mPackagePrefix);
@@ -1713,8 +1791,20 @@
}
@Override
+ public void visitHistoricalFeatureOps(HistoricalFeatureOps ops) {
+ if ((mFilter & FILTER_BY_FEATURE_ID) != 0 && !Objects.equals(mFilterPackage,
+ ops.getFeatureId())) {
+ return;
+ }
+ mWriter.print(mFeaturePrefix);
+ mWriter.print("Feature ");
+ mWriter.print(ops.getFeatureId());
+ mWriter.println(":");
+ }
+
+ @Override
public void visitHistoricalOp(HistoricalOp ops) {
- if (mFilterOp != AppOpsManager.OP_NONE && mFilterOp != ops.getOpCode()) {
+ if ((mFilter & FILTER_BY_OP_NAMES) != 0 && mFilterOp != ops.getOpCode()) {
return;
}
mWriter.print(mEntryPrefix);
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index bc4dc8f..eedeeea 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -1700,6 +1700,14 @@
}
}
+ /** @see AudioManager#getDevicesForAttributes(AudioAttributes) */
+ public @NonNull ArrayList<AudioDeviceAddress> getDevicesForAttributes(
+ @NonNull AudioAttributes attributes) {
+ Objects.requireNonNull(attributes);
+ enforceModifyAudioRoutingPermission();
+ return AudioSystem.getDevicesForAttributes(attributes);
+ }
+
/** @see AudioManager#adjustVolume(int, int) */
public void adjustSuggestedStreamVolume(int direction, int suggestedStreamType, int flags,
@@ -2185,8 +2193,8 @@
}
private void enforceModifyAudioRoutingPermission() {
- if (mContext.checkCallingPermission(
- android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+ if (mContext.checkCallingOrSelfPermission(
+ android.Manifest.permission.MODIFY_AUDIO_ROUTING)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Missing MODIFY_AUDIO_ROUTING permission");
}
@@ -6419,7 +6427,7 @@
return false;
}
boolean suppress = false;
- if (resolvedStream == DEFAULT_VOL_STREAM_NO_PLAYBACK && mController != null) {
+ if (resolvedStream != AudioSystem.STREAM_MUSIC && mController != null) {
final long now = SystemClock.uptimeMillis();
if ((flags & AudioManager.FLAG_SHOW_UI) != 0 && !mVisible) {
// ui will become visible
diff --git a/services/core/java/com/android/server/backup/PeopleBackupHelper.java b/services/core/java/com/android/server/backup/PeopleBackupHelper.java
new file mode 100644
index 0000000..e58a051
--- /dev/null
+++ b/services/core/java/com/android/server/backup/PeopleBackupHelper.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.backup;
+
+import android.app.backup.BlobBackupHelper;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+import com.android.server.people.PeopleServiceInternal;
+
+class PeopleBackupHelper extends BlobBackupHelper {
+
+ private static final String TAG = PeopleBackupHelper.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ // Current schema of the backup state blob.
+ private static final int STATE_VERSION = 1;
+
+ // Key under which conversation infos state blob is committed to backup.
+ private static final String KEY_CONVERSATIONS = "people_conversation_infos";
+
+ private final int mUserId;
+
+ PeopleBackupHelper(int userId) {
+ super(STATE_VERSION, KEY_CONVERSATIONS);
+ mUserId = userId;
+ }
+
+ @Override
+ protected byte[] getBackupPayload(String key) {
+ if (!KEY_CONVERSATIONS.equals(key)) {
+ Slog.w(TAG, "Unexpected backup key " + key);
+ return new byte[0];
+ }
+ PeopleServiceInternal ps = LocalServices.getService(PeopleServiceInternal.class);
+ if (DEBUG) {
+ Slog.d(TAG, "Handling backup of " + key);
+ }
+ return ps.backupConversationInfos(mUserId);
+ }
+
+ @Override
+ protected void applyRestoredPayload(String key, byte[] payload) {
+ if (!KEY_CONVERSATIONS.equals(key)) {
+ Slog.w(TAG, "Unexpected restore key " + key);
+ return;
+ }
+ PeopleServiceInternal ps = LocalServices.getService(PeopleServiceInternal.class);
+ if (DEBUG) {
+ Slog.d(TAG, "Handling restore of " + key);
+ }
+ ps.restoreConversationInfos(mUserId, key, payload);
+ }
+}
diff --git a/services/core/java/com/android/server/backup/SystemBackupAgent.java b/services/core/java/com/android/server/backup/SystemBackupAgent.java
index 35e8f56..1f4563b 100644
--- a/services/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/services/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -55,6 +55,7 @@
private static final String SHORTCUT_MANAGER_HELPER = "shortcut_manager";
private static final String ACCOUNT_MANAGER_HELPER = "account_manager";
private static final String SLICES_HELPER = "slices";
+ private static final String PEOPLE_HELPER = "people";
// These paths must match what the WallpaperManagerService uses. The leaf *_FILENAME
// are also used in the full-backup file format, so must not change unless steps are
@@ -99,6 +100,7 @@
addHelper(SHORTCUT_MANAGER_HELPER, new ShortcutBackupHelper());
addHelper(ACCOUNT_MANAGER_HELPER, new AccountManagerBackupHelper());
addHelper(SLICES_HELPER, new SliceBackupHelper(this));
+ addHelper(PEOPLE_HELPER, new PeopleBackupHelper(mUserId));
}
@Override
diff --git a/services/core/java/com/android/server/biometrics/BiometricService.java b/services/core/java/com/android/server/biometrics/BiometricService.java
index e1a9f3b..8dd3242 100644
--- a/services/core/java/com/android/server/biometrics/BiometricService.java
+++ b/services/core/java/com/android/server/biometrics/BiometricService.java
@@ -1400,7 +1400,8 @@
if (mCurrentAuthSession.mTokenEscrow != null) {
mKeyStore.addAuthToken(mCurrentAuthSession.mTokenEscrow);
}
- mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded();
+ mCurrentAuthSession.mClientReceiver.onAuthenticationSucceeded(
+ Utils.getAuthenticationTypeForResult(reason));
break;
case BiometricPrompt.DISMISSED_REASON_NEGATIVE:
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index 19f5358..389763b 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -22,6 +22,7 @@
import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricManager;
import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.biometrics.BiometricPrompt.AuthenticationResultType;
import android.os.Build;
import android.os.Bundle;
import android.os.UserHandle;
@@ -210,4 +211,30 @@
}
return biometricManagerCode;
}
+
+ /**
+ * Converts a {@link BiometricPrompt} dismissal reason to an authentication type at the level of
+ * granularity supported by {@link BiometricPrompt.AuthenticationResult}.
+ *
+ * @param reason The reason that the {@link BiometricPrompt} was dismissed. Must be one of:
+ * {@link BiometricPrompt#DISMISSED_REASON_CREDENTIAL_CONFIRMED},
+ * {@link BiometricPrompt#DISMISSED_REASON_BIOMETRIC_CONFIRMED}, or
+ * {@link BiometricPrompt#DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED}
+ * @return An integer representing the authentication type for {@link
+ * BiometricPrompt.AuthenticationResult}.
+ * @throws IllegalArgumentException if given an invalid dismissal reason.
+ */
+ public static @AuthenticationResultType int getAuthenticationTypeForResult(int reason) {
+ switch (reason) {
+ case BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED:
+ return BiometricPrompt.AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL;
+
+ case BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED:
+ case BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED:
+ return BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC;
+
+ default:
+ throw new IllegalArgumentException("Unsupported dismissal reason: " + reason);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/compat/CompatConfig.java b/services/core/java/com/android/server/compat/CompatConfig.java
index cf83dd6..8d26176 100644
--- a/services/core/java/com/android/server/compat/CompatConfig.java
+++ b/services/core/java/com/android/server/compat/CompatConfig.java
@@ -17,8 +17,10 @@
package com.android.server.compat;
import android.compat.Compatibility.ChangeConfig;
+import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.os.Environment;
+import android.os.RemoteException;
import android.text.TextUtils;
import android.util.LongArray;
import android.util.LongSparseArray;
@@ -26,8 +28,11 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.compat.AndroidBuildClassifier;
import com.android.internal.compat.CompatibilityChangeConfig;
import com.android.internal.compat.CompatibilityChangeInfo;
+import com.android.internal.compat.IOverrideValidator;
+import com.android.internal.compat.OverrideAllowedState;
import com.android.server.compat.config.Change;
import com.android.server.compat.config.XmlParser;
@@ -54,22 +59,14 @@
private static final String TAG = "CompatConfig";
- private static final CompatConfig sInstance = new CompatConfig().initConfigFromLib(
- Environment.buildPath(
- Environment.getRootDirectory(), "etc", "compatconfig"));
-
@GuardedBy("mChanges")
private final LongSparseArray<CompatChange> mChanges = new LongSparseArray<>();
- @VisibleForTesting
- CompatConfig() {
- }
+ private IOverrideValidator mOverrideValidator;
- /**
- * @return The static instance of this class to be used within the system server.
- */
- static CompatConfig get() {
- return sInstance;
+ @VisibleForTesting
+ CompatConfig(AndroidBuildClassifier androidBuildClassifier, Context context) {
+ mOverrideValidator = new OverrideValidatorImpl(androidBuildClassifier, context, this);
}
/**
@@ -159,8 +156,12 @@
* @param enabled If the change should be enabled or disabled.
* @return {@code true} if the change existed before adding the override.
*/
- boolean addOverride(long changeId, String packageName, boolean enabled) {
+ boolean addOverride(long changeId, String packageName, boolean enabled)
+ throws RemoteException, SecurityException {
boolean alreadyKnown = true;
+ OverrideAllowedState allowedState =
+ mOverrideValidator.getOverrideAllowedState(changeId, packageName);
+ allowedState.enforce(changeId, packageName);
synchronized (mChanges) {
CompatChange c = mChanges.get(changeId);
if (c == null) {
@@ -186,6 +187,20 @@
}
/**
+ * Returns the minimum sdk version for which this change should be enabled (or 0 if it is not
+ * target sdk gated).
+ */
+ int minTargetSdkForChangeId(long changeId) {
+ synchronized (mChanges) {
+ CompatChange c = mChanges.get(changeId);
+ if (c == null) {
+ return 0;
+ }
+ return c.getEnableAfterTargetSdk();
+ }
+ }
+
+ /**
* Removes an override previously added via {@link #addOverride(long, String, boolean)}. This
* restores the default behaviour for the given change and app, once any app processes have been
* restarted.
@@ -194,34 +209,44 @@
* @param packageName The app package name that was overridden.
* @return {@code true} if an override existed;
*/
- boolean removeOverride(long changeId, String packageName) {
+ boolean removeOverride(long changeId, String packageName)
+ throws RemoteException, SecurityException {
boolean overrideExists = false;
synchronized (mChanges) {
CompatChange c = mChanges.get(changeId);
- if (c != null) {
- overrideExists = true;
- c.removePackageOverride(packageName);
+ try {
+ if (c != null) {
+ OverrideAllowedState allowedState =
+ mOverrideValidator.getOverrideAllowedState(changeId, packageName);
+ allowedState.enforce(changeId, packageName);
+ overrideExists = true;
+ c.removePackageOverride(packageName);
+ }
+ } catch (RemoteException e) {
+ // Should never occur, since validator is in the same process.
+ throw new RuntimeException("Unable to call override validator!", e);
}
}
return overrideExists;
}
/**
- * Overrides the enabled state for a given change and app. This method is intended to be used
- * *only* for debugging purposes.
+ * Overrides the enabled state for a given change and app.
*
* <p>Note, package overrides are not persistent and will be lost on system or runtime restart.
*
* @param overrides list of overrides to default changes config.
* @param packageName app for which the overrides will be applied.
*/
- void addOverrides(CompatibilityChangeConfig overrides, String packageName) {
+ void addOverrides(CompatibilityChangeConfig overrides, String packageName)
+ throws RemoteException, SecurityException {
synchronized (mChanges) {
for (Long changeId : overrides.enabledChanges()) {
addOverride(changeId, packageName, true);
}
for (Long changeId : overrides.disabledChanges()) {
addOverride(changeId, packageName, false);
+
}
}
}
@@ -235,10 +260,22 @@
*
* @param packageName The package for which the overrides should be purged.
*/
- void removePackageOverrides(String packageName) {
+ void removePackageOverrides(String packageName) throws RemoteException, SecurityException {
synchronized (mChanges) {
for (int i = 0; i < mChanges.size(); ++i) {
- mChanges.valueAt(i).removePackageOverride(packageName);
+ try {
+ CompatChange change = mChanges.valueAt(i);
+ OverrideAllowedState allowedState =
+ mOverrideValidator.getOverrideAllowedState(change.getId(),
+ packageName);
+ allowedState.enforce(change.getId(), packageName);
+ if (change != null) {
+ mChanges.valueAt(i).removePackageOverride(packageName);
+ }
+ } catch (RemoteException e) {
+ // Should never occur, since validator is in the same process.
+ throw new RuntimeException("Unable to call override validator!", e);
+ }
}
}
}
@@ -326,17 +363,25 @@
}
}
- CompatConfig initConfigFromLib(File libraryDir) {
+ static CompatConfig create(AndroidBuildClassifier androidBuildClassifier, Context context) {
+ CompatConfig config = new CompatConfig(androidBuildClassifier, context);
+ config.initConfigFromLib(Environment.buildPath(
+ Environment.getRootDirectory(), "etc", "compatconfig"));
+ config.initConfigFromLib(Environment.buildPath(
+ Environment.getRootDirectory(), "system_ext", "etc", "compatconfig"));
+ return config;
+ }
+
+ void initConfigFromLib(File libraryDir) {
if (!libraryDir.exists() || !libraryDir.isDirectory()) {
Slog.e(TAG, "No directory " + libraryDir + ", skipping");
- return this;
+ return;
}
for (File f : libraryDir.listFiles()) {
Slog.d(TAG, "Found a config file: " + f.getPath());
//TODO(b/138222363): Handle duplicate ids across config files.
readConfig(f);
}
- return this;
}
private void readConfig(File configFile) {
@@ -350,4 +395,7 @@
}
}
+ IOverrideValidator getOverrideValidator() {
+ return mOverrideValidator;
+ }
}
diff --git a/services/core/java/com/android/server/compat/OverrideValidatorImpl.java b/services/core/java/com/android/server/compat/OverrideValidatorImpl.java
new file mode 100644
index 0000000..dfc0080
--- /dev/null
+++ b/services/core/java/com/android/server/compat/OverrideValidatorImpl.java
@@ -0,0 +1,94 @@
+/*
+ * 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 com.android.server.compat;
+
+import static com.android.internal.compat.OverrideAllowedState.ALLOWED;
+import static com.android.internal.compat.OverrideAllowedState.DISABLED_NON_TARGET_SDK;
+import static com.android.internal.compat.OverrideAllowedState.DISABLED_NOT_DEBUGGABLE;
+import static com.android.internal.compat.OverrideAllowedState.DISABLED_TARGET_SDK_TOO_HIGH;
+import static com.android.internal.compat.OverrideAllowedState.PACKAGE_DOES_NOT_EXIST;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.compat.AndroidBuildClassifier;
+import com.android.internal.compat.IOverrideValidator;
+import com.android.internal.compat.OverrideAllowedState;
+
+/**
+ * Implementation of the policy for allowing compat change overrides.
+ */
+public class OverrideValidatorImpl extends IOverrideValidator.Stub {
+
+ private AndroidBuildClassifier mAndroidBuildClassifier;
+ private Context mContext;
+ private CompatConfig mCompatConfig;
+
+ @VisibleForTesting
+ OverrideValidatorImpl(AndroidBuildClassifier androidBuildClassifier,
+ Context context, CompatConfig config) {
+ mAndroidBuildClassifier = androidBuildClassifier;
+ mContext = context;
+ mCompatConfig = config;
+ }
+
+ @Override
+ public OverrideAllowedState getOverrideAllowedState(long changeId, String packageName) {
+ boolean debuggableBuild = false;
+ boolean finalBuild = false;
+
+ debuggableBuild = mAndroidBuildClassifier.isDebuggableBuild();
+ finalBuild = mAndroidBuildClassifier.isFinalBuild();
+
+ // Allow any override for userdebug or eng builds.
+ if (debuggableBuild) {
+ return new OverrideAllowedState(ALLOWED, -1, -1);
+ }
+ PackageManager packageManager = mContext.getPackageManager();
+ if (packageManager == null) {
+ throw new IllegalStateException("No PackageManager!");
+ }
+ ApplicationInfo applicationInfo;
+ try {
+ applicationInfo = packageManager.getApplicationInfo(packageName, 0);
+ } catch (NameNotFoundException e) {
+ return new OverrideAllowedState(PACKAGE_DOES_NOT_EXIST, -1, -1);
+ }
+ int appTargetSdk = applicationInfo.targetSdkVersion;
+ // Only allow overriding debuggable apps.
+ if ((applicationInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) == 0) {
+ return new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1);
+ }
+ int minTargetSdk = mCompatConfig.minTargetSdkForChangeId(changeId);
+ // Do not allow overriding non-target sdk gated changes on user builds
+ if (minTargetSdk == -1) {
+ return new OverrideAllowedState(DISABLED_NON_TARGET_SDK, appTargetSdk, minTargetSdk);
+ }
+ // Allow overriding any change for debuggable apps on non-final builds.
+ if (!finalBuild) {
+ return new OverrideAllowedState(ALLOWED, appTargetSdk, minTargetSdk);
+ }
+ // Only allow to opt-in for a targetSdk gated change.
+ if (applicationInfo.targetSdkVersion < minTargetSdk) {
+ return new OverrideAllowedState(ALLOWED, appTargetSdk, minTargetSdk);
+ }
+ return new OverrideAllowedState(DISABLED_TARGET_SDK_TOO_HIGH, appTargetSdk, minTargetSdk);
+ }
+}
diff --git a/services/core/java/com/android/server/compat/PlatformCompat.java b/services/core/java/com/android/server/compat/PlatformCompat.java
index 6ec4b9f..bb8b12e 100644
--- a/services/core/java/com/android/server/compat/PlatformCompat.java
+++ b/services/core/java/com/android/server/compat/PlatformCompat.java
@@ -27,9 +27,12 @@
import android.util.Slog;
import android.util.StatsLog;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.compat.AndroidBuildClassifier;
import com.android.internal.compat.ChangeReporter;
import com.android.internal.compat.CompatibilityChangeConfig;
import com.android.internal.compat.CompatibilityChangeInfo;
+import com.android.internal.compat.IOverrideValidator;
import com.android.internal.compat.IPlatformCompat;
import com.android.internal.util.DumpUtils;
import com.android.server.LocalServices;
@@ -46,11 +49,21 @@
private final Context mContext;
private final ChangeReporter mChangeReporter;
+ private final CompatConfig mCompatConfig;
public PlatformCompat(Context context) {
mContext = context;
mChangeReporter = new ChangeReporter(
StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__SYSTEM_SERVER);
+ mCompatConfig = CompatConfig.create(new AndroidBuildClassifier(), mContext);
+ }
+
+ @VisibleForTesting
+ PlatformCompat(Context context, CompatConfig compatConfig) {
+ mContext = context;
+ mChangeReporter = new ChangeReporter(
+ StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__SOURCE__SYSTEM_SERVER);
+ mCompatConfig = compatConfig;
}
@Override
@@ -75,7 +88,7 @@
@Override
public boolean isChangeEnabled(long changeId, ApplicationInfo appInfo) {
- if (CompatConfig.get().isChangeEnabled(changeId, appInfo)) {
+ if (mCompatConfig.isChangeEnabled(changeId, appInfo)) {
reportChange(changeId, appInfo.uid,
StatsLog.APP_COMPATIBILITY_CHANGE_REPORTED__STATE__ENABLED);
return true;
@@ -122,57 +135,59 @@
* otherwise.
*/
public boolean registerListener(long changeId, CompatChange.ChangeListener listener) {
- return CompatConfig.get().registerListener(changeId, listener);
+ return mCompatConfig.registerListener(changeId, listener);
}
@Override
- public void setOverrides(CompatibilityChangeConfig overrides, String packageName) {
- CompatConfig.get().addOverrides(overrides, packageName);
+ public void setOverrides(CompatibilityChangeConfig overrides, String packageName)
+ throws RemoteException, SecurityException {
+ mCompatConfig.addOverrides(overrides, packageName);
killPackage(packageName);
}
@Override
- public void setOverridesForTest(CompatibilityChangeConfig overrides, String packageName) {
- CompatConfig.get().addOverrides(overrides, packageName);
+ public void setOverridesForTest(CompatibilityChangeConfig overrides, String packageName)
+ throws RemoteException, SecurityException {
+ mCompatConfig.addOverrides(overrides, packageName);
}
@Override
- public void clearOverrides(String packageName) {
- CompatConfig config = CompatConfig.get();
- config.removePackageOverrides(packageName);
+ public void clearOverrides(String packageName) throws RemoteException, SecurityException {
+ mCompatConfig.removePackageOverrides(packageName);
killPackage(packageName);
}
@Override
- public void clearOverridesForTest(String packageName) {
- CompatConfig config = CompatConfig.get();
- config.removePackageOverrides(packageName);
+ public void clearOverridesForTest(String packageName)
+ throws RemoteException, SecurityException {
+ mCompatConfig.removePackageOverrides(packageName);
}
@Override
- public boolean clearOverride(long changeId, String packageName) {
- boolean existed = CompatConfig.get().removeOverride(changeId, packageName);
+ public boolean clearOverride(long changeId, String packageName)
+ throws RemoteException, SecurityException {
+ boolean existed = mCompatConfig.removeOverride(changeId, packageName);
killPackage(packageName);
return existed;
}
@Override
public CompatibilityChangeConfig getAppConfig(ApplicationInfo appInfo) {
- return CompatConfig.get().getAppConfig(appInfo);
+ return mCompatConfig.getAppConfig(appInfo);
}
@Override
public CompatibilityChangeInfo[] listAllChanges() {
- return CompatConfig.get().dumpChanges();
+ return mCompatConfig.dumpChanges();
}
/**
* Check whether the change is known to the compat config.
- * @param changeId
+ *
* @return {@code true} if the change is known.
*/
public boolean isKnownChangeId(long changeId) {
- return CompatConfig.get().isKnownChangeId(changeId);
+ return mCompatConfig.isKnownChangeId(changeId);
}
@@ -182,11 +197,11 @@
*
* @param appInfo The app in question
* @return A sorted long array of change IDs. We use a primitive array to minimize memory
- * footprint: Every app process will store this array statically so we aim to reduce
- * overhead as much as possible.
+ * footprint: Every app process will store this array statically so we aim to reduce
+ * overhead as much as possible.
*/
public long[] getDisabledChanges(ApplicationInfo appInfo) {
- return CompatConfig.get().getDisabledChanges(appInfo);
+ return mCompatConfig.getDisabledChanges(appInfo);
}
/**
@@ -196,18 +211,24 @@
* @return The change ID, or {@code -1} if no change with that name exists.
*/
public long lookupChangeId(String name) {
- return CompatConfig.get().lookupChangeId(name);
+ return mCompatConfig.lookupChangeId(name);
}
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, "platform_compat", pw)) return;
- CompatConfig.get().dumpConfig(pw);
+ mCompatConfig.dumpConfig(pw);
+ }
+
+ @Override
+ public IOverrideValidator getOverrideValidator() {
+ return mCompatConfig.getOverrideValidator();
}
/**
* Clears information stored about events reported on behalf of an app.
* To be called once upon app start or end. A second call would be a no-op.
+ *
* @param appInfo the app to reset
*/
public void resetReporting(ApplicationInfo appInfo) {
diff --git a/services/core/java/com/android/server/connectivity/DataConnectionStats.java b/services/core/java/com/android/server/connectivity/DataConnectionStats.java
index 1b1c546..5010e46 100644
--- a/services/core/java/com/android/server/connectivity/DataConnectionStats.java
+++ b/services/core/java/com/android/server/connectivity/DataConnectionStats.java
@@ -16,6 +16,9 @@
package com.android.server.connectivity;
+import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
+import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
+
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -24,6 +27,7 @@
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
+import android.telephony.NetworkRegistrationInfo;
import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
@@ -91,7 +95,10 @@
boolean visible = (simReadyOrUnknown || isCdma()) // we only check the sim state for GSM
&& hasService()
&& mDataState == TelephonyManager.DATA_CONNECTED;
- int networkType = mServiceState.getDataNetworkType();
+ NetworkRegistrationInfo regInfo =
+ mServiceState.getNetworkRegistrationInfo(DOMAIN_PS, TRANSPORT_TYPE_WWAN);
+ int networkType = regInfo == null ? TelephonyManager.NETWORK_TYPE_UNKNOWN
+ : regInfo.getAccessNetworkTechnology();
if (DEBUG) Log.d(TAG, String.format("Noting data connection for network type %s: %svisible",
networkType, visible ? "" : "not "));
try {
diff --git a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
index 6fa999c..04c792a 100644
--- a/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
+++ b/services/core/java/com/android/server/connectivity/MultipathPolicyTracker.java
@@ -26,6 +26,7 @@
import static android.net.NetworkPolicy.LIMIT_DISABLED;
import static android.net.NetworkPolicy.WARNING_DISABLED;
import static android.provider.Settings.Global.NETWORK_DEFAULT_DAILY_MULTIPATH_QUOTA_BYTES;
+import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID;
import static com.android.server.net.NetworkPolicyManagerInternal.QUOTA_TYPE_MULTIPATH;
import static com.android.server.net.NetworkPolicyManagerService.OPPORTUNISTIC_QUOTA_UNKNOWN;
@@ -46,19 +47,18 @@
import android.net.NetworkPolicy;
import android.net.NetworkPolicyManager;
import android.net.NetworkRequest;
+import android.net.NetworkSpecifier;
import android.net.NetworkStats;
import android.net.NetworkTemplate;
-import android.net.StringNetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
+import android.net.Uri;
import android.os.BestClock;
import android.os.Handler;
import android.os.SystemClock;
-import android.net.Uri;
import android.os.UserHandle;
import android.provider.Settings;
import android.telephony.TelephonyManager;
-import android.util.DataUnit;
import android.util.DebugUtils;
-import android.util.Pair;
import android.util.Range;
import android.util.Slog;
@@ -74,7 +74,6 @@
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;
-import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
@@ -185,7 +184,6 @@
// Track information on mobile networks as they come and go.
class MultipathTracker {
final Network network;
- final int subId;
final String subscriberId;
private long mQuota;
@@ -198,13 +196,14 @@
public MultipathTracker(Network network, NetworkCapabilities nc) {
this.network = network;
this.mNetworkCapabilities = new NetworkCapabilities(nc);
- try {
- subId = Integer.parseInt(
- ((StringNetworkSpecifier) nc.getNetworkSpecifier()).toString());
- } catch (ClassCastException | NullPointerException | NumberFormatException e) {
+ NetworkSpecifier specifier = nc.getNetworkSpecifier();
+ int subId = INVALID_SUBSCRIPTION_ID;
+ if (specifier instanceof TelephonyNetworkSpecifier) {
+ subId = ((TelephonyNetworkSpecifier) specifier).getSubscriptionId();
+ } else {
throw new IllegalStateException(String.format(
- "Can't get subId from mobile network %s (%s): %s",
- network, nc, e.getMessage()));
+ "Can't get subId from mobile network %s (%s)",
+ network, nc));
}
TelephonyManager tele = mContext.getSystemService(TelephonyManager.class);
diff --git a/services/core/java/com/android/server/connectivity/Nat464Xlat.java b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
index aea6d8d..f636d67 100644
--- a/services/core/java/com/android/server/connectivity/Nat464Xlat.java
+++ b/services/core/java/com/android/server/connectivity/Nat464Xlat.java
@@ -116,7 +116,8 @@
&& !lp.hasIpv4Address();
// If the network tells us it doesn't use clat, respect that.
- final boolean skip464xlat = (nai.netMisc() != null) && nai.netMisc().skip464xlat;
+ final boolean skip464xlat = (nai.netAgentConfig() != null)
+ && nai.netAgentConfig().skip464xlat;
return supported && connected && isIpv6OnlyNetwork && !skip464xlat;
}
diff --git a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
index 5e085ca..c1ab551 100644
--- a/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
+++ b/services/core/java/com/android/server/connectivity/NetworkAgentInfo.java
@@ -23,9 +23,9 @@
import android.net.INetworkMonitor;
import android.net.LinkProperties;
import android.net.Network;
+import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
-import android.net.NetworkMisc;
import android.net.NetworkMonitorManager;
import android.net.NetworkRequest;
import android.net.NetworkScore;
@@ -127,7 +127,7 @@
// This should only be modified by ConnectivityService, via setNetworkCapabilities().
// TODO: make this private with a getter.
public NetworkCapabilities networkCapabilities;
- public final NetworkMisc networkMisc;
+ public final NetworkAgentConfig networkAgentConfig;
// Indicates if netd has been told to create this Network. From this point on the appropriate
// routing rules are setup and routes are added so packets can begin flowing over the Network.
// This is a sticky bit; once set it is never cleared.
@@ -261,7 +261,7 @@
public NetworkAgentInfo(Messenger messenger, AsyncChannel ac, Network net, NetworkInfo info,
LinkProperties lp, NetworkCapabilities nc, @NonNull NetworkScore ns, Context context,
- Handler handler, NetworkMisc misc, ConnectivityService connService, INetd netd,
+ Handler handler, NetworkAgentConfig config, ConnectivityService connService, INetd netd,
IDnsResolver dnsResolver, INetworkManagementService nms, int factorySerialNumber) {
this.messenger = messenger;
asyncChannel = ac;
@@ -274,7 +274,7 @@
mConnService = connService;
mContext = context;
mHandler = handler;
- networkMisc = misc;
+ networkAgentConfig = config;
this.factorySerialNumber = factorySerialNumber;
}
@@ -309,8 +309,8 @@
return mConnService;
}
- public NetworkMisc netMisc() {
- return networkMisc;
+ public NetworkAgentConfig netAgentConfig() {
+ return networkAgentConfig;
}
public Handler handler() {
@@ -487,7 +487,8 @@
// selected and we're trying to see what its score could be. This ensures that we don't tear
// down an explicitly selected network before the user gets a chance to prefer it when
// a higher-scoring network (e.g., Ethernet) is available.
- if (networkMisc.explicitlySelected && (networkMisc.acceptUnvalidated || pretendValidated)) {
+ if (networkAgentConfig.explicitlySelected
+ && (networkAgentConfig.acceptUnvalidated || pretendValidated)) {
return ConnectivityConstants.EXPLICITLY_SELECTED_NETWORK_SCORE;
}
@@ -533,7 +534,8 @@
synchronized (this) {
// Network objects are outwardly immutable so there is no point in duplicating.
// Duplicating also precludes sharing socket factories and connection pools.
- final String subscriberId = (networkMisc != null) ? networkMisc.subscriberId : null;
+ final String subscriberId = (networkAgentConfig != null)
+ ? networkAgentConfig.subscriberId : null;
return new NetworkState(new NetworkInfo(networkInfo),
new LinkProperties(linkProperties),
new NetworkCapabilities(networkCapabilities), network, subscriberId, null);
@@ -641,13 +643,13 @@
+ "nc{" + networkCapabilities + "} Score{" + getCurrentScore() + "} "
+ "everValidated{" + everValidated + "} lastValidated{" + lastValidated + "} "
+ "created{" + created + "} lingering{" + isLingering() + "} "
- + "explicitlySelected{" + networkMisc.explicitlySelected + "} "
- + "acceptUnvalidated{" + networkMisc.acceptUnvalidated + "} "
+ + "explicitlySelected{" + networkAgentConfig.explicitlySelected + "} "
+ + "acceptUnvalidated{" + networkAgentConfig.acceptUnvalidated + "} "
+ "everCaptivePortalDetected{" + everCaptivePortalDetected + "} "
+ "lastCaptivePortalDetected{" + lastCaptivePortalDetected + "} "
+ "captivePortalValidationPending{" + captivePortalValidationPending + "} "
+ "partialConnectivity{" + partialConnectivity + "} "
- + "acceptPartialConnectivity{" + networkMisc.acceptPartialConnectivity + "} "
+ + "acceptPartialConnectivity{" + networkAgentConfig.acceptPartialConnectivity + "} "
+ "clat{" + clatd + "} "
+ "}";
}
diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
index 2179518..2c41557 100644
--- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
+++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java
@@ -28,7 +28,7 @@
import android.content.Intent;
import android.content.res.Resources;
import android.net.NetworkSpecifier;
-import android.net.StringNetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
import android.net.wifi.WifiInfo;
import android.os.UserHandle;
import android.telephony.SubscriptionManager;
@@ -223,14 +223,8 @@
// name has been added to it
NetworkSpecifier specifier = nai.networkCapabilities.getNetworkSpecifier();
int subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
- if (specifier instanceof StringNetworkSpecifier) {
- try {
- subId = Integer.parseInt(
- ((StringNetworkSpecifier) specifier).specifier);
- } catch (NumberFormatException e) {
- Slog.e(TAG, "NumberFormatException on "
- + ((StringNetworkSpecifier) specifier).specifier);
- }
+ if (specifier instanceof TelephonyNetworkSpecifier) {
+ subId = ((TelephonyNetworkSpecifier) specifier).getSubscriptionId();
}
details = mTelephonyManager.createForSubscriptionId(subId)
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 6b70e5f..1d2a905 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -53,11 +53,11 @@
import android.net.LocalSocketAddress;
import android.net.Network;
import android.net.NetworkAgent;
+import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
-import android.net.NetworkFactory;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
-import android.net.NetworkMisc;
+import android.net.NetworkProvider;
import android.net.RouteInfo;
import android.net.UidRange;
import android.net.VpnService;
@@ -848,7 +848,7 @@
}
public int getNetId() {
- return mNetworkAgent != null ? mNetworkAgent.netId : NETID_UNSET;
+ return mNetworkAgent != null ? mNetworkAgent.network.netId : NETID_UNSET;
}
private LinkProperties makeLinkProperties() {
@@ -914,7 +914,7 @@
* has certain changes, in which case this method would just return {@code false}.
*/
private boolean updateLinkPropertiesInPlaceIfPossible(NetworkAgent agent, VpnConfig oldConfig) {
- // NetworkMisc cannot be updated without registering a new NetworkAgent.
+ // NetworkAgentConfig cannot be updated without registering a new NetworkAgent.
if (oldConfig.allowBypass != mConfig.allowBypass) {
Log.i(TAG, "Handover not possible due to changes to allowBypass");
return false;
@@ -947,8 +947,8 @@
mNetworkInfo.setDetailedState(DetailedState.CONNECTING, null, null);
- NetworkMisc networkMisc = new NetworkMisc();
- networkMisc.allowBypass = mConfig.allowBypass && !mLockdown;
+ NetworkAgentConfig networkAgentConfig = new NetworkAgentConfig();
+ networkAgentConfig.allowBypass = mConfig.allowBypass && !mLockdown;
mNetworkCapabilities.setEstablishingVpnAppUid(Binder.getCallingUid());
mNetworkCapabilities.setUids(createUserAndRestrictedProfilesRanges(mUserHandle,
@@ -957,8 +957,8 @@
try {
mNetworkAgent = new NetworkAgent(mLooper, mContext, NETWORKTYPE /* logtag */,
mNetworkInfo, mNetworkCapabilities, lp,
- ConnectivityConstants.VPN_DEFAULT_SCORE, networkMisc,
- NetworkFactory.SerialNumber.VPN) {
+ ConnectivityConstants.VPN_DEFAULT_SCORE, networkAgentConfig,
+ NetworkProvider.ID_VPN) {
@Override
public void unwanted() {
// We are user controlled, not driven by NetworkRequest.
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 10386e7..e39d6d5 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -77,7 +77,6 @@
import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
-import android.provider.Settings;
import android.text.TextUtils;
import android.util.IntArray;
import android.util.Pair;
@@ -1346,6 +1345,35 @@
return SurfaceControl.getDisplayedContentSample(token, maxFrames, timestamp);
}
+ void resetBrightnessConfiguration() {
+ setBrightnessConfigurationForUserInternal(null, mContext.getUserId(),
+ mContext.getPackageName());
+ }
+
+ void setAutoBrightnessLoggingEnabled(boolean enabled) {
+ if (mDisplayPowerController != null) {
+ synchronized (mSyncRoot) {
+ mDisplayPowerController.setAutoBrightnessLoggingEnabled(enabled);
+ }
+ }
+ }
+
+ void setDisplayWhiteBalanceLoggingEnabled(boolean enabled) {
+ if (mDisplayPowerController != null) {
+ synchronized (mSyncRoot) {
+ mDisplayPowerController.setDisplayWhiteBalanceLoggingEnabled(enabled);
+ }
+ }
+ }
+
+ void setAmbientColorTemperatureOverride(float cct) {
+ if (mDisplayPowerController != null) {
+ synchronized (mSyncRoot) {
+ mDisplayPowerController.setAmbientColorTemperatureOverride(cct);
+ }
+ }
+ }
+
private void onDesiredDisplayModeSpecsChangedInternal() {
boolean changed = false;
synchronized (mSyncRoot) {
@@ -2249,13 +2277,8 @@
public void onShellCommand(FileDescriptor in, FileDescriptor out,
FileDescriptor err, String[] args, ShellCallback callback,
ResultReceiver resultReceiver) {
- final long token = Binder.clearCallingIdentity();
- try {
- DisplayManagerShellCommand command = new DisplayManagerShellCommand(this);
- command.exec(this, in, out, err, args, callback, resultReceiver);
- } finally {
- Binder.restoreCallingIdentity(token);
- }
+ new DisplayManagerShellCommand(DisplayManagerService.this).exec(this, in, out, err,
+ args, callback, resultReceiver);
}
@Override // Binder call
@@ -2278,40 +2301,6 @@
}
}
- void setBrightness(int brightness) {
- Settings.System.putIntForUser(mContext.getContentResolver(),
- Settings.System.SCREEN_BRIGHTNESS, brightness, UserHandle.USER_CURRENT);
- }
-
- void resetBrightnessConfiguration() {
- setBrightnessConfigurationForUserInternal(null, mContext.getUserId(),
- mContext.getPackageName());
- }
-
- void setAutoBrightnessLoggingEnabled(boolean enabled) {
- if (mDisplayPowerController != null) {
- synchronized (mSyncRoot) {
- mDisplayPowerController.setAutoBrightnessLoggingEnabled(enabled);
- }
- }
- }
-
- void setDisplayWhiteBalanceLoggingEnabled(boolean enabled) {
- if (mDisplayPowerController != null) {
- synchronized (mSyncRoot) {
- mDisplayPowerController.setDisplayWhiteBalanceLoggingEnabled(enabled);
- }
- }
- }
-
- void setAmbientColorTemperatureOverride(float cct) {
- if (mDisplayPowerController != null) {
- synchronized (mSyncRoot) {
- mDisplayPowerController.setAmbientColorTemperatureOverride(cct);
- }
- }
- }
-
private boolean validatePackageName(int uid, String packageName) {
if (packageName != null) {
String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid);
diff --git a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
index 04d28ea..e87ad41 100644
--- a/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
+++ b/services/core/java/com/android/server/display/DisplayManagerShellCommand.java
@@ -16,17 +16,22 @@
package com.android.server.display;
+import android.Manifest;
+import android.content.Context;
import android.content.Intent;
+import android.os.Binder;
import android.os.ShellCommand;
+import android.os.UserHandle;
+import android.provider.Settings;
import java.io.PrintWriter;
class DisplayManagerShellCommand extends ShellCommand {
private static final String TAG = "DisplayManagerShellCommand";
- private final DisplayManagerService.BinderService mService;
+ private final DisplayManagerService mService;
- DisplayManagerShellCommand(DisplayManagerService.BinderService service) {
+ DisplayManagerShellCommand(DisplayManagerService service) {
mService = service;
}
@@ -96,7 +101,19 @@
getErrPrintWriter().println("Error: brightness should be a number between 0 and 1");
return 1;
}
- mService.setBrightness((int) (brightness * 255));
+
+ final Context context = mService.getContext();
+ context.enforceCallingOrSelfPermission(
+ Manifest.permission.CONTROL_DISPLAY_BRIGHTNESS,
+ "Permission required to set the display's brightness");
+ final long token = Binder.clearCallingIdentity();
+ try {
+ Settings.System.putIntForUser(context.getContentResolver(),
+ Settings.System.SCREEN_BRIGHTNESS, (int) (brightness * 255),
+ UserHandle.USER_CURRENT);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
return 0;
}
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index ad728c1..decb1f9 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -73,7 +73,7 @@
private static final int GLOBAL_ID = -1;
// The tolerance within which we consider something approximately equals.
- private static final float EPSILON = 0.001f;
+ private static final float EPSILON = 0.01f;
private final Object mLock = new Object();
private final Context mContext;
@@ -237,15 +237,15 @@
lowestConsideredPriority++;
}
- int defaultModeId = defaultMode.getModeId();
+ int baseModeId = defaultMode.getModeId();
if (availableModes.length > 0) {
- defaultModeId = availableModes[0];
+ baseModeId = availableModes[0];
}
// filterModes function is going to filter the modes based on the voting system. If
// the application requests a given mode with preferredModeId function, it will be
- // stored as defaultModeId.
+ // stored as baseModeId.
return new DesiredDisplayModeSpecs(
- defaultModeId, new RefreshRateRange(minRefreshRate, maxRefreshRate));
+ baseModeId, new RefreshRateRange(minRefreshRate, maxRefreshRate));
}
}
@@ -526,7 +526,7 @@
}
/**
- * Information about the desired display mode to be set by the system. Includes the default
+ * Information about the desired display mode to be set by the system. Includes the base
* mode ID and refresh rate range.
*
* We have this class in addition to SurfaceControl.DesiredDisplayConfigSpecs to make clear the
@@ -535,10 +535,10 @@
*/
public static final class DesiredDisplayModeSpecs {
/**
- * Default mode ID. This is what system defaults to for all other settings, or
+ * Base mode ID. This is what system defaults to for all other settings, or
* if the refresh rate range is not available.
*/
- public int defaultModeId;
+ public int baseModeId;
/**
* The refresh rate range.
*/
@@ -548,9 +548,8 @@
refreshRateRange = new RefreshRateRange();
}
- public DesiredDisplayModeSpecs(
- int defaultModeId, @NonNull RefreshRateRange refreshRateRange) {
- this.defaultModeId = defaultModeId;
+ public DesiredDisplayModeSpecs(int baseModeId, @NonNull RefreshRateRange refreshRateRange) {
+ this.baseModeId = baseModeId;
this.refreshRateRange = refreshRateRange;
}
@@ -559,7 +558,7 @@
*/
@Override
public String toString() {
- return String.format("defaultModeId=%d min=%.0f max=%.0f", defaultModeId,
+ return String.format("baseModeId=%d min=%.0f max=%.0f", baseModeId,
refreshRateRange.min, refreshRateRange.max);
}
/**
@@ -577,7 +576,7 @@
DesiredDisplayModeSpecs desiredDisplayModeSpecs = (DesiredDisplayModeSpecs) other;
- if (defaultModeId != desiredDisplayModeSpecs.defaultModeId) {
+ if (baseModeId != desiredDisplayModeSpecs.baseModeId) {
return false;
}
if (!refreshRateRange.equals(desiredDisplayModeSpecs.refreshRateRange)) {
@@ -588,14 +587,14 @@
@Override
public int hashCode() {
- return Objects.hash(defaultModeId, refreshRateRange);
+ return Objects.hash(baseModeId, refreshRateRange);
}
/**
* Copy values from the other object.
*/
public void copyFrom(DesiredDisplayModeSpecs other) {
- defaultModeId = other.defaultModeId;
+ baseModeId = other.baseModeId;
refreshRateRange.min = other.refreshRateRange.min;
refreshRateRange.max = other.refreshRateRange.max;
}
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index cf94d46..fb8a419 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -272,14 +272,14 @@
// Check whether surface flinger spontaneously changed display config specs out from
// under us. If so, schedule a traversal to reapply our display config specs.
- if (mDisplayModeSpecs.defaultModeId != 0) {
- int activeDefaultMode =
+ if (mDisplayModeSpecs.baseModeId != 0) {
+ int activeBaseMode =
findMatchingModeIdLocked(physicalDisplayConfigSpecs.defaultConfig);
// If we can't map the defaultConfig index to a mode, then the physical display
// configs must have changed, and the code below for handling changes to the
// list of available modes will take care of updating display config specs.
- if (activeDefaultMode != 0) {
- if (mDisplayModeSpecs.defaultModeId != activeDefaultMode
+ if (activeBaseMode != 0) {
+ if (mDisplayModeSpecs.baseModeId != activeBaseMode
|| mDisplayModeSpecs.refreshRateRange.min
!= physicalDisplayConfigSpecs.minRefreshRate
|| mDisplayModeSpecs.refreshRateRange.max
@@ -312,14 +312,14 @@
mDefaultModeId = activeRecord.mMode.getModeId();
}
- // Determine whether the display mode specs' default mode is still there.
- if (mSupportedModes.indexOfKey(mDisplayModeSpecs.defaultModeId) < 0) {
- if (mDisplayModeSpecs.defaultModeId != 0) {
+ // Determine whether the display mode specs' base mode is still there.
+ if (mSupportedModes.indexOfKey(mDisplayModeSpecs.baseModeId) < 0) {
+ if (mDisplayModeSpecs.baseModeId != 0) {
Slog.w(TAG,
- "DisplayModeSpecs default mode no longer available, using currently"
- + " active mode as default.");
+ "DisplayModeSpecs base mode no longer available, using currently"
+ + " active mode.");
}
- mDisplayModeSpecs.defaultModeId = activeRecord.mMode.getModeId();
+ mDisplayModeSpecs.baseModeId = activeRecord.mMode.getModeId();
mDisplayModeSpecsInvalid = true;
}
@@ -648,13 +648,13 @@
@Override
public void setDesiredDisplayModeSpecsLocked(
DisplayModeDirector.DesiredDisplayModeSpecs displayModeSpecs) {
- if (displayModeSpecs.defaultModeId == 0) {
+ if (displayModeSpecs.baseModeId == 0) {
// Bail if the caller is requesting a null mode. We'll get called again shortly with
// a valid mode.
return;
}
- int defaultPhysIndex = findDisplayInfoIndexLocked(displayModeSpecs.defaultModeId);
- if (defaultPhysIndex < 0) {
+ int basePhysIndex = findDisplayInfoIndexLocked(displayModeSpecs.baseModeId);
+ if (basePhysIndex < 0) {
// When a display is hotplugged, it's possible for a mode to be removed that was
// previously valid. Because of the way display changes are propagated through the
// framework, and the caching of the display mode specs in LogicalDisplay, it's
@@ -662,8 +662,7 @@
// mode. This should only happen in extremely rare cases. A followup call will
// contain a valid mode id.
Slog.w(TAG,
- "Ignoring request for invalid default mode id "
- + displayModeSpecs.defaultModeId);
+ "Ignoring request for invalid base mode id " + displayModeSpecs.baseModeId);
updateDeviceInfoLocked();
return;
}
@@ -673,7 +672,7 @@
getHandler().sendMessage(PooledLambda.obtainMessage(
LocalDisplayDevice::setDesiredDisplayModeSpecsAsync, this,
getDisplayTokenLocked(),
- new SurfaceControl.DesiredDisplayConfigSpecs(defaultPhysIndex,
+ new SurfaceControl.DesiredDisplayConfigSpecs(basePhysIndex,
mDisplayModeSpecs.refreshRateRange.min,
mDisplayModeSpecs.refreshRateRange.max)));
}
diff --git a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
index b6255d1..c8e5f6c 100644
--- a/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -317,7 +317,7 @@
@Override
public void setDesiredDisplayModeSpecsLocked(
DisplayModeDirector.DesiredDisplayModeSpecs displayModeSpecs) {
- final int id = displayModeSpecs.defaultModeId;
+ final int id = displayModeSpecs.baseModeId;
int index = -1;
if (id == 0) {
// Use the default.
diff --git a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
index c58000f..8206fef 100644
--- a/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
+++ b/services/core/java/com/android/server/infra/AbstractMasterSystemService.java
@@ -41,7 +41,6 @@
import com.android.internal.content.PackageMonitor;
import com.android.internal.infra.AbstractRemoteService;
import com.android.internal.os.BackgroundThread;
-import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -713,8 +712,8 @@
}
/**
- * Gets a list of all supported users (i.e., those that pass the {@link #isSupported(UserInfo)}
- * check).
+ * Gets a list of all supported users (i.e., those that pass the
+ * {@link #isSupportedUser(TargetUser)}check).
*/
@NonNull
protected List<UserInfo> getSupportedUsers() {
@@ -723,7 +722,7 @@
final List<UserInfo> supportedUsers = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
final UserInfo userInfo = allUsers[i];
- if (isSupported(userInfo)) {
+ if (isSupportedUser(new TargetUser(userInfo))) {
supportedUsers.add(userInfo);
}
}
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index ae6d63a..90358ca 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -83,11 +83,11 @@
import android.widget.Toast;
import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.DumpUtils;
-import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import com.android.server.DisplayThread;
import com.android.server.LocalServices;
@@ -186,6 +186,9 @@
// The associations of input devices to displays by port. Maps from input device port (String)
// to display id (int). Currently only accessed by InputReader.
private final Map<String, Integer> mStaticAssociations;
+ private final Object mAssociationsLock = new Object();
+ @GuardedBy("mAssociationLock")
+ private final Map<String, Integer> mRuntimeAssociations = new HashMap<String, Integer>();
private static native long nativeInit(InputManagerService service,
Context context, MessageQueue messageQueue);
@@ -240,6 +243,7 @@
private static native void nativeSetCustomPointerIcon(long ptr, PointerIcon icon);
private static native void nativeSetPointerCapture(long ptr, boolean detached);
private static native boolean nativeCanDispatchToDisplay(long ptr, int deviceId, int displayId);
+ private static native void nativeNotifyPortAssociationsChanged(long ptr);
// Input event injection constants defined in InputDispatcher.h.
private static final int INPUT_EVENT_INJECTION_SUCCEEDED = 0;
@@ -1723,6 +1727,49 @@
nativeSetCustomPointerIcon(mPtr, icon);
}
+ /**
+ * Add a runtime association between the input port and the display port. This overrides any
+ * static associations.
+ * @param inputPort The port of the input device.
+ * @param displayPort The physical port of the associated display.
+ */
+ @Override // Binder call
+ public void addPortAssociation(@NonNull String inputPort, int displayPort) {
+ if (!checkCallingPermission(
+ android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT,
+ "addPortAssociation()")) {
+ throw new SecurityException(
+ "Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT permission");
+ }
+
+ Objects.requireNonNull(inputPort);
+ synchronized (mAssociationsLock) {
+ mRuntimeAssociations.put(inputPort, displayPort);
+ }
+ nativeNotifyPortAssociationsChanged(mPtr);
+ }
+
+ /**
+ * Remove the runtime association between the input port and the display port. Any existing
+ * static association for the cleared input port will be restored.
+ * @param inputPort The port of the input device to be cleared.
+ */
+ @Override // Binder call
+ public void removePortAssociation(@NonNull String inputPort) {
+ if (!checkCallingPermission(
+ android.Manifest.permission.ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT,
+ "clearPortAssociations()")) {
+ throw new SecurityException(
+ "Requires ASSOCIATE_INPUT_DEVICE_TO_DISPLAY_BY_PORT permission");
+ }
+
+ Objects.requireNonNull(inputPort);
+ synchronized (mAssociationsLock) {
+ mRuntimeAssociations.remove(inputPort);
+ }
+ nativeNotifyPortAssociationsChanged(mPtr);
+ }
+
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
@@ -1743,6 +1790,16 @@
pw.println(" display: " + v);
});
}
+
+ synchronized (mAssociationsLock) {
+ if (!mRuntimeAssociations.isEmpty()) {
+ pw.println("Runtime Associations:");
+ mRuntimeAssociations.forEach((k, v) -> {
+ pw.print(" port: " + k);
+ pw.println(" display: " + v);
+ });
+ }
+ }
}
private boolean checkCallingPermission(String permission, String func) {
@@ -1766,6 +1823,7 @@
@Override
public void monitor() {
synchronized (mInputFilterLock) { }
+ synchronized (mAssociationsLock) { /* Test if blocked by associations lock. */}
nativeMonitor(mPtr);
}
@@ -1930,7 +1988,7 @@
* @return Flattened list
*/
private static List<String> flatten(@NonNull Map<String, Integer> map) {
- List<String> list = new ArrayList<>(map.size() * 2);
+ final List<String> list = new ArrayList<>(map.size() * 2);
map.forEach((k, v)-> {
list.add(k);
list.add(v.toString());
@@ -1943,11 +2001,11 @@
* directory.
*/
private static Map<String, Integer> loadStaticInputPortAssociations() {
- File baseDir = Environment.getVendorDirectory();
- File confFile = new File(baseDir, PORT_ASSOCIATIONS_PATH);
+ final File baseDir = Environment.getVendorDirectory();
+ final File confFile = new File(baseDir, PORT_ASSOCIATIONS_PATH);
try {
- InputStream stream = new FileInputStream(confFile);
+ final InputStream stream = new FileInputStream(confFile);
return ConfigurationProcessor.processInputPortAssociations(stream);
} catch (FileNotFoundException e) {
// Most of the time, file will not exist, which is expected.
@@ -1960,7 +2018,14 @@
// Native callback
private String[] getInputPortAssociations() {
- List<String> associationList = flatten(mStaticAssociations);
+ final Map<String, Integer> associations = new HashMap<>(mStaticAssociations);
+
+ // merge the runtime associations.
+ synchronized (mAssociationsLock) {
+ associations.putAll(mRuntimeAssociations);
+ }
+
+ final List<String> associationList = flatten(associations);
return associationList.toArray(new String[0]);
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index c40e8af..9c42152 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -68,8 +68,21 @@
* @param autofillId {@link AutofillId} of currently focused field.
* @param cb {@link IInlineSuggestionsRequestCallback} used to pass back the request object.
*/
- public abstract void onCreateInlineSuggestionsRequest(ComponentName componentName,
- AutofillId autofillId, IInlineSuggestionsRequestCallback cb);
+ public abstract void onCreateInlineSuggestionsRequest(@UserIdInt int userId,
+ ComponentName componentName, AutofillId autofillId,
+ IInlineSuggestionsRequestCallback cb);
+
+ /**
+ * Force switch to the enabled input method by {@code imeId} for current user. If the input
+ * method with {@code imeId} is not enabled or not installed, do nothing.
+ *
+ * @param imeId The input method ID to be switched to.
+ * @param userId The user ID to be queried.
+ * @return {@code true} if the current input method was successfully switched to the input
+ * method by {@code imeId}; {@code false} the input method with {@code imeId} is not available
+ * to be switched.
+ */
+ public abstract boolean switchToInputMethod(String imeId, @UserIdInt int userId);
/**
* Fake implementation of {@link InputMethodManagerInternal}. All the methods do nothing.
@@ -95,8 +108,14 @@
}
@Override
- public void onCreateInlineSuggestionsRequest(ComponentName componentName,
- AutofillId autofillId, IInlineSuggestionsRequestCallback cb) {
+ public void onCreateInlineSuggestionsRequest(int userId,
+ ComponentName componentName, AutofillId autofillId,
+ IInlineSuggestionsRequestCallback cb) {
+ }
+
+ @Override
+ public boolean switchToInputMethod(String imeId, int userId) {
+ return false;
}
};
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 8b6b614..0bf65bd 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -111,6 +111,7 @@
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
import android.view.autofill.AutofillId;
import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InlineSuggestionsRequest;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputConnectionInspector;
@@ -142,6 +143,7 @@
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.view.IInlineSuggestionsRequestCallback;
+import com.android.internal.view.IInlineSuggestionsResponseCallback;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethod;
import com.android.internal.view.IInputMethodClient;
@@ -1790,15 +1792,18 @@
}
@GuardedBy("mMethodMap")
- private void onCreateInlineSuggestionsRequestLocked(ComponentName componentName,
- AutofillId autofillId, IInlineSuggestionsRequestCallback callback) {
-
+ private void onCreateInlineSuggestionsRequestLocked(@UserIdInt int userId,
+ ComponentName componentName, AutofillId autofillId,
+ IInlineSuggestionsRequestCallback callback) {
final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
try {
- if (imi != null && imi.isInlineSuggestionsEnabled() && mCurMethod != null) {
+ if (userId == mSettings.getCurrentUserId() && imi != null
+ && imi.isInlineSuggestionsEnabled() && mCurMethod != null) {
executeOrSendMessage(mCurMethod,
mCaller.obtainMessageOOOO(MSG_INLINE_SUGGESTIONS_REQUEST, mCurMethod,
- componentName, autofillId, callback));
+ componentName, autofillId,
+ new InlineSuggestionsRequestCallbackDecorator(callback,
+ imi.getPackageName())));
} else {
callback.onInlineSuggestionsUnsupported();
}
@@ -1808,6 +1813,42 @@
}
/**
+ * The decorator which validates the host package name in the
+ * {@link InlineSuggestionsRequest} argument to make sure it matches the IME package name.
+ */
+ private static final class InlineSuggestionsRequestCallbackDecorator
+ extends IInlineSuggestionsRequestCallback.Stub {
+ @NonNull
+ private final IInlineSuggestionsRequestCallback mCallback;
+ @NonNull
+ private final String mImePackageName;
+
+ InlineSuggestionsRequestCallbackDecorator(
+ @NonNull IInlineSuggestionsRequestCallback callback,
+ @NonNull String imePackageName) {
+ mCallback = callback;
+ mImePackageName = imePackageName;
+ }
+
+ @Override
+ public void onInlineSuggestionsUnsupported() throws RemoteException {
+ mCallback.onInlineSuggestionsUnsupported();
+ }
+
+ @Override
+ public void onInlineSuggestionsRequest(InlineSuggestionsRequest request,
+ IInlineSuggestionsResponseCallback callback) throws RemoteException {
+ if (!mImePackageName.equals(request.getHostPackageName())) {
+ throw new SecurityException(
+ "Host package name in the provide request=[" + request.getHostPackageName()
+ + "] doesn't match the IME package name=[" + mImePackageName
+ + "].");
+ }
+ mCallback.onInlineSuggestionsRequest(request, callback);
+ }
+ }
+
+ /**
* @param imiId if null, returns enabled subtypes for the current imi
* @return enabled subtypes of the specified imi
*/
@@ -4471,10 +4512,43 @@
}
}
- private void onCreateInlineSuggestionsRequest(ComponentName componentName,
- AutofillId autofillId, IInlineSuggestionsRequestCallback callback) {
+ private void onCreateInlineSuggestionsRequest(@UserIdInt int userId,
+ ComponentName componentName, AutofillId autofillId,
+ IInlineSuggestionsRequestCallback callback) {
synchronized (mMethodMap) {
- onCreateInlineSuggestionsRequestLocked(componentName, autofillId, callback);
+ onCreateInlineSuggestionsRequestLocked(userId, componentName, autofillId, callback);
+ }
+ }
+
+ private boolean switchToInputMethod(String imeId, @UserIdInt int userId) {
+ synchronized (mMethodMap) {
+ if (userId == mSettings.getCurrentUserId()) {
+ if (!mMethodMap.containsKey(imeId)
+ || !mSettings.getEnabledInputMethodListLocked()
+ .contains(mMethodMap.get(imeId))) {
+ return false; // IME is not is found or not enabled.
+ }
+ setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID);
+ return true;
+ }
+ final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
+ final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
+ final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
+ new ArrayMap<>();
+ AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
+ queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
+ methodMap, methodList);
+ final InputMethodSettings settings = new InputMethodSettings(
+ mContext.getResources(), mContext.getContentResolver(), methodMap,
+ userId, false);
+ if (!methodMap.containsKey(imeId)
+ || !settings.getEnabledInputMethodListLocked()
+ .contains(methodMap.get(imeId))) {
+ return false; // IME is not is found or not enabled.
+ }
+ settings.putSelectedInputMethod(imeId);
+ settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
+ return true;
}
}
@@ -4510,9 +4584,14 @@
}
@Override
- public void onCreateInlineSuggestionsRequest(ComponentName componentName,
+ public void onCreateInlineSuggestionsRequest(int userId, ComponentName componentName,
AutofillId autofillId, IInlineSuggestionsRequestCallback cb) {
- mService.onCreateInlineSuggestionsRequest(componentName, autofillId, cb);
+ mService.onCreateInlineSuggestionsRequest(userId, componentName, autofillId, cb);
+ }
+
+ @Override
+ public boolean switchToInputMethod(String imeId, int userId) {
+ return mService.switchToInputMethod(imeId, userId);
}
}
@@ -5065,31 +5144,7 @@
if (!userHasDebugPriv(userId, shellCommand)) {
continue;
}
- boolean failedToSelectUnknownIme = false;
- if (userId == mSettings.getCurrentUserId()) {
- if (mMethodMap.containsKey(imeId)) {
- setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID);
- } else {
- failedToSelectUnknownIme = true;
- }
- } else {
- final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>();
- final ArrayList<InputMethodInfo> methodList = new ArrayList<>();
- final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
- new ArrayMap<>();
- AdditionalSubtypeUtils.load(additionalSubtypeMap, userId);
- queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap,
- methodMap, methodList);
- final InputMethodSettings settings = new InputMethodSettings(
- mContext.getResources(), mContext.getContentResolver(), methodMap,
- userId, false);
- if (methodMap.containsKey(imeId)) {
- settings.putSelectedInputMethod(imeId);
- settings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
- } else {
- failedToSelectUnknownIme = true;
- }
- }
+ boolean failedToSelectUnknownIme = !switchToInputMethod(imeId, userId);
if (failedToSelectUnknownIme) {
error.print("Unknown input method ");
error.print(imeId);
diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
index 1f9379c..f09795f 100644
--- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java
@@ -191,8 +191,9 @@
}
@Override
- public void onCreateInlineSuggestionsRequest(ComponentName componentName,
- AutofillId autofillId, IInlineSuggestionsRequestCallback cb) {
+ public void onCreateInlineSuggestionsRequest(int userId,
+ ComponentName componentName, AutofillId autofillId,
+ IInlineSuggestionsRequestCallback cb) {
try {
//TODO(b/137800469): support multi client IMEs.
cb.onInlineSuggestionsUnsupported();
@@ -200,6 +201,12 @@
Slog.w(TAG, "Failed to call onInlineSuggestionsUnsupported.", e);
}
}
+
+ @Override
+ public boolean switchToInputMethod(String imeId, @UserIdInt int userId) {
+ reportNotSupported();
+ return false;
+ }
});
}
diff --git a/services/core/java/com/android/server/integrity/IntegrityFileManager.java b/services/core/java/com/android/server/integrity/IntegrityFileManager.java
index 30cafaa..fffe7d9 100644
--- a/services/core/java/com/android/server/integrity/IntegrityFileManager.java
+++ b/services/core/java/com/android/server/integrity/IntegrityFileManager.java
@@ -24,7 +24,10 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.integrity.model.RuleMetadata;
+import com.android.server.integrity.parser.RandomAccessObject;
import com.android.server.integrity.parser.RuleBinaryParser;
+import com.android.server.integrity.parser.RuleIndexRange;
+import com.android.server.integrity.parser.RuleIndexingController;
import com.android.server.integrity.parser.RuleMetadataParser;
import com.android.server.integrity.parser.RuleParseException;
import com.android.server.integrity.parser.RuleParser;
@@ -37,6 +40,7 @@
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.util.Collections;
import java.util.List;
import java.util.Optional;
@@ -44,12 +48,9 @@
public class IntegrityFileManager {
private static final String TAG = "IntegrityFileManager";
- // TODO: this is a prototype implementation of this class. Thus no tests are included.
- // Implementing rule indexing will likely overhaul this class and more tests should be included
- // then.
-
private static final String METADATA_FILE = "metadata";
private static final String RULES_FILE = "rules";
+ private static final String INDEXING_FILE = "indexing";
private static final Object RULES_LOCK = new Object();
private static IntegrityFileManager sInstance = null;
@@ -65,6 +66,7 @@
private final File mStagingDir;
@Nullable private RuleMetadata mRuleMetadataCache;
+ @Nullable private RuleIndexingController mRuleIndexingController;
/** Get the singleton instance of this class. */
public static synchronized IntegrityFileManager getInstance() {
@@ -103,6 +105,8 @@
Slog.e(TAG, "Error reading metadata file.", e);
}
}
+
+ updateRuleIndexingController();
}
/**
@@ -112,7 +116,8 @@
*/
public boolean initialized() {
return new File(mRulesDir, RULES_FILE).exists()
- && new File(mRulesDir, METADATA_FILE).exists();
+ && new File(mRulesDir, METADATA_FILE).exists()
+ && new File(mRulesDir, INDEXING_FILE).exists();
}
/** Write rules to persistent storage. */
@@ -125,12 +130,18 @@
// We don't consider this fatal so we continue execution.
}
- try (FileOutputStream fileOutputStream =
- new FileOutputStream(new File(mStagingDir, RULES_FILE))) {
- mRuleSerializer.serialize(rules, Optional.empty(), fileOutputStream);
+ try (FileOutputStream ruleFileOutputStream =
+ new FileOutputStream(new File(mStagingDir, RULES_FILE));
+ FileOutputStream indexingFileOutputStream =
+ new FileOutputStream(new File(mStagingDir, INDEXING_FILE))) {
+ mRuleSerializer.serialize(
+ rules, Optional.empty(), ruleFileOutputStream, indexingFileOutputStream);
}
switchStagingRulesDir();
+
+ // Update object holding the indexing information.
+ updateRuleIndexingController();
}
/**
@@ -140,13 +151,22 @@
*/
public List<Rule> readRules(AppInstallMetadata appInstallMetadata)
throws IOException, RuleParseException {
- // TODO: select rules by index
synchronized (RULES_LOCK) {
- try (FileInputStream inputStream =
- new FileInputStream(new File(mRulesDir, RULES_FILE))) {
- List<Rule> rules = mRuleParser.parse(inputStream);
- return rules;
+ // Try to identify indexes from the index file.
+ List<RuleIndexRange> ruleReadingIndexes;
+ try {
+ ruleReadingIndexes =
+ mRuleIndexingController.identifyRulesToEvaluate(appInstallMetadata);
+ } catch (Exception e) {
+ Slog.w(TAG, "Error identifying the rule indexes. Trying unindexed.", e);
+ ruleReadingIndexes = Collections.emptyList();
}
+
+ // Read the rules based on the index information when available.
+ File ruleFile = new File(mRulesDir, RULES_FILE);
+ List<Rule> rules =
+ mRuleParser.parse(RandomAccessObject.ofFile(ruleFile), ruleReadingIndexes);
+ return rules;
}
}
@@ -165,6 +185,21 @@
&& tmpDir.renameTo(mStagingDir))) {
throw new IOException("Error switching staging/rules directory");
}
+
+ for (File file : mStagingDir.listFiles()) {
+ file.delete();
+ }
+ }
+ }
+
+ private void updateRuleIndexingController() {
+ File ruleIndexingFile = new File(mRulesDir, INDEXING_FILE);
+ if (ruleIndexingFile.exists()) {
+ try (FileInputStream inputStream = new FileInputStream(ruleIndexingFile)) {
+ mRuleIndexingController = new RuleIndexingController(inputStream);
+ } catch (Exception e) {
+ Slog.e(TAG, "Error parsing the rule indexing file.", e);
+ }
}
}
diff --git a/services/core/java/com/android/server/integrity/model/BitInputStream.java b/services/core/java/com/android/server/integrity/model/BitInputStream.java
index e768fe6..e7cc81e 100644
--- a/services/core/java/com/android/server/integrity/model/BitInputStream.java
+++ b/services/core/java/com/android/server/integrity/model/BitInputStream.java
@@ -19,26 +19,21 @@
import java.io.IOException;
import java.io.InputStream;
-/** A wrapper class for reading a stream of bits. */
+/** A wrapper class for reading a stream of bits.
+ *
+ * <p>Note: this class reads from underlying stream byte-by-byte. It is advised to apply buffering
+ * to underlying streams.
+ */
public class BitInputStream {
- private long mBitPointer;
- private boolean mReadFromStream;
+ private long mBitsRead;
- private byte[] mRuleBytes;
- private InputStream mRuleInputStream;
+ private InputStream mInputStream;
- private byte mCurrentRuleByte;
+ private byte mCurrentByte;
- public BitInputStream(byte[] ruleBytes) {
- this.mRuleBytes = ruleBytes;
- this.mBitPointer = 0;
- this.mReadFromStream = false;
- }
-
- public BitInputStream(InputStream ruleInputStream) {
- this.mRuleInputStream = ruleInputStream;
- this.mReadFromStream = true;
+ public BitInputStream(InputStream inputStream) {
+ mInputStream = inputStream;
}
/**
@@ -52,15 +47,15 @@
int count = 0;
while (count++ < numOfBits) {
- if (mBitPointer % 8 == 0) {
- mCurrentRuleByte = getNextByte();
+ if (mBitsRead % 8 == 0) {
+ mCurrentByte = getNextByte();
}
- int offset = 7 - (int) (mBitPointer % 8);
+ int offset = 7 - (int) (mBitsRead % 8);
component <<= 1;
- component |= (mCurrentRuleByte >>> offset) & 1;
+ component |= (mCurrentByte >>> offset) & 1;
- mBitPointer++;
+ mBitsRead++;
}
return component;
@@ -68,22 +63,10 @@
/** Check if there are bits left in the stream. */
public boolean hasNext() throws IOException {
- if (mReadFromStream) {
- return mRuleInputStream.available() > 0;
- } else {
- return mBitPointer / 8 < mRuleBytes.length;
- }
+ return mInputStream.available() > 0;
}
private byte getNextByte() throws IOException {
- if (mReadFromStream) {
- return (byte) mRuleInputStream.read();
- } else {
- int idx = (int) (mBitPointer / 8);
- if (idx >= mRuleBytes.length) {
- throw new IllegalArgumentException(String.format("Invalid byte index: %d", idx));
- }
- return mRuleBytes[idx];
- }
+ return (byte) mInputStream.read();
}
}
diff --git a/services/core/java/com/android/server/integrity/model/BitOutputStream.java b/services/core/java/com/android/server/integrity/model/BitOutputStream.java
index b8ea041..7d1bb3f 100644
--- a/services/core/java/com/android/server/integrity/model/BitOutputStream.java
+++ b/services/core/java/com/android/server/integrity/model/BitOutputStream.java
@@ -16,17 +16,26 @@
package com.android.server.integrity.model;
-import java.util.BitSet;
+import static com.android.server.integrity.model.ComponentBitSize.BYTE_BITS;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Arrays;
/** A wrapper class for writing a stream of bits. */
public class BitOutputStream {
- private BitSet mBitSet;
- private int mIndex;
+ private static final int BUFFER_SIZE = 4 * 1024;
- public BitOutputStream() {
- mBitSet = new BitSet();
- mIndex = 0;
+ private int mNextBitIndex;
+
+ private final OutputStream mOutputStream;
+ private final byte[] mBuffer;
+
+ public BitOutputStream(OutputStream outputStream) {
+ mBuffer = new byte[BUFFER_SIZE];
+ mNextBitIndex = 0;
+ mOutputStream = outputStream;
}
/**
@@ -35,15 +44,17 @@
* @param numOfBits The number of bits used to represent the value.
* @param value The value to convert to bits.
*/
- public void setNext(int numOfBits, int value) {
+ public void setNext(int numOfBits, int value) throws IOException {
if (numOfBits <= 0) {
return;
}
- int offset = 1 << (numOfBits - 1);
+
+ // optional: we can do some clever size checking to "OR" an entire segment of bits instead
+ // of setting bits one by one, but it is probably not worth it.
+ int nextBitMask = 1 << (numOfBits - 1);
while (numOfBits-- > 0) {
- mBitSet.set(mIndex, (value & offset) != 0);
- offset >>>= 1;
- mIndex++;
+ setNext((value & nextBitMask) != 0);
+ nextBitMask >>>= 1;
}
}
@@ -52,35 +63,43 @@
*
* @param value The value to set the bit to.
*/
- public void setNext(boolean value) {
- mBitSet.set(mIndex, value);
- mIndex++;
+ public void setNext(boolean value) throws IOException {
+ int byteToWrite = mNextBitIndex / BYTE_BITS;
+ if (byteToWrite == BUFFER_SIZE) {
+ mOutputStream.write(mBuffer);
+ reset();
+ byteToWrite = 0;
+ }
+ if (value) {
+ mBuffer[byteToWrite] |= 1 << (BYTE_BITS - 1 - (mNextBitIndex % BYTE_BITS));
+ }
+ mNextBitIndex++;
}
/** Set the next bit in the stream to true. */
- public void setNext() {
+ public void setNext() throws IOException {
setNext(/* value= */ true);
}
- /** Convert BitSet in big-endian to ByteArray in big-endian. */
- public byte[] toByteArray() {
- int bitSetSize = mBitSet.length();
- int numOfBytes = bitSetSize / 8;
- if (bitSetSize % 8 != 0) {
- numOfBytes++;
+ /**
+ * Flush the data written to the underlying {@link java.io.OutputStream}. Any unfinished bytes
+ * will be padded with 0.
+ */
+ public void flush() throws IOException {
+ int endByte = mNextBitIndex / BYTE_BITS;
+ if (mNextBitIndex % BYTE_BITS != 0) {
+ // If next bit is not the first bit of a byte, then mNextBitIndex / BYTE_BITS would be
+ // the byte that includes already written bits. We need to increment it so this byte
+ // gets written.
+ endByte++;
}
- byte[] bytes = new byte[numOfBytes];
- for (int i = 0; i < mBitSet.length(); i++) {
- if (mBitSet.get(i)) {
- bytes[i / 8] |= 1 << (7 - (i % 8));
- }
- }
- return bytes;
+ mOutputStream.write(mBuffer, 0, endByte);
+ reset();
}
- /** Clear the stream. */
- public void clear() {
- mBitSet.clear();
- mIndex = 0;
+ /** Reset this output stream to start state. */
+ private void reset() {
+ mNextBitIndex = 0;
+ Arrays.fill(mBuffer, (byte) 0);
}
}
diff --git a/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java b/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java
new file mode 100644
index 0000000..ceed054
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/model/ByteTrackedOutputStream.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.integrity.model;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * An output stream that tracks the total number written bytes since construction and allows
+ * querying this value any time during the execution.
+ *
+ * <p>This class is used for constructing the rule indexing.
+ */
+public class ByteTrackedOutputStream extends OutputStream {
+
+ private int mWrittenBytesCount;
+ private final OutputStream mOutputStream;
+
+ public ByteTrackedOutputStream(OutputStream outputStream) {
+ mWrittenBytesCount = 0;
+ mOutputStream = outputStream;
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ mWrittenBytesCount++;
+ mOutputStream.write(b);
+ }
+
+ /**
+ * Writes the given bytes into the output stream provided in constructor and updates the total
+ * number of written bytes.
+ */
+ @Override
+ public void write(byte[] bytes) throws IOException {
+ write(bytes, 0, bytes.length);
+ }
+
+ @Override
+ public void write(byte[] b, int off, int len) throws IOException {
+ mWrittenBytesCount += len;
+ mOutputStream.write(b, off, len);
+ }
+
+ /** Returns the total number of bytes written into the output stream at the requested time. */
+ public int getWrittenBytesCount() {
+ return mWrittenBytesCount;
+ }
+}
diff --git a/services/core/java/com/android/server/integrity/model/ComponentBitSize.java b/services/core/java/com/android/server/integrity/model/ComponentBitSize.java
index 6ec2d5f..c389963 100644
--- a/services/core/java/com/android/server/integrity/model/ComponentBitSize.java
+++ b/services/core/java/com/android/server/integrity/model/ComponentBitSize.java
@@ -23,13 +23,14 @@
* components.
*/
public final class ComponentBitSize {
- public static final int FORMAT_VERSION_BITS = 5;
+ public static final int FORMAT_VERSION_BITS = 8;
+
public static final int EFFECT_BITS = 3;
public static final int KEY_BITS = 4;
public static final int OPERATOR_BITS = 3;
public static final int CONNECTOR_BITS = 2;
public static final int SEPARATOR_BITS = 2;
- public static final int VALUE_SIZE_BITS = 6;
+ public static final int VALUE_SIZE_BITS = 8;
public static final int IS_HASHED_BITS = 1;
public static final int ATOMIC_FORMULA_START = 0;
@@ -38,4 +39,6 @@
public static final int DEFAULT_FORMAT_VERSION = 1;
public static final int SIGNAL_BIT = 1;
+
+ public static final int BYTE_BITS = 8;
}
diff --git a/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java b/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java
new file mode 100644
index 0000000..0c4052a
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/model/IndexingFileConstants.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.integrity.model;
+
+/** A helper class containing special indexing file constants. */
+public final class IndexingFileConstants {
+ // We empirically experimented with different block sizes and identified that 50 is in the
+ // optimal range of efficient computation.
+ public static final int INDEXING_BLOCK_SIZE = 50;
+
+ public static final String START_INDEXING_KEY = "START_KEY";
+ public static final String END_INDEXING_KEY = "END_KEY";
+}
diff --git a/services/core/java/com/android/server/integrity/parser/BinaryFileOperations.java b/services/core/java/com/android/server/integrity/parser/BinaryFileOperations.java
new file mode 100644
index 0000000..2c5b7d3
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/parser/BinaryFileOperations.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.integrity.parser;
+
+import static com.android.server.integrity.model.ComponentBitSize.IS_HASHED_BITS;
+import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS;
+
+import com.android.server.integrity.IntegrityUtils;
+import com.android.server.integrity.model.BitInputStream;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+/**
+ * Helper methods for reading standard data structures from {@link BitInputStream}.
+ */
+public class BinaryFileOperations {
+
+ /**
+ * Read an string value with the given size and hash status from a {@code BitInputStream}.
+ *
+ * If the value is hashed, get the hex-encoding of the value. Serialized values are in raw form.
+ * All hashed values are hex-encoded.
+ */
+ public static String getStringValue(BitInputStream bitInputStream) throws IOException {
+ boolean isHashedValue = bitInputStream.getNext(IS_HASHED_BITS) == 1;
+ int valueSize = bitInputStream.getNext(VALUE_SIZE_BITS);
+ return getStringValue(bitInputStream, valueSize, isHashedValue);
+ }
+
+ /**
+ * Read an string value with the given size and hash status from a {@code BitInputStream}.
+ *
+ * If the value is hashed, get the hex-encoding of the value. Serialized values are in raw form.
+ * All hashed values are hex-encoded.
+ */
+ public static String getStringValue(
+ BitInputStream bitInputStream, int valueSize, boolean isHashedValue)
+ throws IOException {
+ if (!isHashedValue) {
+ StringBuilder value = new StringBuilder();
+ while (valueSize-- > 0) {
+ value.append((char) bitInputStream.getNext(/* numOfBits= */ 8));
+ }
+ return value.toString();
+ }
+ ByteBuffer byteBuffer = ByteBuffer.allocate(valueSize);
+ while (valueSize-- > 0) {
+ byteBuffer.put((byte) (bitInputStream.getNext(/* numOfBits= */ 8) & 0xFF));
+ }
+ return IntegrityUtils.getHexDigest(byteBuffer.array());
+ }
+
+ /** Read an integer value from a {@code BitInputStream}. */
+ public static int getIntValue(BitInputStream bitInputStream) throws IOException {
+ return bitInputStream.getNext(/* numOfBits= */ 32);
+ }
+
+ /** Read an boolean value from a {@code BitInputStream}. */
+ public static boolean getBooleanValue(BitInputStream bitInputStream) throws IOException {
+ return bitInputStream.getNext(/* numOfBits= */ 1) == 1;
+ }
+}
diff --git a/services/core/java/com/android/server/integrity/parser/LimitInputStream.java b/services/core/java/com/android/server/integrity/parser/LimitInputStream.java
new file mode 100644
index 0000000..a91bbb7
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/parser/LimitInputStream.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.integrity.parser;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/** An {@link InputStream} that basically truncates another {@link InputStream} */
+public class LimitInputStream extends FilterInputStream {
+ private int mReadBytes;
+ private final int mLimit;
+
+ public LimitInputStream(InputStream in, int limit) {
+ super(in);
+ if (limit < 0) {
+ throw new IllegalArgumentException("limit " + limit + " cannot be negative");
+ }
+ mReadBytes = 0;
+ mLimit = limit;
+ }
+
+ @Override
+ public int available() throws IOException {
+ return Math.min(super.available(), mLimit - mReadBytes);
+ }
+
+ @Override
+ public int read() throws IOException {
+ if (mReadBytes == mLimit) {
+ return -1;
+ }
+ mReadBytes++;
+ return super.read();
+ }
+
+ @Override
+ public int read(byte[] b) throws IOException {
+ return read(b, 0, b.length);
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ if (len <= 0) {
+ return 0;
+ }
+ int available = available();
+ if (available <= 0) {
+ return -1;
+ }
+ int result = super.read(b, off, Math.min(len, available));
+ mReadBytes += result;
+ return result;
+ }
+
+ @Override
+ public long skip(long n) throws IOException {
+ if (n <= 0) {
+ return 0;
+ }
+ int available = available();
+ if (available <= 0) {
+ return 0;
+ }
+ int bytesToSkip = (int) Math.min(available, n);
+ long bytesSkipped = super.skip(bytesToSkip);
+ mReadBytes += (int) bytesSkipped;
+ return bytesSkipped;
+ }
+}
diff --git a/services/core/java/com/android/server/integrity/parser/RandomAccessInputStream.java b/services/core/java/com/android/server/integrity/parser/RandomAccessInputStream.java
new file mode 100644
index 0000000..206e6a1
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/parser/RandomAccessInputStream.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.integrity.parser;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/** A wrapper around {@link RandomAccessObject} to turn it into a {@link InputStream}. */
+public class RandomAccessInputStream extends InputStream {
+
+ private final RandomAccessObject mRandomAccessObject;
+
+ private int mPosition;
+
+ public RandomAccessInputStream(RandomAccessObject object) throws IOException {
+ mRandomAccessObject = object;
+ mPosition = 0;
+ }
+
+ /** Returns the position of the file pointer. */
+ public int getPosition() {
+ return mPosition;
+ }
+
+ /** See {@link RandomAccessObject#seek(int)} */
+ public void seek(int position) throws IOException {
+ mRandomAccessObject.seek(position);
+ mPosition = position;
+ }
+
+ @Override
+ public int available() throws IOException {
+ return mRandomAccessObject.length() - mPosition;
+ }
+
+ @Override
+ public void close() throws IOException {
+ mRandomAccessObject.close();
+ }
+
+ @Override
+ public int read() throws IOException {
+ if (available() <= 0) {
+ return -1;
+ }
+ mPosition++;
+ return mRandomAccessObject.read();
+ }
+
+ @Override
+ public int read(byte[] b) throws IOException {
+ return read(b, 0, b.length);
+ }
+
+ @Override
+ public int read(byte[] b, int off, int len) throws IOException {
+ if (len <= 0) {
+ return 0;
+ }
+ int available = available();
+ if (available <= 0) {
+ return -1;
+ }
+ int result = mRandomAccessObject.read(b, off, Math.min(len, available));
+ mPosition += result;
+ return result;
+ }
+
+ @Override
+ public long skip(long n) throws IOException {
+ if (n <= 0) {
+ return 0;
+ }
+ int available = available();
+ if (available <= 0) {
+ return 0;
+ }
+ int skipAmount = (int) Math.min(available, n);
+ mPosition += skipAmount;
+ mRandomAccessObject.seek(mPosition);
+ return skipAmount;
+ }
+}
diff --git a/services/core/java/com/android/server/integrity/parser/RandomAccessObject.java b/services/core/java/com/android/server/integrity/parser/RandomAccessObject.java
new file mode 100644
index 0000000..d9b2e38
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/parser/RandomAccessObject.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.integrity.parser;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.ByteBuffer;
+
+/** An interface for random access objects like RandomAccessFile or byte arrays. */
+public abstract class RandomAccessObject {
+
+ /** See {@link RandomAccessFile#seek(long)}. */
+ public abstract void seek(int position) throws IOException;
+
+ /** See {@link RandomAccessFile#read()}. */
+ public abstract int read() throws IOException;
+
+ /** See {@link RandomAccessFile#read(byte[], int, int)}. */
+ public abstract int read(byte[] bytes, int off, int len) throws IOException;
+
+ /** See {@link RandomAccessFile#close()}. */
+ public abstract void close() throws IOException;
+
+ /** See {@link java.io.RandomAccessFile#length()}. */
+ public abstract int length();
+
+ /** Static constructor from a file. */
+ public static RandomAccessObject ofFile(File file) throws IOException {
+ return new RandomAccessFileObject(file);
+ }
+
+ /** Static constructor from a byte array. */
+ public static RandomAccessObject ofBytes(byte[] bytes) {
+ return new RandomAccessByteArrayObject(bytes);
+ }
+
+ private static class RandomAccessFileObject extends RandomAccessObject {
+ private final RandomAccessFile mRandomAccessFile;
+ // We cache the length since File.length() invokes file IO.
+ private final int mLength;
+
+ RandomAccessFileObject(File file) throws IOException {
+ long length = file.length();
+ if (length > Integer.MAX_VALUE) {
+ throw new IOException("Unsupported file size (too big) " + length);
+ }
+
+ mRandomAccessFile = new RandomAccessFile(file, /* mode= */ "r");
+ mLength = (int) length;
+ }
+
+ @Override
+ public void seek(int position) throws IOException {
+ mRandomAccessFile.seek(position);
+ }
+
+ @Override
+ public int read() throws IOException {
+ return mRandomAccessFile.read();
+ }
+
+ @Override
+ public int read(byte[] bytes, int off, int len) throws IOException {
+ return mRandomAccessFile.read(bytes, off, len);
+ }
+
+ @Override
+ public void close() throws IOException {
+ mRandomAccessFile.close();
+ }
+
+ @Override
+ public int length() {
+ return mLength;
+ }
+ }
+
+ private static class RandomAccessByteArrayObject extends RandomAccessObject {
+
+ private final ByteBuffer mBytes;
+
+ RandomAccessByteArrayObject(byte[] bytes) {
+ mBytes = ByteBuffer.wrap(bytes);
+ }
+
+ @Override
+ public void seek(int position) throws IOException {
+ mBytes.position(position);
+ }
+
+ @Override
+ public int read() throws IOException {
+ if (!mBytes.hasRemaining()) {
+ return -1;
+ }
+
+ return mBytes.get() & 0xFF;
+ }
+
+ @Override
+ public int read(byte[] bytes, int off, int len) throws IOException {
+ int bytesToCopy = Math.min(len, mBytes.remaining());
+ if (bytesToCopy <= 0) {
+ return 0;
+ }
+ mBytes.get(bytes, off, len);
+ return bytesToCopy;
+ }
+
+ @Override
+ public void close() throws IOException {}
+
+ @Override
+ public int length() {
+ return mBytes.capacity();
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java
index 8f84abc..90954ff 100644
--- a/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java
+++ b/services/core/java/com/android/server/integrity/parser/RuleBinaryParser.java
@@ -17,6 +17,7 @@
package com.android.server.integrity.parser;
import static com.android.server.integrity.model.ComponentBitSize.ATOMIC_FORMULA_START;
+import static com.android.server.integrity.model.ComponentBitSize.BYTE_BITS;
import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_END;
import static com.android.server.integrity.model.ComponentBitSize.COMPOUND_FORMULA_START;
import static com.android.server.integrity.model.ComponentBitSize.CONNECTOR_BITS;
@@ -28,55 +29,91 @@
import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS;
import static com.android.server.integrity.model.ComponentBitSize.SIGNAL_BIT;
import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS;
+import static com.android.server.integrity.parser.BinaryFileOperations.getBooleanValue;
+import static com.android.server.integrity.parser.BinaryFileOperations.getIntValue;
+import static com.android.server.integrity.parser.BinaryFileOperations.getStringValue;
import android.content.integrity.AtomicFormula;
import android.content.integrity.CompoundFormula;
import android.content.integrity.Formula;
import android.content.integrity.Rule;
-import com.android.server.integrity.IntegrityUtils;
import com.android.server.integrity.model.BitInputStream;
+import java.io.BufferedInputStream;
import java.io.IOException;
-import java.io.InputStream;
-import java.nio.ByteBuffer;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
/** A helper class to parse rules into the {@link Rule} model from Binary representation. */
public class RuleBinaryParser implements RuleParser {
- private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
-
@Override
public List<Rule> parse(byte[] ruleBytes) throws RuleParseException {
- try {
- BitInputStream bitInputStream = new BitInputStream(ruleBytes);
- return parseRules(bitInputStream);
- } catch (Exception e) {
- throw new RuleParseException(e.getMessage(), e);
- }
+ return parse(RandomAccessObject.ofBytes(ruleBytes), Collections.emptyList());
}
@Override
- public List<Rule> parse(InputStream inputStream) throws RuleParseException {
- try {
- BitInputStream bitInputStream = new BitInputStream(inputStream);
- return parseRules(bitInputStream);
+ public List<Rule> parse(RandomAccessObject randomAccessObject, List<RuleIndexRange> indexRanges)
+ throws RuleParseException {
+ try (RandomAccessInputStream randomAccessInputStream =
+ new RandomAccessInputStream(randomAccessObject)) {
+ return parseRules(randomAccessInputStream, indexRanges);
} catch (Exception e) {
throw new RuleParseException(e.getMessage(), e);
}
}
- private List<Rule> parseRules(BitInputStream bitInputStream) throws IOException {
- List<Rule> parsedRules = new ArrayList<>();
+ private List<Rule> parseRules(
+ RandomAccessInputStream randomAccessInputStream,
+ List<RuleIndexRange> indexRanges)
+ throws IOException {
// Read the rule binary file format version.
- bitInputStream.getNext(FORMAT_VERSION_BITS);
+ randomAccessInputStream.skip(FORMAT_VERSION_BITS / BYTE_BITS);
- while (bitInputStream.hasNext()) {
- if (bitInputStream.getNext(SIGNAL_BIT) == 1) {
- parsedRules.add(parseRule(bitInputStream));
+ return indexRanges.isEmpty()
+ ? parseAllRules(randomAccessInputStream)
+ : parseIndexedRules(randomAccessInputStream, indexRanges);
+ }
+
+ private List<Rule> parseAllRules(RandomAccessInputStream randomAccessInputStream)
+ throws IOException {
+ List<Rule> parsedRules = new ArrayList<>();
+
+ BitInputStream inputStream =
+ new BitInputStream(new BufferedInputStream(randomAccessInputStream));
+ while (inputStream.hasNext()) {
+ if (inputStream.getNext(SIGNAL_BIT) == 1) {
+ parsedRules.add(parseRule(inputStream));
+ }
+ }
+
+ return parsedRules;
+ }
+
+ private List<Rule> parseIndexedRules(
+ RandomAccessInputStream randomAccessInputStream,
+ List<RuleIndexRange> indexRanges)
+ throws IOException {
+ List<Rule> parsedRules = new ArrayList<>();
+
+ for (RuleIndexRange range : indexRanges) {
+ randomAccessInputStream.seek(range.getStartIndex());
+
+ BitInputStream inputStream =
+ new BitInputStream(
+ new BufferedInputStream(
+ new LimitInputStream(
+ randomAccessInputStream,
+ range.getEndIndex() - range.getStartIndex())));
+
+ // Read the rules until we reach the end index. available() here is not reliable.
+ while (inputStream.hasNext()) {
+ if (inputStream.getNext(SIGNAL_BIT) == 1) {
+ parsedRules.add(parseRule(inputStream));
+ }
}
}
@@ -145,33 +182,4 @@
throw new IllegalArgumentException(String.format("Unknown key: %d", key));
}
}
-
- // Get value string from stream.
- // If the value is not hashed, get its raw form directly.
- // If the value is hashed, get the hex-encoding of the value. Serialized values are in raw form.
- // All hashed values are hex-encoded.
- private static String getStringValue(
- BitInputStream bitInputStream, int valueSize, boolean isHashedValue)
- throws IOException {
- if (!isHashedValue) {
- StringBuilder value = new StringBuilder();
- while (valueSize-- > 0) {
- value.append((char) bitInputStream.getNext(/* numOfBits= */ 8));
- }
- return value.toString();
- }
- ByteBuffer byteBuffer = ByteBuffer.allocate(valueSize);
- while (valueSize-- > 0) {
- byteBuffer.put((byte) (bitInputStream.getNext(/* numOfBits= */ 8) & 0xFF));
- }
- return IntegrityUtils.getHexDigest(byteBuffer.array());
- }
-
- private static int getIntValue(BitInputStream bitInputStream) throws IOException {
- return bitInputStream.getNext(/* numOfBits= */ 32);
- }
-
- private static boolean getBooleanValue(BitInputStream bitInputStream) throws IOException {
- return bitInputStream.getNext(/* numOfBits= */ 1) == 1;
- }
}
diff --git a/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java b/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java
new file mode 100644
index 0000000..595a035
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/parser/RuleIndexRange.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.integrity.parser;
+
+import android.annotation.Nullable;
+
+/**
+ * A wrapper class to represent an indexing range that is identified by the {@link
+ * RuleIndexingController}.
+ */
+public class RuleIndexRange {
+ private int mStartIndex;
+ private int mEndIndex;
+
+ /** Constructor with start and end indexes. */
+ public RuleIndexRange(int startIndex, int endIndex) {
+ this.mStartIndex = startIndex;
+ this.mEndIndex = endIndex;
+ }
+
+ /** Returns the startIndex. */
+ public int getStartIndex() {
+ return mStartIndex;
+ }
+
+ /** Returns the end index. */
+ public int getEndIndex() {
+ return mEndIndex;
+ }
+
+ @Override
+ public boolean equals(@Nullable Object object) {
+ return mStartIndex == ((RuleIndexRange) object).getStartIndex()
+ && mEndIndex == ((RuleIndexRange) object).getEndIndex();
+ }
+
+ @Override
+ public String toString() {
+ return String.format("Range{%d, %d}", mStartIndex, mEndIndex);
+ }
+}
diff --git a/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java b/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java
new file mode 100644
index 0000000..87eee4e
--- /dev/null
+++ b/services/core/java/com/android/server/integrity/parser/RuleIndexingController.java
@@ -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.server.integrity.parser;
+
+import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY;
+import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY;
+import static com.android.server.integrity.parser.BinaryFileOperations.getIntValue;
+import static com.android.server.integrity.parser.BinaryFileOperations.getStringValue;
+
+import android.content.integrity.AppInstallMetadata;
+
+import com.android.server.integrity.model.BitInputStream;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/** Helper class to identify the necessary indexes that needs to be read. */
+public class RuleIndexingController {
+
+ private static LinkedHashMap<String, Integer> sPackageNameBasedIndexes;
+ private static LinkedHashMap<String, Integer> sAppCertificateBasedIndexes;
+ private static LinkedHashMap<String, Integer> sUnindexedRuleIndexes;
+
+ /**
+ * Provide the indexing file to read and the object will be constructed by reading and
+ * identifying the indexes.
+ */
+ public RuleIndexingController(InputStream inputStream) throws IOException {
+ BitInputStream bitInputStream = new BitInputStream(inputStream);
+ sPackageNameBasedIndexes = getNextIndexGroup(bitInputStream);
+ sAppCertificateBasedIndexes = getNextIndexGroup(bitInputStream);
+ sUnindexedRuleIndexes = getNextIndexGroup(bitInputStream);
+ }
+
+ /**
+ * Returns a list of integers with the starting and ending bytes of the rules that needs to be
+ * read and evaluated.
+ */
+ public List<RuleIndexRange> identifyRulesToEvaluate(AppInstallMetadata appInstallMetadata) {
+ List<RuleIndexRange> indexRanges = new ArrayList<>();
+
+ // Add the range for package name indexes rules.
+ indexRanges.add(
+ searchIndexingKeysRangeContainingKey(
+ sPackageNameBasedIndexes, appInstallMetadata.getPackageName()));
+
+ // Add the range for app certificate indexes rules.
+ indexRanges.add(
+ searchIndexingKeysRangeContainingKey(
+ sAppCertificateBasedIndexes, appInstallMetadata.getAppCertificate()));
+
+ // Add the range for unindexed rules.
+ indexRanges.add(
+ new RuleIndexRange(
+ sUnindexedRuleIndexes.get(START_INDEXING_KEY),
+ sUnindexedRuleIndexes.get(END_INDEXING_KEY)));
+
+ return indexRanges;
+ }
+
+ private LinkedHashMap<String, Integer> getNextIndexGroup(BitInputStream bitInputStream)
+ throws IOException {
+ LinkedHashMap<String, Integer> keyToIndexMap = new LinkedHashMap<>();
+ while (bitInputStream.hasNext()) {
+ String key = getStringValue(bitInputStream);
+ int value = getIntValue(bitInputStream);
+
+ keyToIndexMap.put(key, value);
+
+ if (key.matches(END_INDEXING_KEY)) {
+ break;
+ }
+ }
+ return keyToIndexMap;
+ }
+
+ private static RuleIndexRange searchIndexingKeysRangeContainingKey(
+ LinkedHashMap<String, Integer> indexMap, String searchedKey) {
+ List<String> keys = indexMap.keySet().stream().collect(Collectors.toList());
+ List<String> identifiedKeyRange =
+ searchKeysRangeContainingKey(keys, searchedKey, 0, keys.size() - 1);
+ return new RuleIndexRange(
+ indexMap.get(identifiedKeyRange.get(0)), indexMap.get(identifiedKeyRange.get(1)));
+ }
+
+ private static List<String> searchKeysRangeContainingKey(
+ List<String> sortedKeyList, String key, int startIndex, int endIndex) {
+ if (endIndex - startIndex == 1) {
+ return Arrays.asList(sortedKeyList.get(startIndex), sortedKeyList.get(endIndex));
+ }
+
+ int midKeyIndex = startIndex + ((endIndex - startIndex) / 2);
+ String midKey = sortedKeyList.get(midKeyIndex);
+
+ if (key.compareTo(midKey) >= 0) {
+ return searchKeysRangeContainingKey(sortedKeyList, key, midKeyIndex, endIndex);
+ } else {
+ return searchKeysRangeContainingKey(sortedKeyList, key, startIndex, midKeyIndex);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/integrity/parser/RuleParser.java b/services/core/java/com/android/server/integrity/parser/RuleParser.java
index 81783d5..126dacc 100644
--- a/services/core/java/com/android/server/integrity/parser/RuleParser.java
+++ b/services/core/java/com/android/server/integrity/parser/RuleParser.java
@@ -18,7 +18,6 @@
import android.content.integrity.Rule;
-import java.io.InputStream;
import java.util.List;
/** A helper class to parse rules into the {@link Rule} model. */
@@ -28,5 +27,6 @@
List<Rule> parse(byte[] ruleBytes) throws RuleParseException;
/** Parse rules from an input stream. */
- List<Rule> parse(InputStream inputStream) throws RuleParseException;
+ List<Rule> parse(RandomAccessObject randomAccessObject, List<RuleIndexRange> ruleIndexRanges)
+ throws RuleParseException;
}
diff --git a/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java b/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java
index d405583..53b0c2e 100644
--- a/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java
+++ b/services/core/java/com/android/server/integrity/parser/RuleXmlParser.java
@@ -26,7 +26,6 @@
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
-import java.io.InputStream;
import java.io.StringReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
@@ -62,10 +61,13 @@
}
@Override
- public List<Rule> parse(InputStream inputStream) throws RuleParseException {
+ public List<Rule> parse(RandomAccessObject randomAccessObject, List<RuleIndexRange> indexRanges)
+ throws RuleParseException {
try {
XmlPullParser xmlPullParser = Xml.newPullParser();
- xmlPullParser.setInput(inputStream, StandardCharsets.UTF_8.name());
+ xmlPullParser.setInput(
+ new RandomAccessInputStream(randomAccessObject),
+ StandardCharsets.UTF_8.name());
return parseRules(xmlPullParser);
} catch (Exception e) {
throw new RuleParseException(e.getMessage(), e);
diff --git a/services/core/java/com/android/server/integrity/serializer/ByteTrackedOutputStream.java b/services/core/java/com/android/server/integrity/serializer/ByteTrackedOutputStream.java
deleted file mode 100644
index c8d318f..0000000
--- a/services/core/java/com/android/server/integrity/serializer/ByteTrackedOutputStream.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.integrity.serializer;
-
-import java.io.IOException;
-import java.io.OutputStream;
-
-/**
- * An output stream that tracks the total number written bytes since construction and allows
- * querying this value any time during the execution.
- *
- * This class is used for constructing the rule indexing.
- */
-public class ByteTrackedOutputStream {
-
- private static long sWrittenBytesCount;
- private static OutputStream sOutputStream;
-
- public ByteTrackedOutputStream(OutputStream outputStream) {
- sWrittenBytesCount = 0;
- sOutputStream = outputStream;
- }
-
- /**
- * Writes the given bytes into the output stream provided in constructor and updates the
- * total number of written bytes.
- */
- public void write(byte[] bytes) throws IOException {
- sWrittenBytesCount += bytes.length;
- sOutputStream.write(bytes);
- }
-
- /**
- * Returns the total number of bytes written into the output stream at the requested time.
- */
- public long getWrittenBytesCount() {
- return sWrittenBytesCount;
- }
-}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
index 22af085..6afadba 100644
--- a/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
+++ b/services/core/java/com/android/server/integrity/serializer/RuleBinarySerializer.java
@@ -27,6 +27,9 @@
import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS;
import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS;
import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS;
+import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY;
+import static com.android.server.integrity.model.IndexingFileConstants.INDEXING_BLOCK_SIZE;
+import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY;
import static com.android.server.integrity.serializer.RuleIndexingDetails.APP_CERTIFICATE_INDEXED;
import static com.android.server.integrity.serializer.RuleIndexingDetails.NOT_INDEXED;
import static com.android.server.integrity.serializer.RuleIndexingDetails.PACKAGE_NAME_INDEXED;
@@ -39,33 +42,29 @@
import com.android.internal.util.Preconditions;
import com.android.server.integrity.IntegrityUtils;
import com.android.server.integrity.model.BitOutputStream;
+import com.android.server.integrity.model.ByteTrackedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
-import java.util.TreeMap;
+import java.util.stream.Collectors;
/** A helper class to serialize rules from the {@link Rule} model to Binary representation. */
public class RuleBinarySerializer implements RuleSerializer {
- // The parsing time seems acceptable for 100 rules based on the tests in go/ic-rule-file-format.
- private static final int INDEXING_BLOCK_SIZE = 100;
-
- private static final String START_INDEXING_KEY = "START_KEY";
- private static final String END_INDEXING_KEY = "END_KEY";
-
// Get the byte representation for a list of rules.
@Override
public byte[] serialize(List<Rule> rules, Optional<Integer> formatVersion)
throws RuleSerializeException {
try {
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
- serialize(rules, formatVersion, byteArrayOutputStream);
- return byteArrayOutputStream.toByteArray();
+ ByteArrayOutputStream rulesOutputStream = new ByteArrayOutputStream();
+ serialize(rules, formatVersion, rulesOutputStream, new ByteArrayOutputStream());
+ return rulesOutputStream.toByteArray();
} catch (Exception e) {
throw new RuleSerializeException(e.getMessage(), e);
}
@@ -74,62 +73,76 @@
// Get the byte representation for a list of rules, and write them to an output stream.
@Override
public void serialize(
- List<Rule> rules, Optional<Integer> formatVersion, OutputStream originalOutputStream)
+ List<Rule> rules,
+ Optional<Integer> formatVersion,
+ OutputStream rulesFileOutputStream,
+ OutputStream indexingFileOutputStream)
throws RuleSerializeException {
try {
// Determine the indexing groups and the order of the rules within each indexed group.
- Map<Integer, TreeMap<String, List<Rule>>> indexedRules =
+ Map<Integer, Map<String, List<Rule>>> indexedRules =
RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets(rules);
- ByteTrackedOutputStream outputStream =
- new ByteTrackedOutputStream(originalOutputStream);
+ // Serialize the rules.
+ ByteTrackedOutputStream ruleFileByteTrackedOutputStream =
+ new ByteTrackedOutputStream(rulesFileOutputStream);
+ serializeRuleFileMetadata(formatVersion, ruleFileByteTrackedOutputStream);
+ LinkedHashMap<String, Integer> packageNameIndexes =
+ serializeRuleList(
+ indexedRules.get(PACKAGE_NAME_INDEXED),
+ ruleFileByteTrackedOutputStream);
+ LinkedHashMap<String, Integer> appCertificateIndexes =
+ serializeRuleList(
+ indexedRules.get(APP_CERTIFICATE_INDEXED),
+ ruleFileByteTrackedOutputStream);
+ LinkedHashMap<String, Integer> unindexedRulesIndexes =
+ serializeRuleList(
+ indexedRules.get(NOT_INDEXED), ruleFileByteTrackedOutputStream);
- serializeRuleFileMetadata(formatVersion, outputStream);
-
- Map<String, Long> packageNameIndexes =
- serializeRuleList(indexedRules.get(PACKAGE_NAME_INDEXED), outputStream);
- Map<String, Long> appCertificateIndexes =
- serializeRuleList(indexedRules.get(APP_CERTIFICATE_INDEXED), outputStream);
- Map<String, Long> unindexedRulesIndex =
- serializeRuleList(indexedRules.get(NOT_INDEXED), outputStream);
-
- // TODO(b/145493956): Write these indexes into a index file provided by integrity file
- // manager.
+ // Serialize their indexes.
+ BitOutputStream indexingBitOutputStream = new BitOutputStream(indexingFileOutputStream);
+ serializeIndexGroup(packageNameIndexes, indexingBitOutputStream, /* isIndexed= */ true);
+ serializeIndexGroup(
+ appCertificateIndexes, indexingBitOutputStream, /* isIndexed= */ true);
+ serializeIndexGroup(
+ unindexedRulesIndexes, indexingBitOutputStream, /* isIndexed= */ false);
+ indexingBitOutputStream.flush();
} catch (Exception e) {
throw new RuleSerializeException(e.getMessage(), e);
}
}
- private void serializeRuleFileMetadata(Optional<Integer> formatVersion,
- ByteTrackedOutputStream outputStream) throws IOException {
+ private void serializeRuleFileMetadata(
+ Optional<Integer> formatVersion, ByteTrackedOutputStream outputStream)
+ throws IOException {
int formatVersionValue = formatVersion.orElse(DEFAULT_FORMAT_VERSION);
- BitOutputStream bitOutputStream = new BitOutputStream();
+ BitOutputStream bitOutputStream = new BitOutputStream(outputStream);
bitOutputStream.setNext(FORMAT_VERSION_BITS, formatVersionValue);
- outputStream.write(bitOutputStream.toByteArray());
+ bitOutputStream.flush();
}
- private Map<String, Long> serializeRuleList(TreeMap<String, List<Rule>> rulesMap,
- ByteTrackedOutputStream outputStream)
+ private LinkedHashMap<String, Integer> serializeRuleList(
+ Map<String, List<Rule>> rulesMap, ByteTrackedOutputStream outputStream)
throws IOException {
- Preconditions.checkArgument(rulesMap != null,
- "serializeRuleList should never be called with null rule list.");
+ Preconditions.checkArgument(
+ rulesMap != null, "serializeRuleList should never be called with null rule list.");
- BitOutputStream bitOutputStream = new BitOutputStream();
- Map<String, Long> indexMapping = new TreeMap();
- long indexTracker = 0;
-
+ BitOutputStream bitOutputStream = new BitOutputStream(outputStream);
+ LinkedHashMap<String, Integer> indexMapping = new LinkedHashMap();
indexMapping.put(START_INDEXING_KEY, outputStream.getWrittenBytesCount());
- for (Map.Entry<String, List<Rule>> entry : rulesMap.entrySet()) {
+
+ List<String> sortedKeys = rulesMap.keySet().stream().sorted().collect(Collectors.toList());
+ int indexTracker = 0;
+ for (String key : sortedKeys) {
if (indexTracker >= INDEXING_BLOCK_SIZE) {
- indexMapping.put(entry.getKey(), outputStream.getWrittenBytesCount());
+ indexMapping.put(key, outputStream.getWrittenBytesCount());
indexTracker = 0;
}
- for (Rule rule : entry.getValue()) {
- bitOutputStream.clear();
+ for (Rule rule : rulesMap.get(key)) {
serializeRule(rule, bitOutputStream);
- outputStream.write(bitOutputStream.toByteArray());
+ bitOutputStream.flush();
indexTracker++;
}
}
@@ -138,7 +151,7 @@
return indexMapping;
}
- private void serializeRule(Rule rule, BitOutputStream bitOutputStream) {
+ private void serializeRule(Rule rule, BitOutputStream bitOutputStream) throws IOException {
if (rule == null) {
throw new IllegalArgumentException("Null rule can not be serialized");
}
@@ -153,7 +166,8 @@
bitOutputStream.setNext();
}
- private void serializeFormula(Formula formula, BitOutputStream bitOutputStream) {
+ private void serializeFormula(Formula formula, BitOutputStream bitOutputStream)
+ throws IOException {
if (formula instanceof AtomicFormula) {
serializeAtomicFormula((AtomicFormula) formula, bitOutputStream);
} else if (formula instanceof CompoundFormula) {
@@ -165,7 +179,7 @@
}
private void serializeCompoundFormula(
- CompoundFormula compoundFormula, BitOutputStream bitOutputStream) {
+ CompoundFormula compoundFormula, BitOutputStream bitOutputStream) throws IOException {
if (compoundFormula == null) {
throw new IllegalArgumentException("Null compound formula can not be serialized");
}
@@ -179,7 +193,7 @@
}
private void serializeAtomicFormula(
- AtomicFormula atomicFormula, BitOutputStream bitOutputStream) {
+ AtomicFormula atomicFormula, BitOutputStream bitOutputStream) throws IOException {
if (atomicFormula == null) {
throw new IllegalArgumentException("Null atomic formula can not be serialized");
}
@@ -210,8 +224,35 @@
}
}
+ private void serializeIndexGroup(
+ LinkedHashMap<String, Integer> indexes,
+ BitOutputStream bitOutputStream,
+ boolean isIndexed)
+ throws IOException {
+ // Output the starting location of this indexing group.
+ serializeStringValue(START_INDEXING_KEY, /* isHashedValue= */ false, bitOutputStream);
+ serializeIntValue(indexes.get(START_INDEXING_KEY), bitOutputStream);
+
+ // If the group is indexed, output the locations of the indexes.
+ if (isIndexed) {
+ for (Map.Entry<String, Integer> entry : indexes.entrySet()) {
+ if (!entry.getKey().equals(START_INDEXING_KEY)
+ && !entry.getKey().equals(END_INDEXING_KEY)) {
+ serializeStringValue(
+ entry.getKey(), /* isHashedValue= */ false, bitOutputStream);
+ serializeIntValue(entry.getValue(), bitOutputStream);
+ }
+ }
+ }
+
+ // Output the end location of this indexing group.
+ serializeStringValue(END_INDEXING_KEY, /*isHashedValue= */ false, bitOutputStream);
+ serializeIntValue(indexes.get(END_INDEXING_KEY), bitOutputStream);
+ }
+
private void serializeStringValue(
- String value, boolean isHashedValue, BitOutputStream bitOutputStream) {
+ String value, boolean isHashedValue, BitOutputStream bitOutputStream)
+ throws IOException {
if (value == null) {
throw new IllegalArgumentException("String value can not be null.");
}
@@ -224,11 +265,12 @@
}
}
- private void serializeIntValue(int value, BitOutputStream bitOutputStream) {
+ private void serializeIntValue(int value, BitOutputStream bitOutputStream) throws IOException {
bitOutputStream.setNext(/* numOfBits= */ 32, value);
}
- private void serializeBooleanValue(boolean value, BitOutputStream bitOutputStream) {
+ private void serializeBooleanValue(boolean value, BitOutputStream bitOutputStream)
+ throws IOException {
bitOutputStream.setNext(value);
}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java
index dd871e2..2cbd4ede 100644
--- a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java
+++ b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetails.java
@@ -28,6 +28,8 @@
static final int PACKAGE_NAME_INDEXED = 1;
static final int APP_CERTIFICATE_INDEXED = 2;
+ static final String DEFAULT_RULE_KEY = "N/A";
+
/** Represents which indexed file the rule should be located. */
@IntDef(
value = {
@@ -45,7 +47,7 @@
/** Constructor without a ruleKey for {@code NOT_INDEXED}. */
RuleIndexingDetails(@IndexType int indexType) {
this.mIndexType = indexType;
- this.mRuleKey = null;
+ this.mRuleKey = DEFAULT_RULE_KEY;
}
/** Constructor with a ruleKey for indexed rules. */
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java
index cbc365e..7d9a901 100644
--- a/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java
+++ b/services/core/java/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifier.java
@@ -30,30 +30,27 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
-import java.util.TreeMap;
/** A helper class for identifying the indexing type and key of a given rule. */
class RuleIndexingDetailsIdentifier {
- private static final String DEFAULT_RULE_KEY = "N/A";
-
/**
* Splits a given rule list into three indexing categories. Each rule category is returned as a
* TreeMap that is sorted by their indexing keys -- where keys correspond to package name for
* PACKAGE_NAME_INDEXED rules, app certificate for APP_CERTIFICATE_INDEXED rules and N/A for
* NOT_INDEXED rules.
*/
- public static Map<Integer, TreeMap<String, List<Rule>>> splitRulesIntoIndexBuckets(
+ public static Map<Integer, Map<String, List<Rule>>> splitRulesIntoIndexBuckets(
List<Rule> rules) {
if (rules == null) {
throw new IllegalArgumentException(
"Index buckets cannot be created for null rule list.");
}
- Map<Integer, TreeMap<String, List<Rule>>> typeOrganizedRuleMap = new HashMap();
- typeOrganizedRuleMap.put(NOT_INDEXED, new TreeMap());
- typeOrganizedRuleMap.put(PACKAGE_NAME_INDEXED, new TreeMap());
- typeOrganizedRuleMap.put(APP_CERTIFICATE_INDEXED, new TreeMap());
+ Map<Integer, Map<String, List<Rule>>> typeOrganizedRuleMap = new HashMap();
+ typeOrganizedRuleMap.put(NOT_INDEXED, new HashMap());
+ typeOrganizedRuleMap.put(PACKAGE_NAME_INDEXED, new HashMap<>());
+ typeOrganizedRuleMap.put(APP_CERTIFICATE_INDEXED, new HashMap<>());
// Split the rules into the appropriate indexed pattern. The Tree Maps help us to keep the
// entries sorted by their index key.
@@ -66,21 +63,14 @@
String.format("Malformed rule identified. [%s]", rule.toString()));
}
- String ruleKey =
- indexingDetails.getIndexType() != NOT_INDEXED
- ? indexingDetails.getRuleKey()
- : DEFAULT_RULE_KEY;
+ int ruleIndexType = indexingDetails.getIndexType();
+ String ruleKey = indexingDetails.getRuleKey();
- if (!typeOrganizedRuleMap.get(indexingDetails.getIndexType()).containsKey(ruleKey)) {
- typeOrganizedRuleMap
- .get(indexingDetails.getIndexType())
- .put(ruleKey, new ArrayList());
+ if (!typeOrganizedRuleMap.get(ruleIndexType).containsKey(ruleKey)) {
+ typeOrganizedRuleMap.get(ruleIndexType).put(ruleKey, new ArrayList());
}
- typeOrganizedRuleMap
- .get(indexingDetails.getIndexType())
- .get(ruleKey)
- .add(rule);
+ typeOrganizedRuleMap.get(ruleIndexType).get(ruleKey).add(rule);
}
return typeOrganizedRuleMap;
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleSerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleSerializer.java
index 4fcff65..2941856 100644
--- a/services/core/java/com/android/server/integrity/serializer/RuleSerializer.java
+++ b/services/core/java/com/android/server/integrity/serializer/RuleSerializer.java
@@ -26,10 +26,14 @@
public interface RuleSerializer {
/** Serialize rules to an output stream */
- void serialize(List<Rule> rules, Optional<Integer> formatVersion, OutputStream outputStream)
+ void serialize(
+ List<Rule> rules,
+ Optional<Integer> formatVersion,
+ OutputStream ruleFileOutputStream,
+ OutputStream indexingFileOutputStream)
throws RuleSerializeException;
/** Serialize rules to a ByteArray. */
- byte[] serialize(List<Rule> rule, Optional<Integer> formatVersion)
+ byte[] serialize(List<Rule> rules, Optional<Integer> formatVersion)
throws RuleSerializeException;
}
diff --git a/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java b/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java
index 4c04dbc..8f164e6 100644
--- a/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java
+++ b/services/core/java/com/android/server/integrity/serializer/RuleXmlSerializer.java
@@ -35,7 +35,7 @@
import java.util.List;
import java.util.Map;
import java.util.Optional;
-import java.util.TreeMap;
+import java.util.stream.Collectors;
/** A helper class to serialize rules from the {@link Rule} model to Xml representation. */
public class RuleXmlSerializer implements RuleSerializer {
@@ -56,12 +56,17 @@
@Override
public void serialize(
- List<Rule> rules, Optional<Integer> formatVersion, OutputStream outputStream)
+ List<Rule> rules,
+ Optional<Integer> formatVersion,
+ OutputStream outputStream,
+ OutputStream indexingOutputStream)
throws RuleSerializeException {
try {
XmlSerializer xmlSerializer = Xml.newSerializer();
xmlSerializer.setOutput(outputStream, StandardCharsets.UTF_8.name());
serializeRules(rules, xmlSerializer);
+
+ // TODO(b/145493956): Implement the indexing logic.
} catch (Exception e) {
throw new RuleSerializeException(e.getMessage(), e);
}
@@ -85,7 +90,7 @@
throws RuleSerializeException {
try {
// Determine the indexing groups and the order of the rules within each indexed group.
- Map<Integer, TreeMap<String, List<Rule>>> indexedRules =
+ Map<Integer, Map<String, List<Rule>>> indexedRules =
RuleIndexingDetailsIdentifier.splitRulesIntoIndexBuckets(rules);
// Write the XML formatted rules in order.
@@ -102,11 +107,12 @@
}
}
- private void serializeRuleList(TreeMap<String, List<Rule>> rulesMap,
- XmlSerializer xmlSerializer)
+ private void serializeRuleList(Map<String, List<Rule>> rulesMap, XmlSerializer xmlSerializer)
throws IOException {
- for (Map.Entry<String, List<Rule>> entry : rulesMap.entrySet()) {
- for (Rule rule : entry.getValue()) {
+ List<String> sortedKeyList =
+ rulesMap.keySet().stream().sorted().collect(Collectors.toList());
+ for (String key : sortedKeyList) {
+ for (Rule rule : rulesMap.get(key)) {
serializeRule(rule, xmlSerializer);
}
}
diff --git a/services/core/java/com/android/server/location/AbstractLocationProvider.java b/services/core/java/com/android/server/location/AbstractLocationProvider.java
index ccfc98e..ed6a759 100644
--- a/services/core/java/com/android/server/location/AbstractLocationProvider.java
+++ b/services/core/java/com/android/server/location/AbstractLocationProvider.java
@@ -16,11 +16,11 @@
package com.android.server.location;
+import android.annotation.Nullable;
import android.content.Context;
import android.location.Location;
import android.os.Binder;
import android.os.Bundle;
-import android.os.WorkSource;
import com.android.internal.location.ProviderProperties;
import com.android.internal.location.ProviderRequest;
@@ -29,127 +29,336 @@
import java.io.PrintWriter;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.UnaryOperator;
/**
- * Location Manager's interface for location providers. Always starts as disabled.
+ * Base class for all location providers.
*
* @hide
*/
public abstract class AbstractLocationProvider {
/**
- * Interface for communicating from a location provider back to the location service.
+ * Interface for listening to location providers.
*/
- public interface LocationProviderManager {
+ public interface Listener {
/**
- * May be called to inform the location service of a change in this location provider's
- * enabled/disabled state.
+ * Called when a provider's state changes. May be invoked from any thread. Will be
+ * invoked with a cleared binder identity.
*/
- void onSetEnabled(boolean enabled);
+ void onStateChanged(State oldState, State newState);
/**
- * May be called to inform the location service of a change in this location provider's
- * properties.
- */
- void onSetProperties(ProviderProperties properties);
-
- /**
- * May be called to inform the location service that this provider has a new location
- * available.
+ * Called when a provider has a new location available. May be invoked from any thread. Will
+ * be invoked with a cleared binder identity.
*/
void onReportLocation(Location location);
/**
- * May be called to inform the location service that this provider has a new location
- * available.
+ * Called when a provider has a new location available. May be invoked from any thread. Will
+ * be invoked with a cleared binder identity.
*/
void onReportLocation(List<Location> locations);
}
- protected final Context mContext;
- private final LocationProviderManager mLocationProviderManager;
+ /**
+ * Holds a representation of the public state of a provider.
+ */
+ public static final class State {
- protected AbstractLocationProvider(
- Context context, LocationProviderManager locationProviderManager) {
+ /**
+ * Default state value for a location provider that is disabled with no properties and an
+ * empty provider package list.
+ */
+ public static final State EMPTY_STATE = new State(false, null,
+ Collections.emptySet());
+
+ /**
+ * The provider's enabled state.
+ */
+ public final boolean enabled;
+
+ /**
+ * The provider's properties.
+ */
+ @Nullable public final ProviderProperties properties;
+
+ /**
+ * The provider's package name list - provider packages may be afforded special privileges.
+ */
+ public final Set<String> providerPackageNames;
+
+ private State(boolean enabled, ProviderProperties properties,
+ Set<String> providerPackageNames) {
+ this.enabled = enabled;
+ this.properties = properties;
+ this.providerPackageNames = Objects.requireNonNull(providerPackageNames);
+ }
+
+ private State withEnabled(boolean enabled) {
+ if (enabled == this.enabled) {
+ return this;
+ } else {
+ return new State(enabled, properties, providerPackageNames);
+ }
+ }
+
+ private State withProperties(ProviderProperties properties) {
+ if (properties.equals(this.properties)) {
+ return this;
+ } else {
+ return new State(enabled, properties, providerPackageNames);
+ }
+ }
+
+ private State withProviderPackageNames(Set<String> providerPackageNames) {
+ if (providerPackageNames.equals(this.providerPackageNames)) {
+ return this;
+ } else {
+ return new State(enabled, properties, providerPackageNames);
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof State)) {
+ return false;
+ }
+ State state = (State) o;
+ return enabled == state.enabled && properties == state.properties
+ && providerPackageNames.equals(state.providerPackageNames);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(enabled, properties, providerPackageNames);
+ }
+ }
+
+ // combines listener and state information so that they can be updated atomically with respect
+ // to each other and an ordering established.
+ private static class InternalState {
+ @Nullable public final Listener listener;
+ public final State state;
+
+ private InternalState(@Nullable Listener listener, State state) {
+ this.listener = listener;
+ this.state = state;
+ }
+
+ private InternalState withListener(Listener listener) {
+ if (listener == this.listener) {
+ return this;
+ } else {
+ return new InternalState(listener, state);
+ }
+ }
+
+ private InternalState withState(State state) {
+ if (state.equals(this.state)) {
+ return this;
+ } else {
+ return new InternalState(listener, state);
+ }
+ }
+
+ private InternalState withState(UnaryOperator<State> operator) {
+ return withState(operator.apply(state));
+ }
+ }
+
+ protected final Context mContext;
+ protected final Executor mExecutor;
+
+ // we use a lock-free implementation to update state to ensure atomicity between updating the
+ // provider state and setting the listener, so that the state updates a listener sees are
+ // consistent with when the listener was set (a listener should not see any updates that occur
+ // before it was set, and should not miss any updates that occur after it was set).
+ private final AtomicReference<InternalState> mInternalState;
+
+ protected AbstractLocationProvider(Context context, Executor executor) {
+ this(context, executor, Collections.singleton(context.getPackageName()));
+ }
+
+ protected AbstractLocationProvider(Context context, Executor executor,
+ Set<String> packageNames) {
mContext = context;
- mLocationProviderManager = locationProviderManager;
+ mExecutor = executor;
+ mInternalState = new AtomicReference<>(
+ new InternalState(null, State.EMPTY_STATE.withProviderPackageNames(packageNames)));
}
/**
- * Call this method to report a change in provider enabled/disabled status. May be called from
- * any thread.
+ * Sets the listener and returns the state at the moment the listener was set. The listener can
+ * expect to receive all state updates from after this point.
+ */
+ State setListener(@Nullable Listener listener) {
+ return mInternalState.updateAndGet(
+ internalState -> internalState.withListener(listener)).state;
+ }
+
+ /**
+ * Retrieves the state of the provider.
+ */
+ State getState() {
+ return mInternalState.get().state;
+ }
+
+ /**
+ * Sets the state of the provider to the new state.
+ */
+ void setState(State newState) {
+ InternalState oldInternalState = mInternalState.getAndUpdate(
+ internalState -> internalState.withState(newState));
+ if (newState.equals(oldInternalState.state)) {
+ return;
+ }
+
+ // we know that we only updated the state, so the listener for the old state is the same as
+ // the listener for the new state.
+ if (oldInternalState.listener != null) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ oldInternalState.listener.onStateChanged(oldInternalState.state, newState);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ private void setState(UnaryOperator<State> operator) {
+ InternalState oldInternalState = mInternalState.getAndUpdate(
+ internalState -> internalState.withState(operator));
+
+ // recreate the new state from our knowledge of the old state - unfortunately may result in
+ // an extra allocation, but oh well...
+ State newState = operator.apply(oldInternalState.state);
+
+ if (newState.equals(oldInternalState.state)) {
+ return;
+ }
+
+ // we know that we only updated the state, so the listener for the old state is the same as
+ // the listener for the new state.
+ if (oldInternalState.listener != null) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ oldInternalState.listener.onStateChanged(oldInternalState.state, newState);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
+ /**
+ * The current enabled state of this provider.
+ */
+ protected boolean isEnabled() {
+ return mInternalState.get().state.enabled;
+ }
+
+ /**
+ * The current provider properties of this provider.
+ */
+ @Nullable
+ protected ProviderProperties getProperties() {
+ return mInternalState.get().state.properties;
+ }
+
+ /**
+ * The current package set of this provider.
+ */
+ protected Set<String> getProviderPackages() {
+ return mInternalState.get().state.providerPackageNames;
+ }
+
+ /**
+ * Call this method to report a change in provider enabled/disabled status.
*/
protected void setEnabled(boolean enabled) {
- long identity = Binder.clearCallingIdentity();
- try {
- mLocationProviderManager.onSetEnabled(enabled);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
+ setState(state -> state.withEnabled(enabled));
}
/**
- * Call this method to report a change in provider properties. May be called from
- * any thread.
+ * Call this method to report a change in provider properties.
*/
protected void setProperties(ProviderProperties properties) {
- long identity = Binder.clearCallingIdentity();
- try {
- mLocationProviderManager.onSetProperties(properties);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
+ setState(state -> state.withProperties(properties));
}
/**
- * Call this method to report a new location. May be called from any thread.
+ * Call this method to report a change in provider packages.
+ */
+ protected void setPackageNames(Set<String> packageNames) {
+ setState(state -> state.withProviderPackageNames(packageNames));
+ }
+
+ /**
+ * Call this method to report a new location.
*/
protected void reportLocation(Location location) {
- long identity = Binder.clearCallingIdentity();
- try {
- mLocationProviderManager.onReportLocation(location);
- } finally {
- Binder.restoreCallingIdentity(identity);
+ Listener listener = mInternalState.get().listener;
+ if (listener != null) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ listener.onReportLocation(location);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
}
/**
- * Call this method to report a new location. May be called from any thread.
+ * Call this method to report a new location.
*/
protected void reportLocation(List<Location> locations) {
- long identity = Binder.clearCallingIdentity();
- try {
- mLocationProviderManager.onReportLocation(locations);
- } finally {
- Binder.restoreCallingIdentity(identity);
+ Listener listener = mInternalState.get().listener;
+ if (listener != null) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ listener.onReportLocation(locations);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
}
}
/**
- * Invoked by the location service to return a list of packages currently associated with this
- * provider. May be called from any thread.
+ * Sets a new request and worksource for the provider.
*/
- public List<String> getProviderPackages() {
- return Collections.singletonList(mContext.getPackageName());
+ public final void setRequest(ProviderRequest request) {
+ // all calls into the provider must be moved onto the provider thread to prevent deadlock
+ mExecutor.execute(() -> onSetRequest(request));
}
/**
- * Invoked by the location service to deliver a new request for fulfillment to the provider.
- * Replaces any previous requests completely. Will always be invoked from the location service
- * thread with a cleared binder identity.
+ * Always invoked on the provider executor.
*/
- public abstract void onSetRequest(ProviderRequest request, WorkSource source);
+ protected abstract void onSetRequest(ProviderRequest request);
/**
- * Invoked by the location service to deliver a custom command to this provider. Will always be
- * invoked from the location service thread with a cleared binder identity.
+ * Sends an extra command to the provider for it to interpret as it likes.
*/
- public void onSendExtraCommand(int uid, int pid, String command, Bundle extras) {}
+ public final void sendExtraCommand(int uid, int pid, String command, Bundle extras) {
+ // all calls into the provider must be moved onto the provider thread to prevent deadlock
+ mExecutor.execute(() -> onExtraCommand(uid, pid, command, extras));
+ }
/**
- * Invoked by the location service to dump debug or log information. May be invoked from any
- * thread.
+ * Always invoked on the provider executor.
+ */
+ protected void onExtraCommand(int uid, int pid, String command, Bundle extras) {}
+
+ /**
+ * Dumps debug or log information. May be invoked from any thread.
*/
public abstract void dump(FileDescriptor fd, PrintWriter pw, String[] args);
}
diff --git a/services/core/java/com/android/server/location/GnssLocationProvider.java b/services/core/java/com/android/server/location/GnssLocationProvider.java
index f913ba3..15cf190 100644
--- a/services/core/java/com/android/server/location/GnssLocationProvider.java
+++ b/services/core/java/com/android/server/location/GnssLocationProvider.java
@@ -43,6 +43,7 @@
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.Looper;
import android.os.Message;
import android.os.PersistableBundle;
@@ -74,7 +75,6 @@
import com.android.internal.location.ProviderProperties;
import com.android.internal.location.ProviderRequest;
import com.android.internal.location.gnssmetrics.GnssMetrics;
-import com.android.internal.telephony.TelephonyIntents;
import com.android.server.DeviceIdleInternal;
import com.android.server.LocalServices;
import com.android.server.location.GnssSatelliteBlacklistHelper.GnssSatelliteBlacklistCallback;
@@ -114,8 +114,15 @@
private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
private static final ProviderProperties PROPERTIES = new ProviderProperties(
- true, true, false, false, true, true, true,
- Criteria.POWER_HIGH, Criteria.ACCURACY_FINE);
+ /* requiresNetwork = */false,
+ /* requiresSatellite = */true,
+ /* requiresCell = */false,
+ /* hasMonetaryCost = */false,
+ /* supportAltitude = */true,
+ /* supportsSpeed = */true,
+ /* supportsBearing = */true,
+ Criteria.POWER_HIGH,
+ Criteria.ACCURACY_FINE);
// these need to match GnssPositionMode enum in IGnss.hal
private static final int GPS_POSITION_MODE_STANDALONE = 0;
@@ -508,7 +515,7 @@
mHandler.sendEmptyMessage(UPDATE_LOW_POWER_MODE);
break;
case CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED:
- case TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED:
+ case TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED:
subscriptionOrCarrierConfigChanged();
break;
}
@@ -617,13 +624,12 @@
}
}
- public GnssLocationProvider(Context context, LocationProviderManager locationProviderManager,
- Looper looper) {
- super(context, locationProviderManager);
+ public GnssLocationProvider(Context context, Handler handler) {
+ super(context, new HandlerExecutor(handler));
ensureInitialized();
- mLooper = looper;
+ mLooper = handler.getLooper();
// Create a wake lock
mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
@@ -640,7 +646,7 @@
mTimeoutIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ALARM_TIMEOUT), 0);
mNetworkConnectivityHandler = new GnssNetworkConnectivityHandler(context,
- GnssLocationProvider.this::onNetworkAvailable, looper);
+ GnssLocationProvider.this::onNetworkAvailable, mLooper);
// App ops service to keep track of who is accessing the GPS
mAppOps = mContext.getSystemService(AppOpsManager.class);
@@ -650,7 +656,7 @@
BatteryStats.SERVICE_NAME));
// Construct internal handler
- mHandler = new ProviderHandler(looper);
+ mHandler = new ProviderHandler(mLooper);
// Load GPS configuration and register listeners in the background:
// some operations, such as opening files and registering broadcast receivers, can take a
@@ -694,10 +700,10 @@
};
mGnssMetrics = new GnssMetrics(mBatteryStats);
- mNtpTimeHelper = new NtpTimeHelper(mContext, looper, this);
+ mNtpTimeHelper = new NtpTimeHelper(mContext, mLooper, this);
GnssSatelliteBlacklistHelper gnssSatelliteBlacklistHelper =
new GnssSatelliteBlacklistHelper(mContext,
- looper, this);
+ mLooper, this);
mHandler.post(gnssSatelliteBlacklistHelper::updateSatelliteBlacklist);
mGnssBatchingProvider = new GnssBatchingProvider();
mGnssGeofenceProvider = new GnssGeofenceProvider();
@@ -1048,8 +1054,8 @@
}
@Override
- public void onSetRequest(ProviderRequest request, WorkSource source) {
- sendMessage(SET_REQUEST, 0, new GpsRequest(request, source));
+ public void onSetRequest(ProviderRequest request) {
+ sendMessage(SET_REQUEST, 0, new GpsRequest(request, request.workSource));
}
private void handleSetRequest(ProviderRequest request, WorkSource source) {
@@ -1186,7 +1192,7 @@
}
@Override
- public void onSendExtraCommand(int uid, int pid, String command, Bundle extras) {
+ public void onExtraCommand(int uid, int pid, String command, Bundle extras) {
long identity = Binder.clearCallingIdentity();
try {
@@ -2065,10 +2071,8 @@
}
/**
- * This method is bound to {@link #GnssLocationProvider(Context, LocationProviderManager,
- * Looper)}.
- * It is in charge of loading properties and registering for events that will be posted to
- * this handler.
+ * This method is bound to the constructor. It is in charge of loading properties and
+ * registering for events that will be posted to this handler.
*/
private void handleInitialize() {
// class_init_native() already initializes the GNSS service handle during class loading.
@@ -2091,7 +2095,7 @@
intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
intentFilter.addAction(Intent.ACTION_SCREEN_ON);
intentFilter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
- intentFilter.addAction(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
+ intentFilter.addAction(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
mContext.registerReceiver(mBroadcastReceiver, intentFilter, null, this);
mNetworkConnectivityHandler.registerNetworkCallbacks();
diff --git a/services/core/java/com/android/server/location/LocationProviderProxy.java b/services/core/java/com/android/server/location/LocationProviderProxy.java
index 694f149..8a149af 100644
--- a/services/core/java/com/android/server/location/LocationProviderProxy.java
+++ b/services/core/java/com/android/server/location/LocationProviderProxy.java
@@ -23,12 +23,13 @@
import android.content.pm.PackageManager;
import android.location.Location;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.RemoteException;
-import android.os.WorkSource;
+import android.util.ArraySet;
import android.util.Log;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.location.ILocationProvider;
import com.android.internal.location.ILocationProviderManager;
import com.android.internal.location.ProviderProperties;
@@ -39,10 +40,8 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
/**
* Proxy for ILocationProvider implementations.
@@ -52,59 +51,64 @@
private static final String TAG = "LocationProviderProxy";
private static final boolean D = LocationManagerService.D;
- // used to ensure that updates to mProviderPackages are atomic
- private final Object mProviderPackagesLock = new Object();
-
- // used to ensure that updates to mRequest and mWorkSource are atomic
- private final Object mRequestLock = new Object();
+ private static final int MAX_ADDITIONAL_PACKAGES = 2;
private final ILocationProviderManager.Stub mManager = new ILocationProviderManager.Stub() {
// executed on binder thread
@Override
public void onSetAdditionalProviderPackages(List<String> packageNames) {
- LocationProviderProxy.this.onSetAdditionalProviderPackages(packageNames);
+ int maxCount = Math.min(MAX_ADDITIONAL_PACKAGES, packageNames.size()) + 1;
+ ArraySet<String> allPackages = new ArraySet<>(maxCount);
+ allPackages.add(mServiceWatcher.getCurrentPackageName());
+ for (String packageName : packageNames) {
+ if (packageNames.size() >= maxCount) {
+ return;
+ }
+
+ try {
+ mContext.getPackageManager().getPackageInfo(packageName, MATCH_SYSTEM_ONLY);
+ allPackages.add(packageName);
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, mServiceWatcher + " specified unknown additional provider package: "
+ + packageName);
+ }
+ }
+
+ setPackageNames(allPackages);
}
// executed on binder thread
@Override
public void onSetEnabled(boolean enabled) {
- LocationProviderProxy.this.setEnabled(enabled);
+ setEnabled(enabled);
}
// executed on binder thread
@Override
public void onSetProperties(ProviderProperties properties) {
- LocationProviderProxy.this.setProperties(properties);
+ setProperties(properties);
}
// executed on binder thread
@Override
public void onReportLocation(Location location) {
- LocationProviderProxy.this.reportLocation(location);
+ reportLocation(location);
}
};
private final ServiceWatcher mServiceWatcher;
- @GuardedBy("mProviderPackagesLock")
- private final CopyOnWriteArrayList<String> mProviderPackages = new CopyOnWriteArrayList<>();
-
- @GuardedBy("mRequestLock")
- @Nullable
- private ProviderRequest mRequest;
- @GuardedBy("mRequestLock")
- private WorkSource mWorkSource;
+ @Nullable private ProviderRequest mRequest;
/**
* Creates a new LocationProviderProxy and immediately begins binding to the best applicable
* service.
*/
@Nullable
- public static LocationProviderProxy createAndBind(
- Context context, LocationProviderManager locationProviderManager, String action,
+ public static LocationProviderProxy createAndBind(Context context, String action,
int overlaySwitchResId, int defaultServicePackageNameResId,
int initialPackageNamesResId) {
- LocationProviderProxy proxy = new LocationProviderProxy(context, locationProviderManager,
+ LocationProviderProxy proxy = new LocationProviderProxy(context, FgThread.getHandler(),
action, overlaySwitchResId, defaultServicePackageNameResId,
initialPackageNamesResId);
if (proxy.bind()) {
@@ -114,14 +118,13 @@
}
}
- private LocationProviderProxy(Context context, LocationProviderManager locationProviderManager,
- String action, int overlaySwitchResId, int defaultServicePackageNameResId,
+ private LocationProviderProxy(Context context, Handler handler, String action,
+ int overlaySwitchResId, int defaultServicePackageNameResId,
int initialPackageNamesResId) {
- super(context, locationProviderManager);
+ super(context, new HandlerExecutor(handler), Collections.emptySet());
mServiceWatcher = new ServiceWatcher(context, TAG, action, overlaySwitchResId,
- defaultServicePackageNameResId, initialPackageNamesResId,
- FgThread.getHandler()) {
+ defaultServicePackageNameResId, initialPackageNamesResId, handler) {
@Override
protected void onBind() {
@@ -130,14 +133,11 @@
@Override
protected void onUnbind() {
- resetProviderPackages(Collections.emptyList());
- setEnabled(false);
- setProperties(null);
+ setState(State.EMPTY_STATE);
}
};
mRequest = null;
- mWorkSource = new WorkSource();
}
private boolean bind() {
@@ -148,77 +148,34 @@
ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
if (D) Log.d(TAG, "applying state to connected service " + mServiceWatcher);
- resetProviderPackages(Collections.emptyList());
+ setPackageNames(Collections.singleton(mServiceWatcher.getCurrentPackageName()));
service.setLocationProviderManager(mManager);
- synchronized (mRequestLock) {
- if (mRequest != null) {
- service.setRequest(mRequest, mWorkSource);
- }
+ if (mRequest != null) {
+ service.setRequest(mRequest, mRequest.workSource);
}
}
@Override
- public List<String> getProviderPackages() {
- synchronized (mProviderPackagesLock) {
- return mProviderPackages;
- }
- }
-
- @Override
- public void onSetRequest(ProviderRequest request, WorkSource source) {
- synchronized (mRequestLock) {
- mRequest = request;
- mWorkSource = source;
- }
+ public void onSetRequest(ProviderRequest request) {
mServiceWatcher.runOnBinder(binder -> {
+ mRequest = request;
ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
- service.setRequest(request, source);
+ service.setRequest(request, request.workSource);
});
}
@Override
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("service=" + mServiceWatcher);
- synchronized (mProviderPackagesLock) {
- if (mProviderPackages.size() > 1) {
- pw.println("additional packages=" + mProviderPackages);
- }
- }
- }
-
- @Override
- public void onSendExtraCommand(int uid, int pid, String command, Bundle extras) {
+ public void onExtraCommand(int uid, int pid, String command, Bundle extras) {
mServiceWatcher.runOnBinder(binder -> {
ILocationProvider service = ILocationProvider.Stub.asInterface(binder);
service.sendExtraCommand(command, extras);
});
}
- private void onSetAdditionalProviderPackages(List<String> packageNames) {
- resetProviderPackages(packageNames);
- }
-
- private void resetProviderPackages(List<String> additionalPackageNames) {
- ArrayList<String> permittedPackages = new ArrayList<>(additionalPackageNames.size());
- for (String packageName : additionalPackageNames) {
- try {
- mContext.getPackageManager().getPackageInfo(packageName, MATCH_SYSTEM_ONLY);
- permittedPackages.add(packageName);
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, mServiceWatcher + " specified unknown additional provider package: "
- + packageName);
- }
- }
-
- synchronized (mProviderPackagesLock) {
- mProviderPackages.clear();
- String myPackage = mServiceWatcher.getCurrentPackageName();
- if (myPackage != null) {
- mProviderPackages.add(myPackage);
- mProviderPackages.addAll(permittedPackages);
- }
- }
+ @Override
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("service=" + mServiceWatcher);
}
}
diff --git a/services/core/java/com/android/server/location/LocationRequestStatistics.java b/services/core/java/com/android/server/location/LocationRequestStatistics.java
index b7ccb26..45c8334 100644
--- a/services/core/java/com/android/server/location/LocationRequestStatistics.java
+++ b/services/core/java/com/android/server/location/LocationRequestStatistics.java
@@ -1,8 +1,29 @@
+/*
+ * 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.location;
import android.os.SystemClock;
import android.util.Log;
+import android.util.TimeUtils;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.util.ArrayList;
import java.util.HashMap;
/**
@@ -17,6 +38,8 @@
public final HashMap<PackageProviderKey, PackageStatistics> statistics
= new HashMap<PackageProviderKey, PackageStatistics>();
+ public final RequestSummaryLimitedHistory history = new RequestSummaryLimitedHistory();
+
/**
* Signals that a package has started requesting locations.
*
@@ -34,6 +57,7 @@
}
stats.startRequesting(intervalMs);
stats.updateForeground(isForeground);
+ history.addRequest(packageName, providerName, intervalMs);
}
/**
@@ -48,6 +72,7 @@
if (stats != null) {
stats.stopRequesting();
}
+ history.removeRequest(packageName, providerName);
}
/**
@@ -77,7 +102,7 @@
*/
public final String providerName;
- public PackageProviderKey(String packageName, String providerName) {
+ PackageProviderKey(String packageName, String providerName) {
this.packageName = packageName;
this.providerName = providerName;
}
@@ -100,6 +125,104 @@
}
/**
+ * A data structure to hold past requests
+ */
+ public static class RequestSummaryLimitedHistory {
+ @VisibleForTesting
+ static final int MAX_SIZE = 100;
+
+ final ArrayList<RequestSummary> mList = new ArrayList<>(MAX_SIZE);
+
+ /**
+ * Append an added location request to the history
+ */
+ @VisibleForTesting
+ void addRequest(String packageName, String providerName, long intervalMs) {
+ addRequestSummary(new RequestSummary(packageName, providerName, intervalMs));
+ }
+
+ /**
+ * Append a removed location request to the history
+ */
+ @VisibleForTesting
+ void removeRequest(String packageName, String providerName) {
+ addRequestSummary(new RequestSummary(
+ packageName, providerName, RequestSummary.REQUEST_ENDED_INTERVAL));
+ }
+
+ private void addRequestSummary(RequestSummary summary) {
+ while (mList.size() >= MAX_SIZE) {
+ mList.remove(0);
+ }
+ mList.add(summary);
+ }
+
+ /**
+ * Dump history to a printwriter (for dumpsys location)
+ */
+ public void dump(IndentingPrintWriter ipw) {
+ long systemElapsedOffsetMillis = System.currentTimeMillis()
+ - SystemClock.elapsedRealtime();
+
+ ipw.println("Last Several Location Requests:");
+ ipw.increaseIndent();
+
+ for (RequestSummary requestSummary : mList) {
+ requestSummary.dump(ipw, systemElapsedOffsetMillis);
+ }
+
+ ipw.decreaseIndent();
+ }
+ }
+
+ /**
+ * A data structure to hold a single request
+ */
+ static class RequestSummary {
+ /**
+ * Name of package requesting location.
+ */
+ private final String mPackageName;
+ /**
+ * Name of provider being requested (e.g. "gps").
+ */
+ private final String mProviderName;
+ /**
+ * Interval Requested, or REQUEST_ENDED_INTERVAL indicating request has ended
+ */
+ private final long mIntervalMillis;
+ /**
+ * Elapsed time of request
+ */
+ private final long mElapsedRealtimeMillis;
+
+ /**
+ * Placeholder for requested ending (other values indicate request started/changed)
+ */
+ static final long REQUEST_ENDED_INTERVAL = -1;
+
+ RequestSummary(String packageName, String providerName, long intervalMillis) {
+ this.mPackageName = packageName;
+ this.mProviderName = providerName;
+ this.mIntervalMillis = intervalMillis;
+ this.mElapsedRealtimeMillis = SystemClock.elapsedRealtime();
+ }
+
+ void dump(IndentingPrintWriter ipw, long systemElapsedOffsetMillis) {
+ StringBuilder s = new StringBuilder();
+ long systemTimeMillis = systemElapsedOffsetMillis + mElapsedRealtimeMillis;
+ s.append("At ").append(TimeUtils.formatForLogging(systemTimeMillis)).append(": ")
+ .append(mIntervalMillis == REQUEST_ENDED_INTERVAL ? "- " : "+ ")
+ .append(String.format("%7s", mProviderName)).append(" request from ")
+ .append(mPackageName);
+ if (mIntervalMillis != REQUEST_ENDED_INTERVAL) {
+ s.append(" at interval ").append(mIntervalMillis / 1000).append(" seconds");
+ }
+ ipw.println(s);
+ }
+ }
+
+ /**
* Usage statistics for a package/provider pair.
*/
public static class PackageStatistics {
diff --git a/services/core/java/com/android/server/location/LocationSettingsStore.java b/services/core/java/com/android/server/location/LocationSettingsStore.java
index f625452..0e8720e 100644
--- a/services/core/java/com/android/server/location/LocationSettingsStore.java
+++ b/services/core/java/com/android/server/location/LocationSettingsStore.java
@@ -16,6 +16,8 @@
package com.android.server.location;
+import static android.location.LocationManager.FUSED_PROVIDER;
+import static android.location.LocationManager.PASSIVE_PROVIDER;
import static android.provider.Settings.Global.LOCATION_BACKGROUND_THROTTLE_INTERVAL_MS;
import static android.provider.Settings.Global.LOCATION_BACKGROUND_THROTTLE_PACKAGE_WHITELIST;
import static android.provider.Settings.Global.LOCATION_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS;
@@ -28,6 +30,7 @@
import android.content.Context;
import android.database.ContentObserver;
import android.net.Uri;
+import android.os.Binder;
import android.os.Handler;
import android.os.UserHandle;
import android.provider.Settings;
@@ -248,6 +251,9 @@
DEFAULT_BACKGROUND_THROTTLE_PROXIMITY_ALERT_INTERVAL_MS);
}
+ /**
+ * Retrieve maximum age of the last location.
+ */
public long getMaxLastLocationAgeMs() {
return Settings.Global.getLong(
mContext.getContentResolver(),
@@ -256,6 +262,29 @@
}
/**
+ * Set a value for the deprecated LOCATION_PROVIDERS_ALLOWED setting. This is used purely for
+ * backwards compatibility for old clients, and may be removed in the future.
+ */
+ public void setLocationProviderAllowed(String provider, boolean enabled, int userId) {
+ // fused and passive provider never get public updates for legacy reasons
+ if (FUSED_PROVIDER.equals(provider) || PASSIVE_PROVIDER.equals(provider)) {
+ return;
+ }
+
+ long identity = Binder.clearCallingIdentity();
+ try {
+ // update LOCATION_PROVIDERS_ALLOWED for best effort backwards compatibility
+ Settings.Secure.putStringForUser(
+ mContext.getContentResolver(),
+ Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
+ (enabled ? "+" : "-") + provider,
+ userId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
* Dump info for debugging.
*/
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
diff --git a/services/core/java/com/android/server/location/MockProvider.java b/services/core/java/com/android/server/location/MockProvider.java
index 472876b..60c9fc1 100644
--- a/services/core/java/com/android/server/location/MockProvider.java
+++ b/services/core/java/com/android/server/location/MockProvider.java
@@ -19,7 +19,6 @@
import android.annotation.Nullable;
import android.content.Context;
import android.location.Location;
-import android.os.WorkSource;
import com.android.internal.location.ProviderProperties;
import com.android.internal.location.ProviderRequest;
@@ -34,41 +33,33 @@
*/
public class MockProvider extends AbstractLocationProvider {
- private boolean mEnabled;
@Nullable private Location mLocation;
- public MockProvider(Context context,
- LocationProviderManager locationProviderManager, ProviderProperties properties) {
- super(context, locationProviderManager);
-
- mEnabled = true;
- mLocation = null;
-
+ public MockProvider(Context context, ProviderProperties properties) {
+ // using a direct executor is only acceptable because this class is so simple it is trivial
+ // to verify that it does not acquire any locks or re-enter LMS from callbacks
+ super(context, Runnable::run);
setProperties(properties);
}
/** Sets the enabled state of this mock provider. */
- public void setEnabled(boolean enabled) {
- mEnabled = enabled;
- super.setEnabled(enabled);
+ public void setProviderEnabled(boolean enabled) {
+ setEnabled(enabled);
}
/** Sets the location to report for this mock provider. */
- public void setLocation(Location l) {
- mLocation = new Location(l);
- if (!mLocation.isFromMockProvider()) {
- mLocation.setIsFromMockProvider(true);
- }
- if (mEnabled) {
- reportLocation(mLocation);
- }
+ public void setProviderLocation(Location l) {
+ Location location = new Location(l);
+ location.setIsFromMockProvider(true);
+ mLocation = location;
+ reportLocation(location);
}
@Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("last location=" + mLocation);
+ pw.println("last mock location=" + mLocation);
}
@Override
- public void onSetRequest(ProviderRequest request, WorkSource source) {}
+ public void onSetRequest(ProviderRequest request) {}
}
diff --git a/services/core/java/com/android/server/location/MockableLocationProvider.java b/services/core/java/com/android/server/location/MockableLocationProvider.java
new file mode 100644
index 0000000..f50dfe7
--- /dev/null
+++ b/services/core/java/com/android/server/location/MockableLocationProvider.java
@@ -0,0 +1,289 @@
+/*
+ * 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 com.android.server.location;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.location.Location;
+import android.os.Bundle;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.location.ProviderRequest;
+import com.android.internal.util.Preconditions;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Represents a location provider that may switch between a mock implementation and a real
+ * implementation. Requires owners to provide a lock object that will be used internally and held
+ * for the duration of all listener callbacks. Owners are reponsible for ensuring this cannot lead
+ * to deadlock.
+ *
+ * In order to ensure deadlock does not occur, the owner must validate that the ONLY lock which can
+ * be held BOTH when calling into this class AND when receiving a callback from this class is the
+ * lock given to this class via the constructor. Holding any other lock is ok as long as there is no
+ * possibility that it can be obtained within both codepaths.
+ *
+ * Holding the given lock guarantees atomicity of any operations on this class for the duration.
+ *
+ * @hide
+ */
+public class MockableLocationProvider extends AbstractLocationProvider {
+
+ private final Object mOwnerLock;
+
+ @GuardedBy("mOwnerLock")
+ @Nullable private AbstractLocationProvider mProvider;
+ @GuardedBy("mOwnerLock")
+ @Nullable private AbstractLocationProvider mRealProvider;
+ @GuardedBy("mOwnerLock")
+ @Nullable private MockProvider mMockProvider;
+
+ @GuardedBy("mOwnerLock")
+ private ProviderRequest mRequest;
+
+ /**
+ * The given lock object will be held any time the listener is invoked, and may also be acquired
+ * and released during the course of invoking any public methods. Holding the given lock ensures
+ * that provider state cannot change except as result of an explicit call by the owner of the
+ * lock into this class. The client is reponsible for ensuring this cannot cause deadlock.
+ *
+ * The client should expect that it may being to receive callbacks as soon as this constructor
+ * is invoked.
+ */
+ public MockableLocationProvider(Context context, Object ownerLock, Listener listener) {
+ // using a direct executor is acceptable because all inbound calls are delegated to the
+ // actual provider implementations which will use their own executors
+ super(context, Runnable::run, Collections.emptySet());
+ mOwnerLock = ownerLock;
+ mRequest = ProviderRequest.EMPTY_REQUEST;
+
+ setListener(listener);
+ }
+
+ /**
+ * Returns the current provider implementation. May be null if there is no current
+ * implementation.
+ */
+ @Nullable
+ public AbstractLocationProvider getProvider() {
+ synchronized (mOwnerLock) {
+ return mProvider;
+ }
+ }
+
+ /**
+ * Sets the real provider implementation, replacing any previous real provider implementation.
+ * May cause an inline invocation of {@link Listener#onStateChanged(State, State)} if this
+ * results in a state change.
+ */
+ public void setRealProvider(@Nullable AbstractLocationProvider provider) {
+ synchronized (mOwnerLock) {
+ if (mRealProvider == provider) {
+ return;
+ }
+
+ mRealProvider = provider;
+ if (!isMock()) {
+ setProviderLocked(mRealProvider);
+ }
+ }
+ }
+
+ /**
+ * Sets the mock provider implementation, replacing any previous mock provider implementation.
+ * Mock implementations are always used instead of real implementations if set. May cause an
+ * inline invocation of {@link Listener#onStateChanged(State, State)} if this results in a
+ * state change.
+ */
+ public void setMockProvider(@Nullable MockProvider provider) {
+ synchronized (mOwnerLock) {
+ if (mMockProvider == provider) {
+ return;
+ }
+
+ mMockProvider = provider;
+ if (mMockProvider != null) {
+ setProviderLocked(mMockProvider);
+ } else {
+ setProviderLocked(mRealProvider);
+ }
+ }
+ }
+
+ @GuardedBy("mOwnerLock")
+ private void setProviderLocked(@Nullable AbstractLocationProvider provider) {
+ if (mProvider == provider) {
+ return;
+ }
+
+ AbstractLocationProvider oldProvider = mProvider;
+ mProvider = provider;
+
+ if (oldProvider != null) {
+ // do this after switching the provider - so even if the old provider is using a direct
+ // executor, if it re-enters this class within setRequest(), it will be ignored
+ oldProvider.setListener(null);
+ oldProvider.setRequest(ProviderRequest.EMPTY_REQUEST);
+ }
+
+ State newState;
+ if (mProvider != null) {
+ newState = mProvider.setListener(new ListenerWrapper(mProvider));
+ } else {
+ newState = State.EMPTY_STATE;
+ }
+
+ ProviderRequest oldRequest = mRequest;
+ setState(newState);
+
+ if (mProvider != null && oldRequest == mRequest) {
+ mProvider.setRequest(mRequest);
+ }
+ }
+
+ /**
+ * Returns true if the current active provider implementation is the mock implementation, and
+ * false otherwise.
+ */
+ public boolean isMock() {
+ synchronized (mOwnerLock) {
+ return mMockProvider != null && mProvider == mMockProvider;
+ }
+ }
+
+ /**
+ * Sets the mock provider implementation's enabled state. Will throw an exception if the mock
+ * provider is not currently the active implementation.
+ */
+ public void setMockProviderEnabled(boolean enabled) {
+ synchronized (mOwnerLock) {
+ Preconditions.checkState(isMock());
+ mMockProvider.setProviderEnabled(enabled);
+ }
+ }
+ /**
+ * Sets the mock provider implementation's location. Will throw an exception if the mock
+ * provider is not currently the active implementation.
+ */
+ public void setMockProviderLocation(Location location) {
+ synchronized (mOwnerLock) {
+ Preconditions.checkState(isMock());
+ mMockProvider.setProviderLocation(location);
+ }
+ }
+
+ @Override
+ public State getState() {
+ return super.getState();
+ }
+
+ /**
+ * Returns the current location request.
+ */
+ public ProviderRequest getCurrentRequest() {
+ synchronized (mOwnerLock) {
+ return mRequest;
+ }
+ }
+
+ protected void onSetRequest(ProviderRequest request) {
+ synchronized (mOwnerLock) {
+ if (request == mRequest) {
+ return;
+ }
+
+ mRequest = request;
+
+ if (mProvider != null) {
+ mProvider.setRequest(request);
+ }
+ }
+ }
+
+ protected void onExtraCommand(int uid, int pid, String command, Bundle extras) {
+ synchronized (mOwnerLock) {
+ if (mProvider != null) {
+ mProvider.sendExtraCommand(uid, pid, command, extras);
+ }
+ }
+ }
+
+ /**
+ * Dumps the current provider implementation.
+ */
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ AbstractLocationProvider provider;
+ synchronized (mOwnerLock) {
+ provider = mProvider;
+ pw.println("request=" + mRequest);
+ }
+
+ if (provider != null) {
+ // dump outside the lock in case the provider wants to acquire its own locks, and since
+ // the default provider dump behavior does not move things onto the provider thread...
+ provider.dump(fd, pw, args);
+ }
+ }
+
+ // ensures that callbacks from the incorrect provider are never visible to clients - this
+ // requires holding the owner's lock for the duration of the callback
+ private class ListenerWrapper implements Listener {
+
+ private final AbstractLocationProvider mListenerProvider;
+
+ private ListenerWrapper(AbstractLocationProvider listenerProvider) {
+ mListenerProvider = listenerProvider;
+ }
+
+ @Override
+ public final void onStateChanged(State oldState, State newState) {
+ synchronized (mOwnerLock) {
+ if (mListenerProvider != mProvider) {
+ return;
+ }
+
+ setState(newState);
+ }
+ }
+
+ @Override
+ public final void onReportLocation(Location location) {
+ synchronized (mOwnerLock) {
+ if (mListenerProvider != mProvider) {
+ return;
+ }
+
+ reportLocation(location);
+ }
+ }
+
+ @Override
+ public final void onReportLocation(List<Location> locations) {
+ synchronized (mOwnerLock) {
+ if (mListenerProvider != mProvider) {
+ return;
+ }
+
+ reportLocation(locations);
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/location/NtpTimeHelper.java b/services/core/java/com/android/server/location/NtpTimeHelper.java
index 67841ac..d2296ea 100644
--- a/services/core/java/com/android/server/location/NtpTimeHelper.java
+++ b/services/core/java/com/android/server/location/NtpTimeHelper.java
@@ -130,7 +130,8 @@
// force refresh NTP cache when outdated
boolean refreshSuccess = true;
- if (mNtpTime.getCacheAge() >= NTP_INTERVAL) {
+ NtpTrustedTime.TimeResult ntpResult = mNtpTime.getCachedTimeResult();
+ if (ntpResult == null || ntpResult.getAgeMillis() >= NTP_INTERVAL) {
// Blocking network operation.
refreshSuccess = mNtpTime.forceRefresh();
}
@@ -140,17 +141,17 @@
// only update when NTP time is fresh
// If refreshSuccess is false, cacheAge does not drop down.
- if (mNtpTime.getCacheAge() < NTP_INTERVAL) {
- long time = mNtpTime.getCachedNtpTime();
- long timeReference = mNtpTime.getCachedNtpTimeReference();
- long certainty = mNtpTime.getCacheCertainty();
+ ntpResult = mNtpTime.getCachedTimeResult();
+ if (ntpResult != null && ntpResult.getAgeMillis() < NTP_INTERVAL) {
+ long time = ntpResult.getTimeMillis();
+ long timeReference = ntpResult.getElapsedRealtimeMillis();
+ long certainty = ntpResult.getCertaintyMillis();
if (DEBUG) {
long now = System.currentTimeMillis();
Log.d(TAG, "NTP server returned: "
- + time + " (" + new Date(time)
- + ") reference: " + timeReference
- + " certainty: " + certainty
+ + time + " (" + new Date(time) + ")"
+ + " ntpResult: " + ntpResult
+ " system time offset: " + (time - now));
}
diff --git a/services/core/java/com/android/server/location/PassiveProvider.java b/services/core/java/com/android/server/location/PassiveProvider.java
index 639b1eb..b338770 100644
--- a/services/core/java/com/android/server/location/PassiveProvider.java
+++ b/services/core/java/com/android/server/location/PassiveProvider.java
@@ -19,7 +19,6 @@
import android.content.Context;
import android.location.Criteria;
import android.location.Location;
-import android.os.WorkSource;
import com.android.internal.location.ProviderProperties;
import com.android.internal.location.ProviderRequest;
@@ -37,13 +36,22 @@
public class PassiveProvider extends AbstractLocationProvider {
private static final ProviderProperties PROPERTIES = new ProviderProperties(
- false, false, false, false, false, false, false,
- Criteria.POWER_LOW, Criteria.ACCURACY_COARSE);
+ /* requiresNetwork = */false,
+ /* requiresSatellite = */false,
+ /* requiresCell = */false,
+ /* hasMonetaryCost = */false,
+ /* supportsAltitude = */false,
+ /* supportsSpeed = */false,
+ /* supportsBearing = */false,
+ Criteria.POWER_LOW,
+ Criteria.ACCURACY_COARSE);
- private boolean mReportLocation;
+ private volatile boolean mReportLocation;
- public PassiveProvider(Context context, LocationProviderManager locationProviderManager) {
- super(context, locationProviderManager);
+ public PassiveProvider(Context context) {
+ // using a direct executor is only acceptable because this class is so simple it is trivial
+ // to verify that it does not acquire any locks or re-enter LMS from callbacks
+ super(context, Runnable::run);
mReportLocation = false;
@@ -52,7 +60,7 @@
}
@Override
- public void onSetRequest(ProviderRequest request, WorkSource source) {
+ public void onSetRequest(ProviderRequest request) {
mReportLocation = request.reportLocation;
}
@@ -63,7 +71,5 @@
}
@Override
- public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
- pw.println("report location=" + mReportLocation);
- }
+ public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {}
}
diff --git a/services/core/java/com/android/server/location/UserInfoStore.java b/services/core/java/com/android/server/location/UserInfoStore.java
new file mode 100644
index 0000000..f282ed2
--- /dev/null
+++ b/services/core/java/com/android/server/location/UserInfoStore.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.location;
+
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.UserInfo;
+import android.os.Binder;
+import android.os.Build;
+import android.os.UserHandle;
+import android.os.UserManager;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
+import com.android.server.FgThread;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Provides accessors and listeners for all user info.
+ */
+public class UserInfoStore {
+
+ /**
+ * Listener for current user changes.
+ */
+ public interface UserChangedListener {
+ /**
+ * Called when the current user changes.
+ */
+ void onUserChanged(@UserIdInt int oldUserId, @UserIdInt int newUserId);
+ }
+
+ private final Context mContext;
+ private final CopyOnWriteArrayList<UserChangedListener> mListeners;
+
+ @GuardedBy("this")
+ @Nullable
+ private UserManager mUserManager;
+
+ @GuardedBy("this")
+ @UserIdInt
+ private int mCurrentUserId;
+
+ @GuardedBy("this")
+ @UserIdInt
+ private int mCachedParentUserId;
+ @GuardedBy("this")
+ private int[] mCachedProfileUserIds;
+
+ public UserInfoStore(Context context) {
+ mContext = context;
+ mListeners = new CopyOnWriteArrayList<>();
+
+ mCurrentUserId = UserHandle.USER_NULL;
+ mCachedParentUserId = UserHandle.USER_NULL;
+ mCachedProfileUserIds = new int[]{UserHandle.USER_NULL};
+ }
+
+ /** Called when system is ready. */
+ public synchronized void onSystemReady() {
+ if (mUserManager != null) {
+ return;
+ }
+
+ mUserManager = mContext.getSystemService(UserManager.class);
+
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
+ intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
+ intentFilter.addAction(Intent.ACTION_MANAGED_PROFILE_REMOVED);
+
+ mContext.registerReceiverAsUser(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action == null) {
+ return;
+ }
+ switch (action) {
+ case Intent.ACTION_USER_SWITCHED:
+ int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
+ UserHandle.USER_NULL);
+ if (userId != UserHandle.USER_NULL) {
+ onUserChanged(userId);
+ }
+ break;
+ case Intent.ACTION_MANAGED_PROFILE_ADDED:
+ case Intent.ACTION_MANAGED_PROFILE_REMOVED:
+ onUserProfilesChanged();
+ break;
+ }
+ }
+ }, UserHandle.ALL, intentFilter, null, FgThread.getHandler());
+
+ mCurrentUserId = ActivityManager.getCurrentUser();
+ }
+
+ /**
+ * Adds a listener for user changed events.
+ */
+ public void addListener(UserChangedListener listener) {
+ mListeners.add(listener);
+ }
+
+ /**
+ * Removes a listener for user changed events.
+ */
+ public void removeListener(UserChangedListener listener) {
+ mListeners.remove(listener);
+ }
+
+ private void onUserChanged(@UserIdInt int newUserId) {
+ int oldUserId;
+ synchronized (this) {
+ if (newUserId == mCurrentUserId) {
+ return;
+ }
+
+ oldUserId = mCurrentUserId;
+ mCurrentUserId = newUserId;
+ }
+
+ for (UserChangedListener listener : mListeners) {
+ listener.onUserChanged(oldUserId, newUserId);
+ }
+ }
+
+ private synchronized void onUserProfilesChanged() {
+ // this intent is only sent to the current user
+ if (mCachedParentUserId == mCurrentUserId) {
+ mCachedParentUserId = UserHandle.USER_NULL;
+ mCachedProfileUserIds = new int[]{UserHandle.USER_NULL};
+ }
+ }
+
+ /**
+ * Returns the user id of the current user.
+ */
+ @UserIdInt
+ public synchronized int getCurrentUserId() {
+ return mCurrentUserId;
+ }
+
+ /**
+ * Returns true if the given user id is either the current user or a profile of the current
+ * user.
+ */
+ public synchronized boolean isCurrentUserOrProfile(@UserIdInt int userId) {
+ return userId == mCurrentUserId || ArrayUtils.contains(
+ getProfileUserIdsForParentUser(mCurrentUserId), userId);
+ }
+
+ /**
+ * Returns the parent user id of the given user id, or the user id itself if the user id either
+ * is a parent or has no profiles.
+ */
+ @UserIdInt
+ public synchronized int getParentUserId(@UserIdInt int userId) {
+ int parentUserId;
+ if (userId == mCachedParentUserId || ArrayUtils.contains(mCachedProfileUserIds, userId)) {
+ parentUserId = mCachedParentUserId;
+ } else {
+ Preconditions.checkState(mUserManager != null);
+
+ long identity = Binder.clearCallingIdentity();
+ try {
+ UserInfo userInfo = mUserManager.getProfileParent(userId);
+ if (userInfo != null) {
+ parentUserId = userInfo.id;
+ } else {
+ // getProfileParent() returns null if the userId is already the parent...
+ parentUserId = userId;
+ }
+
+ // force profiles into cache
+ getProfileUserIdsForParentUser(parentUserId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ return parentUserId;
+ }
+
+ @GuardedBy("this")
+ private int[] getProfileUserIdsForParentUser(@UserIdInt int parentUserId) {
+ Preconditions.checkState(mUserManager != null);
+
+ // only assert on debug builds as this is a more expensive check
+ if (Build.IS_DEBUGGABLE) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ Preconditions.checkArgument(mUserManager.getProfileParent(parentUserId) == null);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ if (parentUserId != mCachedParentUserId) {
+ long identity = Binder.clearCallingIdentity();
+ try {
+ mCachedParentUserId = parentUserId;
+ mCachedProfileUserIds = mUserManager.getProfileIdsWithDisabled(parentUserId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ return mCachedProfileUserIds;
+ }
+
+ /**
+ * Dump info for debugging.
+ */
+ public synchronized void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ pw.println("Current User: " + mCurrentUserId + " " + Arrays.toString(
+ getProfileUserIdsForParentUser(mCurrentUserId)));
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 4546ea4..7233f34 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -20,6 +20,7 @@
import static android.Manifest.permission.READ_CONTACTS;
import static android.content.Context.KEYGUARD_SERVICE;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.UserHandle.USER_ALL;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_NONE;
import static com.android.internal.widget.LockPatternUtils.CREDENTIAL_TYPE_PASSWORD;
@@ -29,6 +30,7 @@
import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback;
import static com.android.internal.widget.LockPatternUtils.SYNTHETIC_PASSWORD_HANDLE_KEY;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE;
import static com.android.internal.widget.LockPatternUtils.USER_FRP;
import static com.android.internal.widget.LockPatternUtils.frpCredentialEnabled;
import static com.android.internal.widget.LockPatternUtils.userOwnsFrpCredential;
@@ -118,6 +120,7 @@
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockSettingsInternal;
import com.android.internal.widget.LockscreenCredential;
+import com.android.internal.widget.RebootEscrowListener;
import com.android.internal.widget.VerifyCredentialResponse;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
@@ -222,7 +225,10 @@
private final RecoverableKeyStoreManager mRecoverableKeyStoreManager;
+ private final RebootEscrowManager mRebootEscrowManager;
+
private boolean mFirstCallToVold;
+
// Current password metric for all users on the device. Updated when user unlocks
// the device or changes password. Removed when user is stopped.
@GuardedBy("this")
@@ -483,6 +489,11 @@
new PasswordSlotManager());
}
+ public RebootEscrowManager getRebootEscrowManager(RebootEscrowManager.Callbacks callbacks,
+ LockSettingsStorage storage) {
+ return new RebootEscrowManager(mContext, callbacks, storage);
+ }
+
public boolean hasEnrolledBiometrics(int userId) {
BiometricManager bm = mContext.getSystemService(BiometricManager.class);
return bm.hasEnrolledBiometrics(userId);
@@ -550,6 +561,9 @@
mSpManager = injector.getSyntheticPasswordManager(mStorage);
+ mRebootEscrowManager = injector.getRebootEscrowManager(new RebootEscrowCallbacks(),
+ mStorage);
+
LocalServices.addService(LockSettingsInternal.class, new LocalService());
}
@@ -782,7 +796,14 @@
migrateOldData();
getGateKeeperService();
mSpManager.initWeaverService();
- // Find the AuthSecret HAL
+ getAuthSecretHal();
+ mDeviceProvisionedObserver.onSystemReady();
+ mRebootEscrowManager.loadRebootEscrowDataIfAvailable();
+ // TODO: maybe skip this for split system user mode.
+ mStorage.prefetchUser(UserHandle.USER_SYSTEM);
+ }
+
+ private void getAuthSecretHal() {
try {
mAuthSecretService = IAuthSecret.getService();
} catch (NoSuchElementException e) {
@@ -790,9 +811,6 @@
} catch (RemoteException e) {
Slog.w(TAG, "Failed to get AuthSecret HAL", e);
}
- mDeviceProvisionedObserver.onSystemReady();
- // TODO: maybe skip this for split system user mode.
- mStorage.prefetchUser(UserHandle.USER_SYSTEM);
}
private void migrateOldData() {
@@ -2466,10 +2484,18 @@
private void onAuthTokenKnownForUser(@UserIdInt int userId, AuthenticationToken auth) {
if (mInjector.isGsiRunning()) {
- Slog.w(TAG, "AuthSecret disabled in GSI");
+ Slog.w(TAG, "Running in GSI; skipping calls to AuthSecret and RebootEscrow");
return;
}
+ mRebootEscrowManager.callToRebootEscrowIfNeeded(userId, auth.getVersion(),
+ auth.getSyntheticPassword());
+
+ callToAuthSecretIfNeeded(userId, auth);
+ }
+
+ private void callToAuthSecretIfNeeded(@UserIdInt int userId,
+ AuthenticationToken auth) {
// Pass the primary user's auth secret to the HAL
if (mAuthSecretService != null && mUserManager.getUserInfo(userId).isPrimary()) {
try {
@@ -3288,5 +3314,46 @@
return LockSettingsService.this.getUserPasswordMetrics(userHandle);
}
+ @Override
+ public void prepareRebootEscrow() {
+ if (!mRebootEscrowManager.prepareRebootEscrow()) {
+ return;
+ }
+ mStrongAuth.requireStrongAuth(STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE, USER_ALL);
+ }
+
+ @Override
+ public void setRebootEscrowListener(RebootEscrowListener listener) {
+ mRebootEscrowManager.setRebootEscrowListener(listener);
+ }
+
+ @Override
+ public void clearRebootEscrow() {
+ if (!mRebootEscrowManager.clearRebootEscrow()) {
+ return;
+ }
+ mStrongAuth.noLongerRequireStrongAuth(STRONG_AUTH_REQUIRED_FOR_UNATTENDED_UPDATE,
+ USER_ALL);
+ }
+
+ @Override
+ public boolean armRebootEscrow() {
+ return mRebootEscrowManager.armRebootEscrowIfNeeded();
+ }
+ }
+
+ private class RebootEscrowCallbacks implements RebootEscrowManager.Callbacks {
+ @Override
+ public boolean isUserSecure(int userId) {
+ return LockSettingsService.this.isUserSecure(userId);
+ }
+
+ @Override
+ public void onRebootEscrowRestored(byte spVersion, byte[] syntheticPassword, int userId) {
+ SyntheticPasswordManager.AuthenticationToken
+ authToken = new SyntheticPasswordManager.AuthenticationToken(spVersion);
+ authToken.recreateDirectly(syntheticPassword);
+ onCredentialVerified(authToken, CHALLENGE_NONE, 0, null, userId);
+ }
}
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
index 0f8561e..4943c25 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsShellCommand.java
@@ -24,6 +24,7 @@
import android.app.ActivityManager;
import android.app.admin.PasswordMetrics;
import android.os.ShellCommand;
+import android.text.TextUtils;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternUtils.RequestThrottledException;
@@ -195,6 +196,9 @@
}
private LockscreenCredential getOldCredential() {
+ if (TextUtils.isEmpty(mOld)) {
+ return LockscreenCredential.createNone();
+ }
if (mLockPatternUtils.isLockPasswordEnabled(mCurrentUserId)) {
final int quality = mLockPatternUtils.getKeyguardStoredPasswordQuality(mCurrentUserId);
if (LockPatternUtils.isQualityAlphabeticPassword(quality)) {
@@ -202,12 +206,15 @@
} else {
return LockscreenCredential.createPin(mOld);
}
- } else if (mLockPatternUtils.isLockPatternEnabled(mCurrentUserId)) {
+ }
+ if (mLockPatternUtils.isLockPatternEnabled(mCurrentUserId)) {
return LockscreenCredential.createPattern(LockPatternUtils.byteArrayToPattern(
mOld.getBytes()));
- } else {
- return LockscreenCredential.createNone();
}
+ // User supplied some old credential but the device has neither password nor pattern,
+ // so just return a password credential (and let it be rejected during LSS verification)
+ return LockscreenCredential.createPassword(mOld);
+
}
private boolean runSetPattern() {
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index 3dab3ce..fec0189 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -81,6 +81,8 @@
private static final String LOCK_PASSWORD_FILE = "gatekeeper.password.key";
private static final String CHILD_PROFILE_LOCK_FILE = "gatekeeper.profile.key";
+ private static final String REBOOT_ESCROW_FILE = "reboot.escrow.key";
+
private static final String SYNTHETIC_PASSWORD_DIRECTORY = "spblob/";
private static final Object DEFAULT = new Object();
@@ -261,6 +263,22 @@
return hasFile(getChildProfileLockFile(userId));
}
+ public void writeRebootEscrow(int userId, byte[] rebootEscrow) {
+ writeFile(getRebootEscrowFile(userId), rebootEscrow);
+ }
+
+ public byte[] readRebootEscrow(int userId) {
+ return readFile(getRebootEscrowFile(userId));
+ }
+
+ public boolean hasRebootEscrow(int userId) {
+ return hasFile(getRebootEscrowFile(userId));
+ }
+
+ public void removeRebootEscrow(int userId) {
+ deleteFile(getRebootEscrowFile(userId));
+ }
+
public boolean hasPassword(int userId) {
return hasFile(getLockPasswordFilename(userId));
}
@@ -384,6 +402,11 @@
return getLockCredentialFilePathForUser(userId, CHILD_PROFILE_LOCK_FILE);
}
+ @VisibleForTesting
+ String getRebootEscrowFile(int userId) {
+ return getLockCredentialFilePathForUser(userId, REBOOT_ESCROW_FILE);
+ }
+
private String getLockCredentialFilePathForUser(int userId, String basename) {
String dataSystemDirectory = Environment.getDataDirectory().getAbsolutePath() +
SYSTEM_DIRECTORY;
@@ -479,18 +502,10 @@
if (parentInfo == null) {
// This user owns its lock settings files - safe to delete them
synchronized (mFileWriteLock) {
- String name = getLockPasswordFilename(userId);
- File file = new File(name);
- if (file.exists()) {
- file.delete();
- mCache.putFile(name, null);
- }
- name = getLockPatternFilename(userId);
- file = new File(name);
- if (file.exists()) {
- file.delete();
- mCache.putFile(name, null);
- }
+ deleteFilesAndRemoveCache(
+ getLockPasswordFilename(userId),
+ getLockPatternFilename(userId),
+ getRebootEscrowFile(userId));
}
} else {
// Managed profile
@@ -512,6 +527,16 @@
}
}
+ private void deleteFilesAndRemoveCache(String... names) {
+ for (String name : names) {
+ File file = new File(name);
+ if (file.exists()) {
+ file.delete();
+ mCache.putFile(name, null);
+ }
+ }
+ }
+
@VisibleForTesting
void closeDatabase() {
mOpenHelper.close();
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java b/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
index a84306c..91cf53e 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStrongAuth.java
@@ -16,10 +16,8 @@
package com.android.server.locksettings;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker
- .STRONG_AUTH_NOT_REQUIRED;
-import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker
- .STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_TIMEOUT;
import android.app.AlarmManager;
import android.app.AlarmManager.OnAlarmListener;
@@ -50,6 +48,7 @@
private static final int MSG_UNREGISTER_TRACKER = 3;
private static final int MSG_REMOVE_USER = 4;
private static final int MSG_SCHEDULE_STRONG_AUTH_TIMEOUT = 5;
+ private static final int MSG_NO_LONGER_REQUIRE_STRONG_AUTH = 6;
private static final String STRONG_AUTH_TIMEOUT_ALARM_TAG =
"LockSettingsStrongAuth.timeoutForUser";
@@ -109,6 +108,26 @@
}
}
+ private void handleNoLongerRequireStrongAuth(int strongAuthReason, int userId) {
+ if (userId == UserHandle.USER_ALL) {
+ for (int i = 0; i < mStrongAuthForUser.size(); i++) {
+ int key = mStrongAuthForUser.keyAt(i);
+ handleNoLongerRequireStrongAuthOneUser(strongAuthReason, key);
+ }
+ } else {
+ handleNoLongerRequireStrongAuthOneUser(strongAuthReason, userId);
+ }
+ }
+
+ private void handleNoLongerRequireStrongAuthOneUser(int strongAuthReason, int userId) {
+ int oldValue = mStrongAuthForUser.get(userId, mDefaultStrongAuthFlags);
+ int newValue = oldValue & ~strongAuthReason;
+ if (oldValue != newValue) {
+ mStrongAuthForUser.put(userId, newValue);
+ notifyStrongAuthTrackers(newValue, userId);
+ }
+ }
+
private void handleRemoveUser(int userId) {
int index = mStrongAuthForUser.indexOfKey(userId);
if (index >= 0) {
@@ -174,6 +193,16 @@
}
}
+ void noLongerRequireStrongAuth(int strongAuthReason, int userId) {
+ if (userId == UserHandle.USER_ALL || userId >= UserHandle.USER_SYSTEM) {
+ mHandler.obtainMessage(MSG_NO_LONGER_REQUIRE_STRONG_AUTH, strongAuthReason,
+ userId).sendToTarget();
+ } else {
+ throw new IllegalArgumentException(
+ "userId must be an explicit user id or USER_ALL");
+ }
+ }
+
public void reportUnlock(int userId) {
requireStrongAuth(STRONG_AUTH_NOT_REQUIRED, userId);
}
@@ -216,6 +245,9 @@
case MSG_SCHEDULE_STRONG_AUTH_TIMEOUT:
handleScheduleStrongAuthTimeout(msg.arg1);
break;
+ case MSG_NO_LONGER_REQUIRE_STRONG_AUTH:
+ handleNoLongerRequireStrongAuth(msg.arg1, msg.arg2);
+ break;
}
}
};
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowData.java b/services/core/java/com/android/server/locksettings/RebootEscrowData.java
new file mode 100644
index 0000000..aee608e
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowData.java
@@ -0,0 +1,178 @@
+/*
+ * 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 com.android.server.locksettings;
+
+import com.android.internal.util.Preconditions;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.KeyGenerator;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * Holds the data necessary to complete a reboot escrow of the Synthetic Password.
+ */
+class RebootEscrowData {
+ /**
+ * This is the current version of the escrow data format. This should be incremented if the
+ * format on disk is changed.
+ */
+ private static final int CURRENT_VERSION = 1;
+
+ /** The secret key will be of this format. */
+ private static final String KEY_ALGO = "AES";
+
+ /** The key size used for encrypting the reboot escrow data. */
+ private static final int KEY_SIZE_BITS = 256;
+
+ /** The algorithm used for the encryption of the key blob. */
+ private static final String CIPHER_ALGO = "AES/GCM/NoPadding";
+
+ private RebootEscrowData(byte spVersion, byte[] iv, byte[] syntheticPassword, byte[] blob,
+ byte[] key) {
+ mSpVersion = spVersion;
+ mIv = iv;
+ mSyntheticPassword = syntheticPassword;
+ mBlob = blob;
+ mKey = key;
+ }
+
+ private final byte mSpVersion;
+ private final byte[] mIv;
+ private final byte[] mSyntheticPassword;
+ private final byte[] mBlob;
+ private final byte[] mKey;
+
+ public byte getSpVersion() {
+ return mSpVersion;
+ }
+
+ public byte[] getIv() {
+ return mIv;
+ }
+
+ public byte[] getSyntheticPassword() {
+ return mSyntheticPassword;
+ }
+
+ public byte[] getBlob() {
+ return mBlob;
+ }
+
+ public byte[] getKey() {
+ return mKey;
+ }
+
+ static SecretKeySpec fromKeyBytes(byte[] keyBytes) {
+ return new SecretKeySpec(keyBytes, KEY_ALGO);
+ }
+
+ static RebootEscrowData fromEncryptedData(SecretKeySpec keySpec, byte[] blob)
+ throws IOException {
+ Preconditions.checkNotNull(keySpec);
+ Preconditions.checkNotNull(blob);
+
+ DataInputStream dis = new DataInputStream(new ByteArrayInputStream(blob));
+ int version = dis.readInt();
+ if (version != CURRENT_VERSION) {
+ throw new IOException("Unsupported version " + version);
+ }
+
+ byte spVersion = dis.readByte();
+
+ int ivSize = dis.readInt();
+ if (ivSize < 0 || ivSize > 32) {
+ throw new IOException("IV out of range: " + ivSize);
+ }
+ byte[] iv = new byte[ivSize];
+ dis.readFully(iv);
+
+ int cipherTextSize = dis.readInt();
+ if (cipherTextSize < 0) {
+ throw new IOException("Invalid cipher text size: " + cipherTextSize);
+ }
+
+ byte[] cipherText = new byte[cipherTextSize];
+ dis.readFully(cipherText);
+
+ final byte[] syntheticPassword;
+ try {
+ Cipher c = Cipher.getInstance(CIPHER_ALGO);
+ c.init(Cipher.DECRYPT_MODE, keySpec, new IvParameterSpec(iv));
+ syntheticPassword = c.doFinal(cipherText);
+ } catch (NoSuchAlgorithmException | InvalidKeyException | BadPaddingException
+ | IllegalBlockSizeException | NoSuchPaddingException
+ | InvalidAlgorithmParameterException e) {
+ throw new IOException("Could not decrypt ciphertext", e);
+ }
+
+ return new RebootEscrowData(spVersion, iv, syntheticPassword, blob, keySpec.getEncoded());
+ }
+
+ static RebootEscrowData fromSyntheticPassword(byte spVersion, byte[] syntheticPassword)
+ throws IOException {
+ Preconditions.checkNotNull(syntheticPassword);
+
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ DataOutputStream dos = new DataOutputStream(bos);
+
+ final SecretKey secretKey;
+ try {
+ KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGO);
+ keyGenerator.init(KEY_SIZE_BITS, new SecureRandom());
+ secretKey = keyGenerator.generateKey();
+ } catch (NoSuchAlgorithmException e) {
+ throw new IOException("Could not generate new secret key", e);
+ }
+
+ final byte[] cipherText;
+ final byte[] iv;
+ try {
+ Cipher cipher = Cipher.getInstance(CIPHER_ALGO);
+ cipher.init(Cipher.ENCRYPT_MODE, secretKey);
+ cipherText = cipher.doFinal(syntheticPassword);
+ iv = cipher.getIV();
+ } catch (NoSuchAlgorithmException | BadPaddingException | IllegalBlockSizeException
+ | NoSuchPaddingException | InvalidKeyException e) {
+ throw new IOException("Could not encrypt reboot escrow data", e);
+ }
+
+ dos.writeInt(CURRENT_VERSION);
+ dos.writeByte(spVersion);
+ dos.writeInt(iv.length);
+ dos.write(iv);
+ dos.writeInt(cipherText.length);
+ dos.write(cipherText);
+
+ return new RebootEscrowData(spVersion, iv, syntheticPassword, bos.toByteArray(),
+ secretKey.getEncoded());
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
new file mode 100644
index 0000000..46ea9d1
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings;
+
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.hardware.rebootescrow.IRebootEscrow;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserManager;
+import android.util.Slog;
+import android.util.StatsLog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.widget.RebootEscrowListener;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.NoSuchElementException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.crypto.spec.SecretKeySpec;
+
+class RebootEscrowManager {
+ private static final String TAG = "RebootEscrowManager";
+
+ /**
+ * Used to track when the reboot escrow is wanted. Set to false when mRebootEscrowReady is
+ * true.
+ */
+ private final AtomicBoolean mRebootEscrowWanted = new AtomicBoolean(false);
+
+ /** Used to track when reboot escrow is ready. */
+ private boolean mRebootEscrowReady;
+
+ /** Notified when mRebootEscrowReady changes. */
+ private RebootEscrowListener mRebootEscrowListener;
+
+ /**
+ * Stores the reboot escrow data between when it's supplied and when
+ * {@link #armRebootEscrowIfNeeded()} is called.
+ */
+ private RebootEscrowData mPendingRebootEscrowData;
+
+ private final UserManager mUserManager;
+
+ private final Injector mInjector;
+
+ private final LockSettingsStorage mStorage;
+
+ private final Callbacks mCallbacks;
+
+ interface Callbacks {
+ boolean isUserSecure(int userId);
+ void onRebootEscrowRestored(byte spVersion, byte[] syntheticPassword, int userId);
+ }
+
+ static class Injector {
+ protected Context mContext;
+
+ Injector(Context context) {
+ mContext = context;
+ }
+
+ public Context getContext() {
+ return mContext;
+ }
+ public UserManager getUserManager() {
+ return (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ }
+
+ public @Nullable IRebootEscrow getRebootEscrow() {
+ try {
+ return IRebootEscrow.Stub.asInterface(ServiceManager.getService(
+ "android.hardware.rebootescrow.IRebootEscrow/default"));
+ } catch (NoSuchElementException e) {
+ Slog.i(TAG, "Device doesn't implement RebootEscrow HAL");
+ }
+ return null;
+ }
+ }
+
+ RebootEscrowManager(Context context, Callbacks callbacks, LockSettingsStorage storage) {
+ this(new Injector(context), callbacks, storage);
+ }
+
+ @VisibleForTesting
+ RebootEscrowManager(Injector injector, Callbacks callbacks,
+ LockSettingsStorage storage) {
+ mInjector = injector;
+ mCallbacks = callbacks;
+ mStorage = storage;
+ mUserManager = injector.getUserManager();
+ }
+
+ void loadRebootEscrowDataIfAvailable() {
+ List<UserInfo> users = mUserManager.getUsers();
+ List<UserInfo> rebootEscrowUsers = new ArrayList<>();
+ for (UserInfo user : users) {
+ if (mCallbacks.isUserSecure(user.id) && mStorage.hasRebootEscrow(user.id)) {
+ rebootEscrowUsers.add(user);
+ }
+ }
+
+ if (rebootEscrowUsers.isEmpty()) {
+ return;
+ }
+
+ SecretKeySpec escrowKey = getAndClearRebootEscrowKey();
+ if (escrowKey == null) {
+ Slog.w(TAG, "Had reboot escrow data for users, but no key; removing escrow storage.");
+ for (UserInfo user : users) {
+ mStorage.removeRebootEscrow(user.id);
+ }
+ StatsLog.write(StatsLog.REBOOT_ESCROW_RECOVERY_REPORTED, false);
+ return;
+ }
+
+ boolean allUsersUnlocked = true;
+ for (UserInfo user : rebootEscrowUsers) {
+ allUsersUnlocked &= restoreRebootEscrowForUser(user.id, escrowKey);
+ }
+ StatsLog.write(StatsLog.REBOOT_ESCROW_RECOVERY_REPORTED, allUsersUnlocked);
+ }
+
+ private SecretKeySpec getAndClearRebootEscrowKey() {
+ IRebootEscrow rebootEscrow = mInjector.getRebootEscrow();
+ if (rebootEscrow == null) {
+ return null;
+ }
+
+ try {
+ byte[] escrowKeyBytes = rebootEscrow.retrieveKey();
+ if (escrowKeyBytes == null) {
+ Slog.w(TAG, "Had reboot escrow data for users, but could not retrieve key");
+ return null;
+ } else if (escrowKeyBytes.length != 32) {
+ Slog.e(TAG, "IRebootEscrow returned key of incorrect size "
+ + escrowKeyBytes.length);
+ return null;
+ }
+
+ // Make sure we didn't get the null key.
+ int zero = 0;
+ for (int i = 0; i < escrowKeyBytes.length; i++) {
+ zero |= escrowKeyBytes[i];
+ }
+ if (zero == 0) {
+ Slog.w(TAG, "IRebootEscrow returned an all-zeroes key");
+ return null;
+ }
+
+ // Overwrite the existing key with the null key
+ rebootEscrow.storeKey(new byte[32]);
+
+ return RebootEscrowData.fromKeyBytes(escrowKeyBytes);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Could not retrieve escrow data");
+ return null;
+ }
+ }
+
+ private boolean restoreRebootEscrowForUser(@UserIdInt int userId, SecretKeySpec escrowKey) {
+ if (!mStorage.hasRebootEscrow(userId)) {
+ return false;
+ }
+
+ try {
+ byte[] blob = mStorage.readRebootEscrow(userId);
+ mStorage.removeRebootEscrow(userId);
+
+ RebootEscrowData escrowData = RebootEscrowData.fromEncryptedData(escrowKey, blob);
+
+ mCallbacks.onRebootEscrowRestored(escrowData.getSpVersion(),
+ escrowData.getSyntheticPassword(), userId);
+ return true;
+ } catch (IOException e) {
+ Slog.w(TAG, "Could not load reboot escrow data for user " + userId, e);
+ }
+ return false;
+ }
+
+ void callToRebootEscrowIfNeeded(@UserIdInt int userId, byte spVersion,
+ byte[] syntheticPassword) {
+ if (!mRebootEscrowWanted.compareAndSet(true, false)) {
+ return;
+ }
+
+ IRebootEscrow rebootEscrow = mInjector.getRebootEscrow();
+ if (rebootEscrow == null) {
+ setRebootEscrowReady(false);
+ return;
+ }
+
+ final RebootEscrowData escrowData;
+ try {
+ escrowData = RebootEscrowData.fromSyntheticPassword(spVersion, syntheticPassword);
+ } catch (IOException e) {
+ setRebootEscrowReady(false);
+ Slog.w(TAG, "Could not escrow reboot data", e);
+ return;
+ }
+
+ mPendingRebootEscrowData = escrowData;
+ mStorage.writeRebootEscrow(userId, escrowData.getBlob());
+
+ setRebootEscrowReady(true);
+ }
+
+ private void clearRebootEscrowIfNeeded() {
+ mRebootEscrowWanted.set(false);
+ setRebootEscrowReady(false);
+
+ IRebootEscrow rebootEscrow = mInjector.getRebootEscrow();
+ if (rebootEscrow == null) {
+ return;
+ }
+
+ try {
+ rebootEscrow.storeKey(new byte[32]);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Could not call RebootEscrow HAL to shred key");
+ }
+
+ List<UserInfo> users = mUserManager.getUsers();
+ for (UserInfo user : users) {
+ mStorage.removeRebootEscrow(user.id);
+ }
+ }
+
+ boolean armRebootEscrowIfNeeded() {
+ if (!mRebootEscrowReady) {
+ return false;
+ }
+
+ IRebootEscrow rebootEscrow = mInjector.getRebootEscrow();
+ if (rebootEscrow == null) {
+ return false;
+ }
+
+ RebootEscrowData escrowData = mPendingRebootEscrowData;
+ if (escrowData == null) {
+ return false;
+ }
+
+ boolean armedRebootEscrow = false;
+ try {
+ rebootEscrow.storeKey(escrowData.getKey());
+ armedRebootEscrow = true;
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed escrow secret to RebootEscrow HAL", e);
+ }
+ return armedRebootEscrow;
+ }
+
+ private void setRebootEscrowReady(boolean ready) {
+ if (mRebootEscrowReady != ready) {
+ mRebootEscrowListener.onPreparedForReboot(ready);
+ }
+ mRebootEscrowReady = ready;
+ }
+
+ boolean prepareRebootEscrow() {
+ if (mInjector.getRebootEscrow() == null) {
+ return false;
+ }
+
+ clearRebootEscrowIfNeeded();
+ mRebootEscrowWanted.set(true);
+ return true;
+ }
+
+ boolean clearRebootEscrow() {
+ if (mInjector.getRebootEscrow() == null) {
+ return false;
+ }
+
+ clearRebootEscrowIfNeeded();
+ return true;
+ }
+
+ void setRebootEscrowListener(RebootEscrowListener listener) {
+ mRebootEscrowListener = listener;
+ }
+}
diff --git a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
index 0fe16be..b726e57 100644
--- a/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/services/core/java/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -38,7 +38,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.Preconditions;
import com.android.internal.widget.ICheckCredentialProgressCallback;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockscreenCredential;
@@ -285,6 +284,14 @@
public byte[] getSyntheticPassword() {
return mSyntheticPassword;
}
+
+ /**
+ * Returns the version of this AuthenticationToken for use with reconstructing
+ * this with a synthetic password version.
+ */
+ public byte getVersion() {
+ return mVersion;
+ }
}
static class PasswordData {
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
index 29338ba0..52750f3 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncTask.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.security.Scrypt;
import android.security.keystore.recovery.KeyChainProtectionParams;
import android.security.keystore.recovery.KeyChainSnapshot;
@@ -163,16 +164,28 @@
}
private void syncKeys() throws RemoteException {
+ int generation = mPlatformKeyManager.getGenerationId(mUserId);
if (mCredentialType == LockPatternUtils.CREDENTIAL_TYPE_NONE) {
// Application keys for the user will not be available for sync.
Log.w(TAG, "Credentials are not set for user " + mUserId);
- int generation = mPlatformKeyManager.getGenerationId(mUserId);
- mPlatformKeyManager.invalidatePlatformKey(mUserId, generation);
+ if (generation < PlatformKeyManager.MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED
+ || mUserId != UserHandle.USER_SYSTEM) {
+ // Only invalidate keys with legacy protection param.
+ mPlatformKeyManager.invalidatePlatformKey(mUserId, generation);
+ }
return;
}
if (isCustomLockScreen()) {
- Log.w(TAG, "Unsupported credential type " + mCredentialType + "for user " + mUserId);
- mRecoverableKeyStoreDb.invalidateKeysForUserIdOnCustomScreenLock(mUserId);
+ Log.w(TAG, "Unsupported credential type " + mCredentialType + " for user " + mUserId);
+ // Keys will be synced when user starts using non custom screen lock.
+ if (generation < PlatformKeyManager.MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED
+ || mUserId != UserHandle.USER_SYSTEM) {
+ mRecoverableKeyStoreDb.invalidateKeysForUserIdOnCustomScreenLock(mUserId);
+ }
+ return;
+ }
+ if (mPlatformKeyManager.isDeviceLocked(mUserId) && mUserId == UserHandle.USER_SYSTEM) {
+ Log.w(TAG, "Can't sync keys for locked user " + mUserId);
return;
}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
index 0ad6c2a..0761cde 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
@@ -67,8 +67,9 @@
* @hide
*/
public class PlatformKeyManager {
- private static final String TAG = "PlatformKeyManager";
+ static final int MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED = 1000000;
+ private static final String TAG = "PlatformKeyManager";
private static final String KEY_ALGORITHM = "AES";
private static final int KEY_SIZE_BITS = 256;
private static final String KEY_ALIAS_PREFIX =
@@ -131,14 +132,14 @@
/**
* Returns {@code true} if the platform key is available. A platform key won't be available if
- * the user has not set up a lock screen.
+ * device is locked.
*
* @param userId The ID of the user to whose lock screen the platform key must be bound.
*
* @hide
*/
- public boolean isAvailable(int userId) {
- return mContext.getSystemService(KeyguardManager.class).isDeviceSecure(userId);
+ public boolean isDeviceLocked(int userId) {
+ return mContext.getSystemService(KeyguardManager.class).isDeviceLocked(userId);
}
/**
@@ -169,7 +170,6 @@
* @param userId The ID of the user to whose lock screen the platform key must be bound.
* @throws NoSuchAlgorithmException if AES is unavailable - should never happen.
* @throws KeyStoreException if there is an error in AndroidKeyStore.
- * @throws InsecureUserException if the user does not have a lock screen set.
* @throws IOException if there was an issue with local database update.
* @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}.
*
@@ -177,13 +177,8 @@
*/
@VisibleForTesting
void regenerate(int userId)
- throws NoSuchAlgorithmException, KeyStoreException, InsecureUserException, IOException,
+ throws NoSuchAlgorithmException, KeyStoreException, IOException,
RemoteException {
- if (!isAvailable(userId)) {
- throw new InsecureUserException(String.format(
- Locale.US, "%d does not have a lock screen set.", userId));
- }
-
int generationId = getGenerationId(userId);
int nextId;
if (generationId == -1) {
@@ -192,6 +187,7 @@
invalidatePlatformKey(userId, generationId);
nextId = generationId + 1;
}
+ generationId = Math.max(generationId, MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED);
generateAndLoadKey(userId, nextId);
}
@@ -203,7 +199,6 @@
* @throws KeyStoreException if there was an AndroidKeyStore error.
* @throws UnrecoverableKeyException if the key could not be recovered.
* @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
- * @throws InsecureUserException if the user does not have a lock screen set.
* @throws IOException if there was an issue with local database update.
* @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}.
*
@@ -211,7 +206,7 @@
*/
public PlatformEncryptionKey getEncryptKey(int userId)
throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException,
- InsecureUserException, IOException, RemoteException {
+ IOException, RemoteException {
init(userId);
try {
// Try to see if the decryption key is still accessible before using the encryption key.
@@ -234,12 +229,11 @@
* @throws KeyStoreException if there was an AndroidKeyStore error.
* @throws UnrecoverableKeyException if the key could not be recovered.
* @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
- * @throws InsecureUserException if the user does not have a lock screen set.
*
* @hide
*/
private PlatformEncryptionKey getEncryptKeyInternal(int userId) throws KeyStoreException,
- UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException {
+ UnrecoverableKeyException, NoSuchAlgorithmException {
int generationId = getGenerationId(userId);
String alias = getEncryptAlias(userId, generationId);
if (!isKeyLoaded(userId, generationId)) {
@@ -258,7 +252,6 @@
* @throws KeyStoreException if there was an AndroidKeyStore error.
* @throws UnrecoverableKeyException if the key could not be recovered.
* @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
- * @throws InsecureUserException if the user does not have a lock screen set.
* @throws IOException if there was an issue with local database update.
* @throws RemoteException if there was an issue communicating with {@link IGateKeeperService}.
*
@@ -266,7 +259,7 @@
*/
public PlatformDecryptionKey getDecryptKey(int userId)
throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException,
- InsecureUserException, IOException, RemoteException {
+ IOException, RemoteException {
init(userId);
try {
PlatformDecryptionKey decryptionKey = getDecryptKeyInternal(userId);
@@ -288,12 +281,11 @@
* @throws KeyStoreException if there was an AndroidKeyStore error.
* @throws UnrecoverableKeyException if the key could not be recovered.
* @throws NoSuchAlgorithmException if AES is unavailable - should never occur.
- * @throws InsecureUserException if the user does not have a lock screen set.
*
* @hide
*/
private PlatformDecryptionKey getDecryptKeyInternal(int userId) throws KeyStoreException,
- UnrecoverableKeyException, NoSuchAlgorithmException, InsecureUserException {
+ UnrecoverableKeyException, NoSuchAlgorithmException {
int generationId = getGenerationId(userId);
String alias = getDecryptAlias(userId, generationId);
if (!isKeyLoaded(userId, generationId)) {
@@ -340,13 +332,8 @@
* @hide
*/
void init(int userId)
- throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException, IOException,
+ throws KeyStoreException, NoSuchAlgorithmException, IOException,
RemoteException {
- if (!isAvailable(userId)) {
- throw new InsecureUserException(String.format(
- Locale.US, "%d does not have a lock screen set.", userId));
- }
-
int generationId = getGenerationId(userId);
if (isKeyLoaded(userId, generationId)) {
Log.i(TAG, String.format(
@@ -363,6 +350,7 @@
generationId++;
}
+ generationId = Math.max(generationId, MIN_GENERATION_ID_FOR_UNLOCKED_DEVICE_REQUIRED);
generateAndLoadKey(userId, generationId);
}
@@ -440,12 +428,16 @@
KeyProtection.Builder decryptionKeyProtection =
new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
- .setUserAuthenticationRequired(true)
- .setUserAuthenticationValidityDurationSeconds(
- USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS)
.setBlockModes(KeyProperties.BLOCK_MODE_GCM)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE);
- if (userId != UserHandle.USER_SYSTEM) {
+ // Skip UserAuthenticationRequired for main user
+ if (userId == UserHandle.USER_SYSTEM) {
+ decryptionKeyProtection.setUnlockedDeviceRequired(true);
+ } else {
+ // With setUnlockedDeviceRequired, KeyStore thinks that device is locked .
+ decryptionKeyProtection.setUserAuthenticationRequired(true);
+ decryptionKeyProtection.setUserAuthenticationValidityDurationSeconds(
+ USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS);
// Bind decryption key to secondary profile lock screen secret.
long secureUserId = getGateKeeperService().getSecureUserId(userId);
// TODO(b/124095438): Propagate this failure instead of silently failing.
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
index 383d5cf..6d97ed7 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -19,7 +19,6 @@
import static android.security.keystore.recovery.RecoveryController.ERROR_BAD_CERTIFICATE_FORMAT;
import static android.security.keystore.recovery.RecoveryController.ERROR_DECRYPTION_FAILED;
import static android.security.keystore.recovery.RecoveryController.ERROR_DOWNGRADE_CERTIFICATE;
-import static android.security.keystore.recovery.RecoveryController.ERROR_INSECURE_USER;
import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_CERTIFICATE;
import static android.security.keystore.recovery.RecoveryController.ERROR_INVALID_KEY_FORMAT;
import static android.security.keystore.recovery.RecoveryController.ERROR_NO_SNAPSHOT_PENDING;
@@ -46,7 +45,6 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.HexDump;
-import com.android.internal.util.Preconditions;
import com.android.server.locksettings.recoverablekeystore.certificate.CertParsingException;
import com.android.server.locksettings.recoverablekeystore.certificate.CertUtils;
import com.android.server.locksettings.recoverablekeystore.certificate.CertValidationException;
@@ -76,8 +74,9 @@
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
-import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
import javax.crypto.AEADBadTagException;
@@ -89,13 +88,14 @@
*/
public class RecoverableKeyStoreManager {
private static final String TAG = "RecoverableKeyStoreMgr";
+ private static final long SYNC_DELAY_MILLIS = 2000;
private static RecoverableKeyStoreManager mInstance;
private final Context mContext;
private final RecoverableKeyStoreDb mDatabase;
private final RecoverySessionStorage mRecoverySessionStorage;
- private final ExecutorService mExecutorService;
+ private final ScheduledExecutorService mExecutorService;
private final RecoverySnapshotListenersStorage mListenersStorage;
private final RecoverableKeyGenerator mRecoverableKeyGenerator;
private final RecoverySnapshotStorage mSnapshotStorage;
@@ -136,7 +136,7 @@
context.getApplicationContext(),
db,
new RecoverySessionStorage(),
- Executors.newSingleThreadExecutor(),
+ Executors.newScheduledThreadPool(1),
snapshotStorage,
new RecoverySnapshotListenersStorage(),
platformKeyManager,
@@ -152,7 +152,7 @@
Context context,
RecoverableKeyStoreDb recoverableKeyStoreDb,
RecoverySessionStorage recoverySessionStorage,
- ExecutorService executorService,
+ ScheduledExecutorService executorService,
RecoverySnapshotStorage snapshotStorage,
RecoverySnapshotListenersStorage listenersStorage,
PlatformKeyManager platformKeyManager,
@@ -724,8 +724,6 @@
throw new RuntimeException(e);
} catch (KeyStoreException | UnrecoverableKeyException | IOException e) {
throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
- } catch (InsecureUserException e) {
- throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
}
try {
@@ -793,8 +791,6 @@
throw new RuntimeException(e);
} catch (KeyStoreException | UnrecoverableKeyException | IOException e) {
throw new ServiceSpecificException(ERROR_SERVICE_INTERNAL_ERROR, e.getMessage());
- } catch (InsecureUserException e) {
- throw new ServiceSpecificException(ERROR_INSECURE_USER, e.getMessage());
}
try {
@@ -915,7 +911,7 @@
int storedHashType, @NonNull byte[] credential, int userId) {
// So as not to block the critical path unlocking the phone, defer to another thread.
try {
- mExecutorService.execute(KeySyncTask.newInstance(
+ mExecutorService.schedule(KeySyncTask.newInstance(
mContext,
mDatabase,
mSnapshotStorage,
@@ -923,7 +919,10 @@
userId,
storedHashType,
credential,
- /*credentialUpdated=*/ false));
+ /*credentialUpdated=*/ false),
+ SYNC_DELAY_MILLIS,
+ TimeUnit.MILLISECONDS
+ );
} catch (NoSuchAlgorithmException e) {
Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e);
} catch (KeyStoreException e) {
@@ -947,7 +946,7 @@
int userId) {
// So as not to block the critical path unlocking the phone, defer to another thread.
try {
- mExecutorService.execute(KeySyncTask.newInstance(
+ mExecutorService.schedule(KeySyncTask.newInstance(
mContext,
mDatabase,
mSnapshotStorage,
@@ -955,7 +954,10 @@
userId,
storedHashType,
credential,
- /*credentialUpdated=*/ true));
+ /*credentialUpdated=*/ true),
+ SYNC_DELAY_MILLIS,
+ TimeUnit.MILLISECONDS
+ );
} catch (NoSuchAlgorithmException e) {
Log.wtf(TAG, "Should never happen - algorithm unavailable for KeySync", e);
} catch (KeyStoreException e) {
diff --git a/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java b/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
index 1d39177..b0bccb8 100644
--- a/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
+++ b/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
@@ -172,13 +172,14 @@
*/
public void cleanUpAudioPlaybackUids(int mediaButtonSessionUid) {
synchronized (mLock) {
- int userId = UserHandle.getUserId(mediaButtonSessionUid);
+ int userId = UserHandle.getUserHandleForUid(mediaButtonSessionUid).getIdentifier();
for (int i = mSortedAudioPlaybackClientUids.size() - 1; i >= 0; i--) {
if (mSortedAudioPlaybackClientUids.get(i) == mediaButtonSessionUid) {
break;
}
int uid = mSortedAudioPlaybackClientUids.get(i);
- if (userId == UserHandle.getUserId(uid) && !isPlaybackActive(uid)) {
+ if (userId == UserHandle.getUserHandleForUid(uid).getIdentifier()
+ && !isPlaybackActive(uid)) {
// Clean up unnecessary UIDs.
// It doesn't need to be managed profile aware because it's just to prevent
// the list from increasing indefinitely. The media button session updating
diff --git a/services/core/java/com/android/server/media/BluetoothRouteProvider.java b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
new file mode 100644
index 0000000..b67335a
--- /dev/null
+++ b/services/core/java/com/android/server/media/BluetoothRouteProvider.java
@@ -0,0 +1,324 @@
+/*
+ * 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.media;
+
+import android.annotation.NonNull;
+import android.bluetooth.BluetoothA2dp;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHearingAid;
+import android.bluetooth.BluetoothProfile;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.media.MediaRoute2Info;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseBooleanArray;
+
+import com.android.internal.R;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+class BluetoothRouteProvider {
+ private static final String TAG = "BTRouteProvider";
+ private static BluetoothRouteProvider sInstance;
+
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ final Map<String, BluetoothRouteInfo> mBluetoothRoutes = new HashMap<>();
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ BluetoothA2dp mA2dpProfile;
+ @SuppressWarnings("WeakerAccess") /* synthetic access */
+ BluetoothHearingAid mHearingAidProfile;
+
+ private final Context mContext;
+ private final BluetoothAdapter mBluetoothAdapter;
+ private final BluetoothRoutesUpdatedListener mListener;
+ private final Map<String, BluetoothEventReceiver> mEventReceiverMap = new HashMap<>();
+ private final IntentFilter mIntentFilter = new IntentFilter();
+ private final BroadcastReceiver mBroadcastReceiver = new BluetoothBroadcastReceiver();
+ private final BluetoothProfileListener mProfileListener = new BluetoothProfileListener();
+
+ private BluetoothDevice mActiveDevice = null;
+
+ static synchronized BluetoothRouteProvider getInstance(@NonNull Context context,
+ @NonNull BluetoothRoutesUpdatedListener listener) {
+ Objects.requireNonNull(context);
+ Objects.requireNonNull(listener);
+
+ if (sInstance == null) {
+ BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter();
+ if (btAdapter == null) {
+ return null;
+ }
+ sInstance = new BluetoothRouteProvider(context, btAdapter, listener);
+ }
+ return sInstance;
+ }
+
+ private BluetoothRouteProvider(Context context, BluetoothAdapter btAdapter,
+ BluetoothRoutesUpdatedListener listener) {
+ mContext = context;
+ mBluetoothAdapter = btAdapter;
+ mListener = listener;
+ buildBluetoothRoutes();
+
+ mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.A2DP);
+ mBluetoothAdapter.getProfileProxy(mContext, mProfileListener, BluetoothProfile.HEARING_AID);
+
+ // Bluetooth on/off broadcasts
+ addEventReceiver(BluetoothAdapter.ACTION_STATE_CHANGED, new AdapterStateChangedReceiver());
+
+ // Pairing broadcasts
+ addEventReceiver(BluetoothDevice.ACTION_BOND_STATE_CHANGED, new BondStateChangedReceiver());
+
+ DeviceStateChangedRecevier deviceStateChangedReceiver = new DeviceStateChangedRecevier();
+ addEventReceiver(BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED, deviceStateChangedReceiver);
+ addEventReceiver(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED, deviceStateChangedReceiver);
+ addEventReceiver(BluetoothHearingAid.ACTION_ACTIVE_DEVICE_CHANGED,
+ deviceStateChangedReceiver);
+ addEventReceiver(BluetoothHearingAid.ACTION_CONNECTION_STATE_CHANGED,
+ deviceStateChangedReceiver);
+
+ mContext.registerReceiver(mBroadcastReceiver, mIntentFilter, null, null);
+ }
+
+ private void addEventReceiver(String action, BluetoothEventReceiver eventReceiver) {
+ mEventReceiverMap.put(action, eventReceiver);
+ mIntentFilter.addAction(action);
+ }
+
+ private void buildBluetoothRoutes() {
+ mBluetoothRoutes.clear();
+ for (BluetoothDevice device : mBluetoothAdapter.getBondedDevices()) {
+ if (device.isConnected()) {
+ BluetoothRouteInfo newBtRoute = createBluetoothRoute(device);
+ mBluetoothRoutes.put(device.getAddress(), newBtRoute);
+ }
+ }
+ }
+
+ @NonNull List<MediaRoute2Info> getBluetoothRoutes() {
+ ArrayList<MediaRoute2Info> routes = new ArrayList<>();
+ for (BluetoothRouteInfo btRoute : mBluetoothRoutes.values()) {
+ routes.add(btRoute.route);
+ }
+ return routes;
+ }
+
+ private void notifyBluetoothRoutesUpdated() {
+ if (mListener != null) {
+ mListener.onBluetoothRoutesUpdated(getBluetoothRoutes());
+ }
+ }
+
+ private BluetoothRouteInfo createBluetoothRoute(BluetoothDevice device) {
+ BluetoothRouteInfo newBtRoute = new BluetoothRouteInfo();
+ newBtRoute.btDevice = device;
+ newBtRoute.route = new MediaRoute2Info.Builder(device.getAddress(), device.getName())
+ .addFeature(SystemMediaRoute2Provider.TYPE_LIVE_AUDIO)
+ .setConnectionState(MediaRoute2Info.CONNECTION_STATE_DISCONNECTED)
+ .setDescription(mContext.getResources().getText(
+ R.string.bluetooth_a2dp_audio_route_name).toString())
+ .build();
+ newBtRoute.connectedProfiles = new SparseBooleanArray();
+ return newBtRoute;
+ }
+
+ private void setRouteConnectionStateForDevice(BluetoothDevice device,
+ @MediaRoute2Info.ConnectionState int state) {
+ if (device == null) {
+ Log.w(TAG, "setRouteConnectionStateForDevice: device shouldn't be null");
+ return;
+ }
+ BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
+ if (btRoute == null) {
+ Log.w(TAG, "setRouteConnectionStateForDevice: route shouldn't be null");
+ return;
+ }
+ if (btRoute.route.getConnectionState() != state) {
+ btRoute.route = new MediaRoute2Info.Builder(btRoute.route)
+ .setConnectionState(state).build();
+ }
+ }
+
+ interface BluetoothRoutesUpdatedListener {
+ void onBluetoothRoutesUpdated(@NonNull List<MediaRoute2Info> routes);
+ }
+
+ private class BluetoothRouteInfo {
+ public BluetoothDevice btDevice;
+ public MediaRoute2Info route;
+ public SparseBooleanArray connectedProfiles;
+ }
+
+ // These callbacks run on the main thread.
+ private final class BluetoothProfileListener implements BluetoothProfile.ServiceListener {
+ public void onServiceConnected(int profile, BluetoothProfile proxy) {
+ switch (profile) {
+ case BluetoothProfile.A2DP:
+ mA2dpProfile = (BluetoothA2dp) proxy;
+ break;
+ case BluetoothProfile.HEARING_AID:
+ mHearingAidProfile = (BluetoothHearingAid) proxy;
+ break;
+ default:
+ return;
+ }
+ for (BluetoothDevice device : proxy.getConnectedDevices()) {
+ BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
+ if (btRoute == null) {
+ btRoute = createBluetoothRoute(device);
+ mBluetoothRoutes.put(device.getAddress(), btRoute);
+ }
+ btRoute.connectedProfiles.put(profile, true);
+ }
+ }
+
+ public void onServiceDisconnected(int profile) {
+ switch (profile) {
+ case BluetoothProfile.A2DP:
+ mA2dpProfile = null;
+ break;
+ case BluetoothProfile.HEARING_AID:
+ mHearingAidProfile = null;
+ break;
+ default:
+ return;
+ }
+ }
+ }
+ private class BluetoothBroadcastReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+
+ BluetoothEventReceiver receiver = mEventReceiverMap.get(action);
+ if (receiver != null) {
+ receiver.onReceive(context, intent, device);
+ }
+ }
+ }
+
+ private interface BluetoothEventReceiver {
+ void onReceive(Context context, Intent intent, BluetoothDevice device);
+ }
+
+ private class AdapterStateChangedReceiver implements BluetoothEventReceiver {
+ public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+ int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
+ if (state == BluetoothAdapter.STATE_OFF
+ || state == BluetoothAdapter.STATE_TURNING_OFF) {
+ mBluetoothRoutes.clear();
+ notifyBluetoothRoutesUpdated();
+ } else if (state == BluetoothAdapter.STATE_ON) {
+ buildBluetoothRoutes();
+ if (!mBluetoothRoutes.isEmpty()) {
+ notifyBluetoothRoutesUpdated();
+ }
+ }
+ }
+ }
+
+ private class BondStateChangedReceiver implements BluetoothEventReceiver {
+ public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+ int bondState = intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
+ BluetoothDevice.ERROR);
+ BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
+ if (bondState == BluetoothDevice.BOND_BONDED && btRoute == null) {
+ btRoute = createBluetoothRoute(device);
+ if (mA2dpProfile != null && mA2dpProfile.getConnectedDevices().contains(device)) {
+ btRoute.connectedProfiles.put(BluetoothProfile.A2DP, true);
+ }
+ if (mHearingAidProfile != null
+ && mHearingAidProfile.getConnectedDevices().contains(device)) {
+ btRoute.connectedProfiles.put(BluetoothProfile.HEARING_AID, true);
+ }
+ mBluetoothRoutes.put(device.getAddress(), btRoute);
+ notifyBluetoothRoutesUpdated();
+ } else if (bondState == BluetoothDevice.BOND_NONE
+ && mBluetoothRoutes.remove(device.getAddress()) != null) {
+ notifyBluetoothRoutesUpdated();
+ }
+ }
+ }
+
+ private class DeviceStateChangedRecevier implements BluetoothEventReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent, BluetoothDevice device) {
+ switch (intent.getAction()) {
+ case BluetoothA2dp.ACTION_ACTIVE_DEVICE_CHANGED:
+ String prevActiveDeviceAddress =
+ (mActiveDevice == null) ? null : mActiveDevice.getAddress();
+ String curActiveDeviceAddress =
+ (device == null) ? null : device.getAddress();
+ if (!TextUtils.equals(prevActiveDeviceAddress, curActiveDeviceAddress)) {
+ if (mActiveDevice != null) {
+ setRouteConnectionStateForDevice(mActiveDevice,
+ MediaRoute2Info.CONNECTION_STATE_DISCONNECTED);
+ }
+ if (device != null) {
+ setRouteConnectionStateForDevice(device,
+ MediaRoute2Info.CONNECTION_STATE_CONNECTED);
+ }
+ notifyBluetoothRoutesUpdated();
+ mActiveDevice = device;
+ }
+ break;
+ case BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED:
+ handleConnectionStateChanged(BluetoothProfile.A2DP, intent, device);
+ break;
+ }
+ }
+
+ private void handleConnectionStateChanged(int profile, Intent intent,
+ BluetoothDevice device) {
+ int state = intent.getIntExtra(BluetoothProfile.EXTRA_STATE, -1);
+ BluetoothRouteInfo btRoute = mBluetoothRoutes.get(device.getAddress());
+ if (state == BluetoothProfile.STATE_CONNECTED) {
+ if (btRoute == null) {
+ btRoute = createBluetoothRoute(device);
+ mBluetoothRoutes.put(device.getAddress(), btRoute);
+ btRoute.connectedProfiles.put(profile, true);
+ notifyBluetoothRoutesUpdated();
+ } else {
+ btRoute.connectedProfiles.put(profile, true);
+ }
+ } else if (state == BluetoothProfile.STATE_DISCONNECTING
+ || state == BluetoothProfile.STATE_DISCONNECTED) {
+ if (btRoute != null) {
+ btRoute.connectedProfiles.delete(profile);
+ if (btRoute.connectedProfiles.size() == 0) {
+ mBluetoothRoutes.remove(device.getAddress());
+ if (mActiveDevice != null
+ && TextUtils.equals(mActiveDevice.getAddress(),
+ device.getAddress())) {
+ mActiveDevice = null;
+ }
+ notifyBluetoothRoutesUpdated();
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java
index f11b70e..408c1c9 100644
--- a/services/core/java/com/android/server/media/MediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java
@@ -20,22 +20,25 @@
import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Intent;
-import android.media.MediaRoute2Info;
import android.media.MediaRoute2ProviderInfo;
-import android.media.RouteSessionInfo;
+import android.media.RoutingSessionInfo;
+
+import com.android.internal.annotations.GuardedBy;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
import java.util.Objects;
abstract class MediaRoute2Provider {
final ComponentName mComponentName;
final String mUniqueId;
+ final Object mLock = new Object();
Callback mCallback;
private volatile MediaRoute2ProviderInfo mProviderInfo;
- private volatile List<RouteSessionInfo> mSessionInfos = Collections.emptyList();
+
+ @GuardedBy("mLock")
+ final List<RoutingSessionInfo> mSessionInfos = new ArrayList<>();
MediaRoute2Provider(@NonNull ComponentName componentName) {
mComponentName = Objects.requireNonNull(componentName, "Component name must not be null.");
@@ -47,16 +50,16 @@
}
public abstract void requestCreateSession(String packageName, String routeId,
- String controlCategory, long requestId);
- public abstract void releaseSession(int sessionId);
+ String routeType, long requestId);
+ public abstract void releaseSession(String sessionId);
- public abstract void selectRoute(int sessionId, MediaRoute2Info route);
- public abstract void deselectRoute(int sessionId, MediaRoute2Info route);
- public abstract void transferToRoute(int sessionId, MediaRoute2Info route);
+ public abstract void selectRoute(String sessionId, String routeId);
+ public abstract void deselectRoute(String sessionId, String routeId);
+ public abstract void transferToRoute(String sessionId, String routeId);
- public abstract void sendControlRequest(MediaRoute2Info route, Intent request);
- public abstract void requestSetVolume(MediaRoute2Info route, int volume);
- public abstract void requestUpdateVolume(MediaRoute2Info route, int delta);
+ public abstract void sendControlRequest(String routeId, Intent request);
+ public abstract void requestSetVolume(String routeId, int volume);
+ public abstract void requestUpdateVolume(String routeId, int delta);
@NonNull
public String getUniqueId() {
@@ -69,12 +72,13 @@
}
@NonNull
- public List<RouteSessionInfo> getSessionInfos() {
- return mSessionInfos;
+ public List<RoutingSessionInfo> getSessionInfos() {
+ synchronized (mLock) {
+ return mSessionInfos;
+ }
}
- void setAndNotifyProviderState(MediaRoute2ProviderInfo providerInfo,
- List<RouteSessionInfo> sessionInfos) {
+ void setProviderState(MediaRoute2ProviderInfo providerInfo) {
if (providerInfo == null) {
mProviderInfo = null;
} else {
@@ -82,20 +86,19 @@
.setUniqueId(mUniqueId)
.build();
}
- List<RouteSessionInfo> sessionInfoWithProviderId = new ArrayList<RouteSessionInfo>();
- for (RouteSessionInfo sessionInfo : sessionInfos) {
- sessionInfoWithProviderId.add(
- new RouteSessionInfo.Builder(sessionInfo)
- .setProviderId(mUniqueId)
- .build());
- }
- mSessionInfos = sessionInfoWithProviderId;
+ }
+ void notifyProviderState() {
if (mCallback != null) {
mCallback.onProviderStateChanged(this);
}
}
+ void setAndNotifyProviderState(MediaRoute2ProviderInfo providerInfo) {
+ setProviderState(providerInfo);
+ notifyProviderState();
+ }
+
public boolean hasComponentName(String packageName, String className) {
return mComponentName.getPackageName().equals(packageName)
&& mComponentName.getClassName().equals(className);
@@ -104,9 +107,11 @@
public interface Callback {
void onProviderStateChanged(@Nullable MediaRoute2Provider provider);
void onSessionCreated(@NonNull MediaRoute2Provider provider,
- @Nullable RouteSessionInfo sessionInfo, long requestId);
- // TODO: Remove this when MediaRouter2ServiceImpl notifies clients of session changes.
- void onSessionInfoChanged(@NonNull MediaRoute2Provider provider,
- @NonNull RouteSessionInfo sessionInfo);
+ @Nullable RoutingSessionInfo sessionInfo, long requestId);
+ void onSessionCreationFailed(@NonNull MediaRoute2Provider provider, long requestId);
+ void onSessionUpdated(@NonNull MediaRoute2Provider provider,
+ @NonNull RoutingSessionInfo sessionInfo);
+ void onSessionReleased(@NonNull MediaRoute2Provider provider,
+ @NonNull RoutingSessionInfo sessionInfo);
}
}
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
index f8d8f9f..3840d02 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderProxy.java
@@ -17,17 +17,15 @@
package com.android.server.media;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.media.IMediaRoute2Provider;
import android.media.IMediaRoute2ProviderClient;
-import android.media.MediaRoute2Info;
import android.media.MediaRoute2ProviderInfo;
import android.media.MediaRoute2ProviderService;
-import android.media.RouteSessionInfo;
+import android.media.RoutingSessionInfo;
import android.os.Handler;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
@@ -38,8 +36,6 @@
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
-import java.util.Collections;
-import java.util.List;
import java.util.Objects;
/**
@@ -77,17 +73,17 @@
}
@Override
- public void requestCreateSession(String packageName, String routeId, String controlCategory,
+ public void requestCreateSession(String packageName, String routeId, String routeType,
long requestId) {
if (mConnectionReady) {
- mActiveConnection.requestCreateSession(packageName, routeId, controlCategory,
+ mActiveConnection.requestCreateSession(packageName, routeId, routeType,
requestId);
updateBinding();
}
}
@Override
- public void releaseSession(int sessionId) {
+ public void releaseSession(String sessionId) {
if (mConnectionReady) {
mActiveConnection.releaseSession(sessionId);
updateBinding();
@@ -95,46 +91,46 @@
}
@Override
- public void selectRoute(int sessionId, MediaRoute2Info route) {
+ public void selectRoute(String sessionId, String routeId) {
if (mConnectionReady) {
- mActiveConnection.selectRoute(sessionId, route.getId());
+ mActiveConnection.selectRoute(sessionId, routeId);
}
}
@Override
- public void deselectRoute(int sessionId, MediaRoute2Info route) {
+ public void deselectRoute(String sessionId, String routeId) {
if (mConnectionReady) {
- mActiveConnection.deselectRoute(sessionId, route.getId());
+ mActiveConnection.deselectRoute(sessionId, routeId);
}
}
@Override
- public void transferToRoute(int sessionId, MediaRoute2Info route) {
+ public void transferToRoute(String sessionId, String routeId) {
if (mConnectionReady) {
- mActiveConnection.transferToRoute(sessionId, route.getId());
+ mActiveConnection.transferToRoute(sessionId, routeId);
}
}
@Override
- public void sendControlRequest(MediaRoute2Info route, Intent request) {
+ public void sendControlRequest(String routeId, Intent request) {
if (mConnectionReady) {
- mActiveConnection.sendControlRequest(route.getId(), request);
+ mActiveConnection.sendControlRequest(routeId, request);
updateBinding();
}
}
@Override
- public void requestSetVolume(MediaRoute2Info route, int volume) {
+ public void requestSetVolume(String routeId, int volume) {
if (mConnectionReady) {
- mActiveConnection.requestSetVolume(route.getId(), volume);
+ mActiveConnection.requestSetVolume(routeId, volume);
updateBinding();
}
}
@Override
- public void requestUpdateVolume(MediaRoute2Info route, int delta) {
+ public void requestUpdateVolume(String routeId, int delta) {
if (mConnectionReady) {
- mActiveConnection.requestUpdateVolume(route.getId(), delta);
+ mActiveConnection.requestUpdateVolume(routeId, delta);
updateBinding();
}
}
@@ -271,39 +267,126 @@
}
private void onProviderStateUpdated(Connection connection,
- MediaRoute2ProviderInfo providerInfo, List<RouteSessionInfo> sessionInfos) {
+ MediaRoute2ProviderInfo providerInfo) {
if (mActiveConnection != connection) {
return;
}
if (DEBUG) {
Slog.d(TAG, this + ": State changed ");
}
- setAndNotifyProviderState(providerInfo, sessionInfos);
+ setAndNotifyProviderState(providerInfo);
}
- private void onSessionCreated(Connection connection, @Nullable RouteSessionInfo sessionInfo,
+ private void onSessionCreated(Connection connection, RoutingSessionInfo sessionInfo,
long requestId) {
if (mActiveConnection != connection) {
return;
}
- if (sessionInfo != null) {
- sessionInfo = new RouteSessionInfo.Builder(sessionInfo)
- .setProviderId(getUniqueId())
- .build();
+
+ if (sessionInfo == null) {
+ Slog.w(TAG, "onSessionCreated: Ignoring null sessionInfo sent from " + mComponentName);
+ return;
}
+
+ sessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
+ .setProviderId(getUniqueId())
+ .build();
+
+ boolean duplicateSessionAlreadyExists = false;
+ synchronized (mLock) {
+ for (int i = 0; i < mSessionInfos.size(); i++) {
+ if (mSessionInfos.get(i).getId().equals(sessionInfo.getId())) {
+ duplicateSessionAlreadyExists = true;
+ break;
+ }
+ }
+ mSessionInfos.add(sessionInfo);
+ }
+
+ if (duplicateSessionAlreadyExists) {
+ Slog.w(TAG, "onSessionCreated: Duplicate session already exists. Ignoring.");
+ return;
+ }
+
mCallback.onSessionCreated(this, sessionInfo, requestId);
}
- private void onSessionInfoChanged(Connection connection, RouteSessionInfo sessionInfo) {
+ private void onSessionCreationFailed(Connection connection, long requestId) {
+ if (mActiveConnection != connection) {
+ return;
+ }
+
+ if (requestId == MediaRoute2ProviderService.REQUEST_ID_UNKNOWN) {
+ Slog.w(TAG, "onSessionCreationFailed: Ignoring requestId REQUEST_ID_UNKNOWN");
+ return;
+ }
+
+ mCallback.onSessionCreationFailed(this, requestId);
+ }
+
+ private void onSessionUpdated(Connection connection, RoutingSessionInfo sessionInfo) {
if (mActiveConnection != connection) {
return;
}
if (sessionInfo == null) {
- Slog.w(TAG, "onSessionInfoChanged: Ignoring null sessionInfo sent from "
+ Slog.w(TAG, "onSessionUpdated: Ignoring null sessionInfo sent from "
+ mComponentName);
return;
}
- mCallback.onSessionInfoChanged(this, sessionInfo);
+
+ sessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
+ .setProviderId(getUniqueId())
+ .build();
+
+ boolean found = false;
+ synchronized (mLock) {
+ for (int i = 0; i < mSessionInfos.size(); i++) {
+ if (mSessionInfos.get(i).getId().equals(sessionInfo.getId())) {
+ mSessionInfos.set(i, sessionInfo);
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found) {
+ Slog.w(TAG, "onSessionUpdated: Matching session info not found");
+ return;
+ }
+
+ mCallback.onSessionUpdated(this, sessionInfo);
+ }
+
+ private void onSessionReleased(Connection connection, RoutingSessionInfo sessionInfo) {
+ if (mActiveConnection != connection) {
+ return;
+ }
+ if (sessionInfo == null) {
+ Slog.w(TAG, "onSessionReleased: Ignoring null sessionInfo sent from " + mComponentName);
+ return;
+ }
+
+ sessionInfo = new RoutingSessionInfo.Builder(sessionInfo)
+ .setProviderId(getUniqueId())
+ .build();
+
+ boolean found = false;
+ synchronized (mLock) {
+ for (int i = 0; i < mSessionInfos.size(); i++) {
+ if (mSessionInfos.get(i).getId().equals(sessionInfo.getId())) {
+ mSessionInfos.remove(i);
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (!found) {
+ Slog.w(TAG, "onSessionReleased: Matching session info not found");
+ return;
+ }
+
+ mCallback.onSessionReleased(this, sessionInfo);
}
private void disconnect() {
@@ -311,7 +394,7 @@
mConnectionReady = false;
mActiveConnection.dispose();
mActiveConnection = null;
- setAndNotifyProviderState(null, Collections.emptyList());
+ setAndNotifyProviderState(null);
}
}
@@ -346,17 +429,17 @@
mClient.dispose();
}
- public void requestCreateSession(String packageName, String routeId, String controlCategory,
+ public void requestCreateSession(String packageName, String routeId, String routeType,
long requestId) {
try {
mProvider.requestCreateSession(packageName, routeId,
- controlCategory, requestId);
+ routeType, requestId);
} catch (RemoteException ex) {
Slog.e(TAG, "Failed to deliver request to create a session.", ex);
}
}
- public void releaseSession(int sessionId) {
+ public void releaseSession(String sessionId) {
try {
mProvider.releaseSession(sessionId);
} catch (RemoteException ex) {
@@ -364,7 +447,7 @@
}
}
- public void selectRoute(int sessionId, String routeId) {
+ public void selectRoute(String sessionId, String routeId) {
try {
mProvider.selectRoute(sessionId, routeId);
} catch (RemoteException ex) {
@@ -372,7 +455,7 @@
}
}
- public void deselectRoute(int sessionId, String routeId) {
+ public void deselectRoute(String sessionId, String routeId) {
try {
mProvider.deselectRoute(sessionId, routeId);
} catch (RemoteException ex) {
@@ -380,7 +463,7 @@
}
}
- public void transferToRoute(int sessionId, String routeId) {
+ public void transferToRoute(String sessionId, String routeId) {
try {
mProvider.transferToRoute(sessionId, routeId);
} catch (RemoteException ex) {
@@ -417,19 +500,24 @@
mHandler.post(() -> onConnectionDied(Connection.this));
}
- void postProviderStateUpdated(MediaRoute2ProviderInfo providerInfo,
- List<RouteSessionInfo> sessionInfos) {
- mHandler.post(() -> onProviderStateUpdated(Connection.this,
- providerInfo, sessionInfos));
+ void postProviderStateUpdated(MediaRoute2ProviderInfo providerInfo) {
+ mHandler.post(() -> onProviderStateUpdated(Connection.this, providerInfo));
}
- void postSessionCreated(@Nullable RouteSessionInfo sessionInfo, long requestId) {
- mHandler.post(() -> onSessionCreated(Connection.this, sessionInfo,
- requestId));
+ void postSessionCreated(RoutingSessionInfo sessionInfo, long requestId) {
+ mHandler.post(() -> onSessionCreated(Connection.this, sessionInfo, requestId));
}
- void postSessionInfoChanged(RouteSessionInfo sessionInfo) {
- mHandler.post(() -> onSessionInfoChanged(Connection.this, sessionInfo));
+ void postSessionCreationFailed(long requestId) {
+ mHandler.post(() -> onSessionCreationFailed(Connection.this, requestId));
+ }
+
+ void postSessionUpdated(RoutingSessionInfo sessionInfo) {
+ mHandler.post(() -> onSessionUpdated(Connection.this, sessionInfo));
+ }
+
+ void postSessionReleased(RoutingSessionInfo sessionInfo) {
+ mHandler.post(() -> onSessionReleased(Connection.this, sessionInfo));
}
}
@@ -445,16 +533,15 @@
}
@Override
- public void updateState(MediaRoute2ProviderInfo providerInfo,
- List<RouteSessionInfo> sessionInfos) {
+ public void updateState(MediaRoute2ProviderInfo providerInfo) {
Connection connection = mConnectionRef.get();
if (connection != null) {
- connection.postProviderStateUpdated(providerInfo, sessionInfos);
+ connection.postProviderStateUpdated(providerInfo);
}
}
@Override
- public void notifySessionCreated(@Nullable RouteSessionInfo sessionInfo, long requestId) {
+ public void notifySessionCreated(RoutingSessionInfo sessionInfo, long requestId) {
Connection connection = mConnectionRef.get();
if (connection != null) {
connection.postSessionCreated(sessionInfo, requestId);
@@ -462,10 +549,26 @@
}
@Override
- public void notifySessionInfoChanged(RouteSessionInfo sessionInfo) {
+ public void notifySessionCreationFailed(long requestId) {
Connection connection = mConnectionRef.get();
if (connection != null) {
- connection.postSessionInfoChanged(sessionInfo);
+ connection.postSessionCreationFailed(requestId);
+ }
+ }
+
+ @Override
+ public void notifySessionUpdated(RoutingSessionInfo sessionInfo) {
+ Connection connection = mConnectionRef.get();
+ if (connection != null) {
+ connection.postSessionUpdated(sessionInfo);
+ }
+ }
+
+ @Override
+ public void notifySessionReleased(RoutingSessionInfo sessionInfo) {
+ Connection connection = mConnectionRef.get();
+ if (connection != null) {
+ connection.postSessionReleased(sessionInfo);
}
}
}
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 82d2250..161afb5 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -16,10 +16,12 @@
package com.android.server.media;
+import static android.media.MediaRouter2Utils.getOriginalId;
+import static android.media.MediaRouter2Utils.getProviderId;
+
import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
@@ -28,7 +30,9 @@
import android.media.IMediaRouter2Manager;
import android.media.MediaRoute2Info;
import android.media.MediaRoute2ProviderInfo;
-import android.media.RouteSessionInfo;
+import android.media.MediaRoute2ProviderService;
+import android.media.RouteDiscoveryPreference;
+import android.media.RoutingSessionInfo;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -79,7 +83,6 @@
@GuardedBy("mLock")
private int mCurrentUserId = -1;
-
MediaRouter2ServiceImpl(Context context) {
mContext = context;
}
@@ -87,19 +90,25 @@
@NonNull
public List<MediaRoute2Info> getSystemRoutes() {
final int uid = Binder.getCallingUid();
- final int userId = UserHandle.getUserId(uid);
+ final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
- Collection<MediaRoute2Info> systemRoutes;
- synchronized (mLock) {
- UserRecord userRecord = mUserRecords.get(userId);
- if (userRecord == null) {
- userRecord = new UserRecord(userId);
- mUserRecords.put(userId, userRecord);
- initializeUserLocked(userRecord);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ Collection<MediaRoute2Info> systemRoutes;
+ synchronized (mLock) {
+ UserRecord userRecord = getOrCreateUserRecordLocked(userId);
+ MediaRoute2ProviderInfo providerInfo =
+ userRecord.mHandler.mSystemProvider.getProviderInfo();
+ if (providerInfo != null) {
+ systemRoutes = providerInfo.getRoutes();
+ } else {
+ systemRoutes = Collections.emptyList();
+ }
}
- systemRoutes = userRecord.mHandler.mSystemProvider.getProviderInfo().getRoutes();
+ return new ArrayList<>(systemRoutes);
+ } finally {
+ Binder.restoreCallingIdentity(token);
}
- return new ArrayList<>(systemRoutes);
}
public void registerClient(@NonNull IMediaRouter2Client client,
@@ -108,7 +117,7 @@
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
- final int userId = UserHandle.getUserId(uid);
+ final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
final boolean trusted = mContext.checkCallingOrSelfPermission(
android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
== PackageManager.PERMISSION_GRANTED;
@@ -143,7 +152,7 @@
final int uid = Binder.getCallingUid();
final int pid = Binder.getCallingPid();
- final int userId = UserHandle.getUserId(uid);
+ final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
final long token = Binder.clearCallingIdentity();
try {
@@ -169,18 +178,18 @@
}
public void requestCreateSession(IMediaRouter2Client client, MediaRoute2Info route,
- String controlCategory, int requestId) {
+ String routeFeature, int requestId) {
Objects.requireNonNull(client, "client must not be null");
Objects.requireNonNull(route, "route must not be null");
- if (TextUtils.isEmpty(controlCategory)) {
- throw new IllegalArgumentException("controlCategory must not be empty");
+ if (TextUtils.isEmpty(routeFeature)) {
+ throw new IllegalArgumentException("routeFeature must not be empty");
}
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- requestCreateSessionLocked(client, route, controlCategory, requestId);
+ requestCreateSessionLocked(client, route, routeFeature, requestId);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -191,6 +200,9 @@
MediaRoute2Info route) {
Objects.requireNonNull(client, "client must not be null");
Objects.requireNonNull(route, "route must not be null");
+ if (TextUtils.isEmpty(uniqueSessionId)) {
+ throw new IllegalArgumentException("uniqueSessionId must not be empty");
+ }
final long token = Binder.clearCallingIdentity();
try {
@@ -207,6 +219,9 @@
MediaRoute2Info route) {
Objects.requireNonNull(client, "client must not be null");
Objects.requireNonNull(route, "route must not be null");
+ if (TextUtils.isEmpty(uniqueSessionId)) {
+ throw new IllegalArgumentException("uniqueSessionId must not be empty");
+ }
final long token = Binder.clearCallingIdentity();
try {
@@ -222,6 +237,9 @@
MediaRoute2Info route) {
Objects.requireNonNull(client, "client must not be null");
Objects.requireNonNull(route, "route must not be null");
+ if (TextUtils.isEmpty(uniqueSessionId)) {
+ throw new IllegalArgumentException("uniqueSessionId must not be empty");
+ }
final long token = Binder.clearCallingIdentity();
try {
@@ -233,6 +251,22 @@
}
}
+ public void releaseSession(IMediaRouter2Client client, String uniqueSessionId) {
+ Objects.requireNonNull(client, "client must not be null");
+ if (TextUtils.isEmpty(uniqueSessionId)) {
+ throw new IllegalArgumentException("uniqueSessionId must not be empty");
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ releaseSessionLocked(client, uniqueSessionId);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
public void sendControlRequest(@NonNull IMediaRouter2Client client,
@NonNull MediaRoute2Info route, @NonNull Intent request) {
Objects.requireNonNull(client, "client must not be null");
@@ -249,16 +283,16 @@
}
}
- public void setControlCategories(@NonNull IMediaRouter2Client client,
- @NonNull List<String> categories) {
+ public void setDiscoveryRequest2(@NonNull IMediaRouter2Client client,
+ @NonNull RouteDiscoveryPreference preference) {
Objects.requireNonNull(client, "client must not be null");
- Objects.requireNonNull(categories, "categories must not be null");
+ Objects.requireNonNull(preference, "preference must not be null");
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
Client2Record clientRecord = mAllClientRecords.get(client.asBinder());
- setControlCategoriesLocked(clientRecord, categories);
+ setDiscoveryRequestLocked(clientRecord, preference);
}
} finally {
Binder.restoreCallingIdentity(token);
@@ -336,7 +370,7 @@
}
@NonNull
- public List<RouteSessionInfo> getActiveSessions(IMediaRouter2Manager manager) {
+ public List<RoutingSessionInfo> getActiveSessions(IMediaRouter2Manager manager) {
final long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -387,12 +421,7 @@
int uid, int pid, String packageName, int userId, boolean trusted) {
final IBinder binder = client.asBinder();
if (mAllClientRecords.get(binder) == null) {
- UserRecord userRecord = mUserRecords.get(userId);
- if (userRecord == null) {
- userRecord = new UserRecord(userId);
- mUserRecords.put(userId, userRecord);
- initializeUserLocked(userRecord);
- }
+ UserRecord userRecord = getOrCreateUserRecordLocked(userId);
Client2Record clientRecord = new Client2Record(userRecord, client, uid, pid,
packageName, trusted);
try {
@@ -421,7 +450,7 @@
}
private void requestCreateSessionLocked(@NonNull IMediaRouter2Client client,
- @NonNull MediaRoute2Info route, @NonNull String controlCategory, long requestId) {
+ @NonNull MediaRoute2Info route, @NonNull String routeFeature, long requestId) {
final IBinder binder = client.asBinder();
final Client2Record clientRecord = mAllClientRecords.get(binder);
@@ -434,7 +463,7 @@
clientRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::requestCreateSessionOnHandler,
clientRecord.mUserRecord.mHandler,
- clientRecord, route, controlCategory, requestId));
+ clientRecord, route, routeFeature, requestId));
}
}
@@ -477,13 +506,26 @@
}
}
- private void setControlCategoriesLocked(Client2Record clientRecord, List<String> categories) {
+ private void releaseSessionLocked(@NonNull IMediaRouter2Client client, String uniqueSessionId) {
+ final IBinder binder = client.asBinder();
+ final Client2Record clientRecord = mAllClientRecords.get(binder);
+
if (clientRecord != null) {
- if (clientRecord.mControlCategories.equals(categories)) {
+ clientRecord.mUserRecord.mHandler.sendMessage(
+ obtainMessage(UserHandler::releaseSessionOnHandler,
+ clientRecord.mUserRecord.mHandler,
+ clientRecord, uniqueSessionId));
+ }
+ }
+
+ private void setDiscoveryRequestLocked(Client2Record clientRecord,
+ RouteDiscoveryPreference discoveryRequest) {
+ if (clientRecord != null) {
+ if (clientRecord.mDiscoveryPreference.equals(discoveryRequest)) {
return;
}
- clientRecord.mControlCategories = categories;
+ clientRecord.mDiscoveryPreference = discoveryRequest;
clientRecord.mUserRecord.mHandler.sendMessage(
obtainMessage(UserHandler::updateClientUsage,
clientRecord.mUserRecord.mHandler, clientRecord));
@@ -531,12 +573,7 @@
final IBinder binder = manager.asBinder();
ManagerRecord managerRecord = mAllManagerRecords.get(binder);
if (managerRecord == null) {
- boolean newUser = false;
- UserRecord userRecord = mUserRecords.get(userId);
- if (userRecord == null) {
- userRecord = new UserRecord(userId);
- newUser = true;
- }
+ UserRecord userRecord = getOrCreateUserRecordLocked(userId);
managerRecord = new ManagerRecord(userRecord, manager, uid, pid, packageName, trusted);
try {
binder.linkToDeath(managerRecord, 0);
@@ -544,11 +581,6 @@
throw new RuntimeException("Media router manager died prematurely.", ex);
}
- if (newUser) {
- mUserRecords.put(userId, userRecord);
- initializeUserLocked(userRecord);
- }
-
userRecord.mManagerRecords.add(managerRecord);
mAllManagerRecords.put(binder, managerRecord);
@@ -590,9 +622,9 @@
}
long uniqueRequestId = toUniqueRequestId(managerRecord.mClientId, requestId);
if (clientRecord != null && managerRecord.mTrusted) {
- //TODO: select category properly
+ //TODO: select route feature properly
requestCreateSessionLocked(clientRecord.mClient, route,
- route.getSupportedCategories().get(0), uniqueRequestId);
+ route.getFeatures().get(0), uniqueRequestId);
}
}
}
@@ -621,7 +653,7 @@
}
}
- private List<RouteSessionInfo> getActiveSessionsLocked(IMediaRouter2Manager manager) {
+ private List<RoutingSessionInfo> getActiveSessionsLocked(IMediaRouter2Manager manager) {
final IBinder binder = manager.asBinder();
ManagerRecord managerRecord = mAllManagerRecords.get(binder);
@@ -629,21 +661,24 @@
return Collections.emptyList();
}
- List<RouteSessionInfo> sessionInfos = new ArrayList<>();
+ List<RoutingSessionInfo> sessionInfos = new ArrayList<>();
for (MediaRoute2Provider provider : managerRecord.mUserRecord.mHandler.mMediaProviders) {
sessionInfos.addAll(provider.getSessionInfos());
}
return sessionInfos;
}
- private void initializeUserLocked(UserRecord userRecord) {
- if (DEBUG) {
- Slog.d(TAG, userRecord + ": Initialized");
+ private UserRecord getOrCreateUserRecordLocked(int userId) {
+ UserRecord userRecord = mUserRecords.get(userId);
+ if (userRecord == null) {
+ userRecord = new UserRecord(userId);
+ mUserRecords.put(userId, userRecord);
+ if (userId == mCurrentUserId) {
+ userRecord.mHandler.sendMessage(
+ obtainMessage(UserHandler::start, userRecord.mHandler));
+ }
}
- if (userRecord.mUserId == mCurrentUserId) {
- userRecord.mHandler.sendMessage(
- obtainMessage(UserHandler::start, userRecord.mHandler));
- }
+ return userRecord;
}
private void disposeUserIfNeededLocked(UserRecord userRecord) {
@@ -707,7 +742,7 @@
public final boolean mTrusted;
public final int mClientId;
- public List<String> mControlCategories;
+ public RouteDiscoveryPreference mDiscoveryPreference;
public boolean mIsManagerSelecting;
public MediaRoute2Info mSelectingRoute;
public MediaRoute2Info mSelectedRoute;
@@ -717,7 +752,7 @@
mUserRecord = userRecord;
mPackageName = packageName;
mSelectRouteSequenceNumbers = new ArrayList<>();
- mControlCategories = Collections.emptyList();
+ mDiscoveryPreference = RouteDiscoveryPreference.EMPTY;
mClient = client;
mUid = uid;
mPid = pid;
@@ -835,25 +870,39 @@
@Override
public void onProviderStateChanged(@NonNull MediaRoute2Provider provider) {
- sendMessage(PooledLambda.obtainMessage(UserHandler::updateProvider, this, provider));
+ sendMessage(PooledLambda.obtainMessage(UserHandler::onProviderStateChangedOnHandler,
+ this, provider));
}
@Override
public void onSessionCreated(@NonNull MediaRoute2Provider provider,
- @Nullable RouteSessionInfo sessionInfo, long requestId) {
- sendMessage(PooledLambda.obtainMessage(UserHandler::handleCreateSessionResultOnHandler,
+ @NonNull RoutingSessionInfo sessionInfo, long requestId) {
+ sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionCreatedOnHandler,
this, provider, sessionInfo, requestId));
}
@Override
- public void onSessionInfoChanged(@NonNull MediaRoute2Provider provider,
- @NonNull RouteSessionInfo sessionInfo) {
- sendMessage(PooledLambda.obtainMessage(UserHandler::updateSession,
+ public void onSessionCreationFailed(@NonNull MediaRoute2Provider provider, long requestId) {
+ sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionCreationFailedOnHandler,
+ this, provider, requestId));
+ }
+
+ @Override
+ public void onSessionUpdated(@NonNull MediaRoute2Provider provider,
+ @NonNull RoutingSessionInfo sessionInfo) {
+ sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionInfoChangedOnHandler,
+ this, provider, sessionInfo));
+ }
+
+ @Override
+ public void onSessionReleased(MediaRoute2Provider provider,
+ RoutingSessionInfo sessionInfo) {
+ sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionReleasedOnHandler,
this, provider, sessionInfo));
}
//TODO: notify session info updates
- private void updateProvider(MediaRoute2Provider provider) {
+ private void onProviderStateChangedOnHandler(MediaRoute2Provider provider) {
int providerIndex = getProviderInfoIndex(provider.getUniqueId());
MediaRoute2ProviderInfo providerInfo = provider.getProviderInfo();
MediaRoute2ProviderInfo prevInfo =
@@ -890,7 +939,7 @@
Slog.w(TAG, "Ignoring invalid route : " + route);
continue;
}
- MediaRoute2Info prevRoute = prevInfo.getRoute(route.getId());
+ MediaRoute2Info prevRoute = prevInfo.getRoute(route.getOriginalId());
if (prevRoute != null) {
if (!Objects.equals(prevRoute, route)) {
@@ -936,7 +985,7 @@
}
private void requestCreateSessionOnHandler(Client2Record clientRecord,
- MediaRoute2Info route, String controlCategory, long requestId) {
+ MediaRoute2Info route, String routeFeature, long requestId) {
final MediaRoute2Provider provider = findProvider(route.getProviderId());
if (provider == null) {
@@ -946,20 +995,20 @@
return;
}
- if (!route.getSupportedCategories().contains(controlCategory)) {
+ if (!route.getFeatures().contains(routeFeature)) {
Slog.w(TAG, "Ignoring session creation request since the given route=" + route
- + " doesn't support the given category=" + controlCategory);
+ + " doesn't support the given feature=" + routeFeature);
notifySessionCreationFailed(clientRecord, toClientRequestId(requestId));
return;
}
// TODO: Apply timeout for each request (How many seconds should we wait?)
SessionCreationRequest request = new SessionCreationRequest(
- clientRecord, route, controlCategory, requestId);
+ clientRecord, route, routeFeature, requestId);
mSessionCreationRequests.add(request);
- provider.requestCreateSession(clientRecord.mPackageName, route.getId(),
- controlCategory, requestId);
+ provider.requestCreateSession(clientRecord.mPackageName, route.getOriginalId(),
+ routeFeature, requestId);
}
private void selectRouteOnHandler(@NonNull Client2Record clientRecord,
@@ -975,7 +1024,7 @@
if (provider == null) {
return;
}
- provider.selectRoute(RouteSessionInfo.getSessionId(uniqueSessionId, providerId), route);
+ provider.selectRoute(getOriginalId(uniqueSessionId), route.getOriginalId());
}
private void deselectRouteOnHandler(@NonNull Client2Record clientRecord,
@@ -991,8 +1040,7 @@
if (provider == null) {
return;
}
- provider.deselectRoute(
- RouteSessionInfo.getSessionId(uniqueSessionId, providerId), route);
+ provider.deselectRoute(getOriginalId(uniqueSessionId), route.getOriginalId());
}
private void transferToRouteOnHandler(@NonNull Client2Record clientRecord,
@@ -1008,8 +1056,8 @@
if (provider == null) {
return;
}
- provider.transferToRoute(
- RouteSessionInfo.getSessionId(uniqueSessionId, providerId), route);
+ provider.transferToRoute(getOriginalId(uniqueSessionId),
+ route.getOriginalId());
}
private boolean checkArgumentsForSessionControl(@NonNull Client2Record clientRecord,
@@ -1040,20 +1088,64 @@
return false;
}
- try {
- RouteSessionInfo.getSessionId(uniqueSessionId, providerId);
- } catch (Exception ex) {
- Slog.w(TAG, "Failed to get int session id from unique session id. "
- + "uniqueSessionId=" + uniqueSessionId + " providerId=" + providerId);
+ final String sessionId = getOriginalId(uniqueSessionId);
+ if (sessionId == null) {
+ Slog.w(TAG, "Failed to get original session id from unique session id. "
+ + "uniqueSessionId=" + uniqueSessionId);
return false;
}
return true;
}
- private void handleCreateSessionResultOnHandler(
- @NonNull MediaRoute2Provider provider, @Nullable RouteSessionInfo sessionInfo,
- long requestId) {
+ private void releaseSessionOnHandler(@NonNull Client2Record clientRecord,
+ String uniqueSessionId) {
+ if (TextUtils.isEmpty(uniqueSessionId)) {
+ Slog.w(TAG, "Ignoring releasing session with empty unique session ID.");
+ return;
+ }
+
+ final Client2Record matchingRecord = mSessionToClientMap.get(uniqueSessionId);
+ if (matchingRecord != clientRecord) {
+ Slog.w(TAG, "Ignoring releasing session from non-matching client."
+ + " packageName=" + clientRecord.mPackageName
+ + " uniqueSessionId=" + uniqueSessionId);
+ return;
+ }
+
+ final String providerId = getProviderId(uniqueSessionId);
+ if (providerId == null) {
+ Slog.w(TAG, "Ignoring releasing session with invalid unique session ID. "
+ + "uniqueSessionId=" + uniqueSessionId);
+ return;
+ }
+
+ final String sessionId = getOriginalId(uniqueSessionId);
+ if (sessionId == null) {
+ Slog.w(TAG, "Ignoring releasing session with invalid unique session ID. "
+ + "uniqueSessionId=" + uniqueSessionId + " providerId=" + providerId);
+ return;
+ }
+
+ final MediaRoute2Provider provider = findProvider(providerId);
+ if (provider == null) {
+ Slog.w(TAG, "Ignoring releasing session since no provider found for given "
+ + "providerId=" + providerId);
+ return;
+ }
+
+ provider.releaseSession(sessionId);
+ }
+
+ private void onSessionCreatedOnHandler(@NonNull MediaRoute2Provider provider,
+ @NonNull RoutingSessionInfo sessionInfo, long requestId) {
+
+ if (requestId == MediaRoute2ProviderService.REQUEST_ID_UNKNOWN) {
+ // The session is created without any matching request.
+ // TODO: Tell managers for the session creation
+ return;
+ }
+
SessionCreationRequest matchingRequest = null;
for (SessionCreationRequest request : mSessionCreationRequests) {
@@ -1081,16 +1173,16 @@
}
String originalRouteId = matchingRequest.mRoute.getId();
- String originalCategory = matchingRequest.mControlCategory;
+ String originalRouteFeature = matchingRequest.mRouteFeature;
Client2Record client2Record = matchingRequest.mClientRecord;
if (!sessionInfo.getSelectedRoutes().contains(originalRouteId)
- || !TextUtils.equals(originalCategory,
- sessionInfo.getControlCategory())) {
+ || !TextUtils.equals(originalRouteFeature,
+ sessionInfo.getRouteFeature())) {
Slog.w(TAG, "Created session doesn't match the original request."
+ " originalRouteId=" + originalRouteId
- + ", originalCategory=" + originalCategory + ", requestId=" + requestId
- + ", sessionInfo=" + sessionInfo);
+ + ", originalRouteFeature=" + originalRouteFeature
+ + ", requestId=" + requestId + ", sessionInfo=" + sessionInfo);
notifySessionCreationFailed(matchingRequest.mClientRecord,
toClientRequestId(requestId));
return;
@@ -1099,29 +1191,63 @@
// Succeeded
notifySessionCreated(matchingRequest.mClientRecord,
sessionInfo, toClientRequestId(requestId));
- mSessionToClientMap.put(sessionInfo.getUniqueSessionId(), client2Record);
+ mSessionToClientMap.put(sessionInfo.getId(), client2Record);
// TODO: Tell managers for the session creation
}
- private void updateSession(@NonNull MediaRoute2Provider provider,
- @NonNull RouteSessionInfo sessionInfo) {
- RouteSessionInfo sessionInfoWithProviderId = new RouteSessionInfo.Builder(sessionInfo)
- .setProviderId(provider.getUniqueId())
- .build();
+ private void onSessionCreationFailedOnHandler(@NonNull MediaRoute2Provider provider,
+ long requestId) {
+ SessionCreationRequest matchingRequest = null;
+
+ for (SessionCreationRequest request : mSessionCreationRequests) {
+ if (request.mRequestId == requestId
+ && TextUtils.equals(
+ request.mRoute.getProviderId(), provider.getUniqueId())) {
+ matchingRequest = request;
+ break;
+ }
+ }
+
+ if (matchingRequest == null) {
+ Slog.w(TAG, "Ignoring session creation failed result for unknown request. "
+ + "requestId=" + requestId);
+ return;
+ }
+
+ mSessionCreationRequests.remove(matchingRequest);
+ notifySessionCreationFailed(matchingRequest.mClientRecord,
+ toClientRequestId(requestId));
+ }
+
+ private void onSessionInfoChangedOnHandler(@NonNull MediaRoute2Provider provider,
+ @NonNull RoutingSessionInfo sessionInfo) {
Client2Record client2Record = mSessionToClientMap.get(
- sessionInfoWithProviderId.getUniqueSessionId());
+ sessionInfo.getId());
if (client2Record == null) {
- Slog.w(TAG, "No matching client found for session=" + sessionInfoWithProviderId);
+ Slog.w(TAG, "No matching client found for session=" + sessionInfo);
// TODO: Tell managers for the session update
return;
}
- notifySessionInfoChanged(client2Record, sessionInfoWithProviderId);
+ notifySessionInfoChanged(client2Record, sessionInfo);
// TODO: Tell managers for the session update
}
- private void notifySessionCreated(Client2Record clientRecord, RouteSessionInfo sessionInfo,
- int requestId) {
+ private void onSessionReleasedOnHandler(@NonNull MediaRoute2Provider provider,
+ @NonNull RoutingSessionInfo sessionInfo) {
+
+ Client2Record client2Record = mSessionToClientMap.get(sessionInfo.getId());
+ if (client2Record == null) {
+ Slog.w(TAG, "No matching client found for session=" + sessionInfo);
+ // TODO: Tell managers for the session release
+ return;
+ }
+ notifySessionReleased(client2Record, sessionInfo);
+ // TODO: Tell managers for the session release
+ }
+
+ private void notifySessionCreated(Client2Record clientRecord,
+ RoutingSessionInfo sessionInfo, int requestId) {
try {
clientRecord.mClient.notifySessionCreated(sessionInfo, requestId);
} catch (RemoteException ex) {
@@ -1140,7 +1266,7 @@
}
private void notifySessionInfoChanged(Client2Record clientRecord,
- RouteSessionInfo sessionInfo) {
+ RoutingSessionInfo sessionInfo) {
try {
clientRecord.mClient.notifySessionInfoChanged(sessionInfo);
} catch (RemoteException ex) {
@@ -1149,24 +1275,34 @@
}
}
+ private void notifySessionReleased(Client2Record clientRecord,
+ RoutingSessionInfo sessionInfo) {
+ try {
+ clientRecord.mClient.notifySessionReleased(sessionInfo);
+ } catch (RemoteException ex) {
+ Slog.w(TAG, "Failed to notify client of the session release."
+ + " Client probably died.", ex);
+ }
+ }
+
private void sendControlRequest(MediaRoute2Info route, Intent request) {
final MediaRoute2Provider provider = findProvider(route.getProviderId());
if (provider != null) {
- provider.sendControlRequest(route, request);
+ provider.sendControlRequest(route.getOriginalId(), request);
}
}
private void requestSetVolume(MediaRoute2Info route, int volume) {
final MediaRoute2Provider provider = findProvider(route.getProviderId());
if (provider != null) {
- provider.requestSetVolume(route, volume);
+ provider.requestSetVolume(route.getOriginalId(), volume);
}
}
private void requestUpdateVolume(MediaRoute2Info route, int delta) {
final MediaRoute2Provider provider = findProvider(route.getProviderId());
if (provider != null) {
- provider.requestUpdateVolume(route, delta);
+ provider.requestUpdateVolume(route.getOriginalId(), delta);
}
}
@@ -1314,8 +1450,8 @@
try {
manager.notifyRouteSelected(clientRecord.mPackageName,
clientRecord.mSelectedRoute);
- manager.notifyControlCategoriesChanged(clientRecord.mPackageName,
- clientRecord.mControlCategories);
+ manager.notifyPreferredFeaturesChanged(clientRecord.mPackageName,
+ clientRecord.mDiscoveryPreference.getPreferredFeatures());
} catch (RemoteException ex) {
Slog.w(TAG, "Failed to update client usage. Manager probably died.", ex);
}
@@ -1334,15 +1470,15 @@
final class SessionCreationRequest {
public final Client2Record mClientRecord;
public final MediaRoute2Info mRoute;
- public final String mControlCategory;
+ public final String mRouteFeature;
public final long mRequestId;
SessionCreationRequest(@NonNull Client2Record clientRecord,
@NonNull MediaRoute2Info route,
- @NonNull String controlCategory, long requestId) {
+ @NonNull String routeFeature, long requestId) {
mClientRecord = clientRecord;
mRoute = route;
- mControlCategory = controlCategory;
+ mRouteFeature = routeFeature;
mRequestId = requestId;
}
}
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index 3e2bf4e..c80a898 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -39,7 +39,8 @@
import android.media.MediaRouterClientState;
import android.media.RemoteDisplayState;
import android.media.RemoteDisplayState.RemoteDisplayInfo;
-import android.media.RouteSessionInfo;
+import android.media.RouteDiscoveryPreference;
+import android.media.RoutingSessionInfo;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -459,8 +460,8 @@
// Binder call
@Override
public void requestCreateSession(IMediaRouter2Client client, MediaRoute2Info route,
- String controlCategory, int requestId) {
- mService2.requestCreateSession(client, route, controlCategory, requestId);
+ String routeType, int requestId) {
+ mService2.requestCreateSession(client, route, routeType, requestId);
}
// Binder call
@@ -484,6 +485,12 @@
// Binder call
@Override
+ public void releaseSession(IMediaRouter2Client client, String sessionId) {
+ mService2.releaseSession(client, sessionId);
+ }
+
+ // Binder call
+ @Override
public void sendControlRequest(IMediaRouter2Client client, MediaRoute2Info route,
Intent request) {
mService2.sendControlRequest(client, route, request);
@@ -513,8 +520,8 @@
}
// Binder call
@Override
- public void setControlCategories(IMediaRouter2Client client, List<String> categories) {
- mService2.setControlCategories(client, categories);
+ public void setDiscoveryRequest2(IMediaRouter2Client client, RouteDiscoveryPreference request) {
+ mService2.setDiscoveryRequest2(client, request);
}
// Binder call
@@ -545,7 +552,7 @@
// Binder call
@Override
- public List<RouteSessionInfo> getActiveSessions(IMediaRouter2Manager manager) {
+ public List<RoutingSessionInfo> getActiveSessions(IMediaRouter2Manager manager) {
return mService2.getActiveSessions(manager);
}
@@ -572,7 +579,8 @@
void restoreRoute(int uid) {
ClientRecord clientRecord = null;
synchronized (mLock) {
- UserRecord userRecord = mUserRecords.get(UserHandle.getUserId(uid));
+ UserRecord userRecord = mUserRecords.get(
+ UserHandle.getUserHandleForUid(uid).getIdentifier());
if (userRecord != null && userRecord.mClientRecords != null) {
for (ClientRecord cr : userRecord.mClientRecords) {
if (validatePackageName(uid, cr.mPackageName)) {
diff --git a/services/core/java/com/android/server/media/MediaSession2Record.java b/services/core/java/com/android/server/media/MediaSession2Record.java
new file mode 100644
index 0000000..b21d2e7
--- /dev/null
+++ b/services/core/java/com/android/server/media/MediaSession2Record.java
@@ -0,0 +1,179 @@
+/*
+ * 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 com.android.server.media;
+
+import android.media.MediaController2;
+import android.media.Session2CommandGroup;
+import android.media.Session2Token;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.Looper;
+import android.os.ResultReceiver;
+import android.os.UserHandle;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+
+/**
+ * Keeps the record of {@link Session2Token} helps to send command to the corresponding session.
+ */
+// TODO(jaewan): Do not call service method directly -- introduce listener instead.
+public class MediaSession2Record implements MediaSessionRecordImpl {
+ private static final String TAG = "MediaSession2Record";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private final Session2Token mSessionToken;
+ @GuardedBy("mLock")
+ private final HandlerExecutor mHandlerExecutor;
+ @GuardedBy("mLock")
+ private final MediaController2 mController;
+ @GuardedBy("mLock")
+ private final MediaSessionService mService;
+ @GuardedBy("mLock")
+ private boolean mIsConnected;
+
+ public MediaSession2Record(Session2Token sessionToken, MediaSessionService service,
+ Looper handlerLooper) {
+ mSessionToken = sessionToken;
+ mService = service;
+ mHandlerExecutor = new HandlerExecutor(new Handler(handlerLooper));
+ mController = new MediaController2.Builder(service.getContext(), sessionToken)
+ .setControllerCallback(mHandlerExecutor, new Controller2Callback())
+ .build();
+ }
+
+ @Override
+ public String getPackageName() {
+ return mSessionToken.getPackageName();
+ }
+
+ public Session2Token getSession2Token() {
+ return mSessionToken;
+ }
+
+ @Override
+ public int getUid() {
+ return mSessionToken.getUid();
+ }
+
+ @Override
+ public int getUserId() {
+ return UserHandle.getUserHandleForUid(mSessionToken.getUid()).getIdentifier();
+ }
+
+ @Override
+ public boolean isSystemPriority() {
+ // System priority session is currently only allowed for telephony, and it's OK to stick to
+ // the media1 API at this moment.
+ return false;
+ }
+
+ @Override
+ public void adjustVolume(String packageName, String opPackageName, int pid, int uid,
+ boolean asSystemService, int direction, int flags, boolean useSuggested) {
+ // TODO(jaewan): Add API to adjust volume.
+ }
+
+ @Override
+ public boolean isActive() {
+ synchronized (mLock) {
+ return mIsConnected;
+ }
+ }
+
+ @Override
+ public boolean checkPlaybackActiveState(boolean expected) {
+ synchronized (mLock) {
+ return mIsConnected && mController.isPlaybackActive() == expected;
+ }
+ }
+
+ @Override
+ public boolean isPlaybackTypeLocal() {
+ // TODO(jaewan): Implement -- need API to know whether the playback is remote or local.
+ return true;
+ }
+
+ @Override
+ public void close() {
+ synchronized (mLock) {
+ // Call close regardless of the mIsAvailable. This may be called when it's not yet
+ // connected.
+ mController.close();
+ }
+ }
+
+ @Override
+ public boolean sendMediaButton(String packageName, int pid, int uid, boolean asSystemService,
+ KeyEvent ke, int sequenceId, ResultReceiver cb) {
+ // TODO(jaewan): Implement.
+ return false;
+ }
+
+ @Override
+ public void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "token=" + mSessionToken);
+ pw.println(prefix + "controller=" + mController);
+
+ final String indent = prefix + " ";
+ pw.println(indent + "playbackActive=" + mController.isPlaybackActive());
+ }
+
+ @Override
+ public String toString() {
+ // TODO(jaewan): Also add getId().
+ return getPackageName() + " (userId=" + getUserId() + ")";
+ }
+
+ private class Controller2Callback extends MediaController2.ControllerCallback {
+ @Override
+ public void onConnected(MediaController2 controller, Session2CommandGroup allowedCommands) {
+ if (DEBUG) {
+ Log.d(TAG, "connected to " + mSessionToken + ", allowed=" + allowedCommands);
+ }
+ synchronized (mLock) {
+ mIsConnected = true;
+ }
+ mService.onSessionActiveStateChanged(MediaSession2Record.this);
+ }
+
+ @Override
+ public void onDisconnected(MediaController2 controller) {
+ if (DEBUG) {
+ Log.d(TAG, "disconnected from " + mSessionToken);
+ }
+ synchronized (mLock) {
+ mIsConnected = false;
+ }
+ mService.onSessionDied(MediaSession2Record.this);
+ }
+
+ @Override
+ public void onPlaybackActiveChanged(MediaController2 controller, boolean playbackActive) {
+ if (DEBUG) {
+ Log.d(TAG, "playback active changed, " + mSessionToken + ", active="
+ + playbackActive);
+ }
+ mService.onSessionPlaybackStateChanged(MediaSession2Record.this, playbackActive);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java
index aa24ed2..1905571 100644
--- a/services/core/java/com/android/server/media/MediaSessionRecord.java
+++ b/services/core/java/com/android/server/media/MediaSessionRecord.java
@@ -56,13 +56,15 @@
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
/**
* This is the system implementation of a Session. Apps will interact with the
* MediaSession wrapper class instead.
*/
-public class MediaSessionRecord implements IBinder.DeathRecipient, AutoCloseable {
+// TODO(jaewan): Do not call service method directly -- introduce listener instead.
+public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionRecordImpl {
private static final String TAG = "MediaSessionRecord";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -72,6 +74,24 @@
*/
private static final int OPTIMISTIC_VOLUME_TIMEOUT = 1000;
+ /**
+ * These are states that usually indicate the user took an action and should
+ * bump priority regardless of the old state.
+ */
+ private static final List<Integer> ALWAYS_PRIORITY_STATES = Arrays.asList(
+ PlaybackState.STATE_FAST_FORWARDING,
+ PlaybackState.STATE_REWINDING,
+ PlaybackState.STATE_SKIPPING_TO_PREVIOUS,
+ PlaybackState.STATE_SKIPPING_TO_NEXT);
+ /**
+ * These are states that usually indicate the user took an action if they
+ * were entered from a non-priority state.
+ */
+ private static final List<Integer> TRANSITION_PRIORITY_STATES = Arrays.asList(
+ PlaybackState.STATE_BUFFERING,
+ PlaybackState.STATE_CONNECTING,
+ PlaybackState.STATE_PLAYING);
+
private final MessageHandler mHandler;
private final int mOwnerPid;
@@ -115,6 +135,7 @@
private int mMaxVolume = 0;
private int mCurrentVolume = 0;
private int mOptimisticVolume = -1;
+ private String mVolumeControlId;
// End volume handling fields
private boolean mIsActive = false;
@@ -170,6 +191,7 @@
*
* @return Info that identifies this session.
*/
+ @Override
public String getPackageName() {
return mPackageName;
}
@@ -188,6 +210,7 @@
*
* @return The UID for this session.
*/
+ @Override
public int getUid() {
return mOwnerUid;
}
@@ -197,6 +220,7 @@
*
* @return The user id for this session.
*/
+ @Override
public int getUserId() {
return mUserId;
}
@@ -207,6 +231,7 @@
*
* @return True if this is a system priority session, false otherwise
*/
+ @Override
public boolean isSystemPriority() {
return (mFlags & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0;
}
@@ -220,7 +245,6 @@
* @param opPackageName The op package that made the original volume request.
* @param pid The pid that made the original volume request.
* @param uid The uid that made the original volume request.
- * @param caller caller binder. can be {@code null} if it's from the volume key.
* @param asSystemService {@code true} if the event sent to the session as if it was come from
* the system service instead of the app process. This helps sessions to distinguish
* between the key injection by the app and key events from the hardware devices.
@@ -318,9 +342,13 @@
/**
* Check if this session has been set to active by the app.
+ * <p>
+ * It's not used to prioritize sessions for dispatching media keys since API 26, but still used
+ * to filter session list in MediaSessionManager#getActiveSessions().
*
* @return True if the session is active, false otherwise.
*/
+ @Override
public boolean isActive() {
return mIsActive && !mDestroyed;
}
@@ -333,6 +361,7 @@
* @param expected True if playback is expected to be active. false otherwise.
* @return True if the session's playback matches with the expectation. false otherwise.
*/
+ @Override
public boolean checkPlaybackActiveState(boolean expected) {
if (mPlaybackState == null) {
return false;
@@ -345,13 +374,14 @@
*
* @return {@code true} if the playback is local.
*/
- public boolean isPlaybackLocal() {
+ @Override
+ public boolean isPlaybackTypeLocal() {
return mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL;
}
@Override
public void binderDied() {
- mService.sessionDied(this);
+ mService.onSessionDied(this);
}
/**
@@ -383,7 +413,7 @@
* @param sequenceId (optional) sequence id. Use this only when a wake lock is needed.
* @param cb (optional) result receiver to receive callback. Use this only when a wake lock is
* needed.
- * @return {@code true} if the attempt to send media button was successfuly.
+ * @return {@code true} if the attempt to send media button was successfully.
* {@code false} otherwise.
*/
public boolean sendMediaButton(String packageName, int pid, int uid, boolean asSystemService,
@@ -392,6 +422,7 @@
cb);
}
+ @Override
public void dump(PrintWriter pw, String prefix) {
pw.println(prefix + mTag + " " + this);
@@ -684,7 +715,7 @@
if (mVolumeType == PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
int current = mOptimisticVolume != -1 ? mOptimisticVolume : mCurrentVolume;
return new PlaybackInfo(mVolumeType, mVolumeControlType, mMaxVolume, current,
- mAudioAttrs);
+ mAudioAttrs, mVolumeControlId);
}
volumeType = mVolumeType;
attributes = mAudioAttrs;
@@ -693,7 +724,7 @@
int max = mAudioManager.getStreamMaxVolume(stream);
int current = mAudioManager.getStreamVolume(stream);
return new PlaybackInfo(volumeType, VolumeProvider.VOLUME_CONTROL_ABSOLUTE, max,
- current, attributes);
+ current, attributes, null);
}
private final Runnable mClearOptimisticVolumeRunnable = new Runnable() {
@@ -712,7 +743,7 @@
public void destroySession() throws RemoteException {
final long token = Binder.clearCallingIdentity();
try {
- mService.destroySession(MediaSessionRecord.this);
+ mService.onSessionDied(MediaSessionRecord.this);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -734,7 +765,7 @@
mIsActive = active;
final long token = Binder.clearCallingIdentity();
try {
- mService.updateSession(MediaSessionRecord.this);
+ mService.onSessionActiveStateChanged(MediaSessionRecord.this);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -801,12 +832,16 @@
? PlaybackState.STATE_NONE : mPlaybackState.getState();
int newState = state == null
? PlaybackState.STATE_NONE : state.getState();
+ boolean shouldUpdatePriority = ALWAYS_PRIORITY_STATES.contains(newState)
+ || (!TRANSITION_PRIORITY_STATES.contains(oldState)
+ && TRANSITION_PRIORITY_STATES.contains(newState));
synchronized (mLock) {
mPlaybackState = state;
}
final long token = Binder.clearCallingIdentity();
try {
- mService.onSessionPlaystateChanged(MediaSessionRecord.this, oldState, newState);
+ mService.onSessionPlaybackStateChanged(
+ MediaSessionRecord.this, shouldUpdatePriority);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -852,6 +887,7 @@
synchronized (mLock) {
typeChanged = mVolumeType == PlaybackInfo.PLAYBACK_TYPE_REMOTE;
mVolumeType = PlaybackInfo.PLAYBACK_TYPE_LOCAL;
+ mVolumeControlId = null;
if (attributes != null) {
mAudioAttrs = attributes;
} else {
@@ -870,13 +906,15 @@
}
@Override
- public void setPlaybackToRemote(int control, int max) throws RemoteException {
+ public void setPlaybackToRemote(int control, int max, String controlId)
+ throws RemoteException {
boolean typeChanged;
synchronized (mLock) {
typeChanged = mVolumeType == PlaybackInfo.PLAYBACK_TYPE_LOCAL;
mVolumeType = PlaybackInfo.PLAYBACK_TYPE_REMOTE;
mVolumeControlType = control;
mMaxVolume = max;
+ mVolumeControlId = controlId;
}
if (typeChanged) {
final long token = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/media/MediaSessionRecordImpl.java b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
new file mode 100644
index 0000000..2cde89a7
--- /dev/null
+++ b/services/core/java/com/android/server/media/MediaSessionRecordImpl.java
@@ -0,0 +1,143 @@
+/*
+ * 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 com.android.server.media;
+
+import android.media.AudioManager;
+import android.os.ResultReceiver;
+import android.view.KeyEvent;
+
+import java.io.PrintWriter;
+
+/**
+ * Common interfaces between {@link MediaSessionRecord} and {@link MediaSession2Record}.
+ */
+public interface MediaSessionRecordImpl extends AutoCloseable {
+
+ /**
+ * Get the info for this session.
+ *
+ * @return Info that identifies this session.
+ */
+ String getPackageName();
+
+ /**
+ * Get the UID this session was created for.
+ *
+ * @return The UID for this session.
+ */
+ int getUid();
+
+ /**
+ * Get the user id this session was created for.
+ *
+ * @return The user id for this session.
+ */
+ int getUserId();
+
+ /**
+ * Check if this session has system priorty and should receive media buttons
+ * before any other sessions.
+ *
+ * @return True if this is a system priority session, false otherwise
+ */
+ boolean isSystemPriority();
+
+ /**
+ * Send a volume adjustment to the session owner. Direction must be one of
+ * {@link AudioManager#ADJUST_LOWER}, {@link AudioManager#ADJUST_RAISE},
+ * {@link AudioManager#ADJUST_SAME}.
+ *
+ * @param packageName The package that made the original volume request.
+ * @param opPackageName The op package that made the original volume request.
+ * @param pid The pid that made the original volume request.
+ * @param uid The uid that made the original volume request.
+ * @param asSystemService {@code true} if the event sent to the session as if it was come from
+ * the system service instead of the app process. This helps sessions to distinguish
+ * between the key injection by the app and key events from the hardware devices.
+ * Should be used only when the volume key events aren't handled by foreground
+ * activity. {@code false} otherwise to tell session about the real caller.
+ * @param direction The direction to adjust volume in.
+ * @param flags Any of the flags from {@link AudioManager}.
+ * @param useSuggested True to use adjustSuggestedStreamVolume instead of
+ */
+ void adjustVolume(String packageName, String opPackageName, int pid, int uid,
+ boolean asSystemService, int direction, int flags, boolean useSuggested);
+
+ /**
+ * Check if this session has been set to active by the app. (i.e. ready to receive command and
+ * getters are available).
+ *
+ * @return True if the session is active, false otherwise.
+ */
+ // TODO(jaewan): Find better naming, or remove this from the MediaSessionRecordImpl.
+ boolean isActive();
+
+ /**
+ * Check if the session's playback active state matches with the expectation. This always return
+ * {@code false} if the playback state is unknown (e.g. {@code null}), where we cannot know the
+ * actual playback state associated with the session.
+ *
+ * @param expected True if playback is expected to be active. false otherwise.
+ * @return True if the session's playback matches with the expectation. false otherwise.
+ */
+ boolean checkPlaybackActiveState(boolean expected);
+
+ /**
+ * Check whether the playback type is local or remote.
+ * <p>
+ * <ul>
+ * <li>Local: volume changes the stream volume because playback happens on this device.</li>
+ * <li>Remote: volume is sent to the apps callback because playback happens on the remote
+ * device and we cannot know how to control the volume of it.</li>
+ * </ul>
+ *
+ * @return {@code true} if the playback is local. {@code false} if the playback is remote.
+ */
+ boolean isPlaybackTypeLocal();
+
+ /**
+ * Sends media button.
+ *
+ * @param packageName caller package name
+ * @param pid caller pid
+ * @param uid caller uid
+ * @param asSystemService {@code true} if the event sent to the session as if it was come from
+ * the system service instead of the app process.
+ * @param ke key events
+ * @param sequenceId (optional) sequence id. Use this only when a wake lock is needed.
+ * @param cb (optional) result receiver to receive callback. Use this only when a wake lock is
+ * needed.
+ * @return {@code true} if the attempt to send media button was successfully.
+ * {@code false} otherwise.
+ */
+ boolean sendMediaButton(String packageName, int pid, int uid, boolean asSystemService,
+ KeyEvent ke, int sequenceId, ResultReceiver cb);
+
+ /**
+ * Dumps internal state
+ *
+ * @param pw print writer
+ * @param prefix prefix
+ */
+ void dump(PrintWriter pw, String prefix);
+
+ /**
+ * Override {@link AutoCloseable#close} to tell not to throw exception.
+ */
+ @Override
+ void close();
+}
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index 0f059db..4a6fcdf7 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -40,10 +40,7 @@
import android.media.AudioManagerInternal;
import android.media.AudioPlaybackConfiguration;
import android.media.AudioSystem;
-import android.media.IAudioService;
import android.media.IRemoteVolumeController;
-import android.media.MediaController2;
-import android.media.Session2CommandGroup;
import android.media.Session2Token;
import android.media.session.IActiveSessionsListener;
import android.media.session.IOnMediaKeyEventDispatchedListener;
@@ -61,7 +58,6 @@
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
-import android.os.HandlerExecutor;
import android.os.IBinder;
import android.os.Message;
import android.os.PowerManager;
@@ -123,11 +119,6 @@
@GuardedBy("mLock")
private final ArrayList<SessionsListenerRecord> mSessionsListeners =
new ArrayList<SessionsListenerRecord>();
- // Map user id as index to list of Session2Tokens
- // TODO: Keep session2 info in MediaSessionStack for prioritizing both session1 and session2 in
- // one place.
- @GuardedBy("mLock")
- private final SparseArray<List<Session2Token>> mSession2TokensPerUser = new SparseArray<>();
@GuardedBy("mLock")
private final List<Session2TokensListenerRecord> mSession2TokensListenerRecords =
new ArrayList<>();
@@ -175,7 +166,8 @@
}
synchronized (mLock) {
FullUserRecord user = getFullUserRecordLocked(
- UserHandle.getUserId(config.getClientUid()));
+ UserHandle.getUserHandleForUid(config.getClientUid())
+ .getIdentifier());
if (user != null) {
user.mPriorityStack.updateMediaButtonSessionIfNeeded();
}
@@ -189,16 +181,11 @@
updateUser();
}
- private IAudioService getAudioService() {
- IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
- return IAudioService.Stub.asInterface(b);
- }
-
private boolean isGlobalPriorityActiveLocked() {
return mGlobalPrioritySession != null && mGlobalPrioritySession.isActive();
}
- void updateSession(MediaSessionRecord record) {
+ void onSessionActiveStateChanged(MediaSessionRecordImpl record) {
synchronized (mLock) {
FullUserRecord user = getFullUserRecordLocked(record.getUserId());
if (user == null) {
@@ -215,12 +202,14 @@
Log.w(TAG, "Unknown session updated. Ignoring.");
return;
}
- user.mPriorityStack.onSessionStateChange(record);
+ user.mPriorityStack.onSessionActiveStateChanged(record);
}
- mHandler.postSessionsChanged(record.getUserId());
+
+ mHandler.postSessionsChanged(record);
}
}
+ // Currently only media1 can become global priority session.
void setGlobalPrioritySession(MediaSessionRecord record) {
synchronized (mLock) {
FullUserRecord user = getFullUserRecordLocked(record.getUserId());
@@ -266,11 +255,13 @@
List<Session2Token> getSession2TokensLocked(int userId) {
List<Session2Token> list = new ArrayList<>();
if (userId == USER_ALL) {
- for (int i = 0; i < mSession2TokensPerUser.size(); i++) {
- list.addAll(mSession2TokensPerUser.valueAt(i));
+ int size = mUserRecords.size();
+ for (int i = 0; i < size; i++) {
+ list.addAll(mUserRecords.valueAt(i).mPriorityStack.getSession2Tokens(userId));
}
} else {
- list.addAll(mSession2TokensPerUser.get(userId));
+ FullUserRecord user = getFullUserRecordLocked(userId);
+ list.addAll(user.mPriorityStack.getSession2Tokens(userId));
}
return list;
}
@@ -297,14 +288,15 @@
}
}
- void onSessionPlaystateChanged(MediaSessionRecord record, int oldState, int newState) {
+ void onSessionPlaybackStateChanged(MediaSessionRecordImpl record,
+ boolean shouldUpdatePriority) {
synchronized (mLock) {
FullUserRecord user = getFullUserRecordLocked(record.getUserId());
if (user == null || !user.mPriorityStack.contains(record)) {
Log.d(TAG, "Unknown session changed playback state. Ignoring.");
return;
}
- user.mPriorityStack.onPlaystateChanged(record, oldState, newState);
+ user.mPriorityStack.onPlaybackStateChanged(record, shouldUpdatePriority);
}
}
@@ -347,7 +339,6 @@
user.destroySessionsForUserLocked(userId);
}
}
- mSession2TokensPerUser.remove(userId);
updateUser();
}
}
@@ -366,13 +357,7 @@
}
}
- void sessionDied(MediaSessionRecord session) {
- synchronized (mLock) {
- destroySessionLocked(session);
- }
- }
-
- void destroySession(MediaSessionRecord session) {
+ void onSessionDied(MediaSessionRecordImpl session) {
synchronized (mLock) {
destroySessionLocked(session);
}
@@ -393,9 +378,6 @@
mUserRecords.put(userInfo.id, new FullUserRecord(userInfo.id));
}
}
- if (mSession2TokensPerUser.get(userInfo.id) == null) {
- mSession2TokensPerUser.put(userInfo.id, new ArrayList<>());
- }
}
}
// Ensure that the current full user exists.
@@ -405,9 +387,6 @@
Log.w(TAG, "Cannot find FullUserInfo for the current user " + currentFullUserId);
mCurrentFullUserRecord = new FullUserRecord(currentFullUserId);
mUserRecords.put(currentFullUserId, mCurrentFullUserRecord);
- if (mSession2TokensPerUser.get(currentFullUserId) == null) {
- mSession2TokensPerUser.put(currentFullUserId, new ArrayList<>());
- }
}
mFullUserIds.put(currentFullUserId, currentFullUserId);
}
@@ -444,7 +423,7 @@
* 5. We need to unlink to death from the cb binder
* 6. We need to tell the session to do any final cleanup (onDestroy)
*/
- private void destroySessionLocked(MediaSessionRecord session) {
+ private void destroySessionLocked(MediaSessionRecordImpl session) {
if (DEBUG) {
Log.d(TAG, "Destroying " + session);
}
@@ -461,7 +440,7 @@
}
session.close();
- mHandler.postSessionsChanged(session.getUserId());
+ mHandler.postSessionsChanged(session);
}
private void enforcePackageName(String packageName, int uid) {
@@ -494,8 +473,8 @@
if (mContext
.checkPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL, pid, uid)
!= PackageManager.PERMISSION_GRANTED
- && !isEnabledNotificationListener(compName, UserHandle.getUserId(uid),
- resolvedUserId)) {
+ && !isEnabledNotificationListener(compName,
+ UserHandle.getUserHandleForUid(uid).getIdentifier(), resolvedUserId)) {
throw new SecurityException("Missing permission to control media.");
}
}
@@ -541,15 +520,6 @@
return false;
}
- private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
- String callerPackageName, ISessionCallback cb, String tag, Bundle sessionInfo)
- throws RemoteException {
- synchronized (mLock) {
- return createSessionLocked(callerPid, callerUid, userId, callerPackageName, cb,
- tag, sessionInfo);
- }
- }
-
/*
* When a session is created the following things need to happen.
* 1. Its callback binder needs a link to death
@@ -557,29 +527,31 @@
* 3. It needs to be added to the priority stack.
* 4. It needs to be added to the relevant user record.
*/
- private MediaSessionRecord createSessionLocked(int callerPid, int callerUid, int userId,
+ private MediaSessionRecord createSessionInternal(int callerPid, int callerUid, int userId,
String callerPackageName, ISessionCallback cb, String tag, Bundle sessionInfo) {
- FullUserRecord user = getFullUserRecordLocked(userId);
- if (user == null) {
- Log.w(TAG, "Request from invalid user: " + userId + ", pkg=" + callerPackageName);
- throw new RuntimeException("Session request from invalid user.");
- }
+ synchronized (mLock) {
+ FullUserRecord user = getFullUserRecordLocked(userId);
+ if (user == null) {
+ Log.w(TAG, "Request from invalid user: " + userId + ", pkg=" + callerPackageName);
+ throw new RuntimeException("Session request from invalid user.");
+ }
- final MediaSessionRecord session;
- try {
- session = new MediaSessionRecord(callerPid, callerUid, userId,
- callerPackageName, cb, tag, sessionInfo, this, mHandler.getLooper());
- } catch (RemoteException e) {
- throw new RuntimeException("Media Session owner died prematurely.", e);
- }
+ final MediaSessionRecord session;
+ try {
+ session = new MediaSessionRecord(callerPid, callerUid, userId,
+ callerPackageName, cb, tag, sessionInfo, this, mHandler.getLooper());
+ } catch (RemoteException e) {
+ throw new RuntimeException("Media Session owner died prematurely.", e);
+ }
- user.mPriorityStack.addSession(session);
- mHandler.postSessionsChanged(userId);
+ user.mPriorityStack.addSession(session);
+ mHandler.postSessionsChanged(session);
- if (DEBUG) {
- Log.d(TAG, "Created session for " + callerPackageName + " with tag " + tag);
+ if (DEBUG) {
+ Log.d(TAG, "Created session for " + callerPackageName + " with tag " + tag);
+ }
+ return session;
}
- return session;
}
private int findIndexOfSessionsListenerLocked(IActiveSessionsListener listener) {
@@ -600,16 +572,16 @@
return -1;
}
- private void pushSessionsChanged(int userId) {
+ private void pushSession1Changed(int userId) {
synchronized (mLock) {
FullUserRecord user = getFullUserRecordLocked(userId);
if (user == null) {
- Log.w(TAG, "pushSessionsChanged failed. No user with id=" + userId);
+ Log.w(TAG, "pushSession1ChangedOnHandler failed. No user with id=" + userId);
return;
}
List<MediaSessionRecord> records = getActiveSessionsLocked(userId);
int size = records.size();
- ArrayList<MediaSession.Token> tokens = new ArrayList<MediaSession.Token>();
+ ArrayList<MediaSession.Token> tokens = new ArrayList<>();
for (int i = 0; i < size; i++) {
tokens.add(records.get(i).getSessionToken());
}
@@ -629,6 +601,27 @@
}
}
+ void pushSession2Changed(int userId) {
+ synchronized (mLock) {
+ List<Session2Token> allSession2Tokens = getSession2TokensLocked(USER_ALL);
+ List<Session2Token> session2Tokens = getSession2TokensLocked(userId);
+
+ for (int i = mSession2TokensListenerRecords.size() - 1; i >= 0; i--) {
+ Session2TokensListenerRecord listenerRecord = mSession2TokensListenerRecords.get(i);
+ try {
+ if (listenerRecord.userId == USER_ALL) {
+ listenerRecord.listener.onSession2TokensChanged(allSession2Tokens);
+ } else if (listenerRecord.userId == userId) {
+ listenerRecord.listener.onSession2TokensChanged(session2Tokens);
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to notify Session2Token change. Removing listener.", e);
+ mSession2TokensListenerRecords.remove(i);
+ }
+ }
+ }
+ }
+
private void pushRemoteVolumeUpdateLocked(int userId) {
FullUserRecord user = getFullUserRecordLocked(userId);
if (user == null) {
@@ -638,8 +631,13 @@
synchronized (mLock) {
int size = mRemoteVolumeControllers.beginBroadcast();
- MediaSessionRecord record = user.mPriorityStack.getDefaultRemoteSession(userId);
- MediaSession.Token token = record == null ? null : record.getSessionToken();
+ MediaSessionRecordImpl record = user.mPriorityStack.getDefaultRemoteSession(userId);
+ if (record instanceof MediaSession2Record) {
+ // TODO(jaewan): Implement
+ return;
+ }
+ MediaSession.Token token = record == null
+ ? null : ((MediaSessionRecord) record).getSessionToken();
for (int i = size - 1; i >= 0; i--) {
try {
@@ -653,34 +651,15 @@
}
}
- void pushSession2TokensChangedLocked(int userId) {
- List<Session2Token> allSession2Tokens = getSession2TokensLocked(USER_ALL);
- List<Session2Token> session2Tokens = getSession2TokensLocked(userId);
-
- for (int i = mSession2TokensListenerRecords.size() - 1; i >= 0; i--) {
- Session2TokensListenerRecord listenerRecord = mSession2TokensListenerRecords.get(i);
- try {
- if (listenerRecord.userId == USER_ALL) {
- listenerRecord.listener.onSession2TokensChanged(allSession2Tokens);
- } else if (listenerRecord.userId == userId) {
- listenerRecord.listener.onSession2TokensChanged(session2Tokens);
- }
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to notify Session2Token change. Removing listener.", e);
- mSession2TokensListenerRecords.remove(i);
- }
- }
- }
-
/**
* Called when the media button receiver for the {@code record} is changed.
*
* @param record the media session whose media button receiver is updated.
*/
- public void onMediaButtonReceiverChanged(MediaSessionRecord record) {
+ public void onMediaButtonReceiverChanged(MediaSessionRecordImpl record) {
synchronized (mLock) {
FullUserRecord user = getFullUserRecordLocked(record.getUserId());
- MediaSessionRecord mediaButtonSession =
+ MediaSessionRecordImpl mediaButtonSession =
user.mPriorityStack.getMediaButtonSession();
if (record == mediaButtonSession) {
user.rememberMediaButtonReceiverLocked(mediaButtonSession);
@@ -868,39 +847,34 @@
pw.println(indent + "Restored MediaButtonReceiverComponentType: "
+ mRestoredMediaButtonReceiverComponentType);
mPriorityStack.dump(pw, indent);
- pw.println(indent + "Session2Tokens:");
- for (int i = 0; i < mSession2TokensPerUser.size(); i++) {
- List<Session2Token> list = mSession2TokensPerUser.valueAt(i);
- if (list == null || list.size() == 0) {
- continue;
- }
- for (Session2Token token : list) {
- pw.println(indent + " " + token);
- }
- }
}
@Override
- public void onMediaButtonSessionChanged(MediaSessionRecord oldMediaButtonSession,
- MediaSessionRecord newMediaButtonSession) {
+ public void onMediaButtonSessionChanged(MediaSessionRecordImpl oldMediaButtonSession,
+ MediaSessionRecordImpl newMediaButtonSession) {
if (DEBUG_KEY_EVENT) {
Log.d(TAG, "Media button session is changed to " + newMediaButtonSession);
}
synchronized (mLock) {
if (oldMediaButtonSession != null) {
- mHandler.postSessionsChanged(oldMediaButtonSession.getUserId());
+ mHandler.postSessionsChanged(oldMediaButtonSession);
}
if (newMediaButtonSession != null) {
rememberMediaButtonReceiverLocked(newMediaButtonSession);
- mHandler.postSessionsChanged(newMediaButtonSession.getUserId());
+ mHandler.postSessionsChanged(newMediaButtonSession);
}
pushAddressedPlayerChangedLocked();
}
}
// Remember media button receiver and keep it in the persistent storage.
- public void rememberMediaButtonReceiverLocked(MediaSessionRecord record) {
- PendingIntent receiver = record.getMediaButtonReceiver();
+ public void rememberMediaButtonReceiverLocked(MediaSessionRecordImpl record) {
+ if (record instanceof MediaSession2Record) {
+ // TODO(jaewan): Implement
+ return;
+ }
+ MediaSessionRecord sessionRecord = (MediaSessionRecord) record;
+ PendingIntent receiver = sessionRecord.getMediaButtonReceiver();
mLastMediaButtonReceiver = receiver;
mRestoredMediaButtonReceiver = null;
mRestoredMediaButtonReceiverComponentType = COMPONENT_TYPE_INVALID;
@@ -925,10 +899,15 @@
private void pushAddressedPlayerChangedLocked(
IOnMediaKeyEventSessionChangedListener callback) {
try {
- MediaSessionRecord mediaButtonSession = getMediaButtonSessionLocked();
+ MediaSessionRecordImpl mediaButtonSession = getMediaButtonSessionLocked();
if (mediaButtonSession != null) {
- callback.onMediaKeyEventSessionChanged(mediaButtonSession.getPackageName(),
- mediaButtonSession.getSessionToken());
+ if (mediaButtonSession instanceof MediaSessionRecord) {
+ MediaSessionRecord session1 = (MediaSessionRecord) mediaButtonSession;
+ callback.onMediaKeyEventSessionChanged(session1.getPackageName(),
+ session1.getSessionToken());
+ } else {
+ // TODO(jaewan): Implement
+ }
} else if (mCurrentFullUserRecord.mLastMediaButtonReceiver != null) {
callback.onMediaKeyEventSessionChanged(
mCurrentFullUserRecord.mLastMediaButtonReceiver
@@ -951,7 +930,7 @@
}
}
- private MediaSessionRecord getMediaButtonSessionLocked() {
+ private MediaSessionRecordImpl getMediaButtonSessionLocked() {
return isGlobalPriorityActiveLocked()
? mGlobalPrioritySession : mPriorityStack.getMediaButtonSession();
}
@@ -1132,14 +1111,13 @@
throw new SecurityException("Unexpected Session2Token's UID, expected=" + uid
+ " but actually=" + sessionToken.getUid());
}
- Controller2Callback callback = new Controller2Callback(sessionToken);
- // Note: It's safe not to keep controller here because it wouldn't be GC'ed until
- // it's closed.
- // TODO: Keep controller as well for better readability
- // because the GC behavior isn't straightforward.
- MediaController2 controller = new MediaController2.Builder(mContext, sessionToken)
- .setControllerCallback(new HandlerExecutor(mHandler), callback)
- .build();
+ MediaSession2Record record = new MediaSession2Record(
+ sessionToken, MediaSessionService.this, mHandler.getLooper());
+ synchronized (mLock) {
+ FullUserRecord user = getFullUserRecordLocked(record.getUserId());
+ user.mPriorityStack.addSession(record);
+ }
+ // Do not immediately notify changes -- do so when framework can dispatch command
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -1180,7 +1158,8 @@
null /* optional packageName */);
List<Session2Token> result;
synchronized (mLock) {
- result = getSession2TokensLocked(resolvedUserId);
+ FullUserRecord user = getFullUserRecordLocked(userId);
+ result = user.mPriorityStack.getSession2Tokens(resolvedUserId);
}
return new ParceledListSlice(result);
} finally {
@@ -2018,7 +1997,7 @@
private void dispatchAdjustVolumeLocked(String packageName, String opPackageName, int pid,
int uid, boolean asSystemService, int suggestedStream, int direction, int flags) {
- MediaSessionRecord session = isGlobalPriorityActiveLocked() ? mGlobalPrioritySession
+ MediaSessionRecordImpl session = isGlobalPriorityActiveLocked() ? mGlobalPrioritySession
: mCurrentFullUserRecord.mPriorityStack.getDefaultVolumeSession();
boolean preferSuggestedStream = false;
@@ -2109,7 +2088,13 @@
private void dispatchMediaKeyEventLocked(String packageName, int pid, int uid,
boolean asSystemService, KeyEvent keyEvent, boolean needWakeLock) {
- MediaSessionRecord session = mCurrentFullUserRecord.getMediaButtonSessionLocked();
+ if (mCurrentFullUserRecord.getMediaButtonSessionLocked()
+ instanceof MediaSession2Record) {
+ // TODO(jaewan): Implement
+ return;
+ }
+ MediaSessionRecord session =
+ (MediaSessionRecord) mCurrentFullUserRecord.getMediaButtonSessionLocked();
if (session != null) {
if (DEBUG_KEY_EVENT) {
Log.d(TAG, "Sending " + keyEvent + " to " + session);
@@ -2389,15 +2374,19 @@
}
final class MessageHandler extends Handler {
- private static final int MSG_SESSIONS_CHANGED = 1;
- private static final int MSG_VOLUME_INITIAL_DOWN = 2;
+ private static final int MSG_SESSIONS_1_CHANGED = 1;
+ private static final int MSG_SESSIONS_2_CHANGED = 2;
+ private static final int MSG_VOLUME_INITIAL_DOWN = 3;
private final SparseArray<Integer> mIntegerCache = new SparseArray<>();
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
- case MSG_SESSIONS_CHANGED:
- pushSessionsChanged((int) msg.obj);
+ case MSG_SESSIONS_1_CHANGED:
+ pushSession1Changed((int) msg.obj);
+ break;
+ case MSG_SESSIONS_2_CHANGED:
+ pushSession2Changed((int) msg.obj);
break;
case MSG_VOLUME_INITIAL_DOWN:
synchronized (mLock) {
@@ -2412,41 +2401,19 @@
}
}
- public void postSessionsChanged(int userId) {
+ public void postSessionsChanged(MediaSessionRecordImpl record) {
// Use object instead of the arguments when posting message to remove pending requests.
- Integer userIdInteger = mIntegerCache.get(userId);
+ Integer userIdInteger = mIntegerCache.get(record.getUserId());
if (userIdInteger == null) {
- userIdInteger = Integer.valueOf(userId);
- mIntegerCache.put(userId, userIdInteger);
+ userIdInteger = Integer.valueOf(record.getUserId());
+ mIntegerCache.put(record.getUserId(), userIdInteger);
}
- removeMessages(MSG_SESSIONS_CHANGED, userIdInteger);
- obtainMessage(MSG_SESSIONS_CHANGED, userIdInteger).sendToTarget();
+
+ int msg = (record instanceof MediaSessionRecord)
+ ? MSG_SESSIONS_1_CHANGED : MSG_SESSIONS_2_CHANGED;
+ removeMessages(msg, userIdInteger);
+ obtainMessage(msg, userIdInteger).sendToTarget();
}
}
- private class Controller2Callback extends MediaController2.ControllerCallback {
- private final Session2Token mToken;
-
- Controller2Callback(Session2Token token) {
- mToken = token;
- }
-
- @Override
- public void onConnected(MediaController2 controller, Session2CommandGroup allowedCommands) {
- synchronized (mLock) {
- int userId = UserHandle.getUserId(mToken.getUid());
- mSession2TokensPerUser.get(userId).add(mToken);
- pushSession2TokensChangedLocked(userId);
- }
- }
-
- @Override
- public void onDisconnected(MediaController2 controller) {
- synchronized (mLock) {
- int userId = UserHandle.getUserId(mToken.getUid());
- mSession2TokensPerUser.get(userId).remove(mToken);
- pushSession2TokensChangedLocked(userId);
- }
- }
- }
}
diff --git a/services/core/java/com/android/server/media/MediaSessionStack.java b/services/core/java/com/android/server/media/MediaSessionStack.java
index 732563f..7bb7cf4 100644
--- a/services/core/java/com/android/server/media/MediaSessionStack.java
+++ b/services/core/java/com/android/server/media/MediaSessionStack.java
@@ -16,8 +16,8 @@
package com.android.server.media;
+import android.media.Session2Token;
import android.media.session.MediaSession;
-import android.media.session.PlaybackState;
import android.os.Debug;
import android.os.UserHandle;
import android.util.IntArray;
@@ -45,51 +45,30 @@
/**
* Called when the media button session is changed.
*/
- void onMediaButtonSessionChanged(MediaSessionRecord oldMediaButtonSession,
- MediaSessionRecord newMediaButtonSession);
+ void onMediaButtonSessionChanged(MediaSessionRecordImpl oldMediaButtonSession,
+ MediaSessionRecordImpl newMediaButtonSession);
}
/**
- * These are states that usually indicate the user took an action and should
- * bump priority regardless of the old state.
+ * Sorted list of the media sessions
*/
- private static final int[] ALWAYS_PRIORITY_STATES = {
- PlaybackState.STATE_FAST_FORWARDING,
- PlaybackState.STATE_REWINDING,
- PlaybackState.STATE_SKIPPING_TO_PREVIOUS,
- PlaybackState.STATE_SKIPPING_TO_NEXT };
- /**
- * These are states that usually indicate the user took an action if they
- * were entered from a non-priority state.
- */
- private static final int[] TRANSITION_PRIORITY_STATES = {
- PlaybackState.STATE_BUFFERING,
- PlaybackState.STATE_CONNECTING,
- PlaybackState.STATE_PLAYING };
-
- /**
- * Sorted list of the media sessions.
- * The session of which PlaybackState is changed to ALWAYS_PRIORITY_STATES or
- * TRANSITION_PRIORITY_STATES comes first.
- * @see #shouldUpdatePriority
- */
- private final List<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
+ private final List<MediaSessionRecordImpl> mSessions = new ArrayList<>();
private final AudioPlayerStateMonitor mAudioPlayerStateMonitor;
private final OnMediaButtonSessionChangedListener mOnMediaButtonSessionChangedListener;
/**
* The media button session which receives media key events.
- * It could be null if the previous media buttion session is released.
+ * It could be null if the previous media button session is released.
*/
- private MediaSessionRecord mMediaButtonSession;
+ private MediaSessionRecordImpl mMediaButtonSession;
- private MediaSessionRecord mCachedVolumeDefault;
+ private MediaSessionRecordImpl mCachedVolumeDefault;
/**
* Cache the result of the {@link #getActiveSessions} per user.
*/
- private final SparseArray<ArrayList<MediaSessionRecord>> mCachedActiveLists =
+ private final SparseArray<List<MediaSessionRecord>> mCachedActiveLists =
new SparseArray<>();
MediaSessionStack(AudioPlayerStateMonitor monitor, OnMediaButtonSessionChangedListener listener) {
@@ -102,7 +81,7 @@
*
* @param record The record to add.
*/
- public void addSession(MediaSessionRecord record) {
+ public void addSession(MediaSessionRecordImpl record) {
mSessions.add(record);
clearCache(record.getUserId());
@@ -117,7 +96,7 @@
*
* @param record The record to remove.
*/
- public void removeSession(MediaSessionRecord record) {
+ public void removeSession(MediaSessionRecordImpl record) {
mSessions.remove(record);
if (mMediaButtonSession == record) {
// When the media button session is removed, nullify the media button session and do not
@@ -131,7 +110,7 @@
/**
* Return if the record exists in the priority tracker.
*/
- public boolean contains(MediaSessionRecord record) {
+ public boolean contains(MediaSessionRecordImpl record) {
return mSessions.contains(record);
}
@@ -142,9 +121,12 @@
* @return the MediaSessionRecord. Can be {@code null} if the session is gone meanwhile.
*/
public MediaSessionRecord getMediaSessionRecord(MediaSession.Token sessionToken) {
- for (MediaSessionRecord record : mSessions) {
- if (Objects.equals(record.getSessionToken(), sessionToken)) {
- return record;
+ for (MediaSessionRecordImpl record : mSessions) {
+ if (record instanceof MediaSessionRecord) {
+ MediaSessionRecord session1 = (MediaSessionRecord) record;
+ if (Objects.equals(session1.getSessionToken(), sessionToken)) {
+ return session1;
+ }
}
}
return null;
@@ -154,15 +136,15 @@
* Notify the priority tracker that a session's playback state changed.
*
* @param record The record that changed.
- * @param oldState Its old playback state.
- * @param newState Its new playback state.
+ * @param shouldUpdatePriority {@code true} if the record needs to prioritized
*/
- public void onPlaystateChanged(MediaSessionRecord record, int oldState, int newState) {
- if (shouldUpdatePriority(oldState, newState)) {
+ public void onPlaybackStateChanged(
+ MediaSessionRecordImpl record, boolean shouldUpdatePriority) {
+ if (shouldUpdatePriority) {
mSessions.remove(record);
mSessions.add(0, record);
clearCache(record.getUserId());
- } else if (!MediaSession.isActiveState(newState)) {
+ } else if (record.checkPlaybackActiveState(false)) {
// Just clear the volume cache when a state goes inactive
mCachedVolumeDefault = null;
}
@@ -172,7 +154,7 @@
// In that case, we pick the media session whose PlaybackState matches
// the audio playback configuration.
if (mMediaButtonSession != null && mMediaButtonSession.getUid() == record.getUid()) {
- MediaSessionRecord newMediaButtonSession =
+ MediaSessionRecordImpl newMediaButtonSession =
findMediaButtonSession(mMediaButtonSession.getUid());
if (newMediaButtonSession != mMediaButtonSession) {
updateMediaButtonSession(newMediaButtonSession);
@@ -185,7 +167,7 @@
*
* @param record The record that changed.
*/
- public void onSessionStateChange(MediaSessionRecord record) {
+ public void onSessionActiveStateChanged(MediaSessionRecordImpl record) {
// For now just clear the cache. Eventually we'll selectively clear
// depending on what changed.
clearCache(record.getUserId());
@@ -203,7 +185,7 @@
}
IntArray audioPlaybackUids = mAudioPlayerStateMonitor.getSortedAudioPlaybackClientUids();
for (int i = 0; i < audioPlaybackUids.size(); i++) {
- MediaSessionRecord mediaButtonSession =
+ MediaSessionRecordImpl mediaButtonSession =
findMediaButtonSession(audioPlaybackUids.get(i));
if (mediaButtonSession != null) {
// Found the media button session.
@@ -225,9 +207,9 @@
* @return The media button session. Returns {@code null} if the app doesn't have a media
* session.
*/
- private MediaSessionRecord findMediaButtonSession(int uid) {
- MediaSessionRecord mediaButtonSession = null;
- for (MediaSessionRecord session : mSessions) {
+ private MediaSessionRecordImpl findMediaButtonSession(int uid) {
+ MediaSessionRecordImpl mediaButtonSession = null;
+ for (MediaSessionRecordImpl session : mSessions) {
if (uid == session.getUid()) {
if (session.checkPlaybackActiveState(
mAudioPlayerStateMonitor.isPlaybackActive(session.getUid()))) {
@@ -253,8 +235,8 @@
* for all users in this {@link MediaSessionStack}.
* @return All the active sessions in priority order.
*/
- public ArrayList<MediaSessionRecord> getActiveSessions(int userId) {
- ArrayList<MediaSessionRecord> cachedActiveList = mCachedActiveLists.get(userId);
+ public List<MediaSessionRecord> getActiveSessions(int userId) {
+ List<MediaSessionRecord> cachedActiveList = mCachedActiveLists.get(userId);
if (cachedActiveList == null) {
cachedActiveList = getPriorityList(true, userId);
mCachedActiveLists.put(userId, cachedActiveList);
@@ -263,26 +245,46 @@
}
/**
+ * Gets the session2 tokens.
+ *
+ * @param userId The user to check. It can be {@link UserHandle#USER_ALL} to get all session2
+ * tokens for all users in this {@link MediaSessionStack}.
+ * @return All session2 tokens.
+ */
+ public List<Session2Token> getSession2Tokens(int userId) {
+ ArrayList<Session2Token> session2Records = new ArrayList<>();
+ for (MediaSessionRecordImpl record : mSessions) {
+ if ((userId == UserHandle.USER_ALL || record.getUserId() == userId)
+ && record.isActive()
+ && record instanceof MediaSession2Record) {
+ MediaSession2Record session2 = (MediaSession2Record) record;
+ session2Records.add(session2.getSession2Token());
+ }
+ }
+ return session2Records;
+ }
+
+ /**
* Get the media button session which receives the media button events.
*
* @return The media button session or null.
*/
- public MediaSessionRecord getMediaButtonSession() {
+ public MediaSessionRecordImpl getMediaButtonSession() {
return mMediaButtonSession;
}
- private void updateMediaButtonSession(MediaSessionRecord newMediaButtonSession) {
- MediaSessionRecord oldMediaButtonSession = mMediaButtonSession;
+ private void updateMediaButtonSession(MediaSessionRecordImpl newMediaButtonSession) {
+ MediaSessionRecordImpl oldMediaButtonSession = mMediaButtonSession;
mMediaButtonSession = newMediaButtonSession;
mOnMediaButtonSessionChangedListener.onMediaButtonSessionChanged(
oldMediaButtonSession, newMediaButtonSession);
}
- public MediaSessionRecord getDefaultVolumeSession() {
+ public MediaSessionRecordImpl getDefaultVolumeSession() {
if (mCachedVolumeDefault != null) {
return mCachedVolumeDefault;
}
- ArrayList<MediaSessionRecord> records = getPriorityList(true, UserHandle.USER_ALL);
+ List<MediaSessionRecord> records = getPriorityList(true, UserHandle.USER_ALL);
int size = records.size();
for (int i = 0; i < size; i++) {
MediaSessionRecord record = records.get(i);
@@ -294,13 +296,13 @@
return null;
}
- public MediaSessionRecord getDefaultRemoteSession(int userId) {
- ArrayList<MediaSessionRecord> records = getPriorityList(true, userId);
+ public MediaSessionRecordImpl getDefaultRemoteSession(int userId) {
+ List<MediaSessionRecord> records = getPriorityList(true, userId);
int size = records.size();
for (int i = 0; i < size; i++) {
MediaSessionRecord record = records.get(i);
- if (!record.isPlaybackLocal()) {
+ if (!record.isPlaybackTypeLocal()) {
return record;
}
}
@@ -308,16 +310,11 @@
}
public void dump(PrintWriter pw, String prefix) {
- ArrayList<MediaSessionRecord> sortedSessions = getPriorityList(false,
- UserHandle.USER_ALL);
- int count = sortedSessions.size();
pw.println(prefix + "Media button session is " + mMediaButtonSession);
- pw.println(prefix + "Sessions Stack - have " + count + " sessions:");
+ pw.println(prefix + "Sessions Stack - have " + mSessions.size() + " sessions:");
String indent = prefix + " ";
- for (int i = 0; i < count; i++) {
- MediaSessionRecord record = sortedSessions.get(i);
+ for (MediaSessionRecordImpl record : mSessions) {
record.dump(pw, indent);
- pw.println();
}
}
@@ -335,17 +332,19 @@
* will return sessions for all users.
* @return The priority sorted list of sessions.
*/
- public ArrayList<MediaSessionRecord> getPriorityList(boolean activeOnly, int userId) {
- ArrayList<MediaSessionRecord> result = new ArrayList<MediaSessionRecord>();
+ public List<MediaSessionRecord> getPriorityList(boolean activeOnly, int userId) {
+ List<MediaSessionRecord> result = new ArrayList<MediaSessionRecord>();
int lastPlaybackActiveIndex = 0;
int lastActiveIndex = 0;
- int size = mSessions.size();
- for (int i = 0; i < size; i++) {
- final MediaSessionRecord session = mSessions.get(i);
+ for (MediaSessionRecordImpl record : mSessions) {
+ if (!(record instanceof MediaSessionRecord)) {
+ continue;
+ }
+ final MediaSessionRecord session = (MediaSessionRecord) record;
- if (userId != UserHandle.USER_ALL && userId != session.getUserId()) {
- // Filter out sessions for the wrong user
+ if ((userId != UserHandle.USER_ALL && userId != session.getUserId())) {
+ // Filter out sessions for the wrong user or session2.
continue;
}
@@ -369,26 +368,6 @@
return result;
}
- private boolean shouldUpdatePriority(int oldState, int newState) {
- if (containsState(newState, ALWAYS_PRIORITY_STATES)) {
- return true;
- }
- if (!containsState(oldState, TRANSITION_PRIORITY_STATES)
- && containsState(newState, TRANSITION_PRIORITY_STATES)) {
- return true;
- }
- return false;
- }
-
- private boolean containsState(int state, int[] states) {
- for (int i = 0; i < states.length; i++) {
- if (states[i] == state) {
- return true;
- }
- }
- return false;
- }
-
private void clearCache(int userId) {
mCachedVolumeDefault = null;
mCachedActiveLists.remove(userId);
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 8fdfcbf..6695227 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -30,12 +30,12 @@
import android.os.Looper;
import android.os.RemoteException;
import android.os.ServiceManager;
-import android.text.TextUtils;
import android.util.Log;
import com.android.internal.R;
import java.util.Collections;
+import java.util.List;
/**
* Provides routes for local playbacks such as phone speaker, wired headset, or Bluetooth speakers.
@@ -48,13 +48,14 @@
static final String BLUETOOTH_ROUTE_ID = "BLUETOOTH_ROUTE";
// TODO: Move these to a proper place
- public static final String CATEGORY_LIVE_AUDIO = "android.media.intent.category.LIVE_AUDIO";
- public static final String CATEGORY_LIVE_VIDEO = "android.media.intent.category.LIVE_VIDEO";
+ public static final String TYPE_LIVE_AUDIO = "android.media.intent.route.TYPE_LIVE_AUDIO";
+ public static final String TYPE_LIVE_VIDEO = "android.media.intent.route.TYPE_LIVE_VIDEO";
private final AudioManager mAudioManager;
private final IAudioService mAudioService;
private final Handler mHandler;
private final Context mContext;
+ private final BluetoothRouteProvider mBtRouteProvider;
private static ComponentName sComponentName = new ComponentName(
SystemMediaRoute2Provider.class.getPackageName$(),
@@ -62,7 +63,7 @@
//TODO: Clean up these when audio manager support multiple bt devices
MediaRoute2Info mDefaultRoute;
- MediaRoute2Info mBluetoothA2dpRoute;
+ @NonNull List<MediaRoute2Info> mBluetoothRoutes = Collections.EMPTY_LIST;
final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo();
final IAudioRoutesObserver.Stub mAudioRoutesObserver = new IAudioRoutesObserver.Stub() {
@@ -87,48 +88,52 @@
mAudioService = IAudioService.Stub.asInterface(
ServiceManager.getService(Context.AUDIO_SERVICE));
+ mBtRouteProvider = BluetoothRouteProvider.getInstance(context, (routes) -> {
+ mBluetoothRoutes = routes;
+ publishRoutes();
+ });
initializeRoutes();
}
@Override
- public void requestCreateSession(String packageName, String routeId, String controlCategory,
+ public void requestCreateSession(String packageName, String routeId, String routeType,
long requestId) {
// Do nothing
}
@Override
- public void releaseSession(int sessionId) {
+ public void releaseSession(String sessionId) {
// Do nothing
}
@Override
- public void selectRoute(int sessionId, MediaRoute2Info route) {
+ public void selectRoute(String sessionId, String routeId) {
//TODO: implement method
}
@Override
- public void deselectRoute(int sessionId, MediaRoute2Info route) {
+ public void deselectRoute(String sessionId, String routeId) {
//TODO: implement method
}
@Override
- public void transferToRoute(int sessionId, MediaRoute2Info route) {
+ public void transferToRoute(String sessionId, String routeId) {
//TODO: implement method
}
//TODO: implement method
@Override
- public void sendControlRequest(@NonNull MediaRoute2Info route, @NonNull Intent request) {
+ public void sendControlRequest(@NonNull String routeId, @NonNull Intent request) {
}
//TODO: implement method
@Override
- public void requestSetVolume(MediaRoute2Info route, int volume) {
+ public void requestSetVolume(String routeId, int volume) {
}
//TODO: implement method
@Override
- public void requestUpdateVolume(MediaRoute2Info route, int delta) {
+ public void requestUpdateVolume(String routeId, int delta) {
}
void initializeRoutes() {
@@ -141,8 +146,8 @@
: MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
.setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
.setVolume(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC))
- .addSupportedCategory(CATEGORY_LIVE_AUDIO)
- .addSupportedCategory(CATEGORY_LIVE_VIDEO)
+ .addFeature(TYPE_LIVE_AUDIO)
+ .addFeature(TYPE_LIVE_VIDEO)
.build();
AudioRoutesInfo newAudioRoutes = null;
@@ -157,7 +162,15 @@
updateAudioRoutes(newAudioRoutes);
}
- publishRoutes();
+ mBluetoothRoutes = mBtRouteProvider.getBluetoothRoutes();
+
+ MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder();
+ builder.addRoute(mDefaultRoute);
+ for (MediaRoute2Info route : mBluetoothRoutes) {
+ builder.addRoute(route);
+ }
+ setProviderState(builder.build());
+ mHandler.post(() -> notifyProviderState());
}
void updateAudioRoutes(AudioRoutesInfo newRoutes) {
@@ -181,25 +194,10 @@
: MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
.setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
.setVolume(mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC))
- .addSupportedCategory(CATEGORY_LIVE_AUDIO)
- .addSupportedCategory(CATEGORY_LIVE_VIDEO)
+ .addFeature(TYPE_LIVE_AUDIO)
+ .addFeature(TYPE_LIVE_VIDEO)
.build();
- if (!TextUtils.equals(newRoutes.bluetoothName, mCurAudioRoutesInfo.bluetoothName)) {
- mCurAudioRoutesInfo.bluetoothName = newRoutes.bluetoothName;
- if (mCurAudioRoutesInfo.bluetoothName != null) {
- //TODO: mark as bluetooth once MediaRoute2Info has device type
- mBluetoothA2dpRoute = new MediaRoute2Info.Builder(BLUETOOTH_ROUTE_ID,
- mCurAudioRoutesInfo.bluetoothName)
- .setDescription(mContext.getResources().getText(
- R.string.bluetooth_a2dp_audio_route_name).toString())
- .addSupportedCategory(CATEGORY_LIVE_AUDIO)
- .build();
- } else {
- mBluetoothA2dpRoute = null;
- }
- }
-
publishRoutes();
}
@@ -207,15 +205,13 @@
* The first route should be the currently selected system route.
* For example, if there are two system routes (BT and device speaker),
* BT will be the first route in the list.
- *
- * TODO: Support multiple BT devices
*/
void publishRoutes() {
MediaRoute2ProviderInfo.Builder builder = new MediaRoute2ProviderInfo.Builder();
- if (mBluetoothA2dpRoute != null) {
- builder.addRoute(mBluetoothA2dpRoute);
- }
builder.addRoute(mDefaultRoute);
- setAndNotifyProviderState(builder.build(), Collections.emptyList());
+ for (MediaRoute2Info route : mBluetoothRoutes) {
+ builder.addRoute(route);
+ }
+ setAndNotifyProviderState(builder.build());
}
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
index 7f650ee..b24a938 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerInternal.java
@@ -18,8 +18,10 @@
import static com.android.server.net.NetworkPolicyManagerService.isUidNetworkingBlockedInternal;
+import android.annotation.NonNull;
import android.net.Network;
import android.net.NetworkTemplate;
+import android.net.netstats.provider.AbstractNetworkStatsProvider;
import android.telephony.SubscriptionPlan;
import java.util.Set;
@@ -126,4 +128,12 @@
*/
public abstract void setMeteredRestrictedPackagesAsync(
Set<String> packageNames, int userId);
+
+ /**
+ * Notifies that any of the {@link AbstractNetworkStatsProvider} has reached its quota
+ * which was set through {@link AbstractNetworkStatsProvider#setLimit(String, long)}.
+ *
+ * @param tag the human readable identifier of the custom network stats provider.
+ */
+ public abstract void onStatsProviderLimitReached(@NonNull String tag);
}
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 2390da5e..c518614 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -75,6 +75,7 @@
import static android.net.NetworkTemplate.MATCH_WIFI;
import static android.net.NetworkTemplate.buildTemplateMobileAll;
import static android.net.TrafficStats.MB_IN_BYTES;
+import static android.net.netstats.provider.AbstractNetworkStatsProvider.QUOTA_UNLIMITED;
import static android.os.Trace.TRACE_TAG_NETWORK;
import static android.provider.Settings.Global.NETPOLICY_OVERRIDE_ENABLED;
import static android.provider.Settings.Global.NETPOLICY_QUOTA_ENABLED;
@@ -160,7 +161,7 @@
import android.net.NetworkState;
import android.net.NetworkStats;
import android.net.NetworkTemplate;
-import android.net.StringNetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
import android.net.TrafficStats;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiManager;
@@ -391,6 +392,7 @@
private static final int MSG_METERED_RESTRICTED_PACKAGES_CHANGED = 17;
private static final int MSG_SET_NETWORK_TEMPLATE_ENABLED = 18;
private static final int MSG_SUBSCRIPTION_PLANS_CHANGED = 19;
+ private static final int MSG_STATS_PROVIDER_LIMIT_REACHED = 20;
private static final int UID_MSG_STATE_CHANGED = 100;
private static final int UID_MSG_GONE = 101;
@@ -1780,7 +1782,7 @@
final TelephonyManager tm = mContext.getSystemService(TelephonyManager.class);
for (int i = 0; i < matchingSubIds.size(); i++) {
final int subId = matchingSubIds.get(i);
- tm.setPolicyDataEnabled(enabled, subId);
+ tm.createForSubscriptionId(subId).setPolicyDataEnabled(enabled);
}
}
}
@@ -1819,7 +1821,7 @@
final List<String[]> mergedSubscriberIdsList = new ArrayList();
final SparseArray<String> subIdToSubscriberId = new SparseArray<>(subList.size());
- for (SubscriptionInfo sub : subList) {
+ for (final SubscriptionInfo sub : subList) {
final TelephonyManager tmSub = tm.createForSubscriptionId(sub.getSubscriptionId());
final String subscriberId = tmSub.getSubscriberId();
if (!TextUtils.isEmpty(subscriberId)) {
@@ -2875,17 +2877,6 @@
}
@Override
- public void onTetheringChanged(String iface, boolean tethering) {
- // No need to enforce permission because setRestrictBackground() will do it.
- synchronized (mUidRulesFirstLock) {
- if (mRestrictBackground && tethering) {
- Log.d(TAG, "Tethering on (" + iface +"); disable Data Saver");
- setRestrictBackground(false);
- }
- }
- }
-
- @Override
public void setRestrictBackground(boolean restrictBackground) {
Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "setRestrictBackground");
try {
@@ -3046,12 +3037,13 @@
// Verify they're not lying about package name
mAppOps.checkPackage(callingUid, callingPackage);
+ final SubscriptionManager sm;
final SubscriptionInfo si;
final PersistableBundle config;
final long token = Binder.clearCallingIdentity();
try {
- si = mContext.getSystemService(SubscriptionManager.class)
- .getActiveSubscriptionInfo(subId);
+ sm = mContext.getSystemService(SubscriptionManager.class);
+ si = sm.getActiveSubscriptionInfo(subId);
config = mCarrierConfigManager.getConfigForSubId(subId);
} finally {
Binder.restoreCallingIdentity(token);
@@ -3059,7 +3051,7 @@
// First check: is caller the CarrierService?
if (si != null) {
- if (si.isEmbedded() && si.canManageSubscription(mContext, callingPackage)) {
+ if (si.isEmbedded() && sm.canManageSubscription(si, callingPackage)) {
return;
}
}
@@ -4517,19 +4509,36 @@
mListeners.finishBroadcast();
return true;
}
- case MSG_LIMIT_REACHED: {
- final String iface = (String) msg.obj;
+ case MSG_STATS_PROVIDER_LIMIT_REACHED: {
+ mNetworkStats.forceUpdate();
synchronized (mNetworkPoliciesSecondLock) {
- if (mMeteredIfaces.contains(iface)) {
- // force stats update to make sure we have
- // numbers that caused alert to trigger.
- mNetworkStats.forceUpdate();
-
- updateNetworkEnabledNL();
- updateNotificationsNL();
+ // Some providers might hit the limit reached event prior to others. Thus,
+ // re-calculate and update interface quota for every provider is needed.
+ updateNetworkRulesNL();
+ updateNetworkEnabledNL();
+ updateNotificationsNL();
+ }
+ return true;
+ }
+ case MSG_LIMIT_REACHED: {
+ final String iface = (String) msg.obj;
+ synchronized (mNetworkPoliciesSecondLock) {
+ // fast return if not needed.
+ if (!mMeteredIfaces.contains(iface)) {
+ return true;
}
}
+
+ // force stats update to make sure the service have the numbers that caused
+ // alert to trigger.
+ mNetworkStats.forceUpdate();
+
+ synchronized (mNetworkPoliciesSecondLock) {
+ updateNetworkRulesNL();
+ updateNetworkEnabledNL();
+ updateNotificationsNL();
+ }
return true;
}
case MSG_RESTRICT_BACKGROUND_CHANGED: {
@@ -4572,14 +4581,18 @@
return true;
}
case MSG_UPDATE_INTERFACE_QUOTA: {
- removeInterfaceQuota((String) msg.obj);
+ final String iface = (String) msg.obj;
// int params need to be stitched back into a long
- setInterfaceQuota((String) msg.obj,
- ((long) msg.arg1 << 32) | (msg.arg2 & 0xFFFFFFFFL));
+ final long quota = ((long) msg.arg1 << 32) | (msg.arg2 & 0xFFFFFFFFL);
+ removeInterfaceQuota(iface);
+ setInterfaceQuota(iface, quota);
+ mNetworkStats.setStatsProviderLimit(iface, quota);
return true;
}
case MSG_REMOVE_INTERFACE_QUOTA: {
- removeInterfaceQuota((String) msg.obj);
+ final String iface = (String) msg.obj;
+ removeInterfaceQuota(iface);
+ mNetworkStats.setStatsProviderLimit(iface, QUOTA_UNLIMITED);
return true;
}
case MSG_RESET_FIREWALL_RULES_BY_UID: {
@@ -5234,6 +5247,12 @@
mHandler.obtainMessage(MSG_METERED_RESTRICTED_PACKAGES_CHANGED,
userId, 0, packageNames).sendToTarget();
}
+
+ @Override
+ public void onStatsProviderLimitReached(@NonNull String tag) {
+ Log.v(TAG, "onStatsProviderLimitReached: " + tag);
+ mHandler.obtainMessage(MSG_STATS_PROVIDER_LIMIT_REACHED).sendToTarget();
+ }
}
private void setMeteredRestrictedPackagesInternal(Set<String> packageNames, int userId) {
@@ -5262,16 +5281,12 @@
}
private int parseSubId(NetworkState state) {
- // TODO: moved to using a legitimate NetworkSpecifier instead of string parsing
int subId = INVALID_SUBSCRIPTION_ID;
if (state != null && state.networkCapabilities != null
&& state.networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
NetworkSpecifier spec = state.networkCapabilities.getNetworkSpecifier();
- if (spec instanceof StringNetworkSpecifier) {
- try {
- subId = Integer.parseInt(((StringNetworkSpecifier) spec).specifier);
- } catch (NumberFormatException e) {
- }
+ if (spec instanceof TelephonyNetworkSpecifier) {
+ subId = ((TelephonyNetworkSpecifier) spec).getSubscriptionId();
}
}
return subId;
diff --git a/services/core/java/com/android/server/net/NetworkStatsFactory.java b/services/core/java/com/android/server/net/NetworkStatsFactory.java
index 3ca1803..22b01be 100644
--- a/services/core/java/com/android/server/net/NetworkStatsFactory.java
+++ b/services/core/java/com/android/server/net/NetworkStatsFactory.java
@@ -229,7 +229,7 @@
entry.txPackets += reader.nextLong();
}
- stats.addValues(entry);
+ stats.addEntry(entry);
reader.finishLine();
}
} catch (NullPointerException|NumberFormatException e) {
@@ -279,7 +279,7 @@
entry.txBytes = reader.nextLong();
entry.txPackets = reader.nextLong();
- stats.addValues(entry);
+ stats.addEntry(entry);
reader.finishLine();
}
} catch (NullPointerException|NumberFormatException e) {
@@ -439,7 +439,7 @@
if ((limitIfaces == null || ArrayUtils.contains(limitIfaces, entry.iface))
&& (limitUid == UID_ALL || limitUid == entry.uid)
&& (limitTag == TAG_ALL || limitTag == entry.tag)) {
- stats.addValues(entry);
+ stats.addEntry(entry);
}
reader.finishLine();
diff --git a/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java b/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java
index 4843ede..6d72cb5 100644
--- a/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java
+++ b/services/core/java/com/android/server/net/NetworkStatsManagerInternal.java
@@ -16,6 +16,7 @@
package com.android.server.net;
+import android.annotation.NonNull;
import android.net.NetworkStats;
import android.net.NetworkTemplate;
@@ -34,4 +35,10 @@
/** Force update of statistics. */
public abstract void forceUpdate();
+
+ /**
+ * Set the quota limit to all registered custom network stats providers.
+ * Note that invocation of any interface will be sent to all providers.
+ */
+ public abstract void setStatsProviderLimit(@NonNull String iface, long quota);
}
diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java
index ec8a8e7..1dcff07 100644
--- a/services/core/java/com/android/server/net/NetworkStatsService.java
+++ b/services/core/java/com/android/server/net/NetworkStatsService.java
@@ -18,6 +18,7 @@
import static android.Manifest.permission.ACCESS_NETWORK_STATE;
import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
+import static android.Manifest.permission.UPDATE_DEVICE_STATS;
import static android.content.Intent.ACTION_SHUTDOWN;
import static android.content.Intent.ACTION_UID_REMOVED;
import static android.content.Intent.ACTION_USER_REMOVED;
@@ -27,6 +28,7 @@
import static android.net.NetworkStack.checkNetworkStackPermission;
import static android.net.NetworkStats.DEFAULT_NETWORK_ALL;
import static android.net.NetworkStats.IFACE_ALL;
+import static android.net.NetworkStats.IFACE_VT;
import static android.net.NetworkStats.INTERFACES_ALL;
import static android.net.NetworkStats.METERED_ALL;
import static android.net.NetworkStats.ROAMING_ALL;
@@ -70,6 +72,7 @@
import static com.android.server.NetworkManagementSocketTagger.setKernelCounterSet;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.usage.NetworkStatsManager;
@@ -96,6 +99,9 @@
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
import android.net.TrafficStats;
+import android.net.netstats.provider.INetworkStatsProvider;
+import android.net.netstats.provider.INetworkStatsProviderCallback;
+import android.net.netstats.provider.NetworkStatsProviderCallback;
import android.os.BestClock;
import android.os.Binder;
import android.os.DropBoxManager;
@@ -108,6 +114,7 @@
import android.os.Message;
import android.os.Messenger;
import android.os.PowerManager;
+import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
@@ -175,7 +182,7 @@
* This avoids firing the global alert too often on devices with high transfer speeds and
* high quota.
*/
- private static final int PERFORM_POLL_DELAY_MS = 1000;
+ private static final int DEFAULT_PERFORM_POLL_DELAY_MS = 1000;
private static final String TAG_NETSTATS_ERROR = "netstats_error";
@@ -211,13 +218,15 @@
/**
* Virtual network interface for video telephony. This is for VT data usage counting purpose.
*/
- public static final String VT_INTERFACE = "vt_data0";
+ // TODO: Remove this after no one is using it.
+ public static final String VT_INTERFACE = NetworkStats.IFACE_VT;
/**
* Settings that can be changed externally.
*/
public interface NetworkStatsSettings {
public long getPollInterval();
+ public long getPollDelay();
public boolean getSampleEnabled();
public boolean getAugmentEnabled();
@@ -246,6 +255,7 @@
}
private final Object mStatsLock = new Object();
+ private final Object mStatsProviderLock = new Object();
/** Set of currently active ifaces. */
@GuardedBy("mStatsLock")
@@ -270,6 +280,9 @@
private final DropBoxNonMonotonicObserver mNonMonotonicObserver =
new DropBoxNonMonotonicObserver();
+ private final RemoteCallbackList<NetworkStatsProviderCallbackImpl> mStatsProviderCbList =
+ new RemoteCallbackList<>();
+
@GuardedBy("mStatsLock")
private NetworkStatsRecorder mDevRecorder;
@GuardedBy("mStatsLock")
@@ -500,9 +513,9 @@
}
/**
- * Register for a global alert that is delivered through
- * {@link INetworkManagementEventObserver} once a threshold amount of data
- * has been transferred.
+ * Register for a global alert that is delivered through {@link INetworkManagementEventObserver}
+ * or {@link NetworkStatsProviderCallback#onAlertReached()} once a threshold amount of data has
+ * been transferred.
*/
private void registerGlobalAlert() {
try {
@@ -512,6 +525,7 @@
} catch (RemoteException e) {
// ignored; service lives in system_server
}
+ invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.setAlert(mGlobalAlertBytes));
}
@Override
@@ -712,7 +726,7 @@
final NetworkStatsHistory.Entry entry = history.getValues(start, end, now, null);
final NetworkStats stats = new NetworkStats(end - start, 1);
- stats.addValues(new NetworkStats.Entry(IFACE_ALL, UID_ALL, SET_ALL, TAG_NONE,
+ stats.addEntry(new NetworkStats.Entry(IFACE_ALL, UID_ALL, SET_ALL, TAG_NONE,
METERED_ALL, ROAMING_ALL, DEFAULT_NETWORK_ALL, entry.rxBytes, entry.rxPackets,
entry.txBytes, entry.txPackets, entry.operations));
return stats;
@@ -801,8 +815,7 @@
@Override
public void incrementOperationCount(int uid, int tag, int operationCount) {
if (Binder.getCallingUid() != uid) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.UPDATE_DEVICE_STATS, TAG);
+ mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG);
}
if (operationCount < 0) {
@@ -1093,7 +1106,7 @@
/**
* Observer that watches for {@link INetworkManagementService} alerts.
*/
- private INetworkManagementEventObserver mAlertObserver = new BaseNetworkObserver() {
+ private final INetworkManagementEventObserver mAlertObserver = new BaseNetworkObserver() {
@Override
public void limitReached(String limitName, String iface) {
// only someone like NMS should be calling us
@@ -1104,7 +1117,7 @@
// such a call pending; UID stats are handled during normal polling interval.
if (!mHandler.hasMessages(MSG_PERFORM_POLL_REGISTER_ALERT)) {
mHandler.sendEmptyMessageDelayed(MSG_PERFORM_POLL_REGISTER_ALERT,
- PERFORM_POLL_DELAY_MS);
+ mSettings.getPollDelay());
}
}
}
@@ -1179,8 +1192,8 @@
ident.getSubType(), ident.getSubscriberId(), ident.getNetworkId(),
ident.getRoaming(), true /* metered */,
true /* onDefaultNetwork */);
- findOrCreateNetworkIdentitySet(mActiveIfaces, VT_INTERFACE).add(vtIdent);
- findOrCreateNetworkIdentitySet(mActiveUidIfaces, VT_INTERFACE).add(vtIdent);
+ findOrCreateNetworkIdentitySet(mActiveIfaces, IFACE_VT).add(vtIdent);
+ findOrCreateNetworkIdentitySet(mActiveUidIfaces, IFACE_VT).add(vtIdent);
}
if (isMobile) {
@@ -1250,6 +1263,14 @@
xtSnapshot.combineAllValues(tetherSnapshot);
devSnapshot.combineAllValues(tetherSnapshot);
+ // Snapshot for dev/xt stats from all custom stats providers. Counts per-interface data
+ // from stats providers that isn't already counted by dev and XT stats.
+ Trace.traceBegin(TRACE_TAG_NETWORK, "snapshotStatsProvider");
+ final NetworkStats providersnapshot = getNetworkStatsFromProviders(STATS_PER_IFACE);
+ Trace.traceEnd(TRACE_TAG_NETWORK);
+ xtSnapshot.combineAllValues(providersnapshot);
+ devSnapshot.combineAllValues(providersnapshot);
+
// For xt/dev, we pass a null VPN array because usage is aggregated by UID, so VPN traffic
// can't be reattributed to responsible apps.
Trace.traceBegin(TRACE_TAG_NETWORK, "recordDev");
@@ -1353,6 +1374,10 @@
performSampleLocked();
}
+ // request asynchronous stats update from all providers for next poll.
+ // TODO: request with a valid token.
+ invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.requestStatsUpdate(0 /* unused */));
+
// finally, dispatch updated event to any listeners
final Intent updatedIntent = new Intent(ACTION_NETWORK_STATS_UPDATED);
updatedIntent.setFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
@@ -1474,6 +1499,12 @@
public void forceUpdate() {
NetworkStatsService.this.forceUpdate();
}
+
+ @Override
+ public void setStatsProviderLimit(@NonNull String iface, long quota) {
+ Slog.v(TAG, "setStatsProviderLimit(" + iface + "," + quota + ")");
+ invokeForAllStatsProviderCallbacks((cb) -> cb.mProvider.setLimit(iface, quota));
+ }
}
@Override
@@ -1688,6 +1719,12 @@
uidSnapshot.combineAllValues(vtStats);
}
+ // get a stale copy of uid stats snapshot provided by providers.
+ final NetworkStats providerStats = getNetworkStatsFromProviders(STATS_PER_UID);
+ providerStats.filter(UID_ALL, ifaces, TAG_ALL);
+ mStatsFactory.apply464xlatAdjustments(uidSnapshot, providerStats, mUseBpfTrafficStats);
+ uidSnapshot.combineAllValues(providerStats);
+
uidSnapshot.combineAllValues(mUidOperations);
return uidSnapshot;
@@ -1724,6 +1761,152 @@
}
}
+ /**
+ * Registers a custom provider of {@link android.net.NetworkStats} to combine the network
+ * statistics that cannot be seen by the kernel to system. To unregister, invoke the
+ * {@code unregister()} of the returned callback.
+ *
+ * @param tag a human readable identifier of the custom network stats provider.
+ * @param provider the binder interface of
+ * {@link android.net.netstats.provider.AbstractNetworkStatsProvider} that
+ * needs to be registered to the system.
+ *
+ * @return a binder interface of
+ * {@link android.net.netstats.provider.NetworkStatsProviderCallback}, which can be
+ * used to report events to the system.
+ */
+ public @NonNull INetworkStatsProviderCallback registerNetworkStatsProvider(
+ @NonNull String tag, @NonNull INetworkStatsProvider provider) {
+ mContext.enforceCallingOrSelfPermission(UPDATE_DEVICE_STATS, TAG);
+ Objects.requireNonNull(provider, "provider is null");
+ Objects.requireNonNull(tag, "tag is null");
+ try {
+ NetworkStatsProviderCallbackImpl callback = new NetworkStatsProviderCallbackImpl(
+ tag, provider, mAlertObserver, mStatsProviderCbList);
+ mStatsProviderCbList.register(callback);
+ Log.d(TAG, "registerNetworkStatsProvider from " + callback.mTag + " uid/pid="
+ + getCallingUid() + "/" + getCallingPid());
+ return callback;
+ } catch (RemoteException e) {
+ Log.e(TAG, "registerNetworkStatsProvider failed", e);
+ }
+ return null;
+ }
+
+ // Collect stats from local cache of providers.
+ private @NonNull NetworkStats getNetworkStatsFromProviders(int how) {
+ final NetworkStats ret = new NetworkStats(0L, 0);
+ invokeForAllStatsProviderCallbacks((cb) -> ret.combineAllValues(cb.getCachedStats(how)));
+ return ret;
+ }
+
+ @FunctionalInterface
+ private interface ThrowingConsumer<S, T extends Throwable> {
+ void accept(S s) throws T;
+ }
+
+ private void invokeForAllStatsProviderCallbacks(
+ @NonNull ThrowingConsumer<NetworkStatsProviderCallbackImpl, RemoteException> task) {
+ synchronized (mStatsProviderCbList) {
+ final int length = mStatsProviderCbList.beginBroadcast();
+ try {
+ for (int i = 0; i < length; i++) {
+ final NetworkStatsProviderCallbackImpl cb =
+ mStatsProviderCbList.getBroadcastItem(i);
+ try {
+ task.accept(cb);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Fail to broadcast to provider: " + cb.mTag, e);
+ }
+ }
+ } finally {
+ mStatsProviderCbList.finishBroadcast();
+ }
+ }
+ }
+
+ private static class NetworkStatsProviderCallbackImpl extends INetworkStatsProviderCallback.Stub
+ implements IBinder.DeathRecipient {
+ @NonNull final String mTag;
+ @NonNull private final Object mProviderStatsLock = new Object();
+ @NonNull final INetworkStatsProvider mProvider;
+ @NonNull final INetworkManagementEventObserver mAlertObserver;
+ @NonNull final RemoteCallbackList<NetworkStatsProviderCallbackImpl> mStatsProviderCbList;
+
+ @GuardedBy("mProviderStatsLock")
+ // STATS_PER_IFACE and STATS_PER_UID
+ private final NetworkStats mIfaceStats = new NetworkStats(0L, 0);
+ @GuardedBy("mProviderStatsLock")
+ private final NetworkStats mUidStats = new NetworkStats(0L, 0);
+
+ NetworkStatsProviderCallbackImpl(
+ @NonNull String tag, @NonNull INetworkStatsProvider provider,
+ @NonNull INetworkManagementEventObserver alertObserver,
+ @NonNull RemoteCallbackList<NetworkStatsProviderCallbackImpl> cbList)
+ throws RemoteException {
+ mTag = tag;
+ mProvider = provider;
+ mProvider.asBinder().linkToDeath(this, 0);
+ mAlertObserver = alertObserver;
+ mStatsProviderCbList = cbList;
+ }
+
+ @NonNull
+ public NetworkStats getCachedStats(int how) {
+ synchronized (mProviderStatsLock) {
+ NetworkStats stats;
+ switch (how) {
+ case STATS_PER_IFACE:
+ stats = mIfaceStats;
+ break;
+ case STATS_PER_UID:
+ stats = mUidStats;
+ break;
+ default:
+ throw new IllegalArgumentException("Invalid type: " + how);
+ }
+ // Return a defensive copy instead of local reference.
+ return stats.clone();
+ }
+ }
+
+ @Override
+ public void onStatsUpdated(int token, @Nullable NetworkStats ifaceStats,
+ @Nullable NetworkStats uidStats) {
+ // TODO: 1. Use token to map ifaces to correct NetworkIdentity.
+ // 2. Store the difference and store it directly to the recorder.
+ synchronized (mProviderStatsLock) {
+ if (ifaceStats != null) mIfaceStats.combineAllValues(ifaceStats);
+ if (uidStats != null) mUidStats.combineAllValues(uidStats);
+ }
+ }
+
+ @Override
+ public void onAlertReached() throws RemoteException {
+ mAlertObserver.limitReached(LIMIT_GLOBAL_ALERT, null /* unused */);
+ }
+
+ @Override
+ public void onLimitReached() {
+ Log.d(TAG, mTag + ": onLimitReached");
+ LocalServices.getService(NetworkPolicyManagerInternal.class)
+ .onStatsProviderLimitReached(mTag);
+ }
+
+ @Override
+ public void binderDied() {
+ Log.d(TAG, mTag + ": binderDied");
+ mStatsProviderCbList.unregister(this);
+ }
+
+ @Override
+ public void unregister() {
+ Log.d(TAG, mTag + ": unregister");
+ mStatsProviderCbList.unregister(this);
+ }
+
+ }
+
@VisibleForTesting
static class HandlerCallback implements Handler.Callback {
private final NetworkStatsService mService;
@@ -1813,6 +1996,10 @@
return getGlobalLong(NETSTATS_POLL_INTERVAL, 30 * MINUTE_IN_MILLIS);
}
@Override
+ public long getPollDelay() {
+ return DEFAULT_PERFORM_POLL_DELAY_MS;
+ }
+ @Override
public long getGlobalAlertBytes(long def) {
return getGlobalLong(NETSTATS_GLOBAL_ALERT_BYTES, def);
}
diff --git a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
index d66fd57..eaf120e 100644
--- a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
@@ -42,8 +42,10 @@
return null;
}
- record.updateNotificationChannel(mConfig.getNotificationChannel(record.sbn.getPackageName(),
- record.sbn.getUid(), record.getChannel().getId(), false));
+ record.updateNotificationChannel(mConfig.getConversationNotificationChannel(
+ record.sbn.getPackageName(),
+ record.sbn.getUid(), record.getChannel().getId(),
+ record.getNotification().getShortcutId(), true, false));
return null;
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index f42f4f7..1d49364 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -25,6 +25,7 @@
import static android.app.Notification.FLAG_NO_CLEAR;
import static android.app.Notification.FLAG_ONGOING_EVENT;
import static android.app.Notification.FLAG_ONLY_ALERT_ONCE;
+import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT;
import static android.app.NotificationManager.ACTION_APP_BLOCK_STATE_CHANGED;
import static android.app.NotificationManager.ACTION_AUTOMATIC_ZEN_RULE_STATUS_CHANGED;
import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED;
@@ -471,7 +472,8 @@
private static final String LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_VALUE = "value";
private RankingHelper mRankingHelper;
- private PreferencesHelper mPreferencesHelper;
+ @VisibleForTesting
+ PreferencesHelper mPreferencesHelper;
private final UserProfiles mUserProfiles = new UserProfiles();
private NotificationListeners mListeners;
@@ -1009,12 +1011,14 @@
if (clearEffects) {
clearEffects();
}
+ mAssistants.onPanelRevealed(items);
}
@Override
public void onPanelHidden() {
MetricsLogger.hidden(getContext(), MetricsEvent.NOTIFICATION_PANEL);
EventLogTags.writeNotificationPanelHidden();
+ mAssistants.onPanelHidden();
}
@Override
@@ -1061,6 +1065,7 @@
reportSeen(r);
}
r.setVisibility(true, nv.rank, nv.count);
+ mAssistants.notifyAssistantVisibilityChangedLocked(r.sbn, true);
boolean isHun = (nv.location
== NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP);
// hasBeenVisiblyExpanded must be called after updating the expansion state of
@@ -1079,6 +1084,7 @@
NotificationRecord r = mNotificationsByKey.get(nv.key);
if (r == null) continue;
r.setVisibility(false, nv.rank, nv.count);
+ mAssistants.notifyAssistantVisibilityChangedLocked(r.sbn, false);
nv.recycle();
}
}
@@ -1182,21 +1188,14 @@
@Override
public void onNotificationBubbleChanged(String key, boolean isBubble) {
- String pkg;
- synchronized (mNotificationLock) {
- NotificationRecord r = mNotificationsByKey.get(key);
- pkg = r != null && r.sbn != null ? r.sbn.getPackageName() : null;
- }
- boolean isAppForeground = pkg != null
- && mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
synchronized (mNotificationLock) {
NotificationRecord r = mNotificationsByKey.get(key);
if (r != null) {
final StatusBarNotification n = r.sbn;
final int callingUid = n.getUid();
- pkg = n.getPackageName();
+ final String pkg = n.getPackageName();
if (isBubble && isNotificationAppropriateToBubble(r, pkg, callingUid,
- null /* oldEntry */, isAppForeground)) {
+ null /* oldEntry */)) {
r.getNotification().flags |= FLAG_BUBBLE;
} else {
r.getNotification().flags &= ~FLAG_BUBBLE;
@@ -2227,8 +2226,8 @@
maybeNotifyChannelOwner(pkg, uid, preUpdate, channel);
if (!fromListener) {
- final NotificationChannel modifiedChannel =
- mPreferencesHelper.getNotificationChannel(pkg, uid, channel.getId(), false);
+ final NotificationChannel modifiedChannel = mPreferencesHelper.getNotificationChannel(
+ pkg, uid, channel.getId(), false);
mListeners.notifyNotificationChannelChanged(
pkg, UserHandle.getUserHandleForUid(uid),
modifiedChannel, NOTIFICATION_CHANNEL_OR_GROUP_UPDATED);
@@ -2489,7 +2488,7 @@
.setUid(r.sbn.getUid())
.setChannelId(r.getChannel().getId())
.setChannelName(r.getChannel().getName().toString())
- .setPostedTimeMs(r.sbn.getPostTime())
+ .setPostedTimeMs(System.currentTimeMillis())
.setTitle(getHistoryTitle(r.getNotification()))
.setText(getHistoryText(
r.sbn.getPackageContext(getContext()), r.getNotification()))
@@ -3025,31 +3024,55 @@
@Override
public void createNotificationChannels(String pkg,
- ParceledListSlice channelsList) throws RemoteException {
+ ParceledListSlice channelsList) {
checkCallerIsSystemOrSameApp(pkg);
createNotificationChannelsImpl(pkg, Binder.getCallingUid(), channelsList);
}
@Override
public void createNotificationChannelsForPackage(String pkg, int uid,
- ParceledListSlice channelsList) throws RemoteException {
- checkCallerIsSystem();
+ ParceledListSlice channelsList) {
+ enforceSystemOrSystemUI("only system can call this");
createNotificationChannelsImpl(pkg, uid, channelsList);
}
@Override
+ public void createConversationNotificationChannelForPackage(String pkg, int uid,
+ NotificationChannel parentChannel, String conversationId) {
+ enforceSystemOrSystemUI("only system can call this");
+ Preconditions.checkNotNull(parentChannel);
+ Preconditions.checkNotNull(conversationId);
+ String parentId = parentChannel.getId();
+ NotificationChannel conversationChannel = parentChannel;
+ conversationChannel.setId(String.format(
+ CONVERSATION_CHANNEL_ID_FORMAT, parentId, conversationId));
+ conversationChannel.setConversationId(parentId, conversationId);
+ createNotificationChannelsImpl(
+ pkg, uid, new ParceledListSlice(Arrays.asList(conversationChannel)));
+ }
+
+ @Override
public NotificationChannel getNotificationChannel(String callingPkg, int userId,
String targetPkg, String channelId) {
+ return getConversationNotificationChannel(
+ callingPkg, userId, targetPkg, channelId, true, null);
+ }
+
+ @Override
+ public NotificationChannel getConversationNotificationChannel(String callingPkg, int userId,
+ String targetPkg, String channelId, boolean returnParentIfNoConversationChannel,
+ String conversationId) {
if (canNotifyAsPackage(callingPkg, targetPkg, userId)
- || isCallingUidSystem()) {
+ || isCallerIsSystemOrSystemUi()) {
int targetUid = -1;
try {
targetUid = mPackageManagerClient.getPackageUidAsUser(targetPkg, userId);
} catch (NameNotFoundException e) {
/* ignore */
}
- return mPreferencesHelper.getNotificationChannel(
- targetPkg, targetUid, channelId, false /* includeDeleted */);
+ return mPreferencesHelper.getConversationNotificationChannel(
+ targetPkg, targetUid, channelId, conversationId,
+ returnParentIfNoConversationChannel, false /* includeDeleted */);
}
throw new SecurityException("Pkg " + callingPkg
+ " cannot read channels for " + targetPkg + " in " + userId);
@@ -3080,6 +3103,30 @@
}
@Override
+ public void deleteConversationNotificationChannels(String pkg, int uid,
+ String conversationId) {
+ checkCallerIsSystem();
+ final int callingUid = Binder.getCallingUid();
+ List<NotificationChannel> channels =
+ mPreferencesHelper.getNotificationChannelsByConversationId(
+ pkg, uid, conversationId);
+ if (!channels.isEmpty()) {
+ for (NotificationChannel nc : channels) {
+ cancelAllNotificationsInt(MY_UID, MY_PID, pkg, nc.getId(), 0, 0, true,
+ UserHandle.getUserId(callingUid), REASON_CHANNEL_BANNED, null);
+ mPreferencesHelper.deleteNotificationChannel(pkg, callingUid, nc.getId());
+ mListeners.notifyNotificationChannelChanged(pkg,
+ UserHandle.getUserHandleForUid(callingUid),
+ mPreferencesHelper.getNotificationChannel(
+ pkg, callingUid, nc.getId(), true),
+ NOTIFICATION_CHANNEL_OR_GROUP_DELETED);
+ }
+ handleSavePolicyFile();
+ }
+ }
+
+
+ @Override
public NotificationChannelGroup getNotificationChannelGroup(String pkg, String groupId) {
checkCallerIsSystemOrSameApp(pkg);
return mPreferencesHelper.getNotificationChannelGroupWithChannels(
@@ -5210,8 +5257,15 @@
if (mIsTelevision && (new Notification.TvExtender(notification)).getChannelId() != null) {
channelId = (new Notification.TvExtender(notification)).getChannelId();
}
- final NotificationChannel channel = mPreferencesHelper.getNotificationChannel(pkg,
- notificationUid, channelId, false /* includeDeleted */);
+ // TODO: flag this behavior
+ String shortcutId = notification.getShortcutId();
+ if (shortcutId == null
+ && notification.getNotificationStyle() == Notification.MessagingStyle.class) {
+ shortcutId = id + tag + NotificationChannel.PLACEHOLDER_CONVERSATION_ID;
+ }
+ final NotificationChannel channel = mPreferencesHelper.getConversationNotificationChannel(
+ pkg, notificationUid, channelId, shortcutId,
+ true /* parent ok */, false /* includeDeleted */);
if (channel == null) {
final String noChannelStr = "No Channel found for "
+ "pkg=" + pkg
@@ -5386,7 +5440,7 @@
private void flagNotificationForBubbles(NotificationRecord r, String pkg, int userId,
NotificationRecord oldRecord, boolean isAppForeground) {
Notification notification = r.getNotification();
- if (isNotificationAppropriateToBubble(r, pkg, userId, oldRecord, isAppForeground)) {
+ if (isNotificationAppropriateToBubble(r, pkg, userId, oldRecord)) {
notification.flags |= FLAG_BUBBLE;
} else {
notification.flags &= ~FLAG_BUBBLE;
@@ -5406,7 +5460,7 @@
* accounting for user choice & policy.
*/
private boolean isNotificationAppropriateToBubble(NotificationRecord r, String pkg, int userId,
- NotificationRecord oldRecord, boolean isAppForeground) {
+ NotificationRecord oldRecord) {
Notification notification = r.getNotification();
if (!canBubble(r, pkg, userId)) {
// no log: canBubble has its own
@@ -5418,11 +5472,6 @@
return false;
}
- if (isAppForeground) {
- // If the app is foreground it always gets to bubble
- return true;
- }
-
if (oldRecord != null && (oldRecord.getNotification().flags & FLAG_BUBBLE) != 0) {
// This is an update to an active bubble
return true;
@@ -5438,7 +5487,7 @@
boolean isMessageStyle = Notification.MessagingStyle.class.equals(
notification.getNotificationStyle());
if (!isMessageStyle && (peopleList == null || peopleList.isEmpty())) {
- logBubbleError(r.getKey(), "if not foreground, must have a person and be "
+ logBubbleError(r.getKey(), "Must have a person and be "
+ "Notification.MessageStyle or Notification.CATEGORY_CALL");
return false;
}
@@ -5446,6 +5495,11 @@
// Communication is a message or a call
boolean isCall = CATEGORY_CALL.equals(notification.category);
boolean hasForegroundService = (notification.flags & FLAG_FOREGROUND_SERVICE) != 0;
+ if (hasForegroundService && !isCall) {
+ logBubbleError(r.getKey(),
+ "foreground services must be Notification.CATEGORY_CALL to bubble");
+ return false;
+ }
if (isMessageStyle) {
if (hasValidRemoteInput(notification)) {
return true;
@@ -5459,7 +5513,7 @@
logBubbleError(r.getKey(), "calls require foreground service");
return false;
}
- logBubbleError(r.getKey(), "if not foreground, must be "
+ logBubbleError(r.getKey(), "Must be "
+ "Notification.MessageStyle or Notification.CATEGORY_CALL");
return false;
}
@@ -7847,6 +7901,14 @@
return isUidSystemOrPhone(Binder.getCallingUid());
}
+ private boolean isCallerIsSystemOrSystemUi() {
+ if (isCallerSystemOrPhone()) {
+ return true;
+ }
+ return getContext().checkCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE)
+ == PERMISSION_GRANTED;
+ }
+
private void checkCallerIsSystemOrShell() {
int callingUid = Binder.getCallingUid();
if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) {
@@ -8304,6 +8366,32 @@
}
}
+ protected void onPanelRevealed(int items) {
+ for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) {
+ mHandler.post(() -> {
+ final INotificationListener assistant = (INotificationListener) info.service;
+ try {
+ assistant.onPanelRevealed(items);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "unable to notify assistant (panel revealed): " + info, ex);
+ }
+ });
+ }
+ }
+
+ protected void onPanelHidden() {
+ for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) {
+ mHandler.post(() -> {
+ final INotificationListener assistant = (INotificationListener) info.service;
+ try {
+ assistant.onPanelHidden();
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "unable to notify assistant (panel hidden): " + info, ex);
+ }
+ });
+ }
+ }
+
boolean hasUserSet(int userId) {
synchronized (mLock) {
return mUserSetMap.getOrDefault(userId, false);
@@ -8371,6 +8459,24 @@
}
@GuardedBy("mNotificationLock")
+ void notifyAssistantVisibilityChangedLocked(
+ final StatusBarNotification sbn,
+ final boolean isVisible) {
+ final String key = sbn.getKey();
+ Slog.d(TAG, "notifyAssistantVisibilityChangedLocked: " + key);
+ notifyAssistantLocked(
+ sbn,
+ false /* sameUserOnly */,
+ (assistant, sbnHolder) -> {
+ try {
+ assistant.onNotificationVisibilityChanged(key, isVisible);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "unable to notify assistant (visible): " + assistant, ex);
+ }
+ });
+ }
+
+ @GuardedBy("mNotificationLock")
void notifyAssistantExpansionChangedLocked(
final StatusBarNotification sbn,
final boolean isUserAction,
diff --git a/services/core/java/com/android/server/notification/OWNERS b/services/core/java/com/android/server/notification/OWNERS
new file mode 100644
index 0000000..5a19656
--- /dev/null
+++ b/services/core/java/com/android/server/notification/OWNERS
@@ -0,0 +1,4 @@
+dsandler@android.com
+juliacr@google.com
+beverlyt@google.com
+pixel@google.com
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index cdb0a17..185d75c 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -770,6 +770,13 @@
channel.setShowBadge(false);
}
channel.setOriginalImportance(channel.getImportance());
+
+ // validate parent
+ if (channel.getParentChannelId() != null) {
+ Preconditions.checkArgument(r.channels.containsKey(channel.getParentChannelId()),
+ "Tried to create a conversation channel without a preexisting parent");
+ }
+
r.channels.put(channel.getId(), channel);
if (channel.canBypassDnd() != mAreChannelsBypassingDnd) {
updateChannelsBypassingDnd(mContext.getUserId());
@@ -851,6 +858,14 @@
public NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
boolean includeDeleted) {
Objects.requireNonNull(pkg);
+ return getConversationNotificationChannel(pkg, uid, channelId, null, true, includeDeleted);
+ }
+
+ @Override
+ public NotificationChannel getConversationNotificationChannel(String pkg, int uid,
+ String channelId, String conversationId, boolean returnParentIfNoConversationChannel,
+ boolean includeDeleted) {
+ Preconditions.checkNotNull(pkg);
synchronized (mPackagePreferences) {
PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
if (r == null) {
@@ -859,11 +874,51 @@
if (channelId == null) {
channelId = NotificationChannel.DEFAULT_CHANNEL_ID;
}
- final NotificationChannel nc = r.channels.get(channelId);
- if (nc != null && (includeDeleted || !nc.isDeleted())) {
+ NotificationChannel channel = null;
+ if (conversationId != null) {
+ // look for an automatically created conversation specific channel
+ channel = findConversationChannel(r, channelId, conversationId, includeDeleted);
+ }
+ if (channel == null && returnParentIfNoConversationChannel) {
+ // look for it just based on its id
+ final NotificationChannel nc = r.channels.get(channelId);
+ if (nc != null && (includeDeleted || !nc.isDeleted())) {
+ return nc;
+ }
+ }
+ return channel;
+ }
+ }
+
+ private NotificationChannel findConversationChannel(PackagePreferences p, String parentId,
+ String conversationId, boolean includeDeleted) {
+ for (NotificationChannel nc : p.channels.values()) {
+ if (conversationId.equals(nc.getConversationId())
+ && parentId.equals(nc.getParentChannelId())
+ && (includeDeleted || !nc.isDeleted())) {
return nc;
}
- return null;
+ }
+ return null;
+ }
+
+ public List<NotificationChannel> getNotificationChannelsByConversationId(String pkg, int uid,
+ String conversationId) {
+ Preconditions.checkNotNull(pkg);
+ Preconditions.checkNotNull(conversationId);
+ List<NotificationChannel> channels = new ArrayList<>();
+ synchronized (mPackagePreferences) {
+ PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid);
+ if (r == null) {
+ return channels;
+ }
+ for (NotificationChannel nc : r.channels.values()) {
+ if (conversationId.equals(nc.getConversationId())
+ && !nc.isDeleted()) {
+ channels.add(nc);
+ }
+ }
+ return channels;
}
}
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index 7816f36..7e98be7 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/com/android/server/notification/RankingConfig.java
@@ -41,10 +41,16 @@
int uid, boolean includeDeleted, boolean includeNonGrouped, boolean includeEmpty);
boolean createNotificationChannel(String pkg, int uid, NotificationChannel channel,
boolean fromTargetApp, boolean hasDndAccess);
- void updateNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromUser);
- NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, boolean includeDeleted);
+ void updateNotificationChannel(String pkg, int uid, NotificationChannel channel,
+ boolean fromUser);
+ NotificationChannel getNotificationChannel(String pkg, int uid, String channelId,
+ boolean includeDeleted);
+ NotificationChannel getConversationNotificationChannel(String pkg, int uid, String channelId,
+ String conversationId, boolean returnParentIfNoConversationChannel,
+ boolean includeDeleted);
void deleteNotificationChannel(String pkg, int uid, String channelId);
void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId);
void permanentlyDeleteNotificationChannels(String pkg, int uid);
- ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid, boolean includeDeleted);
+ ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid,
+ boolean includeDeleted);
}
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index f1947ac..3c31f6a 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -83,6 +83,7 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -925,7 +926,7 @@
/**
* Updates the target packages' set of enabled overlays in PackageManager.
*/
- private void updateOverlayPaths(int userId, List<String> targetPackageNames) {
+ private ArrayList<String> updateOverlayPaths(int userId, List<String> targetPackageNames) {
try {
traceBegin(TRACE_TAG_RRO, "OMS#updateOverlayPaths " + targetPackageNames);
if (DEBUG) {
@@ -955,6 +956,7 @@
}
}
+ final HashSet<String> updatedPackages = new HashSet<>();
final int n = targetPackageNames.size();
for (int i = 0; i < n; i++) {
final String targetPackageName = targetPackageNames.get(i);
@@ -965,11 +967,13 @@
}
if (!pm.setEnabledOverlayPackages(
- userId, targetPackageName, pendingChanges.get(targetPackageName))) {
+ userId, targetPackageName, pendingChanges.get(targetPackageName),
+ updatedPackages)) {
Slog.e(TAG, String.format("Failed to change enabled overlays for %s user %d",
targetPackageName, userId));
}
}
+ return new ArrayList<>(updatedPackages);
} finally {
traceEnd(TRACE_TAG_RRO);
}
@@ -980,10 +984,10 @@
}
private void updateAssets(final int userId, List<String> targetPackageNames) {
- updateOverlayPaths(userId, targetPackageNames);
final IActivityManager am = ActivityManager.getService();
try {
- am.scheduleApplicationInfoChanged(targetPackageNames, userId);
+ final ArrayList<String> updatedPaths = updateOverlayPaths(userId, targetPackageNames);
+ am.scheduleApplicationInfoChanged(updatedPaths, userId);
} catch (RemoteException e) {
// Intentionally left empty.
}
@@ -1154,6 +1158,10 @@
throws RemoteException, IOException {
PackageInfo packageInfo = mPackageManager.getPackageInfo(targetPackageName, 0,
userId);
+ if (packageInfo == null) {
+ throw new IOException("Unable to get target package");
+ }
+
String baseCodePath = packageInfo.applicationInfo.getBaseCodePath();
ApkAssets apkAssets = null;
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 019c952..9623542 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -685,7 +685,7 @@
// Static RROs targeting to "android", ie framework-res.apk, are handled by native layers.
if (targetPackage != null && overlayPackage != null
&& !("android".equals(targetPackageName)
- && overlayPackage.isStaticOverlayPackage())) {
+ && overlayPackage.isStaticOverlayPackage())) {
mIdmapManager.createIdmap(targetPackage, overlayPackage, userId);
}
@@ -703,9 +703,9 @@
if (currentState != newState) {
if (DEBUG) {
Slog.d(TAG, String.format("%s:%d: %s -> %s",
- overlayPackageName, userId,
- OverlayInfo.stateToString(currentState),
- OverlayInfo.stateToString(newState)));
+ overlayPackageName, userId,
+ OverlayInfo.stateToString(currentState),
+ OverlayInfo.stateToString(newState)));
}
modified |= mSettings.setState(overlayPackageName, userId, newState);
}
diff --git a/services/core/java/com/android/server/om/TEST_MAPPING b/services/core/java/com/android/server/om/TEST_MAPPING
new file mode 100644
index 0000000..52163a0
--- /dev/null
+++ b/services/core/java/com/android/server/om/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.om."
+ }
+ ]
+ }
+ ]
+}
diff --git a/services/core/java/com/android/server/people/PeopleServiceInternal.java b/services/core/java/com/android/server/people/PeopleServiceInternal.java
new file mode 100644
index 0000000..c5b868f
--- /dev/null
+++ b/services/core/java/com/android/server/people/PeopleServiceInternal.java
@@ -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.
+ */
+
+package com.android.server.people;
+
+import android.annotation.NonNull;
+import android.service.appprediction.IPredictionService;
+
+/**
+ * @hide Only for use within the system server.
+ */
+public abstract class PeopleServiceInternal extends IPredictionService.Stub {
+
+ /**
+ * The number conversation infos will be dynamic, based on the currently installed apps on the
+ * device. All of which should be combined into a single blob to be backed up.
+ */
+ public abstract byte[] backupConversationInfos(@NonNull int userId);
+
+ /**
+ * Multiple conversation infos may exist in the restore payload, child classes are required to
+ * manage the restoration based on how individual conversation infos were originally combined
+ * during backup.
+ */
+ public abstract void restoreConversationInfos(@NonNull int userId, @NonNull String key,
+ @NonNull byte[] payload);
+}
diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java
index 307a07b..2807909 100644
--- a/services/core/java/com/android/server/pm/ApexManager.java
+++ b/services/core/java/com/android/server/pm/ApexManager.java
@@ -32,10 +32,13 @@
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
+import android.content.pm.parsing.AndroidPackage;
import android.os.Environment;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.sysprop.ApexProperties;
+import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.Singleton;
import android.util.Slog;
@@ -44,15 +47,19 @@
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.IndentingPrintWriter;
+import com.google.android.collect.Lists;
+
import java.io.File;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
import java.util.stream.Collectors;
/**
@@ -97,12 +104,27 @@
* Minimal information about APEX mount points and the original APEX package they refer to.
*/
static class ActiveApexInfo {
+ @Nullable public final String apexModuleName;
public final File apexDirectory;
- public final File preinstalledApexPath;
+ public final File preInstalledApexPath;
- private ActiveApexInfo(File apexDirectory, File preinstalledApexPath) {
+ private ActiveApexInfo(File apexDirectory, File preInstalledApexPath) {
+ this(null, apexDirectory, preInstalledApexPath);
+ }
+
+ private ActiveApexInfo(@Nullable String apexModuleName, File apexDirectory,
+ File preInstalledApexPath) {
+ this.apexModuleName = apexModuleName;
this.apexDirectory = apexDirectory;
- this.preinstalledApexPath = preinstalledApexPath;
+ this.preInstalledApexPath = preInstalledApexPath;
+ }
+
+ private ActiveApexInfo(ApexInfo apexInfo) {
+ this(
+ apexInfo.moduleName,
+ new File(Environment.getApexDirectory() + File.separator
+ + apexInfo.moduleName),
+ new File(apexInfo.preinstalledModulePath));
}
}
@@ -232,6 +254,17 @@
abstract boolean uninstallApex(String apexPackagePath);
/**
+ * Registers an APK package as an embedded apk of apex.
+ */
+ abstract void registerApkInApex(AndroidPackage pkg);
+
+ /**
+ * Returns list of {@code packageName} of apks inside the given apex.
+ * @param apexPackageName Package name of the apk container of apex
+ */
+ abstract List<String> getApksInApex(String apexPackageName);
+
+ /**
* Dumps various state information to the provided {@link PrintWriter} object.
*
* @param pw the {@link PrintWriter} object to send information to.
@@ -255,16 +288,33 @@
static class ApexManagerImpl extends ApexManager {
private final IApexService mApexService;
private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private Set<ActiveApexInfo> mActiveApexInfosCache;
+
/**
- * A map from {@code APEX packageName} to the {@Link PackageInfo} generated from the {@code
- * AndroidManifest.xml}
- *
- * <p>Note that key of this map is {@code packageName} field of the corresponding {@code
- * AndroidManifest.xml}.
- */
+ * Contains the list of {@code packageName}s of apks-in-apex for given
+ * {@code apexModuleName}. See {@link #mPackageNameToApexModuleName} to understand the
+ * difference between {@code packageName} and {@code apexModuleName}.
+ */
+ @GuardedBy("mLock")
+ private Map<String, List<String>> mApksInApex = new ArrayMap<>();
+
@GuardedBy("mLock")
private List<PackageInfo> mAllPackagesCache;
+ /**
+ * An APEX is a file format that delivers the apex-payload wrapped in an apk container. The
+ * apk container has a reference name, called {@code packageName}, which is found inside the
+ * {@code AndroidManifest.xml}. The apex payload inside the container also has a reference
+ * name, called {@code apexModuleName}, which is found in {@code apex_manifest.json} file.
+ *
+ * {@link #mPackageNameToApexModuleName} contains the mapping from {@code packageName} of
+ * the apk container to {@code apexModuleName} of the apex-payload inside.
+ */
+ @GuardedBy("mLock")
+ private Map<String, String> mPackageNameToApexModuleName;
+
ApexManagerImpl(IApexService apexService) {
mApexService = apexService;
}
@@ -291,18 +341,25 @@
@Override
List<ActiveApexInfo> getActiveApexInfos() {
- try {
- return Arrays.stream(mApexService.getActivePackages())
- .map(apexInfo -> new ActiveApexInfo(
- new File(
- Environment.getApexDirectory() + File.separator
- + apexInfo.moduleName),
- new File(apexInfo.preinstalledModulePath))).collect(
- Collectors.toList());
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to retrieve packages from apexservice", e);
+ synchronized (mLock) {
+ if (mActiveApexInfosCache == null) {
+ try {
+ mActiveApexInfosCache = new ArraySet<>();
+ final ApexInfo[] activePackages = mApexService.getActivePackages();
+ for (int i = 0; i < activePackages.length; i++) {
+ ApexInfo apexInfo = activePackages[i];
+ mActiveApexInfosCache.add(new ActiveApexInfo(apexInfo));
+ }
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to retrieve packages from apexservice", e);
+ }
+ }
+ if (mActiveApexInfosCache != null) {
+ return new ArrayList<>(mActiveApexInfosCache);
+ } else {
+ return Collections.emptyList();
+ }
}
- return Collections.emptyList();
}
@Override
@@ -318,6 +375,33 @@
}, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
}
+ private void populatePackageNameToApexModuleNameIfNeeded() {
+ synchronized (mLock) {
+ if (mPackageNameToApexModuleName != null) {
+ return;
+ }
+ try {
+ mPackageNameToApexModuleName = new ArrayMap<>();
+ final ApexInfo[] allPkgs = mApexService.getAllPackages();
+ for (int i = 0; i < allPkgs.length; i++) {
+ ApexInfo ai = allPkgs[i];
+ PackageParser.PackageLite pkgLite;
+ try {
+ File apexFile = new File(ai.modulePath);
+ pkgLite = PackageParser.parsePackageLite(apexFile, 0);
+ } catch (PackageParser.PackageParserException pe) {
+ throw new IllegalStateException("Unable to parse: "
+ + ai.modulePath, pe);
+ }
+ mPackageNameToApexModuleName.put(pkgLite.packageName, ai.moduleName);
+ }
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Unable to retrieve packages from apexservice: ", re);
+ throw new RuntimeException(re);
+ }
+ }
+ }
+
private void populateAllPackagesCacheIfNeeded() {
synchronized (mLock) {
if (mAllPackagesCache != null) {
@@ -366,7 +450,6 @@
}
factoryPackagesSet.add(packageInfo.packageName);
}
-
}
} catch (RemoteException re) {
Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString());
@@ -533,6 +616,36 @@
}
}
+ @Override
+ void registerApkInApex(AndroidPackage pkg) {
+ synchronized (mLock) {
+ final Iterator<ActiveApexInfo> it = mActiveApexInfosCache.iterator();
+ while (it.hasNext()) {
+ final ActiveApexInfo aai = it.next();
+ if (pkg.getBaseCodePath().startsWith(aai.apexDirectory.getAbsolutePath())) {
+ List<String> apks = mApksInApex.get(aai.apexModuleName);
+ if (apks == null) {
+ apks = Lists.newArrayList();
+ mApksInApex.put(aai.apexModuleName, apks);
+ }
+ apks.add(pkg.getPackageName());
+ }
+ }
+ }
+ }
+
+ @Override
+ List<String> getApksInApex(String apexPackageName) {
+ populatePackageNameToApexModuleNameIfNeeded();
+ synchronized (mLock) {
+ String moduleName = mPackageNameToApexModuleName.get(apexPackageName);
+ if (moduleName == null) {
+ return Collections.emptyList();
+ }
+ return mApksInApex.getOrDefault(moduleName, Collections.emptyList());
+ }
+ }
+
/**
* Dump information about the packages contained in a particular cache
* @param packagesCache the cache to print information about.
@@ -614,7 +727,6 @@
* updating APEX packages.
*/
private static final class ApexManagerFlattenedApex extends ApexManager {
-
@Override
List<ActiveApexInfo> getActiveApexInfos() {
// There is no apexd running in case of flattened apex
@@ -721,6 +833,16 @@
}
@Override
+ void registerApkInApex(AndroidPackage pkg) {
+ // No-op
+ }
+
+ @Override
+ List<String> getApksInApex(String apexPackageName) {
+ return Collections.emptyList();
+ }
+
+ @Override
void dump(PrintWriter pw, String packageName) {
// No-op
}
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index c4bcf80..3ed3534 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -53,6 +53,7 @@
import java.util.List;
import java.util.Objects;
import java.util.Set;
+import java.util.StringTokenizer;
/**
* The entity responsible for filtering visibility between apps based on declarations in their
@@ -219,10 +220,15 @@
continue;
}
final Uri data = intent.getData();
- if ("content".equalsIgnoreCase(intent.getScheme())
- && data != null
- && Objects.equals(provider.getAuthority(), data.getAuthority())) {
- return true;
+ if (!"content".equalsIgnoreCase(intent.getScheme()) || data == null
+ || provider.getAuthority() == null) {
+ continue;
+ }
+ StringTokenizer authorities = new StringTokenizer(provider.getAuthority(), ";", false);
+ while (authorities.hasMoreElements()) {
+ if (Objects.equals(authorities.nextElement(), data.getAuthority())) {
+ return true;
+ }
}
}
for (int s = ArrayUtils.size(potentialTarget.getServices()) - 1; s >= 0; s--) {
@@ -449,6 +455,7 @@
}
final PackageSetting callingPkgSetting;
final ArraySet<PackageSetting> callingSharedPkgSettings;
+ Trace.beginSection("callingSetting instanceof");
if (callingSetting instanceof PackageSetting) {
callingPkgSetting = (PackageSetting) callingSetting;
callingSharedPkgSettings = null;
@@ -456,6 +463,7 @@
callingPkgSetting = null;
callingSharedPkgSettings = ((SharedUserSetting) callingSetting).packages;
}
+ Trace.endSection();
if (callingPkgSetting != null) {
if (!mFeatureConfig.packageIsEnabled(callingPkgSetting.pkg)) {
@@ -485,6 +493,7 @@
return true;
}
final String targetName = targetPkg.getPackageName();
+ Trace.beginSection("getAppId");
final int callingAppId;
if (callingPkgSetting != null) {
callingAppId = callingPkgSetting.appId;
@@ -492,6 +501,7 @@
callingAppId = callingSharedPkgSettings.valueAt(0).appId; // all should be the same
}
final int targetAppId = targetPkgSetting.appId;
+ Trace.endSection();
if (callingAppId == targetAppId) {
if (DEBUG_LOGGING) {
log(callingSetting, targetPkgSetting, "same app id");
@@ -499,38 +509,64 @@
return false;
}
- if (callingSetting.getPermissionsState().hasPermission(
- Manifest.permission.QUERY_ALL_PACKAGES, UserHandle.getUserId(callingUid))) {
- if (DEBUG_LOGGING) {
- log(callingSetting, targetPkgSetting, "has query-all permission");
+ try {
+ Trace.beginSection("hasPermission");
+ if (callingSetting.getPermissionsState().hasPermission(
+ Manifest.permission.QUERY_ALL_PACKAGES, UserHandle.getUserId(callingUid))) {
+ if (DEBUG_LOGGING) {
+ log(callingSetting, targetPkgSetting, "has query-all permission");
+ }
+ return false;
}
- return false;
+ } finally {
+ Trace.endSection();
}
- if (mForceQueryable.contains(targetAppId)) {
- if (DEBUG_LOGGING) {
- log(callingSetting, targetPkgSetting, "force queryable");
+ try {
+ Trace.beginSection("mForceQueryable");
+ if (mForceQueryable.contains(targetAppId)) {
+ if (DEBUG_LOGGING) {
+ log(callingSetting, targetPkgSetting, "force queryable");
+ }
+ return false;
}
- return false;
+ } finally {
+ Trace.endSection();
}
- if (mQueriesViaPackage.contains(callingAppId, targetAppId)) {
- // the calling package has explicitly declared the target package; allow
- if (DEBUG_LOGGING) {
- log(callingSetting, targetPkgSetting, "queries package");
+ try {
+ Trace.beginSection("mQueriesViaPackage");
+ if (mQueriesViaPackage.contains(callingAppId, targetAppId)) {
+ // the calling package has explicitly declared the target package; allow
+ if (DEBUG_LOGGING) {
+ log(callingSetting, targetPkgSetting, "queries package");
+ }
+ return false;
}
- return false;
- } else if (mQueriesViaIntent.contains(callingAppId, targetAppId)) {
- if (DEBUG_LOGGING) {
- log(callingSetting, targetPkgSetting, "queries intent");
+ } finally {
+ Trace.endSection();
+ }
+ try {
+ Trace.beginSection("mQueriesViaIntent");
+ if (mQueriesViaIntent.contains(callingAppId, targetAppId)) {
+ if (DEBUG_LOGGING) {
+ log(callingSetting, targetPkgSetting, "queries intent");
+ }
+ return false;
}
- return false;
+ } finally {
+ Trace.endSection();
}
- final int targetUid = UserHandle.getUid(userId, targetAppId);
- if (mImplicitlyQueryable.contains(callingUid, targetUid)) {
- if (DEBUG_LOGGING) {
- log(callingSetting, targetPkgSetting, "implicitly queryable for user");
+ try {
+ Trace.beginSection("mImplicitlyQueryable");
+ final int targetUid = UserHandle.getUid(userId, targetAppId);
+ if (mImplicitlyQueryable.contains(callingUid, targetUid)) {
+ if (DEBUG_LOGGING) {
+ log(callingSetting, targetPkgSetting, "implicitly queryable for user");
+ }
+ return false;
}
- return false;
+ } finally {
+ Trace.endSection();
}
if (callingPkgSetting != null) {
if (callingPkgInstruments(callingPkgSetting, targetPkgSetting, targetName)) {
@@ -576,17 +612,22 @@
private static boolean callingPkgInstruments(PackageSetting callingPkgSetting,
PackageSetting targetPkgSetting,
String targetName) {
- final List<ComponentParseUtils.ParsedInstrumentation> inst =
- callingPkgSetting.pkg.getInstrumentations();
- for (int i = ArrayUtils.size(inst) - 1; i >= 0; i--) {
- if (Objects.equals(inst.get(i).getTargetPackage(), targetName)) {
- if (DEBUG_LOGGING) {
- log(callingPkgSetting, targetPkgSetting, "instrumentation");
+ try {
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "callingPkgInstruments");
+ final List<ComponentParseUtils.ParsedInstrumentation> inst =
+ callingPkgSetting.pkg.getInstrumentations();
+ for (int i = ArrayUtils.size(inst) - 1; i >= 0; i--) {
+ if (Objects.equals(inst.get(i).getTargetPackage(), targetName)) {
+ if (DEBUG_LOGGING) {
+ log(callingPkgSetting, targetPkgSetting, "instrumentation");
+ }
+ return true;
}
- return true;
}
+ return false;
+ } finally {
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
- return false;
}
private static void log(SettingBase callingPkgSetting, PackageSetting targetPkgSetting,
@@ -597,7 +638,7 @@
private static void log(SettingBase callingPkgSetting, PackageSetting targetPkgSetting,
String description, Throwable throwable) {
Slog.wtf(TAG,
- "interaction: " + callingPkgSetting.toString()
+ "interaction: " + callingPkgSetting
+ " -> " + targetPkgSetting.name + " "
+ description, throwable);
}
diff --git a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
index d2a6b42..ed71399 100644
--- a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
+++ b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java
@@ -15,45 +15,59 @@
*/
package com.android.server.pm;
+import static android.app.AppOpsManager.OP_INTERACT_ACROSS_PROFILES;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+import android.Manifest;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
import android.app.ActivityOptions;
+import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.IApplicationThread;
import android.app.admin.DevicePolicyEventLogger;
+import android.app.admin.DevicePolicyManagerInternal;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ICrossProfileApps;
+import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.os.Binder;
+import android.os.IBinder;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.stats.devicepolicy.DevicePolicyEnums;
import android.text.TextUtils;
+import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.Preconditions;
+import com.android.internal.app.IAppOpsService;
+import com.android.internal.util.ArrayUtils;
import com.android.server.LocalServices;
+import com.android.server.appop.AppOpsService;
import com.android.server.wm.ActivityTaskManagerInternal;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub {
private static final String TAG = "CrossProfileAppsService";
private Context mContext;
private Injector mInjector;
+ private AppOpsService mAppOpsService;
+ private final DevicePolicyManagerInternal mDpmi;
+ private final IPackageManager mIpm;
public CrossProfileAppsServiceImpl(Context context) {
this(context, new InjectorImpl(context));
@@ -63,11 +77,13 @@
CrossProfileAppsServiceImpl(Context context, Injector injector) {
mContext = context;
mInjector = injector;
+ mIpm = AppGlobals.getPackageManager();
+ mDpmi = LocalServices.getService(DevicePolicyManagerInternal.class);
}
@Override
public List<UserHandle> getTargetUserProfiles(String callingPackage) {
- Preconditions.checkNotNull(callingPackage);
+ Objects.requireNonNull(callingPackage);
verifyCallingPackage(callingPackage);
@@ -87,8 +103,8 @@
ComponentName component,
@UserIdInt int userId,
boolean launchMainActivity) throws RemoteException {
- Preconditions.checkNotNull(callingPackage);
- Preconditions.checkNotNull(component);
+ Objects.requireNonNull(callingPackage);
+ Objects.requireNonNull(component);
verifyCallingPackage(callingPackage);
@@ -152,6 +168,63 @@
userId);
}
+ @Override
+ public boolean canRequestInteractAcrossProfiles(String callingPackage) {
+ Objects.requireNonNull(callingPackage);
+ verifyCallingPackage(callingPackage);
+
+ final List<UserHandle> targetUserProfiles = getTargetUserProfilesUnchecked(
+ callingPackage, mInjector.getCallingUserId());
+ if (targetUserProfiles.isEmpty()) {
+ return false;
+ }
+
+ if (!hasRequestedAppOpPermission(
+ AppOpsManager.opToPermission(OP_INTERACT_ACROSS_PROFILES), callingPackage)) {
+ return false;
+ }
+ return isCrossProfilePackageWhitelisted(callingPackage);
+ }
+
+ private boolean hasRequestedAppOpPermission(String permission, String packageName) {
+ try {
+ String[] packages = mIpm.getAppOpPermissionPackages(permission);
+ return ArrayUtils.contains(packages, packageName);
+ } catch (RemoteException exc) {
+ Slog.e(TAG, "PackageManager dead. Cannot get permission info");
+ return false;
+ }
+ }
+
+ @Override
+ public boolean canInteractAcrossProfiles(String callingPackage) {
+ Objects.requireNonNull(callingPackage);
+ verifyCallingPackage(callingPackage);
+
+ final List<UserHandle> targetUserProfiles = getTargetUserProfilesUnchecked(
+ callingPackage, mInjector.getCallingUserId());
+ if (targetUserProfiles.isEmpty()) {
+ return false;
+ }
+
+ final int callingUid = mInjector.getCallingUid();
+ return isPermissionGranted(Manifest.permission.INTERACT_ACROSS_USERS_FULL, callingUid)
+ || isPermissionGranted(Manifest.permission.INTERACT_ACROSS_USERS, callingUid)
+ || isPermissionGranted(Manifest.permission.INTERACT_ACROSS_PROFILES, callingUid)
+ || AppOpsManager.MODE_ALLOWED == getAppOpsService().noteOperation(
+ OP_INTERACT_ACROSS_PROFILES, callingUid, callingPackage, /* featureId= */ null,
+ /*shouldCollectAsyncNotedOp= */false, /*message= */null);
+ }
+
+ private boolean isCrossProfilePackageWhitelisted(String packageName) {
+ final long ident = mInjector.clearCallingIdentity();
+ try {
+ return mDpmi.getAllCrossProfilePackages().contains(packageName);
+ } finally {
+ mInjector.restoreCallingIdentity(ident);
+ }
+ }
+
private List<UserHandle> getTargetUserProfilesUnchecked(
String callingPackage, @UserIdInt int callingUserId) {
final long ident = mInjector.clearCallingIdentity();
@@ -238,6 +311,19 @@
mInjector.getAppOpsManager().checkPackage(mInjector.getCallingUid(), callingPackage);
}
+ private static boolean isPermissionGranted(String permission, int uid) {
+ return PackageManager.PERMISSION_GRANTED == ActivityManager.checkComponentPermission(
+ permission, uid, /* owningUid= */-1, /* exported= */ true);
+ }
+
+ private AppOpsService getAppOpsService() {
+ if (mAppOpsService == null) {
+ IBinder b = ServiceManager.getService(Context.APP_OPS_SERVICE);
+ mAppOpsService = (AppOpsService) IAppOpsService.Stub.asInterface(b);
+ }
+ return mAppOpsService;
+ }
+
private static class InjectorImpl implements Injector {
private Context mContext;
diff --git a/services/core/java/com/android/server/pm/InstallSource.java b/services/core/java/com/android/server/pm/InstallSource.java
index 0541797..6684e3f 100644
--- a/services/core/java/com/android/server/pm/InstallSource.java
+++ b/services/core/java/com/android/server/pm/InstallSource.java
@@ -18,6 +18,8 @@
import android.annotation.Nullable;
+import com.android.internal.util.Preconditions;
+
import java.util.Objects;
/**
@@ -29,16 +31,27 @@
* An instance of InstallSource representing an absence of knowledge of the source of
* a package. Used in preference to null.
*/
- static final InstallSource EMPTY = new InstallSource(null, null, null, false);
+ static final InstallSource EMPTY = new InstallSource(null, null, null, false, false, null);
/** We also memoize this case because it is common - all un-updated system apps. */
- private static final InstallSource EMPTY_ORPHANED = new InstallSource(null, null, null, true);
+ private static final InstallSource EMPTY_ORPHANED = new InstallSource(
+ null, null, null, true, false, null);
- /** The package that requested the installation, if known. */
+ /**
+ * The package that requested the installation, if known. May not correspond to a currently
+ * installed package if {@link #isInitiatingPackageUninstalled} is true.
+ */
@Nullable
final String initiatingPackageName;
/**
+ * The signing details of the initiating package, if known. Always null if
+ * {@link #initiatingPackageName} is null.
+ */
+ @Nullable
+ final PackageSignatures initiatingPackageSignatures;
+
+ /**
* The package on behalf of which the initiating package requested the installation, if any.
* For example if a downloaded APK is installed via the Package Installer this could be the
* app that performed the download. This value is provided by the initiating package and not
@@ -57,76 +70,120 @@
/** Indicates if the package that was the installerPackageName has been uninstalled. */
final boolean isOrphaned;
+ /**
+ * Indicates if the package in initiatingPackageName has been uninstalled. Always false if
+ * {@link #initiatingPackageName} is null.
+ */
+ final boolean isInitiatingPackageUninstalled;
+
+ static InstallSource create(@Nullable String initiatingPackageName,
+ @Nullable String originatingPackageName, @Nullable String installerPackageName) {
+ return create(initiatingPackageName, originatingPackageName, installerPackageName,
+ false, false);
+ }
+
static InstallSource create(@Nullable String initiatingPackageName,
@Nullable String originatingPackageName, @Nullable String installerPackageName,
- boolean isOrphaned) {
+ boolean isOrphaned, boolean isInitiatingPackageUninstalled) {
return createInternal(
intern(initiatingPackageName),
intern(originatingPackageName),
intern(installerPackageName),
- isOrphaned);
+ isOrphaned, isInitiatingPackageUninstalled, null);
}
private static InstallSource createInternal(@Nullable String initiatingPackageName,
@Nullable String originatingPackageName, @Nullable String installerPackageName,
- boolean isOrphaned) {
+ boolean isOrphaned, boolean isInitiatingPackageUninstalled,
+ @Nullable PackageSignatures initiatingPackageSignatures) {
if (initiatingPackageName == null && originatingPackageName == null
- && installerPackageName == null) {
+ && installerPackageName == null && initiatingPackageSignatures == null
+ && !isInitiatingPackageUninstalled) {
return isOrphaned ? EMPTY_ORPHANED : EMPTY;
}
return new InstallSource(initiatingPackageName, originatingPackageName,
- installerPackageName, isOrphaned);
+ installerPackageName, isOrphaned, isInitiatingPackageUninstalled,
+ initiatingPackageSignatures
+ );
}
private InstallSource(@Nullable String initiatingPackageName,
@Nullable String originatingPackageName, @Nullable String installerPackageName,
- boolean isOrphaned) {
+ boolean isOrphaned, boolean isInitiatingPackageUninstalled,
+ @Nullable PackageSignatures initiatingPackageSignatures) {
+ if (initiatingPackageName == null) {
+ Preconditions.checkArgument(initiatingPackageSignatures == null);
+ Preconditions.checkArgument(!isInitiatingPackageUninstalled);
+ }
this.initiatingPackageName = initiatingPackageName;
this.originatingPackageName = originatingPackageName;
this.installerPackageName = installerPackageName;
this.isOrphaned = isOrphaned;
+ this.isInitiatingPackageUninstalled = isInitiatingPackageUninstalled;
+ this.initiatingPackageSignatures = initiatingPackageSignatures;
}
/**
- * Return an InstallSource the same as this one except with the specified installerPackageName.
+ * Return an InstallSource the same as this one except with the specified
+ * {@link #installerPackageName}.
*/
- InstallSource setInstallerPackage(String installerPackageName) {
+ InstallSource setInstallerPackage(@Nullable String installerPackageName) {
if (Objects.equals(installerPackageName, this.installerPackageName)) {
return this;
}
return createInternal(initiatingPackageName, originatingPackageName,
- intern(installerPackageName), isOrphaned);
+ intern(installerPackageName), isOrphaned, isInitiatingPackageUninstalled,
+ initiatingPackageSignatures
+ );
}
/**
- * Return an InstallSource the same as this one except with the specified value for isOrphaned.
+ * Return an InstallSource the same as this one except with the specified value for
+ * {@link #isOrphaned}.
*/
InstallSource setIsOrphaned(boolean isOrphaned) {
if (isOrphaned == this.isOrphaned) {
return this;
}
return createInternal(initiatingPackageName, originatingPackageName, installerPackageName,
- isOrphaned);
+ isOrphaned, isInitiatingPackageUninstalled, initiatingPackageSignatures);
}
/**
- * Return an InstallSource the same as this one except it does not refer to the specified
- * installer package name (which is being uninstalled).
+ * Return an InstallSource the same as this one except with the specified
+ * {@link #initiatingPackageSignatures}.
*/
- InstallSource removeInstallerPackage(String packageName) {
+ InstallSource setInitiatingPackageSignatures(@Nullable PackageSignatures signatures) {
+ if (signatures == initiatingPackageSignatures) {
+ return this;
+ }
+ return createInternal(initiatingPackageName, originatingPackageName, installerPackageName,
+ isOrphaned, isInitiatingPackageUninstalled, signatures);
+ }
+
+ /**
+ * Return an InstallSource the same as this one updated to reflect that the specified installer
+ * package name has been uninstalled.
+ */
+ InstallSource removeInstallerPackage(@Nullable String packageName) {
if (packageName == null) {
return this;
}
boolean modified = false;
- String initiatingPackageName = this.initiatingPackageName;
+ boolean isInitiatingPackageUninstalled = this.isInitiatingPackageUninstalled;
String originatingPackageName = this.originatingPackageName;
String installerPackageName = this.installerPackageName;
boolean isOrphaned = this.isOrphaned;
- if (packageName.equals(initiatingPackageName)) {
- initiatingPackageName = null;
- modified = true;
+ if (packageName.equals(this.initiatingPackageName)) {
+ if (!isInitiatingPackageUninstalled) {
+ // In this case we deliberately do not clear the package name (and signatures).
+ // We allow an app to retrieve details of its own install initiator even after
+ // it has been uninstalled.
+ isInitiatingPackageUninstalled = true;
+ modified = true;
+ }
}
if (packageName.equals(originatingPackageName)) {
originatingPackageName = null;
@@ -141,8 +198,9 @@
if (!modified) {
return this;
}
+
return createInternal(initiatingPackageName, originatingPackageName, installerPackageName,
- isOrphaned);
+ isOrphaned, isInitiatingPackageUninstalled, initiatingPackageSignatures);
}
@Nullable
diff --git a/services/core/java/com/android/server/pm/InstantAppRegistry.java b/services/core/java/com/android/server/pm/InstantAppRegistry.java
index ffcd6cf..bcfe577 100644
--- a/services/core/java/com/android/server/pm/InstantAppRegistry.java
+++ b/services/core/java/com/android/server/pm/InstantAppRegistry.java
@@ -338,9 +338,8 @@
}
@GuardedBy("mService.mLock")
- public void onPackageUninstalledLPw(@NonNull AndroidPackage pkg,
+ public void onPackageUninstalledLPw(@NonNull AndroidPackage pkg, @Nullable PackageSetting ps,
@NonNull int[] userIds) {
- PackageSetting ps = mService.getPackageSetting(pkg.getPackageName());
if (ps == null) {
return;
}
diff --git a/services/core/java/com/android/server/pm/KeySetManagerService.java b/services/core/java/com/android/server/pm/KeySetManagerService.java
index 70c0f8d..f9cfee1 100644
--- a/services/core/java/com/android/server/pm/KeySetManagerService.java
+++ b/services/core/java/com/android/server/pm/KeySetManagerService.java
@@ -38,6 +38,7 @@
import java.io.PrintWriter;
import java.security.PublicKey;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
/*
@@ -219,10 +220,10 @@
}
public void addScannedPackageLPw(AndroidPackage pkg) {
- Preconditions.checkNotNull(pkg, "Attempted to add null pkg to ksms.");
- Preconditions.checkNotNull(pkg.getPackageName(), "Attempted to add null pkg to ksms.");
+ Objects.requireNonNull(pkg, "Attempted to add null pkg to ksms.");
+ Objects.requireNonNull(pkg.getPackageName(), "Attempted to add null pkg to ksms.");
PackageSetting ps = mPackages.get(pkg.getPackageName());
- Preconditions.checkNotNull(ps, "pkg: " + pkg.getPackageName()
+ Objects.requireNonNull(ps, "pkg: " + pkg.getPackageName()
+ "does not have a corresponding entry in mPackages.");
addSigningKeySetToPackageLPw(ps, pkg.getSigningDetails().publicKeys);
if (pkg.getKeySetMapping() != null) {
@@ -504,7 +505,7 @@
* Adds the given PublicKey to the system, deduping as it goes.
*/
private long addPublicKeyLPw(PublicKey key) {
- Preconditions.checkNotNull(key, "Cannot add null public key!");
+ Objects.requireNonNull(key, "Cannot add null public key!");
long id = getIdForPublicKeyLPr(key);
if (id != PUBLIC_KEY_NOT_FOUND) {
@@ -574,7 +575,7 @@
/* remove refs from common keysets and public keys */
PackageSetting pkg = mPackages.get(packageName);
- Preconditions.checkNotNull(pkg, "pkg name: " + packageName
+ Objects.requireNonNull(pkg, "pkg name: " + packageName
+ "does not have a corresponding entry in mPackages.");
long signingKeySetId = pkg.keySetData.getProperSigningKeySet();
decrementKeySetLPw(signingKeySetId);
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 673e265..c4ef856 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -78,6 +78,7 @@
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
+import java.util.Objects;
/**
* Service that manages requests and callbacks for launchers that support
@@ -136,15 +137,15 @@
public LauncherAppsImpl(Context context) {
mContext = context;
mUm = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
- mUserManagerInternal = Preconditions.checkNotNull(
+ mUserManagerInternal = Objects.requireNonNull(
LocalServices.getService(UserManagerInternal.class));
- mUsageStatsManagerInternal = Preconditions.checkNotNull(
+ mUsageStatsManagerInternal = Objects.requireNonNull(
LocalServices.getService(UsageStatsManagerInternal.class));
- mActivityManagerInternal = Preconditions.checkNotNull(
+ mActivityManagerInternal = Objects.requireNonNull(
LocalServices.getService(ActivityManagerInternal.class));
- mActivityTaskManagerInternal = Preconditions.checkNotNull(
+ mActivityTaskManagerInternal = Objects.requireNonNull(
LocalServices.getService(ActivityTaskManagerInternal.class));
- mShortcutServiceInternal = Preconditions.checkNotNull(
+ mShortcutServiceInternal = Objects.requireNonNull(
LocalServices.getService(ShortcutServiceInternal.class));
mShortcutServiceInternal.addListener(mPackageMonitor);
mCallbackHandler = BackgroundThread.getHandler();
@@ -558,7 +559,7 @@
if (!canAccessProfile(user.getIdentifier(), "Cannot check package")) {
return null;
}
- Preconditions.checkNotNull(component);
+ Objects.requireNonNull(component);
// All right, create the sender.
Intent intent = new Intent(Intent.ACTION_CREATE_SHORTCUT).setComponent(component);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index e2dfa12..6331dd4 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -188,7 +188,7 @@
}
};
- public PackageInstallerService(Context context, PackageManagerService pm, ApexManager am) {
+ public PackageInstallerService(Context context, PackageManagerService pm) {
mContext = context;
mPm = pm;
mPermissionManager = LocalServices.getService(PermissionManagerServiceInternal.class);
@@ -206,9 +206,8 @@
mSessionsDir = new File(Environment.getDataSystemDirectory(), "install_sessions");
mSessionsDir.mkdirs();
- mApexManager = am;
-
- mStagingManager = new StagingManager(this, am, context);
+ mApexManager = ApexManager.getInstance();
+ mStagingManager = new StagingManager(this, context);
}
boolean okToSendBroadcasts() {
@@ -635,7 +634,7 @@
}
}
InstallSource installSource = InstallSource.create(installerPackageName,
- originatingPackageName, requestedInstallerPackageName, false);
+ originatingPackageName, requestedInstallerPackageName);
session = new PackageInstallerSession(mInternalCallback, mContext, mPm, this,
mInstallThread.getLooper(), mStagingManager, sessionId, userId, callingUid,
installSource, params, createdMillis,
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index ac183dc..165bdeb 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -119,7 +119,6 @@
import com.android.internal.os.SomeArgs;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.Preconditions;
import com.android.server.LocalServices;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.dex.DexManager;
@@ -140,6 +139,8 @@
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
+import java.util.Objects;
+import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
@@ -209,6 +210,8 @@
private static final String PROPERTY_NAME_INHERIT_NATIVE = "pi.inherit_native_on_dont_kill";
private static final int[] EMPTY_CHILD_SESSION_ARRAY = {};
+ private static final String SYSTEM_DATA_LOADER_PACKAGE = "android";
+
// TODO: enforce INSTALL_ALLOW_TEST
// TODO: enforce INSTALL_ALLOW_DOWNGRADE
@@ -511,7 +514,7 @@
this.userId = userId;
mOriginalInstallerUid = installerUid;
mInstallerUid = installerUid;
- mInstallSource = Preconditions.checkNotNull(installSource);
+ mInstallSource = Objects.requireNonNull(installSource);
this.params = params;
this.createdMillis = createdMillis;
this.updatedMillis = createdMillis;
@@ -555,6 +558,12 @@
params.dataLoaderParams);
}
}
+
+ if (isStreamingInstallation()
+ && this.params.dataLoaderParams.getComponentName().getPackageName()
+ == SYSTEM_DATA_LOADER_PACKAGE) {
+ assertShellOrSystemCalling("System data loaders");
+ }
}
public SessionInfo generateInfo() {
@@ -770,6 +779,17 @@
}
}
+ private void assertShellOrSystemCalling(String operation) {
+ switch (Binder.getCallingUid()) {
+ case android.os.Process.SHELL_UID:
+ case android.os.Process.ROOT_UID:
+ case android.os.Process.SYSTEM_UID:
+ break;
+ default:
+ throw new SecurityException(operation + " only supported from shell or system");
+ }
+ }
+
private void assertCanWrite(boolean reverseMode) {
if (isDataLoaderInstallation()) {
throw new IllegalStateException(
@@ -780,15 +800,7 @@
assertPreparedAndNotSealedLocked("assertCanWrite");
}
if (reverseMode) {
- switch (Binder.getCallingUid()) {
- case android.os.Process.SHELL_UID:
- case android.os.Process.ROOT_UID:
- case android.os.Process.SYSTEM_UID:
- break;
- default:
- throw new SecurityException(
- "Reverse mode only supported from shell or system");
- }
+ assertShellOrSystemCalling("Reverse mode");
}
}
@@ -1025,13 +1037,24 @@
mHandler.obtainMessage(MSG_COMMIT).sendToTarget();
}
- private class FileSystemConnector extends IPackageInstallerSessionFileSystemConnector.Stub {
+ private final class FileSystemConnector extends
+ IPackageInstallerSessionFileSystemConnector.Stub {
+ final Set<String> mAddedFiles;
+
+ FileSystemConnector(List<InstallationFile> addedFiles) {
+ mAddedFiles = addedFiles.stream().map(file -> file.getName()).collect(
+ Collectors.toSet());
+ }
+
@Override
public void writeData(String name, long offsetBytes, long lengthBytes,
ParcelFileDescriptor incomingFd) {
if (incomingFd == null) {
throw new IllegalArgumentException("incomingFd can't be null");
}
+ if (!mAddedFiles.contains(name)) {
+ throw new SecurityException("File name is not in the list of added files.");
+ }
try {
doWriteInternal(name, offsetBytes, lengthBytes, incomingFd);
} catch (IOException e) {
@@ -1143,7 +1166,7 @@
* permissions.
*/
private boolean markAsCommitted(@NonNull IntentSender statusReceiver) {
- Preconditions.checkNotNull(statusReceiver);
+ Objects.requireNonNull(statusReceiver);
List<PackageInstallerSession> childSessions = getChildSessions();
@@ -1408,8 +1431,8 @@
@Override
public void transfer(String packageName, IntentSender statusReceiver) {
- Preconditions.checkNotNull(statusReceiver);
- Preconditions.checkNotNull(packageName);
+ Objects.requireNonNull(statusReceiver);
+ Objects.requireNonNull(packageName);
try {
assertCanBeTransferredAndReturnNewOwner(packageName);
@@ -1452,7 +1475,7 @@
}
mInstallerUid = uid;
- mInstallSource = InstallSource.create(packageName, null, packageName, false);
+ mInstallSource = InstallSource.create(packageName, null, packageName);
}
} catch (PackageManager.NameNotFoundException e) {
onSessionTransferStatus(statusReceiver, packageName,
@@ -1607,9 +1630,9 @@
localObserver = null;
} else {
if (!params.isMultiPackage) {
- Preconditions.checkNotNull(mPackageName);
- Preconditions.checkNotNull(mSigningDetails);
- Preconditions.checkNotNull(mResolvedBaseFile);
+ Objects.requireNonNull(mPackageName);
+ Objects.requireNonNull(mSigningDetails);
+ Objects.requireNonNull(mResolvedBaseFile);
if (needToAskForPermissionsLocked()) {
// User needs to confirm installation;
@@ -1683,7 +1706,7 @@
computeProgressLocked(true);
// Unpack native libraries for non-incremental installation
- if (isIncrementalInstallation()) {
+ if (!isIncrementalInstallation()) {
extractNativeLibraries(stageDir, params.abiOverride, mayInheritNativeLibs());
}
}
@@ -2390,16 +2413,6 @@
@Override
public void addFile(String name, long lengthBytes, byte[] metadata) {
- if (mIncrementalFileStorages != null) {
- try {
- mIncrementalFileStorages.addFile(new InstallationFile(name, lengthBytes, metadata));
- //TODO(b/136132412): merge incremental and callback installation schemes
- return;
- } catch (IOException ex) {
- throw new IllegalStateException(
- "Failed to add and configure Incremental File: " + name, ex);
- }
- }
if (!isDataLoaderInstallation()) {
throw new IllegalStateException(
"Cannot add files to non-data loader installation session.");
@@ -2412,7 +2425,6 @@
synchronized (mLock) {
assertCallerIsOwnerOrRootLocked();
assertPreparedAndNotSealedLocked("addFile");
-
mFiles.add(FileInfo.added(name, lengthBytes, metadata));
}
}
@@ -2463,12 +2475,10 @@
*/
private void prepareDataLoader()
throws PackageManagerException, StreamingException {
- if (!isStreamingInstallation()) {
+ if (!isDataLoaderInstallation()) {
return;
}
- FileSystemConnector connector = new FileSystemConnector();
-
List<InstallationFile> addedFiles = mFiles.stream().filter(
file -> sAddedFilter.accept(new File(file.name))).map(
file -> new InstallationFile(
@@ -2479,6 +2489,20 @@
file -> file.name.substring(
0, file.name.length() - REMOVE_MARKER_EXTENSION.length())).collect(
Collectors.toList());
+ if (mIncrementalFileStorages != null) {
+ for (InstallationFile file : addedFiles) {
+ try {
+ mIncrementalFileStorages.addFile(file);
+ } catch (IOException ex) {
+ // TODO(b/146080380): add incremental-specific error code
+ throw new PackageManagerException(INSTALL_FAILED_INTERNAL_ERROR,
+ "Failed to add and configure Incremental File: " + file.getName(), ex);
+ }
+ }
+ return;
+ }
+
+ final FileSystemConnector connector = new FileSystemConnector(addedFiles);
DataLoaderManager dataLoaderManager = mContext.getSystemService(DataLoaderManager.class);
if (dataLoaderManager == null) {
@@ -2515,11 +2539,11 @@
}
};
- final DataLoaderParams params = this.params.dataLoaderParams;
-
final FileSystemControlParcel control = new FileSystemControlParcel();
control.callback = connector;
+ final DataLoaderParams params = this.params.dataLoaderParams;
+
Bundle dataLoaderParams = new Bundle();
dataLoaderParams.putParcelable("componentName", params.getComponentName());
dataLoaderParams.putParcelable("control", control);
@@ -3115,7 +3139,7 @@
}
InstallSource installSource = InstallSource.create(installInitiatingPackageName,
- installOriginatingPackageName, installerPackageName, false);
+ installOriginatingPackageName, installerPackageName);
return new PackageInstallerSession(callback, context, pm, sessionProvider,
installerThread, stagingManager, sessionId, userId, installerUid,
installSource, params, createdMillis, stageDir, stageCid, fileInfosArray,
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 04e7372..d0f91c2 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -190,6 +190,7 @@
import android.content.pm.ServiceInfo;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.Signature;
+import android.content.pm.SigningInfo;
import android.content.pm.SuspendDialogInfo;
import android.content.pm.UserInfo;
import android.content.pm.VerifierDeviceIdentity;
@@ -492,6 +493,7 @@
static final int SCAN_AS_PRODUCT = 1 << 20;
static final int SCAN_AS_SYSTEM_EXT = 1 << 21;
static final int SCAN_AS_ODM = 1 << 22;
+ static final int SCAN_AS_APK_IN_APEX = 1 << 23;
@IntDef(flag = true, prefix = { "SCAN_" }, value = {
SCAN_NO_DEX,
@@ -1532,7 +1534,7 @@
final @Nullable String mAppPredictionServicePackage;
final @Nullable String mIncidentReportApproverPackage;
final @Nullable String[] mTelephonyPackages;
- final @NonNull String mServicesSystemSharedLibraryPackageName;
+ final @NonNull String mServicesExtensionPackageName;
final @NonNull String mSharedSystemSharedLibraryPackageName;
private final PackageUsage mPackageUsage = new PackageUsage();
@@ -2589,6 +2591,9 @@
& (SCAN_AS_VENDOR | SCAN_AS_ODM | SCAN_AS_PRODUCT | SCAN_AS_SYSTEM_EXT)) != 0) {
return true;
}
+ if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) {
+ return true;
+ }
return false;
}
@@ -3298,9 +3303,7 @@
} else {
mIntentFilterVerifier = null;
}
- mServicesSystemSharedLibraryPackageName = getRequiredSharedLibraryLPr(
- PackageManager.SYSTEM_SHARED_LIBRARY_SERVICES,
- SharedLibraryInfo.VERSION_UNDEFINED);
+ mServicesExtensionPackageName = getRequiredServicesExtensionPackageLPr();
mSharedSystemSharedLibraryPackageName = getRequiredSharedLibraryLPr(
PackageManager.SYSTEM_SHARED_LIBRARY_SHARED,
SharedLibraryInfo.VERSION_UNDEFINED);
@@ -3310,7 +3313,7 @@
mRequiredUninstallerPackage = null;
mIntentFilterVerifierComponent = null;
mIntentFilterVerifier = null;
- mServicesSystemSharedLibraryPackageName = null;
+ mServicesExtensionPackageName = null;
mSharedSystemSharedLibraryPackageName = null;
}
// PermissionController hosts default permission granting and role management, so it's a
@@ -3332,7 +3335,7 @@
}
}
- mInstallerService = new PackageInstallerService(mContext, this, mApexManager);
+ mInstallerService = new PackageInstallerService(mContext, this);
final Pair<ComponentName, String> instantAppResolverComponent =
getInstantAppResolverLPr();
if (instantAppResolverComponent != null) {
@@ -3740,6 +3743,19 @@
}
}
+ @NonNull
+ private String getRequiredServicesExtensionPackageLPr() {
+ String servicesExtensionPackage =
+ ensureSystemPackageName(
+ mContext.getString(R.string.config_servicesExtensionPackage));
+ if (TextUtils.isEmpty(servicesExtensionPackage)) {
+ throw new RuntimeException(
+ "Required services extension package is missing, check "
+ + "config_servicesExtensionPackage.");
+ }
+ return servicesExtensionPackage;
+ }
+
private @NonNull String getRequiredInstallerLPr() {
final Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
intent.addCategory(Intent.CATEGORY_DEFAULT);
@@ -5344,8 +5360,9 @@
if (!mUserManager.exists(userId)) return null;
final int callingUid = Binder.getCallingUid();
flags = updateFlagsForComponent(flags, userId);
- mPermissionManager.enforceCrossUserPermission(callingUid, userId,
- false /* requireFullPermission */, false /* checkShell */, "get service info");
+ mPermissionManager.enforceCrossUserOrProfilePermission(
+ callingUid, userId, false /* requireFullPermission */, false /* checkShell */,
+ "get service info");
synchronized (mLock) {
ParsedService s = mComponentResolver.getService(component);
if (DEBUG_PACKAGE_INFO) Log.v(
@@ -5461,7 +5478,7 @@
public @NonNull String getServicesSystemSharedLibraryPackageName() {
// allow instant applications
synchronized (mLock) {
- return mServicesSystemSharedLibraryPackageName;
+ return mServicesExtensionPackageName;
}
}
@@ -7795,8 +7812,10 @@
String resolvedType, int flags, int userId, int callingUid,
boolean includeInstantApps) {
if (!mUserManager.exists(userId)) return Collections.emptyList();
- mPermissionManager.enforceCrossUserPermission(callingUid, userId,
- false /*requireFullPermission*/, false /*checkShell*/,
+ mPermissionManager.enforceCrossUserOrProfilePermission(callingUid,
+ userId,
+ false /*requireFullPermission*/,
+ false /*checkShell*/,
"query intent receivers");
final String instantAppPkgName = getInstantAppPackageName(callingUid);
flags = updateFlagsForResolve(flags, userId, callingUid, includeInstantApps);
@@ -11595,6 +11614,23 @@
return false;
}
SharedLibraryInfo libraryInfo = versionedLib.valueAt(libIdx);
+
+ // Remove the shared library overlays from its dependent packages.
+ for (int currentUserId : UserManagerService.getInstance().getUserIds()) {
+ final List<VersionedPackage> dependents = getPackagesUsingSharedLibraryLPr(
+ libraryInfo, 0, currentUserId);
+ if (dependents == null) {
+ continue;
+ }
+ for (VersionedPackage dependentPackage : dependents) {
+ final PackageSetting ps = mSettings.mPackages.get(
+ dependentPackage.getPackageName());
+ if (ps != null) {
+ ps.setOverlayPathsForLibrary(libraryInfo.getName(), null, currentUserId);
+ }
+ }
+ }
+
versionedLib.remove(version);
if (versionedLib.size() <= 0) {
mSharedLibraries.remove(name);
@@ -11710,6 +11746,9 @@
mSettings.insertPackageSettingLPw(pkgSetting, pkg);
// Add the new setting to mPackages
mPackages.put(pkg.getAppInfoPackageName(), pkg);
+ if ((scanFlags & SCAN_AS_APK_IN_APEX) != 0) {
+ mApexManager.registerApkInApex(pkg);
+ }
// Add the package's KeySets to the global KeySetManagerService
KeySetManagerService ksms = mSettings.mKeySetManagerService;
@@ -13452,9 +13491,7 @@
// Okay!
targetPackageSetting.setInstallerPackageName(installerPackageName);
- if (installerPackageName != null) {
- mSettings.mInstallerPackages.add(installerPackageName);
- }
+ mSettings.addInstallerPackageNames(targetPackageSetting.installSource);
scheduleWriteSettingsLocked();
}
}
@@ -15160,9 +15197,10 @@
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "updateSettings");
final String pkgName = pkg.getPackageName();
- final String installerPackageName = installArgs.installSource.installerPackageName;
final int[] installedForUsers = res.origUsers;
final int installReason = installArgs.installReason;
+ InstallSource installSource = installArgs.installSource;
+ final String installerPackageName = installSource.installerPackageName;
if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + pkg.getCodePath());
synchronized (mLock) {
@@ -15171,7 +15209,7 @@
// For system-bundled packages, we assume that installing an upgraded version
// of the package implies that the user actually wants to run that new code,
// so we enable the package.
- PackageSetting ps = mSettings.mPackages.get(pkgName);
+ final PackageSetting ps = mSettings.mPackages.get(pkgName);
final int userId = installArgs.user.getIdentifier();
if (ps != null) {
if (isSystemApp(pkg)) {
@@ -15201,6 +15239,29 @@
// upcoming call to mSettings.writeLPr().
}
}
+
+ // Retrieve the overlays for shared libraries of the package.
+ if (pkg.getUsesLibraryInfos() != null) {
+ for (SharedLibraryInfo sharedLib : pkg.getUsesLibraryInfos()) {
+ for (int currentUserId : UserManagerService.getInstance().getUserIds()) {
+ if (!sharedLib.isDynamic()) {
+ // TODO(146804378): Support overlaying static shared libraries
+ continue;
+ }
+ final PackageSetting libPs = mSettings.mPackages.get(
+ sharedLib.getPackageName());
+ if (libPs == null) {
+ continue;
+ }
+ final String[] overlayPaths = libPs.getOverlayPaths(currentUserId);
+ if (overlayPaths != null) {
+ ps.setOverlayPathsForLibrary(sharedLib.getName(),
+ Arrays.asList(overlayPaths), currentUserId);
+ }
+ }
+ }
+ }
+
// It's implied that when a user requests installation, they want the app to be
// installed and enabled.
if (userId != UserHandle.USER_ALL) {
@@ -15208,8 +15269,16 @@
ps.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, userId, installerPackageName);
}
- ps.setInstallSource(installArgs.installSource);
-
+ if (installSource.initiatingPackageName != null) {
+ final PackageSetting ips = mSettings.mPackages.get(
+ installSource.initiatingPackageName);
+ if (ips != null) {
+ installSource = installSource.setInitiatingPackageSignatures(
+ ips.signatures);
+ }
+ }
+ ps.setInstallSource(installSource);
+ mSettings.addInstallerPackageNames(installSource);
// When replacing an existing package, preserve the original install reason for all
// users that had the package installed before.
@@ -15239,7 +15308,6 @@
res.name = pkgName;
res.uid = pkg.getUid();
res.pkg = pkg;
- mSettings.setInstallerPackageName(pkgName, installerPackageName);
res.setReturnCode(PackageManager.INSTALL_SUCCEEDED);
//to update install status
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "writeSettings");
@@ -17446,7 +17514,8 @@
synchronized (mLock) {
if (res) {
if (pkg != null) {
- mInstantAppRegistry.onPackageUninstalledLPw(pkg, info.removedUsers);
+ mInstantAppRegistry.onPackageUninstalledLPw(pkg, uninstalledPs,
+ info.removedUsers);
}
updateSequenceNumberLP(uninstalledPs, info.removedUsers);
updateInstantAppInstallerLocked(packageName);
@@ -17751,10 +17820,10 @@
ApexManager.ActiveApexInfo apexInfo) {
for (int i = 0, size = SYSTEM_PARTITIONS.size(); i < size; i++) {
SystemPartition sp = SYSTEM_PARTITIONS.get(i);
- if (apexInfo.preinstalledApexPath.getAbsolutePath().startsWith(
+ if (apexInfo.preInstalledApexPath.getAbsolutePath().startsWith(
sp.folder.getAbsolutePath())) {
- return new SystemPartition(apexInfo.apexDirectory, sp.scanFlag,
- false /* hasOverlays */);
+ return new SystemPartition(apexInfo.apexDirectory,
+ sp.scanFlag | SCAN_AS_APK_IN_APEX, false /* hasOverlays */);
}
}
return null;
@@ -19374,7 +19443,7 @@
// PermissionController manages default home directly.
return false;
}
- mPermissionManager.setDefaultHome(currentPackageName, userId, (successful) -> {
+ mPermissionManager.setDefaultHome(packageName, userId, (successful) -> {
if (successful) {
postPreferredActivityChangedBroadcast(userId);
}
@@ -19964,8 +20033,9 @@
String initiatingPackageName;
String originatingPackageName;
+ final InstallSource installSource;
synchronized (mLock) {
- final InstallSource installSource = getInstallSourceLocked(packageName, callingUid);
+ installSource = getInstallSourceLocked(packageName, callingUid);
if (installSource == null) {
return null;
}
@@ -19978,19 +20048,32 @@
}
}
- // All installSource strings are interned, so == is ok here
- if (installSource.initiatingPackageName == installSource.installerPackageName) {
- // The installer and initiator will often be the same, and when they are
- // we can skip doing the same check again.
- initiatingPackageName = installerPackageName;
- } else {
- initiatingPackageName = installSource.initiatingPackageName;
- final PackageSetting ps = mSettings.mPackages.get(initiatingPackageName);
- if (ps == null || shouldFilterApplicationLocked(ps, callingUid, userId)) {
+ if (installSource.isInitiatingPackageUninstalled) {
+ // We can't check visibility in the usual way, since the initiating package is no
+ // longer present. So we apply simpler rules to whether to expose the info:
+ // 1. Instant apps can't see it.
+ // 2. Otherwise only the installed app itself can see it.
+ final boolean isInstantApp = getInstantAppPackageName(callingUid) != null;
+ if (!isInstantApp && isCallerSameApp(packageName, callingUid)) {
+ initiatingPackageName = installSource.initiatingPackageName;
+ } else {
initiatingPackageName = null;
}
-
+ } else {
+ // All installSource strings are interned, so == is ok here
+ if (installSource.initiatingPackageName == installSource.installerPackageName) {
+ // The installer and initiator will often be the same, and when they are
+ // we can skip doing the same check again.
+ initiatingPackageName = installerPackageName;
+ } else {
+ initiatingPackageName = installSource.initiatingPackageName;
+ final PackageSetting ps = mSettings.mPackages.get(initiatingPackageName);
+ if (ps == null || shouldFilterApplicationLocked(ps, callingUid, userId)) {
+ initiatingPackageName = null;
+ }
+ }
}
+
originatingPackageName = installSource.originatingPackageName;
if (originatingPackageName != null) {
final PackageSetting ps = mSettings.mPackages.get(originatingPackageName);
@@ -20000,13 +20083,27 @@
}
}
+ // Remaining work can safely be done outside the lock. (Note that installSource is
+ // immutable so it's ok to carry on reading from it.)
+
if (originatingPackageName != null && mContext.checkCallingOrSelfPermission(
Manifest.permission.INSTALL_PACKAGES) != PackageManager.PERMISSION_GRANTED) {
originatingPackageName = null;
}
- return new InstallSourceInfo(initiatingPackageName, originatingPackageName,
- installerPackageName);
+ // If you can see the initiatingPackageName, and we have valid signing info for it,
+ // then we let you see that too.
+ final SigningInfo initiatingPackageSigningInfo;
+ final PackageSignatures signatures = installSource.initiatingPackageSignatures;
+ if (initiatingPackageName != null && signatures != null
+ && signatures.mSigningDetails != SigningDetails.UNKNOWN) {
+ initiatingPackageSigningInfo = new SigningInfo(signatures.mSigningDetails);
+ } else {
+ initiatingPackageSigningInfo = null;
+ }
+
+ return new InstallSourceInfo(initiatingPackageName, initiatingPackageSigningInfo,
+ originatingPackageName, installerPackageName);
}
@GuardedBy("mLock")
@@ -20110,8 +20207,7 @@
// Disable any carrier apps. We do this very early in boot to prevent the apps from being
// disabled after already being started.
CarrierAppUtils.disableCarrierAppsUntilPrivileged(mContext.getOpPackageName(), this,
- mPermissionManagerService, mContext.getContentResolver(),
- UserHandle.USER_SYSTEM);
+ mPermissionManagerService, UserHandle.USER_SYSTEM, mContext);
disableSkuSpecificApps();
@@ -22205,6 +22301,13 @@
mPermissionManager.onNewUserCreated(userId);
}
+ boolean readPermissionStateForUser(@UserIdInt int userId) {
+ synchronized (mPackages) {
+ mSettings.readPermissionStateForUserSyncLPr(userId);
+ return mSettings.areDefaultRuntimePermissionsGrantedLPr(userId);
+ }
+ }
+
@Override
public VerifierDeviceIdentity getVerifierDeviceIdentity() throws RemoteException {
mContext.enforceCallingOrSelfPermission(
@@ -22779,7 +22882,7 @@
ArrayList<String> systemPackageNames = new ArrayList<>(pkgNames.length);
for (String pkgName: pkgNames) {
- synchronized (mPackages) {
+ synchronized (mLock) {
if (pkgName == null) {
continue;
}
@@ -23057,6 +23160,11 @@
}
@Override
+ public void setDeviceOwnerProtectedPackages(List<String> packageNames) {
+ mProtectedPackages.setDeviceOwnerProtectedPackages(packageNames);
+ }
+
+ @Override
public boolean isPackageDataProtected(int userId, String packageName) {
return mProtectedPackages.isPackageDataProtected(userId, packageName);
}
@@ -23223,9 +23331,11 @@
@Override
public boolean setEnabledOverlayPackages(int userId, @NonNull String targetPackageName,
- @Nullable List<String> overlayPackageNames) {
+ @Nullable List<String> overlayPackageNames,
+ @NonNull Collection<String> outUpdatedPackageNames) {
synchronized (mLock) {
- if (targetPackageName == null || mPackages.get(targetPackageName) == null) {
+ final AndroidPackage targetPkg = mPackages.get(targetPackageName);
+ if (targetPackageName == null || targetPkg == null) {
Slog.e(TAG, "failed to find package " + targetPackageName);
return false;
}
@@ -23244,8 +23354,41 @@
}
}
+ ArraySet<String> updatedPackageNames = null;
+ if (targetPkg.getLibraryNames() != null) {
+ // Set the overlay paths for dependencies of the shared library.
+ updatedPackageNames = new ArraySet<>();
+ for (String libName : targetPkg.getLibraryNames()) {
+ final SharedLibraryInfo info = getSharedLibraryInfoLPr(libName,
+ SharedLibraryInfo.VERSION_UNDEFINED);
+ if (info == null) {
+ continue;
+ }
+ final List<VersionedPackage> dependents = getPackagesUsingSharedLibraryLPr(
+ info, 0, userId);
+ if (dependents == null) {
+ continue;
+ }
+ for (VersionedPackage dependent : dependents) {
+ final PackageSetting ps = mSettings.mPackages.get(
+ dependent.getPackageName());
+ if (ps == null) {
+ continue;
+ }
+ ps.setOverlayPathsForLibrary(libName, overlayPaths, userId);
+ updatedPackageNames.add(dependent.getPackageName());
+ }
+ }
+ }
+
final PackageSetting ps = mSettings.mPackages.get(targetPackageName);
ps.setOverlayPaths(overlayPaths, userId);
+
+ outUpdatedPackageNames.add(targetPackageName);
+ if (updatedPackageNames != null) {
+ outUpdatedPackageNames.addAll(updatedPackageNames);
+ }
+
return true;
}
}
@@ -23435,6 +23578,11 @@
}
@Override
+ public List<String> getApksInApex(String apexPackageName) {
+ return PackageManagerService.this.mApexManager.getApksInApex(apexPackageName);
+ }
+
+ @Override
public void uninstallApex(String packageName, long versionCode, int userId,
IntentSender intentSender, int flags) {
final int callerUid = Binder.getCallingUid();
@@ -23615,7 +23763,7 @@
@Nullable
public PackageSetting getPackageSetting(String packageName) {
- synchronized (mPackages) {
+ synchronized (mLock) {
packageName = resolveInternalPackageNameLPr(
packageName, PackageManager.VERSION_CODE_HIGHEST);
return mSettings.mPackages.get(packageName);
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 10e2780..5adab37 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -34,7 +34,6 @@
import android.content.Intent;
import android.content.IntentSender;
import android.content.pm.ApplicationInfo;
-import android.content.pm.DataLoaderParams;
import android.content.pm.FeatureInfo;
import android.content.pm.IPackageDataObserver;
import android.content.pm.IPackageInstaller;
@@ -137,10 +136,6 @@
private final static String ART_PROFILE_SNAPSHOT_DEBUG_LOCATION = "/data/misc/profman/";
private static final int DEFAULT_WAIT_MS = 60 * 1000;
- private static final String DATA_LOADER_PACKAGE = "android";
- private static final String DATA_LOADER_CLASS =
- "com.android.server.pm.PackageManagerShellCommandDataLoader";
-
final IPackageManager mInterface;
final IPermissionManager mPermissionManager;
final private WeakHashMap<String, Resources> mResourceCache =
@@ -164,7 +159,7 @@
final PrintWriter pw = getOutPrintWriter();
try {
- switch(cmd) {
+ switch (cmd) {
case "path":
return runPath();
case "dump":
@@ -1163,9 +1158,8 @@
private int runStreamingInstall() throws RemoteException {
final InstallParams params = makeInstallParams();
if (params.sessionParams.dataLoaderParams == null) {
- final DataLoaderParams dataLoaderParams = DataLoaderParams.forStreaming(
- new ComponentName(DATA_LOADER_PACKAGE, DATA_LOADER_CLASS), "");
- params.sessionParams.setDataLoaderParams(dataLoaderParams);
+ params.sessionParams.setDataLoaderParams(
+ PackageManagerShellCommandDataLoader.getDataLoaderParams(this));
}
return doRunInstall(params);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
index 1ee9ab8..a814cb8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommandDataLoader.java
@@ -17,18 +17,22 @@
package com.android.server.pm;
import android.annotation.NonNull;
+import android.content.ComponentName;
import android.content.pm.DataLoaderParams;
import android.content.pm.InstallationFile;
import android.os.ParcelFileDescriptor;
+import android.os.ShellCommand;
import android.service.dataloader.DataLoaderService;
import android.text.TextUtils;
import android.util.Slog;
+import android.util.SparseArray;
import libcore.io.IoUtils;
-import java.io.File;
import java.io.IOException;
+import java.lang.ref.WeakReference;
import java.nio.charset.StandardCharsets;
+import java.security.SecureRandom;
import java.util.Collection;
/**
@@ -37,41 +41,109 @@
public class PackageManagerShellCommandDataLoader extends DataLoaderService {
public static final String TAG = "PackageManagerShellCommandDataLoader";
- static class DataLoader implements DataLoaderService.DataLoader {
- private ParcelFileDescriptor mInFd = null;
- private FileSystemConnector mConnector = null;
+ private static final String PACKAGE = "android";
+ private static final String CLASS = PackageManagerShellCommandDataLoader.class.getName();
- private static final String STDIN_PATH = "-";
+ static final SecureRandom sRandom = new SecureRandom();
+ static final SparseArray<WeakReference<ShellCommand>> sShellCommands = new SparseArray<>();
+
+ private static final char ARGS_DELIM = '&';
+ private static final String SHELL_COMMAND_ID_PREFIX = "shellCommandId=";
+ private static final int INVALID_SHELL_COMMAND_ID = -1;
+ private static final int TOO_MANY_PENDING_SHELL_COMMANDS = 10;
+
+ private static final String STDIN_PATH = "-";
+
+ static DataLoaderParams getDataLoaderParams(ShellCommand shellCommand) {
+ int commandId;
+ synchronized (sShellCommands) {
+ // Clean up old references.
+ for (int i = sShellCommands.size() - 1; i >= 0; i--) {
+ WeakReference<ShellCommand> oldRef = sShellCommands.valueAt(i);
+ if (oldRef.get() == null) {
+ sShellCommands.removeAt(i);
+ }
+ }
+
+ // Sanity check.
+ if (sShellCommands.size() > TOO_MANY_PENDING_SHELL_COMMANDS) {
+ Slog.e(TAG, "Too many pending shell commands: " + sShellCommands.size());
+ }
+
+ // Generate new id and put ref to the array.
+ do {
+ commandId = sRandom.nextInt(Integer.MAX_VALUE - 1) + 1;
+ } while (sShellCommands.contains(commandId));
+
+ sShellCommands.put(commandId, new WeakReference<>(shellCommand));
+ }
+
+ final String args = SHELL_COMMAND_ID_PREFIX + commandId;
+ return DataLoaderParams.forStreaming(new ComponentName(PACKAGE, CLASS), args);
+ }
+
+ private static int extractShellCommandId(String args) {
+ int sessionIdIdx = args.indexOf(SHELL_COMMAND_ID_PREFIX);
+ if (sessionIdIdx < 0) {
+ Slog.e(TAG, "Missing shell command id param.");
+ return INVALID_SHELL_COMMAND_ID;
+ }
+ sessionIdIdx += SHELL_COMMAND_ID_PREFIX.length();
+ int delimIdx = args.indexOf(ARGS_DELIM, sessionIdIdx);
+ try {
+ if (delimIdx < 0) {
+ return Integer.parseInt(args.substring(sessionIdIdx));
+ } else {
+ return Integer.parseInt(args.substring(sessionIdIdx, delimIdx));
+ }
+ } catch (NumberFormatException e) {
+ Slog.e(TAG, "Incorrect shell command id format.", e);
+ return INVALID_SHELL_COMMAND_ID;
+ }
+ }
+
+ static class DataLoader implements DataLoaderService.DataLoader {
+ private DataLoaderParams mParams = null;
+ private FileSystemConnector mConnector = null;
@Override
public boolean onCreate(@NonNull DataLoaderParams dataLoaderParams,
@NonNull FileSystemConnector connector) {
+ mParams = dataLoaderParams;
mConnector = connector;
return true;
}
+
@Override
public boolean onPrepareImage(Collection<InstallationFile> addedFiles,
Collection<String> removedFiles) {
+ final int commandId = extractShellCommandId(mParams.getArguments());
+ if (commandId == INVALID_SHELL_COMMAND_ID) {
+ return false;
+ }
+
+ final WeakReference<ShellCommand> shellCommandRef;
+ synchronized (sShellCommands) {
+ shellCommandRef = sShellCommands.get(commandId, null);
+ }
+ final ShellCommand shellCommand =
+ shellCommandRef != null ? shellCommandRef.get() : null;
+ if (shellCommand == null) {
+ Slog.e(TAG, "Missing shell command.");
+ return false;
+ }
try {
for (InstallationFile fileInfo : addedFiles) {
String filePath = new String(fileInfo.getMetadata(), StandardCharsets.UTF_8);
if (STDIN_PATH.equals(filePath) || TextUtils.isEmpty(filePath)) {
- // TODO(b/146080380): add support for STDIN installations.
- if (mInFd == null) {
- Slog.e(TAG, "Invalid stdin file descriptor.");
- return false;
- }
- ParcelFileDescriptor inFd = ParcelFileDescriptor.dup(
- mInFd.getFileDescriptor());
+ final ParcelFileDescriptor inFd = ParcelFileDescriptor.dup(
+ shellCommand.getInFileDescriptor());
mConnector.writeData(fileInfo.getName(), 0, fileInfo.getSize(), inFd);
} else {
- File localFile = new File(filePath);
ParcelFileDescriptor incomingFd = null;
try {
- // TODO(b/146080380): open files via callback into shell command.
- incomingFd = ParcelFileDescriptor.open(localFile,
- ParcelFileDescriptor.MODE_READ_ONLY);
- mConnector.writeData(fileInfo.getName(), 0, localFile.length(),
+ incomingFd = shellCommand.openFileForSystem(filePath, "r");
+ mConnector.writeData(fileInfo.getName(), 0, incomingFd.getStatSize(),
incomingFd);
} finally {
IoUtils.closeQuietly(incomingFd);
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 671450d..f1ac0af 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -41,6 +41,8 @@
import java.io.File;
import java.util.Arrays;
import java.util.List;
+import java.util.Map;
+import java.util.Objects;
import java.util.Set;
/**
@@ -172,7 +174,7 @@
}
public void setInstallSource(InstallSource installSource) {
- this.installSource = Preconditions.checkNotNull(installSource);
+ this.installSource = Objects.requireNonNull(installSource);
}
void removeInstallerPackage(String packageName) {
@@ -311,12 +313,21 @@
}
void setOverlayPaths(List<String> overlayPaths, int userId) {
- modifyUserState(userId).overlayPaths = overlayPaths == null ? null :
- overlayPaths.toArray(new String[overlayPaths.size()]);
+ modifyUserState(userId).setOverlayPaths(overlayPaths == null ? null :
+ overlayPaths.toArray(new String[overlayPaths.size()]));
}
String[] getOverlayPaths(int userId) {
- return readUserState(userId).overlayPaths;
+ return readUserState(userId).getOverlayPaths();
+ }
+
+ void setOverlayPathsForLibrary(String libName, List<String> overlayPaths, int userId) {
+ modifyUserState(userId).setSharedLibraryOverlayPaths(libName,
+ overlayPaths == null ? null : overlayPaths.toArray(new String[0]));
+ }
+
+ Map<String, String[]> getOverlayPathsForLibrary(int userId) {
+ return readUserState(userId).getSharedLibraryOverlayPaths();
}
/** Only use for testing. Do NOT use in production code. */
diff --git a/services/core/java/com/android/server/pm/ProtectedPackages.java b/services/core/java/com/android/server/pm/ProtectedPackages.java
index 231168e..4da3cc3 100644
--- a/services/core/java/com/android/server/pm/ProtectedPackages.java
+++ b/services/core/java/com/android/server/pm/ProtectedPackages.java
@@ -24,6 +24,10 @@
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
+
+import java.util.ArrayList;
+import java.util.List;
/**
* Manages package names that need special protection.
@@ -49,6 +53,10 @@
@GuardedBy("this")
private final String mDeviceProvisioningPackage;
+ @Nullable
+ @GuardedBy("this")
+ private List<String> mDeviceOwnerProtectedPackages;
+
private final Context mContext;
public ProtectedPackages(Context context) {
@@ -70,6 +78,10 @@
: profileOwnerPackages.clone();
}
+ public synchronized void setDeviceOwnerProtectedPackages(List<String> packageNames) {
+ mDeviceOwnerProtectedPackages = new ArrayList<String>(packageNames);
+ }
+
private synchronized boolean hasDeviceOwnerOrProfileOwner(int userId, String packageName) {
if (packageName == null) {
return false;
@@ -105,7 +117,8 @@
* can modify its data or package state.
*/
private synchronized boolean isProtectedPackage(String packageName) {
- return packageName != null && packageName.equals(mDeviceProvisioningPackage);
+ return packageName != null && (packageName.equals(mDeviceProvisioningPackage)
+ || ArrayUtils.contains(mDeviceOwnerProtectedPackages, packageName));
}
/**
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 9642a1a..4f18cb4 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -280,8 +280,11 @@
/** Map from package name to settings */
final ArrayMap<String, PackageSetting> mPackages = new ArrayMap<>();
- /** List of packages that installed other packages */
- final ArraySet<String> mInstallerPackages = new ArraySet<>();
+ /**
+ * List of packages that were involved in installing other packages, i.e. are listed
+ * in at least one app's InstallSource.
+ */
+ private final ArraySet<String> mInstallerPackages = new ArraySet<>();
/** Map from package name to appId and excluded userids */
private final ArrayMap<String, KernelPackageState> mKernelMapping = new ArrayMap<>();
@@ -441,16 +444,6 @@
return mPermissions.canPropagatePermissionToInstantApp(permName);
}
- void setInstallerPackageName(String pkgName, String installerPkgName) {
- PackageSetting p = mPackages.get(pkgName);
- if (p != null) {
- p.setInstallerPackageName(installerPkgName);
- if (installerPkgName != null) {
- mInstallerPackages.add(installerPkgName);
- }
- }
- }
-
/** Gets and optionally creates a new shared user id. */
SharedUserSetting getSharedUserLPw(String name, int pkgFlags, int pkgPrivateFlags,
boolean create) throws PackageManagerException {
@@ -2826,6 +2819,9 @@
if (installSource.initiatingPackageName != null) {
serializer.attribute(null, "installInitiator", installSource.initiatingPackageName);
}
+ if (installSource.isInitiatingPackageUninstalled) {
+ serializer.attribute(null, "installInitiatorUninstalled", "true");
+ }
if (installSource.originatingPackageName != null) {
serializer.attribute(null, "installOriginator", installSource.originatingPackageName);
}
@@ -2843,6 +2839,11 @@
pkg.signatures.writeXml(serializer, "sigs", mPastSignatures);
+ if (installSource.initiatingPackageSignatures != null) {
+ installSource.initiatingPackageSignatures.writeXml(
+ serializer, "install-initiator-sigs", mPastSignatures);
+ }
+
writePermissionsLPr(serializer, pkg.getPermissionsState().getInstallPermissionStates());
writeSigningKeySetLPr(serializer, pkg.keySetData);
@@ -3124,6 +3125,10 @@
return true;
}
+ void readPermissionStateForUserSyncLPr(@UserIdInt int userId) {
+ mRuntimePermissionsPersistence.readStateForUserSyncLPr(userId);
+ }
+
void applyDefaultPreferredAppsLPw(int userId) {
// First pull data from any pre-installed apps.
final PackageManagerInternal pmInternal =
@@ -3578,6 +3583,7 @@
String isOrphaned = null;
String installOriginatingPackageName = null;
String installInitiatingPackageName = null;
+ String installInitiatorUninstalled = null;
String volumeUuid = null;
String categoryHintString = null;
String updateAvailable = null;
@@ -3623,6 +3629,8 @@
isOrphaned = parser.getAttributeValue(null, "isOrphaned");
installInitiatingPackageName = parser.getAttributeValue(null, "installInitiator");
installOriginatingPackageName = parser.getAttributeValue(null, "installOriginator");
+ installInitiatorUninstalled = parser.getAttributeValue(null,
+ "installInitiatorUninstalled");
volumeUuid = parser.getAttributeValue(null, "volumeUuid");
categoryHintString = parser.getAttributeValue(null, "categoryHint");
if (categoryHintString != null) {
@@ -3777,9 +3785,11 @@
}
if (packageSetting != null) {
packageSetting.uidError = "true".equals(uidError);
- packageSetting.installSource = InstallSource.create(
+ InstallSource installSource = InstallSource.create(
installInitiatingPackageName, installOriginatingPackageName,
- installerPackageName, "true".equals(isOrphaned));
+ installerPackageName, "true".equals(isOrphaned),
+ "true".equals(installInitiatorUninstalled));
+ packageSetting.installSource = installSource;
packageSetting.volumeUuid = volumeUuid;
packageSetting.categoryHint = categoryHint;
packageSetting.legacyNativeLibraryPathString = legacyNativeLibraryPathStr;
@@ -3809,9 +3819,7 @@
packageSetting.setEnabled(COMPONENT_ENABLED_STATE_DEFAULT, 0, null);
}
- if (installerPackageName != null) {
- mInstallerPackages.add(installerPackageName);
- }
+ addInstallerPackageNames(installSource);
int outerDepth = parser.getDepth();
int type;
@@ -3857,6 +3865,11 @@
mKeySetRefs.put(id, 1);
}
packageSetting.keySetData.addDefinedKeySet(id, alias);
+ } else if (tagName.equals("install-initiator-sigs")) {
+ final PackageSignatures signatures = new PackageSignatures();
+ signatures.readXml(parser, mPastSignatures);
+ packageSetting.installSource =
+ packageSetting.installSource.setInitiatingPackageSignatures(signatures);
} else if (tagName.equals(TAG_DOMAIN_VERIFICATION)) {
readDomainVerificationLPw(parser, packageSetting);
} else {
@@ -3870,6 +3883,18 @@
}
}
+ void addInstallerPackageNames(InstallSource installSource) {
+ if (installSource.installerPackageName != null) {
+ mInstallerPackages.add(installSource.installerPackageName);
+ }
+ if (installSource.initiatingPackageName != null) {
+ mInstallerPackages.add(installSource.initiatingPackageName);
+ }
+ if (installSource.originatingPackageName != null) {
+ mInstallerPackages.add(installSource.originatingPackageName);
+ }
+ }
+
private void readDisabledComponentsLPw(PackageSettingBase packageSetting, XmlPullParser parser,
int userId) throws IOException, XmlPullParserException {
int outerDepth = parser.getDepth();
@@ -4721,6 +4746,22 @@
}
}
+ Map<String, String[]> sharedLibraryOverlayPaths =
+ ps.getOverlayPathsForLibrary(user.id);
+ if (sharedLibraryOverlayPaths != null) {
+ for (Map.Entry<String, String[]> libOverlayPaths :
+ sharedLibraryOverlayPaths.entrySet()) {
+ if (libOverlayPaths.getValue() == null) {
+ continue;
+ }
+ pw.print(prefix); pw.print(" ");
+ pw.print(libOverlayPaths.getKey()); pw.println(" overlay paths:");
+ for (String path : libOverlayPaths.getValue()) {
+ pw.print(prefix); pw.print(" "); pw.println(path);
+ }
+ }
+ }
+
String lastDisabledAppCaller = ps.getLastDisabledAppCaller(user.id);
if (lastDisabledAppCaller != null) {
pw.print(prefix); pw.print(" lastDisabledCaller: ");
diff --git a/services/core/java/com/android/server/pm/ShortcutBitmapSaver.java b/services/core/java/com/android/server/pm/ShortcutBitmapSaver.java
index 815f885..dc534a7 100644
--- a/services/core/java/com/android/server/pm/ShortcutBitmapSaver.java
+++ b/services/core/java/com/android/server/pm/ShortcutBitmapSaver.java
@@ -38,6 +38,7 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Deque;
+import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingDeque;
@@ -157,7 +158,7 @@
public void saveBitmapLocked(ShortcutInfo shortcut,
int maxDimension, CompressFormat format, int quality) {
final Icon icon = shortcut.getIcon();
- Preconditions.checkNotNull(icon);
+ Objects.requireNonNull(icon);
final Bitmap original = icon.getBitmap();
if (original == null) {
diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java
index 06c71ba..0274aee 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackage.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackage.java
@@ -55,6 +55,7 @@
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;
@@ -454,7 +455,7 @@
public void updateInvisibleShortcutForPinRequestWith(@NonNull ShortcutInfo shortcut) {
final ShortcutInfo source = mShortcuts.get(shortcut.getId());
- Preconditions.checkNotNull(source);
+ Objects.requireNonNull(source);
mShortcutUser.mService.validateShortcutForPinRequest(shortcut);
diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
index 0629d9e..6d9d69e 100644
--- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java
+++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java
@@ -28,6 +28,7 @@
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
+import java.util.Objects;
/**
* All methods should be guarded by {@code #mShortcutUser.mService.mLock}.
@@ -49,7 +50,7 @@
mShortcutUser = shortcutUser;
mPackageUserId = packageUserId;
mPackageName = Preconditions.checkStringNotEmpty(packageName);
- mPackageInfo = Preconditions.checkNotNull(packageInfo);
+ mPackageInfo = Objects.requireNonNull(packageInfo);
}
/**
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index f0a1c70..261418c 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -429,17 +429,17 @@
@VisibleForTesting
ShortcutService(Context context, Looper looper, boolean onlyForPackageManagerApis) {
- mContext = Preconditions.checkNotNull(context);
+ mContext = Objects.requireNonNull(context);
LocalServices.addService(ShortcutServiceInternal.class, new LocalService());
mHandler = new Handler(looper);
mIPackageManager = AppGlobals.getPackageManager();
- mPackageManagerInternal = Preconditions.checkNotNull(
+ mPackageManagerInternal = Objects.requireNonNull(
LocalServices.getService(PackageManagerInternal.class));
- mUserManagerInternal = Preconditions.checkNotNull(
+ mUserManagerInternal = Objects.requireNonNull(
LocalServices.getService(UserManagerInternal.class));
- mUsageStatsManagerInternal = Preconditions.checkNotNull(
+ mUsageStatsManagerInternal = Objects.requireNonNull(
LocalServices.getService(UsageStatsManagerInternal.class));
- mActivityManagerInternal = Preconditions.checkNotNull(
+ mActivityManagerInternal = Objects.requireNonNull(
LocalServices.getService(ActivityManagerInternal.class));
mShortcutRequestPinProcessor = new ShortcutRequestPinProcessor(this, mLock);
@@ -1680,7 +1680,7 @@
"Re-publishing ShortcutInfo returned by server is not supported."
+ " Some information such as icon may lost from shortcut.");
}
- Preconditions.checkNotNull(shortcut, "Null shortcut detected");
+ Objects.requireNonNull(shortcut, "Null shortcut detected");
if (shortcut.getActivity() != null) {
Preconditions.checkState(
shortcut.getPackage().equals(shortcut.getActivity().getPackageName()),
@@ -1947,7 +1947,7 @@
@Override
public boolean requestPinShortcut(String packageName, ShortcutInfo shortcut,
IntentSender resultIntent, int userId) {
- Preconditions.checkNotNull(shortcut);
+ Objects.requireNonNull(shortcut);
Preconditions.checkArgument(shortcut.isEnabled(), "Shortcut must be enabled");
return requestPinItem(packageName, userId, shortcut, null, null, resultIntent);
}
@@ -1955,7 +1955,7 @@
@Override
public Intent createShortcutResultIntent(String packageName, ShortcutInfo shortcut, int userId)
throws RemoteException {
- Preconditions.checkNotNull(shortcut);
+ Objects.requireNonNull(shortcut);
Preconditions.checkArgument(shortcut.isEnabled(), "Shortcut must be enabled");
verifyCaller(packageName, userId);
verifyShortcutInfoPackage(packageName, shortcut);
@@ -2019,7 +2019,7 @@
public void disableShortcuts(String packageName, List shortcutIds,
CharSequence disabledMessage, int disabledMessageResId, @UserIdInt int userId) {
verifyCaller(packageName, userId);
- Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided");
+ Objects.requireNonNull(shortcutIds, "shortcutIds must be provided");
synchronized (mLock) {
throwIfUserLockedL(userId);
@@ -2054,7 +2054,7 @@
@Override
public void enableShortcuts(String packageName, List shortcutIds, @UserIdInt int userId) {
verifyCaller(packageName, userId);
- Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided");
+ Objects.requireNonNull(shortcutIds, "shortcutIds must be provided");
synchronized (mLock) {
throwIfUserLockedL(userId);
@@ -2081,7 +2081,7 @@
public void removeDynamicShortcuts(String packageName, List shortcutIds,
@UserIdInt int userId) {
verifyCaller(packageName, userId);
- Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided");
+ Objects.requireNonNull(shortcutIds, "shortcutIds must be provided");
synchronized (mLock) {
throwIfUserLockedL(userId);
@@ -2256,7 +2256,7 @@
public void reportShortcutUsed(String packageName, String shortcutId, int userId) {
verifyCaller(packageName, userId);
- Preconditions.checkNotNull(shortcutId);
+ Objects.requireNonNull(shortcutId);
if (DEBUG) {
Slog.d(TAG, String.format("reportShortcutUsed: Shortcut %s package %s used on user %d",
@@ -2713,7 +2713,7 @@
@NonNull List<String> shortcutIds, int userId) {
// Calling permission must be checked by LauncherAppsImpl.
Preconditions.checkStringNotEmpty(packageName, "packageName");
- Preconditions.checkNotNull(shortcutIds, "shortcutIds");
+ Objects.requireNonNull(shortcutIds, "shortcutIds");
synchronized (mLock) {
throwIfUserLockedL(userId);
@@ -2766,16 +2766,16 @@
@Override
public void addListener(@NonNull ShortcutChangeListener listener) {
synchronized (mLock) {
- mListeners.add(Preconditions.checkNotNull(listener));
+ mListeners.add(Objects.requireNonNull(listener));
}
}
@Override
public int getShortcutIconResId(int launcherUserId, @NonNull String callingPackage,
@NonNull String packageName, @NonNull String shortcutId, int userId) {
- Preconditions.checkNotNull(callingPackage, "callingPackage");
- Preconditions.checkNotNull(packageName, "packageName");
- Preconditions.checkNotNull(shortcutId, "shortcutId");
+ Objects.requireNonNull(callingPackage, "callingPackage");
+ Objects.requireNonNull(packageName, "packageName");
+ Objects.requireNonNull(shortcutId, "shortcutId");
synchronized (mLock) {
throwIfUserLockedL(userId);
@@ -2800,9 +2800,9 @@
public ParcelFileDescriptor getShortcutIconFd(int launcherUserId,
@NonNull String callingPackage, @NonNull String packageName,
@NonNull String shortcutId, int userId) {
- Preconditions.checkNotNull(callingPackage, "callingPackage");
- Preconditions.checkNotNull(packageName, "packageName");
- Preconditions.checkNotNull(shortcutId, "shortcutId");
+ Objects.requireNonNull(callingPackage, "callingPackage");
+ Objects.requireNonNull(packageName, "packageName");
+ Objects.requireNonNull(shortcutId, "shortcutId");
synchronized (mLock) {
throwIfUserLockedL(userId);
@@ -2854,7 +2854,7 @@
public boolean requestPinAppWidget(@NonNull String callingPackage,
@NonNull AppWidgetProviderInfo appWidget, @Nullable Bundle extras,
@Nullable IntentSender resultIntent, int userId) {
- Preconditions.checkNotNull(appWidget);
+ Objects.requireNonNull(appWidget);
return requestPinItem(callingPackage, userId, null, appWidget, extras, resultIntent);
}
@@ -2865,7 +2865,7 @@
@Override
public boolean isForegroundDefaultLauncher(@NonNull String callingPackage, int callingUid) {
- Preconditions.checkNotNull(callingPackage);
+ Objects.requireNonNull(callingPackage);
final int userId = UserHandle.getUserId(callingUid);
final ComponentName defaultLauncher = getDefaultLauncher(userId);
@@ -3411,7 +3411,7 @@
List<ResolveInfo> queryActivities(@NonNull Intent baseIntent,
@NonNull String packageName, @Nullable ComponentName activity, int userId) {
- baseIntent.setPackage(Preconditions.checkNotNull(packageName));
+ baseIntent.setPackage(Objects.requireNonNull(packageName));
if (activity != null) {
baseIntent.setComponent(activity);
}
@@ -3529,7 +3529,7 @@
@Nullable
ComponentName injectGetPinConfirmationActivity(@NonNull String launcherPackageName,
int launcherUserId, int requestType) {
- Preconditions.checkNotNull(launcherPackageName);
+ Objects.requireNonNull(launcherPackageName);
String action = requestType == LauncherApps.PinItemRequest.REQUEST_TYPE_SHORTCUT ?
LauncherApps.ACTION_CONFIRM_PIN_SHORTCUT :
LauncherApps.ACTION_CONFIRM_PIN_APPWIDGET;
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index 8c207a8..eab3f4d 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -75,7 +75,7 @@
private PackageWithUser(int userId, String packageName) {
this.userId = userId;
- this.packageName = Preconditions.checkNotNull(packageName);
+ this.packageName = Objects.requireNonNull(packageName);
}
public static PackageWithUser of(int userId, String packageName) {
diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java
index 9e462cd..7888d1f 100644
--- a/services/core/java/com/android/server/pm/StagingManager.java
+++ b/services/core/java/com/android/server/pm/StagingManager.java
@@ -33,11 +33,13 @@
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
import android.content.pm.PackageParser;
import android.content.pm.PackageParser.PackageParserException;
import android.content.pm.PackageParser.SigningDetails;
import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
import android.content.pm.ParceledListSlice;
+import android.content.pm.parsing.AndroidPackage;
import android.content.rollback.IRollbackManager;
import android.content.rollback.RollbackInfo;
import android.content.rollback.RollbackManager;
@@ -47,9 +49,12 @@
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
+import android.os.ParcelableException;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.os.UserManagerInternal;
import android.os.storage.IStorageManager;
import android.os.storage.StorageManager;
import android.util.IntArray;
@@ -61,6 +66,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.content.PackageHelper;
import com.android.internal.os.BackgroundThread;
+import com.android.server.LocalServices;
import java.io.File;
import java.io.IOException;
@@ -93,10 +99,11 @@
@GuardedBy("mStagedSessions")
private final SparseIntArray mSessionRollbackIds = new SparseIntArray();
- StagingManager(PackageInstallerService pi, ApexManager am, Context context) {
+ StagingManager(PackageInstallerService pi, Context context) {
mPi = pi;
- mApexManager = am;
mContext = context;
+
+ mApexManager = ApexManager.getInstance();
mPowerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
mPreRebootVerificationHandler = new PreRebootVerificationHandler(
BackgroundThread.get().getLooper());
@@ -334,6 +341,88 @@
return PackageHelper.getStorageManager().needsCheckpoint();
}
+ /**
+ * Apks inside apex are not installed using apk-install flow. They are scanned from the system
+ * directory directly by PackageManager, as such, RollbackManager need to handle their data
+ * separately here.
+ */
+ private void snapshotAndRestoreApkInApexUserData(PackageInstallerSession session) {
+ // We want to process apks inside apex. So current session needs to contain apex.
+ if (!sessionContainsApex(session)) {
+ return;
+ }
+
+ boolean doSnapshotOrRestore =
+ (session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0
+ || session.params.installReason == PackageManager.INSTALL_REASON_ROLLBACK;
+ if (!doSnapshotOrRestore) {
+ return;
+ }
+
+ // Find all the apex sessions that needs processing
+ List<PackageInstallerSession> apexSessions = new ArrayList<>();
+ if (session.isMultiPackage()) {
+ List<PackageInstallerSession> childrenSessions = new ArrayList<>();
+ synchronized (mStagedSessions) {
+ for (int childSessionId : session.getChildSessionIds()) {
+ PackageInstallerSession childSession = mStagedSessions.get(childSessionId);
+ if (childSession != null) {
+ childrenSessions.add(childSession);
+ }
+ }
+ }
+ for (PackageInstallerSession childSession : childrenSessions) {
+ if (sessionContainsApex(childSession)) {
+ apexSessions.add(childSession);
+ }
+ }
+ } else {
+ apexSessions.add(session);
+ }
+
+ // For each apex, process the apks inside it
+ for (PackageInstallerSession apexSession : apexSessions) {
+ List<String> apksInApex = mApexManager.getApksInApex(apexSession.getPackageName());
+ for (String apk: apksInApex) {
+ snapshotAndRestoreApkInApexUserData(apk);
+ }
+ }
+ }
+
+ private void snapshotAndRestoreApkInApexUserData(String packageName) {
+ IRollbackManager rm = IRollbackManager.Stub.asInterface(
+ ServiceManager.getService(Context.ROLLBACK_SERVICE));
+
+ PackageManagerInternal mPmi = LocalServices.getService(PackageManagerInternal.class);
+ AndroidPackage pkg = mPmi.getPackage(packageName);
+ if (pkg == null) {
+ Slog.e(TAG, "Could not find package: " + packageName
+ + "for snapshotting/restoring user data.");
+ return;
+ }
+ final String seInfo = pkg.getSeInfo();
+ final UserManagerInternal um = LocalServices.getService(UserManagerInternal.class);
+ final int[] allUsers = um.getUserIds();
+
+ int appId = -1;
+ long ceDataInode = -1;
+ final PackageSetting ps = (PackageSetting) mPmi.getPackageSetting(packageName);
+ if (ps != null && rm != null) {
+ appId = ps.appId;
+ ceDataInode = ps.getCeDataInode(UserHandle.USER_SYSTEM);
+ // NOTE: We ignore the user specified in the InstallParam because we know this is
+ // an update, and hence need to restore data for all installed users.
+ final int[] installedUsers = ps.queryInstalledUsers(allUsers, true);
+
+ try {
+ rm.snapshotAndRestoreUserData(packageName, installedUsers, appId, ceDataInode,
+ seInfo, 0 /*token*/);
+ } catch (RemoteException re) {
+ Slog.e(TAG, "Error snapshotting/restoring user data: " + re);
+ }
+ }
+ }
+
private void resumeSession(@NonNull PackageInstallerSession session) {
Slog.d(TAG, "Resuming session " + session.sessionId);
@@ -407,6 +496,7 @@
abortCheckpoint();
return;
}
+ snapshotAndRestoreApkInApexUserData(session);
Slog.i(TAG, "APEX packages in session " + session.sessionId
+ " were successfully activated. Proceeding with APK packages, if any");
}
@@ -481,18 +571,17 @@
} else {
params.installFlags |= PackageManager.INSTALL_DISABLE_VERIFICATION;
}
- int apkSessionId = mPi.createSession(
- params, originalSession.getInstallerPackageName(),
- 0 /* UserHandle.SYSTEM */);
- PackageInstallerSession apkSession = mPi.getSession(apkSessionId);
-
try {
+ int apkSessionId = mPi.createSession(
+ params, originalSession.getInstallerPackageName(),
+ 0 /* UserHandle.SYSTEM */);
+ PackageInstallerSession apkSession = mPi.getSession(apkSessionId);
apkSession.open();
for (String apkFilePath : apkFilePaths) {
File apkFile = new File(apkFilePath);
ParcelFileDescriptor pfd = ParcelFileDescriptor.open(apkFile,
ParcelFileDescriptor.MODE_READ_ONLY);
- long sizeBytes = pfd.getStatSize();
+ long sizeBytes = (pfd == null) ? -1 : pfd.getStatSize();
if (sizeBytes < 0) {
Slog.e(TAG, "Unable to get size of: " + apkFilePath);
throw new PackageManagerException(errorCode,
@@ -500,11 +589,11 @@
}
apkSession.write(apkFile.getName(), 0, sizeBytes, pfd);
}
- } catch (IOException e) {
+ return apkSession;
+ } catch (IOException | ParcelableException e) {
Slog.e(TAG, "Failure to install APK staged session " + originalSession.sessionId, e);
- throw new PackageManagerException(errorCode, "Failed to write APK session", e);
+ throw new PackageManagerException(errorCode, "Failed to create/write APK session", e);
}
- return apkSession;
}
/**
@@ -529,7 +618,7 @@
Arrays.stream(session.getChildSessionIds())
// Retrieve cached sessions matching ids.
.mapToObj(i -> mStagedSessions.get(i))
- // Filter only the ones containing APKs.s
+ // Filter only the ones containing APKs.
.filter(childSession -> !isApexSession(childSession))
.collect(Collectors.toList());
}
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index 59a5804..f5f4009 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -48,6 +48,44 @@
"include-filter": "android.permission.cts.PermissionUpdateListenerTest"
}
]
+ },
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {
+ "install-arg": "-t"
+ },
+ {
+ "include-filter": "com.android.server.pm.UserDataPreparerTest"
+ },
+ {
+ "include-filter": "com.android.server.pm.UserLifecycleStressTest"
+ },
+ {
+ "include-filter": "com.android.server.pm.UserManagerServiceCreateProfileTest"
+ },
+ {
+ "include-filter": "com.android.server.pm.UserManagerServiceIdRecyclingTest"
+ },
+ {
+ "include-filter": "com.android.server.pm.UserManagerServiceTest"
+ },
+ {
+ "include-filter": "com.android.server.pm.UserManagerServiceUserInfoTest"
+ },
+ {
+ "include-filter": "com.android.server.pm.UserManagerServiceUserTypeTest"
+ },
+ {
+ "include-filter": "com.android.server.pm.UserManagerTest"
+ },
+ {
+ "include-filter": "com.android.server.pm.UserRestrictionsUtilsTest"
+ },
+ {
+ "include-filter": "com.android.server.pm.UserSystemPackageInstallerTest"
+ }
+ ]
}
],
"imports": [
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 5854e32..66a2b01 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -434,7 +434,7 @@
private final IntentSender mTarget;
public DisableQuietModeUserUnlockedCallback(IntentSender target) {
- Preconditions.checkNotNull(target);
+ Objects.requireNonNull(target);
mTarget = target;
}
@@ -492,6 +492,10 @@
public void onBootPhase(int phase) {
if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
mUms.cleanupPartialUsers();
+
+ if (mUms.mPm.isDeviceUpgrading()) {
+ mUms.cleanupPreCreatedUsers();
+ }
}
}
@@ -617,6 +621,33 @@
}
}
+ /**
+ * Removes any pre-created users from the system. Should be invoked after OTAs, to ensure
+ * pre-created users are not stale. New pre-created pool can be re-created after the update.
+ */
+ void cleanupPreCreatedUsers() {
+ final ArrayList<UserInfo> preCreatedUsers;
+ synchronized (mUsersLock) {
+ final int userSize = mUsers.size();
+ preCreatedUsers = new ArrayList<>(userSize);
+ for (int i = 0; i < userSize; i++) {
+ UserInfo ui = mUsers.valueAt(i).info;
+ if (ui.preCreated) {
+ preCreatedUsers.add(ui);
+ addRemovingUserIdLocked(ui.id);
+ ui.flags |= UserInfo.FLAG_DISABLED;
+ ui.partial = true;
+ }
+ }
+ }
+ final int preCreatedSize = preCreatedUsers.size();
+ for (int i = 0; i < preCreatedSize; i++) {
+ UserInfo ui = preCreatedUsers.get(i);
+ Slog.i(LOG_TAG, "Removing pre-created user " + ui.id);
+ removeUserState(ui.id);
+ }
+ }
+
@Override
public String getUserAccount(@UserIdInt int userId) {
checkManageUserAndAcrossUsersFullPermission("get user account");
@@ -884,7 +915,7 @@
@Override
public boolean requestQuietModeEnabled(@NonNull String callingPackage, boolean enableQuietMode,
@UserIdInt int userId, @Nullable IntentSender target) {
- Preconditions.checkNotNull(callingPackage);
+ Objects.requireNonNull(callingPackage);
if (enableQuietMode && target != null) {
throw new IllegalArgumentException(
@@ -1132,14 +1163,13 @@
}
/**
- * Returns the user type, e.g. {@link UserManager#USER_TYPE_FULL_GUEST}, of the given userId,
- * or null if the user doesn't exist.
+ * Returns whether the given user (specified by userId) is of the given user type, such as
+ * {@link UserManager#USER_TYPE_FULL_GUEST}.
*/
@Override
- public @Nullable String getUserTypeForUser(@UserIdInt int userId) {
- // TODO(b/142482943): Decide on the appropriate permission requirements.
- checkManageOrInteractPermIfCallerInOtherProfileGroup(userId, "getUserTypeForUser");
- return getUserTypeNoChecks(userId);
+ public boolean isUserOfType(@UserIdInt int userId, String userType) {
+ checkManageUsersPermission("check user type");
+ return userType != null && userType.equals(getUserTypeNoChecks(userId));
}
/**
@@ -3079,7 +3109,6 @@
@NonNull String userType, @UserInfoFlag int flags, @UserIdInt int parentId,
boolean preCreate, @Nullable String[] disallowedPackages,
@NonNull TimingsTraceAndSlog t) {
-
final UserTypeDetails userTypeDetails = mUserTypes.get(userType);
if (userTypeDetails == null) {
Slog.e(LOG_TAG, "Cannot create user of invalid user type: " + userType);
@@ -3255,9 +3284,9 @@
mBaseUserRestrictions.append(userId, restrictions);
}
- t.traceBegin("PM.onNewUserCreated");
+ t.traceBegin("PM.onNewUserCreated-" + userId);
mPm.onNewUserCreated(userId);
-
+ t.traceEnd();
if (preCreate) {
// Must start user (which will be stopped right away, through
// UserController.finishUserUnlockedCompleted) so services can properly
@@ -3324,11 +3353,16 @@
preCreatedUser.preCreated = false;
preCreatedUser.creationTime = getCreationTime();
- dispatchUserAddedIntent(preCreatedUser);
synchronized (mPackagesLock) {
writeUserLP(preCreatedUserData);
writeUserListLP();
}
+ updateUserIds();
+ if (!mPm.readPermissionStateForUser(preCreatedUser.id)) {
+ // Could not read the existing permissions, re-grant them.
+ mPm.onNewUserCreated(preCreatedUser.id);
+ }
+ dispatchUserAddedIntent(preCreatedUser);
return preCreatedUser;
}
@@ -3361,6 +3395,9 @@
private void dispatchUserAddedIntent(@NonNull UserInfo userInfo) {
Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED);
addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userInfo.id);
+ // Also, add the UserHandle for mainline modules which can't use the @hide
+ // EXTRA_USER_HANDLE.
+ addedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(userInfo.id));
mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL,
android.Manifest.permission.MANAGE_USERS);
MetricsLogger.count(mContext, userInfo.isGuest() ? TRON_GUEST_CREATED
@@ -3644,9 +3681,12 @@
// wiping the user's system directory and removing from the user list
long ident = Binder.clearCallingIdentity();
try {
- Intent addedIntent = new Intent(Intent.ACTION_USER_REMOVED);
- addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
- mContext.sendOrderedBroadcastAsUser(addedIntent, UserHandle.ALL,
+ Intent removedIntent = new Intent(Intent.ACTION_USER_REMOVED);
+ removedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+ // Also, add the UserHandle for mainline modules which can't use the @hide
+ // EXTRA_USER_HANDLE.
+ removedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId));
+ mContext.sendOrderedBroadcastAsUser(removedIntent, UserHandle.ALL,
android.Manifest.permission.MANAGE_USERS,
new BroadcastReceiver() {
@@ -4028,14 +4068,16 @@
synchronized (mUsersLock) {
final int userSize = mUsers.size();
for (int i = 0; i < userSize; i++) {
- if (!mUsers.valueAt(i).info.partial) {
+ UserInfo userInfo = mUsers.valueAt(i).info;
+ if (!userInfo.partial && !userInfo.preCreated) {
num++;
}
}
final int[] newUsers = new int[num];
int n = 0;
for (int i = 0; i < userSize; i++) {
- if (!mUsers.valueAt(i).info.partial) {
+ UserInfo userInfo = mUsers.valueAt(i).info;
+ if (!userInfo.partial && !userInfo.preCreated) {
newUsers[n++] = mUsers.keyAt(i);
}
}
@@ -4096,7 +4138,10 @@
* recycled.
*/
void reconcileUsers(String volumeUuid) {
- mUserDataPreparer.reconcileUsers(volumeUuid, getUsers(true /* excludeDying */));
+ mUserDataPreparer.reconcileUsers(volumeUuid, getUsers(
+ /* excludePartial= */ true,
+ /* excludeDying= */ true,
+ /* excludePreCreated= */ false));
}
/**
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index 90bd947..89030ed 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -51,6 +51,7 @@
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
/**
@@ -198,13 +199,31 @@
);
/**
- * Special user restrictions that are applied globally when set by the profile owner of a
- * managed profile that was created during the device provisioning flow.
+ * Special user restrictions that profile owner of an organization-owned managed profile can
+ * set on the parent profile instance to apply them globally.
*/
private static final Set<String> PROFILE_OWNER_ORGANIZATION_OWNED_GLOBAL_RESTRICTIONS =
Sets.newArraySet(
UserManager.DISALLOW_CONFIG_DATE_TIME,
- UserManager.DISALLOW_CAMERA
+ UserManager.DISALLOW_CAMERA,
+ UserManager.DISALLOW_ADD_USER,
+ UserManager.DISALLOW_BLUETOOTH,
+ UserManager.DISALLOW_BLUETOOTH_SHARING,
+ UserManager.DISALLOW_CONFIG_BLUETOOTH,
+ UserManager.DISALLOW_CONFIG_CELL_BROADCASTS,
+ UserManager.DISALLOW_CONFIG_LOCATION,
+ UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS,
+ UserManager.DISALLOW_CONFIG_PRIVATE_DNS,
+ UserManager.DISALLOW_CONFIG_TETHERING,
+ UserManager.DISALLOW_CONFIG_WIFI,
+ UserManager.DISALLOW_CONTENT_CAPTURE,
+ UserManager.DISALLOW_CONTENT_SUGGESTIONS,
+ UserManager.DISALLOW_DATA_ROAMING,
+ UserManager.DISALLOW_DEBUGGING_FEATURES,
+ UserManager.DISALLOW_SAFE_BOOT,
+ UserManager.DISALLOW_SHARE_LOCATION,
+ UserManager.DISALLOW_SMS,
+ UserManager.DISALLOW_USB_FILE_TRANSFER
);
/**
@@ -357,7 +376,7 @@
}
public static void merge(@NonNull Bundle dest, @Nullable Bundle in) {
- Preconditions.checkNotNull(dest);
+ Objects.requireNonNull(dest);
Preconditions.checkArgument(dest != in);
if (in == null) {
return;
@@ -661,7 +680,7 @@
public static boolean isSettingRestrictedForUser(Context context, @NonNull String setting,
int userId, String value, int callingUid) {
- Preconditions.checkNotNull(setting);
+ Objects.requireNonNull(setting);
final UserManager mUserManager = context.getSystemService(UserManager.class);
String restriction;
boolean checkAllUser = false;
diff --git a/services/core/java/com/android/server/pm/dex/ArtManagerService.java b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
index 486cfef..0caab6d 100644
--- a/services/core/java/com/android/server/pm/dex/ArtManagerService.java
+++ b/services/core/java/com/android/server/pm/dex/ArtManagerService.java
@@ -62,6 +62,7 @@
import java.io.File;
import java.io.FileNotFoundException;
+import java.util.Objects;
/**
* A system service that provides access to runtime and compiler artifacts.
@@ -180,7 +181,7 @@
}
// Sanity checks on the arguments.
- Preconditions.checkNotNull(callback);
+ Objects.requireNonNull(callback);
boolean bootImageProfile = profileType == ArtManager.PROFILE_BOOT_IMAGE;
if (!bootImageProfile) {
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 29183bb..df24c013 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -222,6 +222,7 @@
// If the dex file is the primary apk (or a split) and not isUsedByOtherApps
// do not record it. This case does not bring any new usable information
// and can be safely skipped.
+ dexPathIndex++;
continue;
}
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index 6d6ec25..46893b2 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -592,12 +592,6 @@
getDefaultSystemHandlerActivityPackageForCategory(Intent.CATEGORY_APP_MAPS, userId),
userId, ALWAYS_LOCATION_PERMISSIONS);
- // Gallery
- grantPermissionsToSystemPackage(
- getDefaultSystemHandlerActivityPackageForCategory(
- Intent.CATEGORY_APP_GALLERY, userId),
- userId, STORAGE_PERMISSIONS);
-
// Email
grantPermissionsToSystemPackage(
getDefaultSystemHandlerActivityPackageForCategory(
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 9cd6f16..0411e29 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -55,6 +55,8 @@
import android.app.ActivityManager;
import android.app.ApplicationPackageManager;
import android.app.IActivityManager;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
@@ -107,6 +109,7 @@
import android.util.SparseBooleanArray;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.compat.IPlatformCompat;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.os.RoSystemProperties;
@@ -224,6 +227,8 @@
private final Handler mHandler;
private final Context mContext;
private final MetricsLogger mMetricsLogger = new MetricsLogger();
+ private final IPlatformCompat mPlatformCompat = IPlatformCompat.Stub.asInterface(
+ ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
/** Internal storage for permissions and related settings */
@GuardedBy("mLock")
@@ -796,7 +801,7 @@
@Override
public int checkPermission(String permName, String pkgName, int userId) {
- // Not using Preconditions.checkNotNull() here for compatibility reasons.
+ // Not using Objects.requireNonNull() here for compatibility reasons.
if (permName == null || pkgName == null) {
return PackageManager.PERMISSION_DENIED;
}
@@ -872,7 +877,7 @@
@Override
public int checkUidPermission(String permName, int uid) {
- // Not using Preconditions.checkNotNull() here for compatibility reasons.
+ // Not using Objects.requireNonNull() here for compatibility reasons.
if (permName == null) {
return PackageManager.PERMISSION_DENIED;
}
@@ -955,7 +960,7 @@
@Override
@Nullable public List<String> getWhitelistedRestrictedPermissions(@NonNull String packageName,
@PermissionWhitelistFlags int flags, @UserIdInt int userId) {
- Preconditions.checkNotNull(packageName);
+ Objects.requireNonNull(packageName);
Preconditions.checkFlagsArgument(flags,
PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
| PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM
@@ -1043,7 +1048,7 @@
@NonNull String permName, @PermissionWhitelistFlags int flags,
@UserIdInt int userId) {
// Other argument checks are done in get/setWhitelistedRestrictedPermissions
- Preconditions.checkNotNull(permName);
+ Objects.requireNonNull(permName);
if (!checkExistsAndEnforceCannotModifyImmutablyRestrictedPermission(permName)) {
return false;
@@ -1086,7 +1091,7 @@
@NonNull String permName, @PermissionWhitelistFlags int flags,
@UserIdInt int userId) {
// Other argument checks are done in get/setWhitelistedRestrictedPermissions
- Preconditions.checkNotNull(permName);
+ Objects.requireNonNull(permName);
if (!checkExistsAndEnforceCannotModifyImmutablyRestrictedPermission(permName)) {
return false;
@@ -1104,7 +1109,7 @@
private boolean setWhitelistedRestrictedPermissionsInternal(@NonNull String packageName,
@Nullable List<String> permissions, @PermissionWhitelistFlags int flags,
@UserIdInt int userId) {
- Preconditions.checkNotNull(packageName);
+ Objects.requireNonNull(packageName);
Preconditions.checkFlagsArgument(flags,
PackageManager.FLAG_PERMISSION_WHITELIST_UPGRADE
| PackageManager.FLAG_PERMISSION_WHITELIST_SYSTEM
@@ -1824,6 +1829,14 @@
return true;
}
+ /**
+ * This change makes it so that apps are told to show rationale for asking for background
+ * location access every time they request.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+ private static final long BACKGROUND_RATIONALE_CHANGE_ID = 147316723L;
+
@Override
public boolean shouldShowRequestPermissionRationale(String permName,
String packageName, int userId) {
@@ -1862,6 +1875,16 @@
return false;
}
+ try {
+ if (permName.equals(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
+ && mPlatformCompat.isChangeEnabledByPackageName(BACKGROUND_RATIONALE_CHANGE_ID,
+ packageName, userId)) {
+ return true;
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Unable to check if compatibility change is enabled.", e);
+ }
+
return (flags & PackageManager.FLAG_PERMISSION_USER_SET) != 0;
}
@@ -3028,7 +3051,7 @@
mContext.enforceCallingPermission(Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS,
"Must hold " + Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS
+ " to register permissions as one time.");
- packageName = Preconditions.checkNotNull(packageName);
+ Objects.requireNonNull(packageName);
long token = Binder.clearCallingIdentity();
try {
@@ -3044,7 +3067,7 @@
mContext.enforceCallingPermission(Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS,
"Must hold " + Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS
+ " to remove permissions as one time.");
- Preconditions.checkNotNull(packageName);
+ Objects.requireNonNull(packageName);
long token = Binder.clearCallingIdentity();
try {
@@ -4005,21 +4028,128 @@
PackageManagerServiceUtils.enforceShellRestriction(mUserManagerInt,
UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId);
}
- if (!requirePermissionWhenSameUser && userId == UserHandle.getUserId(callingUid)) return;
- if (callingUid != Process.SYSTEM_UID && callingUid != Process.ROOT_UID) {
- if (requireFullPermission) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
- } else {
- try {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
- } catch (SecurityException se) {
- mContext.enforceCallingOrSelfPermission(
- android.Manifest.permission.INTERACT_ACROSS_USERS, message);
- }
- }
+ final int callingUserId = UserHandle.getUserId(callingUid);
+ if (hasCrossUserPermission(
+ callingUid, callingUserId, userId, requireFullPermission,
+ requirePermissionWhenSameUser)) {
+ return;
}
+ String errorMessage = buildInvalidCrossUserPermissionMessage(
+ message, requireFullPermission);
+ Slog.w(TAG, errorMessage);
+ throw new SecurityException(errorMessage);
+ }
+
+ /**
+ * Checks if the request is from the system or an app that has the appropriate cross-user
+ * permissions defined as follows:
+ * <ul>
+ * <li>INTERACT_ACROSS_USERS_FULL if {@code requireFullPermission} is true.</li>
+ * <li>INTERACT_ACROSS_USERS if the given {@userId} is in a different profile group
+ * to the caller.</li>
+ * <li>Otherwise, INTERACT_ACROSS_PROFILES if the given {@userId} is in the same profile group
+ * as the caller.</li>
+ * </ul>
+ *
+ * @param checkShell whether to prevent shell from access if there's a debugging restriction
+ * @param message the message to log on security exception
+ */
+ private void enforceCrossUserOrProfilePermission(int callingUid, int userId,
+ boolean requireFullPermission, boolean checkShell,
+ String message) {
+ if (userId < 0) {
+ throw new IllegalArgumentException("Invalid userId " + userId);
+ }
+ if (checkShell) {
+ PackageManagerServiceUtils.enforceShellRestriction(mUserManagerInt,
+ UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId);
+ }
+ final int callingUserId = UserHandle.getUserId(callingUid);
+ if (hasCrossUserPermission(callingUid, callingUserId, userId, requireFullPermission,
+ /*requirePermissionWhenSameUser= */ false)) {
+ return;
+ }
+ final boolean isSameProfileGroup = isSameProfileGroup(callingUserId, userId);
+ if (isSameProfileGroup
+ && hasPermission(android.Manifest.permission.INTERACT_ACROSS_PROFILES)) {
+ return;
+ }
+ String errorMessage = buildInvalidCrossUserOrProfilePermissionMessage(
+ message, requireFullPermission, isSameProfileGroup);
+ Slog.w(TAG, errorMessage);
+ throw new SecurityException(errorMessage);
+ }
+
+ private boolean hasCrossUserPermission(
+ int callingUid, int callingUserId, int userId, boolean requireFullPermission,
+ boolean requirePermissionWhenSameUser) {
+ if (!requirePermissionWhenSameUser && userId == callingUserId) {
+ return true;
+ }
+ if (callingUid == Process.SYSTEM_UID || callingUid == Process.ROOT_UID) {
+ return true;
+ }
+ if (requireFullPermission) {
+ return hasPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+ }
+ return hasPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
+ || hasPermission(Manifest.permission.INTERACT_ACROSS_USERS);
+ }
+
+ private boolean hasPermission(String permission) {
+ return mContext.checkCallingOrSelfPermission(permission)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ private boolean isSameProfileGroup(@UserIdInt int callerUserId, @UserIdInt int userId) {
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return UserManagerService.getInstance().isSameProfileGroup(callerUserId, userId);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ private static String buildInvalidCrossUserPermissionMessage(
+ String message, boolean requireFullPermission) {
+ StringBuilder builder = new StringBuilder();
+ if (message != null) {
+ builder.append(message);
+ builder.append(": ");
+ }
+ builder.append("Requires ");
+ builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+ if (requireFullPermission) {
+ builder.append(".");
+ return builder.toString();
+ }
+ builder.append(" or ");
+ builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS);
+ builder.append(".");
+ return builder.toString();
+ }
+
+ private static String buildInvalidCrossUserOrProfilePermissionMessage(
+ String message, boolean requireFullPermission, boolean isSameProfileGroup) {
+ StringBuilder builder = new StringBuilder();
+ if (message != null) {
+ builder.append(message);
+ builder.append(": ");
+ }
+ builder.append("Requires ");
+ builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+ if (requireFullPermission) {
+ builder.append(".");
+ return builder.toString();
+ }
+ builder.append(" or ");
+ builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS);
+ if (isSameProfileGroup) {
+ builder.append(" or ");
+ builder.append(android.Manifest.permission.INTERACT_ACROSS_PROFILES);
+ }
+ builder.append(".");
+ return builder.toString();
}
@GuardedBy({"mSettings.mLock", "mLock"})
@@ -4215,6 +4345,17 @@
PermissionManagerService.this.enforceCrossUserPermission(callingUid, userId,
requireFullPermission, checkShell, requirePermissionWhenSameUser, message);
}
+
+ @Override
+ public void enforceCrossUserOrProfilePermission(int callingUid, int userId,
+ boolean requireFullPermission, boolean checkShell, String message) {
+ PermissionManagerService.this.enforceCrossUserOrProfilePermission(callingUid,
+ userId,
+ requireFullPermission,
+ checkShell,
+ message);
+ }
+
@Override
public void enforceGrantRevokeRuntimePermissionPermissions(String message) {
PermissionManagerService.this.enforceGrantRevokeRuntimePermissionPermissions(message);
@@ -4453,8 +4594,8 @@
@Override
public void onNewUserCreated(int userId) {
+ mDefaultPermissionGrantPolicy.grantDefaultPermissions(userId);
synchronized (mLock) {
- mDefaultPermissionGrantPolicy.grantDefaultPermissions(userId);
// NOTE: This adds UPDATE_PERMISSIONS_REPLACE_PKG
PermissionManagerService.this.updateAllPermissions(
StorageManager.UUID_PRIVATE_INTERNAL, true, mDefaultPermissionCallback);
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index 0f22619..58a9f42 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -271,6 +271,15 @@
*/
public abstract void enforceCrossUserPermission(int callingUid, int userId,
boolean requireFullPermission, boolean checkShell, @NonNull String message);
+
+ /**
+ * Similar to {@link #enforceCrossUserPermission(int, int, boolean, boolean, String)}
+ * but also allows INTERACT_ACROSS_PROFILES permission if calling user and {@code userId} are
+ * in the same profile group.
+ */
+ public abstract void enforceCrossUserOrProfilePermission(int callingUid, int userId,
+ boolean requireFullPermission, boolean checkShell, @NonNull String message);
+
/**
* @see #enforceCrossUserPermission(int, int, boolean, boolean, String)
* @param requirePermissionWhenSameUser When {@code true}, still require the cross user
diff --git a/services/core/java/com/android/server/policy/LegacyGlobalActions.java b/services/core/java/com/android/server/policy/LegacyGlobalActions.java
index 5271493..6daf516 100644
--- a/services/core/java/com/android/server/policy/LegacyGlobalActions.java
+++ b/services/core/java/com/android/server/policy/LegacyGlobalActions.java
@@ -743,8 +743,8 @@
} else if (TelephonyManager.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED.equals(action)) {
// Airplane mode can be changed after ECM exits if airplane toggle button
// is pressed during ECM mode
- if (!(intent.getBooleanExtra("PHONE_IN_ECM_STATE", false)) &&
- mIsWaitingForEcmExit) {
+ if (!(intent.getBooleanExtra(TelephonyManager.EXTRA_PHONE_IN_ECM_STATE, false))
+ && mIsWaitingForEcmExit) {
mIsWaitingForEcmExit = false;
changeAirplaneModeSystemSetting(true);
}
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index e7269a6..a86c8d7 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -58,6 +58,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IAppOpsCallback;
import com.android.internal.app.IAppOpsService;
+import com.android.internal.infra.AndroidFuture;
import com.android.internal.util.IntPair;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.FgThread;
@@ -68,7 +69,7 @@
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
/**
* This is a permission policy that governs over all permission mechanism
@@ -280,7 +281,7 @@
if (DEBUG) Slog.i(LOG_TAG, "defaultPermsWereGrantedSinceBoot(" + userId + ")");
// Now call into the permission controller to apply policy around permissions
- final CountDownLatch latch = new CountDownLatch(1);
+ final AndroidFuture<Boolean> future = new AndroidFuture<>();
// We need to create a local manager that does not schedule work on the main
// there as we are on the main thread and want to block until the work is
@@ -290,22 +291,22 @@
getUserContext(getContext(), UserHandle.of(userId)),
FgThread.getHandler());
permissionControllerManager.grantOrUpgradeDefaultRuntimePermissions(
- FgThread.getExecutor(),
- (Boolean success) -> {
- if (!success) {
+ FgThread.getExecutor(), successful -> {
+ if (successful) {
+ future.complete(null);
+ } else {
// We are in an undefined state now, let us crash and have
// rescue party suggest a wipe to recover to a good one.
- final String message = "Error granting/upgrading runtime permissions";
+ final String message = "Error granting/upgrading runtime permissions"
+ + " for user " + userId;
Slog.wtf(LOG_TAG, message);
- throw new IllegalStateException(message);
+ future.completeExceptionally(new IllegalStateException(message));
}
- latch.countDown();
- }
- );
+ });
try {
- latch.await();
- } catch (InterruptedException e) {
- /* ignore */
+ future.get();
+ } catch (InterruptedException | ExecutionException e) {
+ throw new IllegalStateException(e);
}
permissionControllerManager.updateUserSensitive();
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 858180b..ea83adb 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -562,10 +562,6 @@
private boolean mScreenshotChordPowerKeyTriggered;
private long mScreenshotChordPowerKeyTime;
- private static final long MOVING_DISPLAY_TO_TOP_DURATION_MILLIS = 10;
- private volatile boolean mMovingDisplayToTopKeyTriggered;
- private volatile long mMovingDisplayToTopKeyTime;
-
// Ringer toggle should reuse timing and triggering from screenshot power and a11y vol up
private int mRingerToggleChord = VOLUME_HUSH_OFF;
@@ -633,7 +629,6 @@
private static final int MSG_POWER_VERY_LONG_PRESS = 25;
private static final int MSG_NOTIFY_USER_ACTIVITY = 26;
private static final int MSG_RINGER_TOGGLE_CHORD = 27;
- private static final int MSG_MOVE_DISPLAY_TO_TOP = 28;
private class PolicyHandler extends Handler {
@Override
@@ -697,16 +692,7 @@
accessibilityShortcutActivated();
break;
case MSG_BUGREPORT_TV:
- boolean customBugreport = mContext.getResources().getBoolean(
- com.android.internal.R.bool.config_customBugreport);
- if (customBugreport) {
- Log.i(TAG, "Triggering a custom bugreport!");
- Intent intent = new Intent(ACTION_CUSTOM_BUGREPORT_REQUESTED);
- intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
- mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
- } else {
- requestFullBugreport();
- }
+ requestFullBugreportOrLaunchHandlerApp();
break;
case MSG_ACCESSIBILITY_TV:
if (mAccessibilityShortcutController.isAccessibilityShortcutAvailable(false)) {
@@ -732,10 +718,6 @@
case MSG_RINGER_TOGGLE_CHORD:
handleRingerChordGesture();
break;
- case MSG_MOVE_DISPLAY_TO_TOP:
- mWindowManagerFuncs.moveDisplayToTop(msg.arg1);
- mMovingDisplayToTopKeyTriggered = false;
- break;
}
}
}
@@ -2554,36 +2536,6 @@
@Override
public long interceptKeyBeforeDispatching(IBinder focusedToken, KeyEvent event,
int policyFlags) {
- final long result = interceptKeyBeforeDispatchingInner(focusedToken, event, policyFlags);
- final int eventDisplayId = event.getDisplayId();
- if (result == 0 && !mPerDisplayFocusEnabled
- && eventDisplayId != INVALID_DISPLAY && eventDisplayId != mTopFocusedDisplayId) {
- // An event is targeting a non-focused display. Try to move the display to top so that
- // it can become the focused display to interact with the user.
- final long eventDownTime = event.getDownTime();
- if (mMovingDisplayToTopKeyTime < eventDownTime) {
- // We have not handled this event yet. Move the display to top, and then tell
- // dispatcher to try again later.
- mMovingDisplayToTopKeyTime = eventDownTime;
- mMovingDisplayToTopKeyTriggered = true;
- mHandler.sendMessage(
- mHandler.obtainMessage(MSG_MOVE_DISPLAY_TO_TOP, eventDisplayId, 0));
- return MOVING_DISPLAY_TO_TOP_DURATION_MILLIS;
- } else if (mMovingDisplayToTopKeyTriggered) {
- // The message has not been handled yet. Tell dispatcher to try again later.
- return MOVING_DISPLAY_TO_TOP_DURATION_MILLIS;
- }
- // The target display is still not the top focused display. Drop the event because the
- // display may not contain any window which can receive keys.
- Slog.w(TAG, "Dropping key targeting non-focused display #" + eventDisplayId
- + " keyCode=" + KeyEvent.keyCodeToString(event.getKeyCode()));
- return -1;
- }
- return result;
- }
-
- private long interceptKeyBeforeDispatchingInner(IBinder focusedToken, KeyEvent event,
- int policyFlags) {
final boolean keyguardOn = keyguardOn();
final int keyCode = event.getKeyCode();
final int repeatCount = event.getRepeatCount();
@@ -3070,12 +3022,14 @@
return mAccessibilityTvScheduled;
}
- private void requestFullBugreport() {
+ private void requestFullBugreportOrLaunchHandlerApp() {
if ("1".equals(SystemProperties.get("ro.debuggable"))
|| Settings.Global.getInt(mContext.getContentResolver(),
Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) == 1) {
try {
- ActivityManager.getService().requestFullBugReport();
+ if (!ActivityManager.getService().launchBugReportHandlerApp()) {
+ ActivityManager.getService().requestFullBugReport();
+ }
} catch (RemoteException e) {
Slog.e(TAG, "Error taking bugreport", e);
}
@@ -3622,7 +3576,6 @@
final boolean canceled = event.isCanceled();
final int keyCode = event.getKeyCode();
final int displayId = event.getDisplayId();
-
final boolean isInjected = (policyFlags & WindowManagerPolicy.FLAG_INJECTED) != 0;
// If screen is off then we treat the case where the keyguard is open but hidden
@@ -4044,6 +3997,23 @@
PowerManager.WAKE_REASON_WAKE_KEY, "android.policy:KEY");
}
+ if ((result & ACTION_PASS_TO_USER) != 0) {
+ // If the key event is targeted to a specific display, then the user is interacting with
+ // that display. Therefore, give focus to the display that the user is interacting with.
+ if (!mPerDisplayFocusEnabled
+ && displayId != INVALID_DISPLAY && displayId != mTopFocusedDisplayId) {
+ // An event is targeting a non-focused display. Move the display to top so that
+ // it can become the focused display to interact with the user.
+ // This should be done asynchronously, once the focus logic is fully moved to input
+ // from windowmanager. Currently, we need to ensure the setInputWindows completes,
+ // which would force the focus event to be queued before the current key event.
+ // TODO(b/70668286): post call to 'moveDisplayToTop' to mHandler instead
+ Log.i(TAG, "Moving non-focused display " + displayId + " to top "
+ + "because a key is targeting it");
+ mWindowManagerFuncs.moveDisplayToTop(displayId);
+ }
+ }
+
return result;
}
@@ -5222,7 +5192,7 @@
final Intent dock = createHomeDockIntent();
if (dock != null) {
int result = ActivityTaskManager.getService()
- .startActivityAsUser(null, null, dock,
+ .startActivityAsUser(null, mContext.getBasePackageName(), dock,
dock.resolveTypeIfNeeded(mContext.getContentResolver()),
null, null, 0,
ActivityManager.START_FLAG_ONLY_IF_NEEDED,
@@ -5233,7 +5203,7 @@
}
}
int result = ActivityTaskManager.getService()
- .startActivityAsUser(null, null, mHomeIntent,
+ .startActivityAsUser(null, mContext.getBasePackageName(), mHomeIntent,
mHomeIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
null, null, 0,
ActivityManager.START_FLAG_ONLY_IF_NEEDED,
diff --git a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
index b0f22e4..f3a6018 100644
--- a/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
+++ b/services/core/java/com/android/server/policy/SoftRestrictedPermissionPolicy.java
@@ -145,7 +145,8 @@
}
@Override
public boolean mayAllowExtraAppOp() {
- return !shouldApplyRestriction && hasRequestedLegacyExternalStorage;
+ return !shouldApplyRestriction && hasRequestedLegacyExternalStorage
+ && targetSDK <= Build.VERSION_CODES.Q;
}
@Override
public boolean mayDenyExtraAppOpIfGranted() {
diff --git a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
index a62bb74..4b3746b 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverStateMachine.java
@@ -45,7 +45,6 @@
import com.android.server.power.BatterySaverStateMachineProto;
import java.io.PrintWriter;
-import java.text.NumberFormat;
/**
* Decides when to enable / disable battery saver.
@@ -796,8 +795,7 @@
manager.notifyAsUser(TAG, DYNAMIC_MODE_NOTIFICATION_ID,
buildNotification(DYNAMIC_MODE_NOTIF_CHANNEL_ID,
- mContext.getResources().getString(
- R.string.dynamic_mode_notification_title),
+ R.string.dynamic_mode_notification_title,
R.string.dynamic_mode_notification_summary,
Intent.ACTION_POWER_USAGE_SUMMARY),
UserHandle.ALL);
@@ -813,13 +811,10 @@
ensureNotificationChannelExists(manager, BATTERY_SAVER_NOTIF_CHANNEL_ID,
R.string.battery_saver_notification_channel_name);
- final String percentage = NumberFormat.getPercentInstance()
- .format((double) mBatteryLevel / 100.0);
manager.notifyAsUser(TAG, STICKY_AUTO_DISABLED_NOTIFICATION_ID,
buildNotification(BATTERY_SAVER_NOTIF_CHANNEL_ID,
- mContext.getResources().getString(
- R.string.battery_saver_charged_notification_title, percentage),
- R.string.battery_saver_off_notification_summary,
+ R.string.battery_saver_off_notification_title,
+ R.string.battery_saver_charged_notification_summary,
Settings.ACTION_BATTERY_SAVER_SETTINGS),
UserHandle.ALL);
});
@@ -834,13 +829,14 @@
manager.createNotificationChannel(channel);
}
- private Notification buildNotification(@NonNull String channelId, @NonNull String title,
+ private Notification buildNotification(@NonNull String channelId, @StringRes int titleId,
@StringRes int summaryId, @NonNull String intentAction) {
Resources res = mContext.getResources();
Intent intent = new Intent(intentAction);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
PendingIntent batterySaverIntent = PendingIntent.getActivity(
mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ final String title = res.getString(titleId);
final String summary = res.getString(summaryId);
return new Notification.Builder(mContext, channelId)
diff --git a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
index fdb14be..c36d5ef 100644
--- a/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
+++ b/services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
@@ -17,8 +17,10 @@
package com.android.server.recoverysystem;
import android.content.Context;
+import android.content.IntentSender;
import android.net.LocalSocket;
import android.net.LocalSocketAddress;
+import android.os.Binder;
import android.os.IRecoverySystem;
import android.os.IRecoverySystemProgressListener;
import android.os.PowerManager;
@@ -28,6 +30,9 @@
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.widget.LockSettingsInternal;
+import com.android.internal.widget.RebootEscrowListener;
+import com.android.server.LocalServices;
import com.android.server.SystemService;
import libcore.io.IoUtils;
@@ -45,7 +50,7 @@
* triggers /system/bin/uncrypt via init to de-encrypt an OTA package on the
* /data partition so that it can be accessed under the recovery image.
*/
-public class RecoverySystemService extends IRecoverySystem.Stub {
+public class RecoverySystemService extends IRecoverySystem.Stub implements RebootEscrowListener {
private static final String TAG = "RecoverySystemService";
private static final boolean DEBUG = false;
@@ -67,6 +72,10 @@
private final Injector mInjector;
private final Context mContext;
+ private boolean mPreparedForReboot;
+ private String mUnattendedRebootToken;
+ private IntentSender mPreparedForRebootIntentSender;
+
static class Injector {
protected final Context mContext;
@@ -78,6 +87,10 @@
return mContext;
}
+ public LockSettingsInternal getLockSettingsService() {
+ return LocalServices.getService(LockSettingsInternal.class);
+ }
+
public PowerManager getPowerManager() {
return (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
}
@@ -120,14 +133,23 @@
* Handles the lifecycle events for the RecoverySystemService.
*/
public static final class Lifecycle extends SystemService {
+ private RecoverySystemService mRecoverySystemService;
+
public Lifecycle(Context context) {
super(context);
}
@Override
+ public void onBootPhase(int phase) {
+ if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+ mRecoverySystemService.onSystemServicesReady();
+ }
+ }
+
+ @Override
public void onStart() {
- RecoverySystemService recoverySystemService = new RecoverySystemService(getContext());
- publishBinderService(Context.RECOVERY_SERVICE, recoverySystemService);
+ mRecoverySystemService = new RecoverySystemService(getContext());
+ publishBinderService(Context.RECOVERY_SERVICE, mRecoverySystemService);
}
}
@@ -141,6 +163,11 @@
mContext = injector.getContext();
}
+ @VisibleForTesting
+ void onSystemServicesReady() {
+ mInjector.getLockSettingsService().setRebootEscrowListener(this);
+ }
+
@Override // Binder call
public boolean uncrypt(String filename, IRecoverySystemProgressListener listener) {
if (DEBUG) Slog.d(TAG, "uncrypt: " + filename);
@@ -255,6 +282,95 @@
}
}
+ @Override // Binder call
+ public boolean requestLskf(String updateToken, IntentSender intentSender) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
+
+ if (updateToken == null) {
+ return false;
+ }
+
+ // No need to prepare again for the same token.
+ if (mPreparedForReboot && updateToken.equals(mUnattendedRebootToken)) {
+ return true;
+ }
+
+ mPreparedForReboot = false;
+ mUnattendedRebootToken = updateToken;
+ mPreparedForRebootIntentSender = intentSender;
+
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ mInjector.getLockSettingsService().prepareRebootEscrow();
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+
+ return true;
+ }
+
+ @Override
+ public void onPreparedForReboot(boolean ready) {
+ if (mUnattendedRebootToken == null) {
+ Slog.w(TAG, "onPreparedForReboot called when mUnattendedRebootToken is null");
+ }
+
+ mPreparedForReboot = ready;
+ if (ready) {
+ sendPreparedForRebootIntentIfNeeded();
+ }
+ }
+
+ private void sendPreparedForRebootIntentIfNeeded() {
+ final IntentSender intentSender = mPreparedForRebootIntentSender;
+ if (intentSender != null) {
+ try {
+ intentSender.sendIntent(null, 0, null, null, null);
+ } catch (IntentSender.SendIntentException e) {
+ Slog.w(TAG, "Could not send intent for prepared reboot: " + e.getMessage());
+ }
+ }
+ }
+
+ @Override // Binder call
+ public boolean clearLskf() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
+
+ mPreparedForReboot = false;
+ mUnattendedRebootToken = null;
+ mPreparedForRebootIntentSender = null;
+
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ mInjector.getLockSettingsService().clearRebootEscrow();
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+
+ return true;
+ }
+
+ @Override // Binder call
+ public boolean rebootWithLskf(String updateToken, String reason) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.RECOVERY, null);
+
+ if (!mPreparedForReboot) {
+ return false;
+ }
+
+ if (updateToken != null && updateToken.equals(mUnattendedRebootToken)) {
+ if (!mInjector.getLockSettingsService().armRebootEscrow()) {
+ return false;
+ }
+
+ PowerManager pm = mInjector.getPowerManager();
+ pm.reboot(reason);
+ return true;
+ }
+
+ return false;
+ }
+
/**
* Check if any of the init services is still running. If so, we cannot
* start a new uncrypt/setup-bcb/clear-bcb service right away; otherwise
diff --git a/services/core/java/com/android/server/rollback/Rollback.java b/services/core/java/com/android/server/rollback/Rollback.java
index 88c1564..5c0dd9a 100644
--- a/services/core/java/com/android/server/rollback/Rollback.java
+++ b/services/core/java/com/android/server/rollback/Rollback.java
@@ -62,7 +62,7 @@
private static final String TAG = "RollbackManager";
- @IntDef(flag = true, prefix = { "ROLLBACK_STATE_" }, value = {
+ @IntDef(prefix = { "ROLLBACK_STATE_" }, value = {
ROLLBACK_STATE_ENABLING,
ROLLBACK_STATE_AVAILABLE,
ROLLBACK_STATE_COMMITTED,
@@ -92,6 +92,19 @@
*/
static final int ROLLBACK_STATE_DELETED = 4;
+ @IntDef(flag = true, prefix = { "MATCH_" }, value = {
+ MATCH_APK_IN_APEX,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface RollbackInfoFlags {}
+
+ /**
+ * {@link RollbackInfo} flag: include {@code RollbackInfo} packages that are apk-in-apex.
+ * These packages do not have their own sessions. They are embedded in an apex which has a
+ * session id.
+ */
+ static final int MATCH_APK_IN_APEX = 1;
+
/**
* The session ID for the staged session if this rollback data represents a staged session,
* {@code -1} otherwise.
@@ -159,6 +172,13 @@
@Nullable public final String mInstallerPackageName;
/**
+ * This array holds all of the rollback tokens associated with package sessions included in
+ * this rollback.
+ */
+ @GuardedBy("mLock")
+ private final IntArray mTokens = new IntArray();
+
+ /**
* Constructs a new, empty Rollback instance.
*
* @param rollbackId the id of the rollback.
@@ -323,8 +343,8 @@
new VersionedPackage(packageName, newVersion),
new VersionedPackage(packageName, installedVersion),
new IntArray() /* pendingBackups */, new ArrayList<>() /* pendingRestores */,
- isApex, new IntArray(), new SparseLongArray() /* ceSnapshotInodes */,
- rollbackDataPolicy);
+ isApex, false /* isApkInApex */, new IntArray(),
+ new SparseLongArray() /* ceSnapshotInodes */, rollbackDataPolicy);
synchronized (mLock) {
info.getPackages().add(packageRollbackInfo);
@@ -334,6 +354,30 @@
}
/**
+ * Enables this rollback for the provided apk-in-apex.
+ *
+ * @return boolean True if the rollback was enabled successfully for the specified package.
+ */
+ boolean enableForPackageInApex(String packageName, long installedVersion,
+ int rollbackDataPolicy) {
+ // TODO(b/147666157): Extract the new version number of apk-in-apex
+ // The new version for the apk-in-apex is set to 0 for now. If the package is then further
+ // updated via non-staged install flow, then RollbackManagerServiceImpl#onPackageReplaced()
+ // will be called and this rollback will be deleted. Other ways of package update have not
+ // been handled yet.
+ PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(
+ new VersionedPackage(packageName, 0 /* newVersion */),
+ new VersionedPackage(packageName, installedVersion),
+ new IntArray() /* pendingBackups */, new ArrayList<>() /* pendingRestores */,
+ false /* isApex */, true /* isApkInApex */, new IntArray(),
+ new SparseLongArray() /* ceSnapshotInodes */, rollbackDataPolicy);
+ synchronized (mLock) {
+ info.getPackages().add(packageRollbackInfo);
+ }
+ return true;
+ }
+
+ /**
* Snapshots user data for the provided package and user ids. Does nothing if this rollback is
* not in the ENABLING state.
*/
@@ -428,6 +472,11 @@
parentSessionId);
for (PackageRollbackInfo pkgRollbackInfo : info.getPackages()) {
+ if (pkgRollbackInfo.isApkInApex()) {
+ // No need to issue a downgrade install request for apk-in-apex. It will
+ // be rolled back when its parent apex is downgraded.
+ continue;
+ }
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
PackageInstaller.SessionParams.MODE_FULL_INSTALL);
String installerPackageName = mInstallerPackageName;
@@ -453,7 +502,8 @@
this, pkgRollbackInfo.getPackageName());
if (packageCodePaths == null) {
sendFailure(context, statusReceiver, RollbackManager.STATUS_FAILURE,
- "Backup copy of package inaccessible");
+ "Backup copy of package: "
+ + pkgRollbackInfo.getPackageName() + " is inaccessible");
return;
}
@@ -696,9 +746,50 @@
}
}
- int getPackageCount() {
+ /**
+ * Returns the number of {@link PackageRollbackInfo} we are storing in this {@link Rollback}
+ * instance. By default, this method does not include apk-in-apex package in the count.
+ *
+ * @param flags Apk-in-apex packages can be included in the count by passing
+ * {@link Rollback#MATCH_APK_IN_APEX}
+ *
+ * @return Counts number of {@link PackageRollbackInfo} stored in the {@link Rollback}
+ * according to {@code flags} passed
+ */
+ int getPackageCount(@RollbackInfoFlags int flags) {
synchronized (mLock) {
- return info.getPackages().size();
+ List<PackageRollbackInfo> packages = info.getPackages();
+ if ((flags & MATCH_APK_IN_APEX) != 0) {
+ return packages.size();
+ }
+
+ int packagesWithoutApkInApex = 0;
+ for (PackageRollbackInfo rollbackInfo : packages) {
+ if (!rollbackInfo.isApkInApex()) {
+ packagesWithoutApkInApex++;
+ }
+ }
+ return packagesWithoutApkInApex;
+ }
+ }
+
+ /**
+ * Adds a rollback token to be associated with this rollback. This may be used to
+ * identify which rollback should be removed in case {@link PackageManager} sends an
+ * {@link Intent#ACTION_CANCEL_ENABLE_ROLLBACK} intent.
+ */
+ void addToken(int token) {
+ synchronized (mLock) {
+ mTokens.add(token);
+ }
+ }
+
+ /**
+ * Returns true if this rollback is associated with the provided {@code token}.
+ */
+ boolean hasToken(int token) {
+ synchronized (mLock) {
+ return mTokens.indexOf(token) != -1;
}
}
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index ae3f368..de48939 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -17,8 +17,10 @@
package com.android.server.rollback;
import android.Manifest;
+import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
+import android.annotation.WorkerThread;
import android.app.AppOpsManager;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -41,6 +43,7 @@
import android.os.Binder;
import android.os.Environment;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.HandlerThread;
import android.os.Process;
import android.os.SystemClock;
@@ -76,11 +79,17 @@
import java.util.Random;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
* Implementation of service that manages APK level rollbacks.
+ *
+ * Threading model:
+ *
+ * - @AnyThread annotates thread-safe methods.
+ * - @WorkerThread annotates methods that should be called from the handler thread only.
*/
class RollbackManagerServiceImpl extends IRollbackManager.Stub {
@@ -127,6 +136,7 @@
private final Context mContext;
private final HandlerThread mHandlerThread;
+ private final Executor mExecutor;
private final Installer mInstaller;
private final RollbackPackageHealthObserver mPackageHealthObserver;
private final AppDataRollbackHelper mAppDataRollbackHelper;
@@ -166,6 +176,7 @@
mHandlerThread = new HandlerThread("RollbackManagerServiceHandler");
mHandlerThread.start();
Watchdog.getInstance().addThread(getHandler(), HANDLER_THREAD_TIMEOUT_DURATION_MILLIS);
+ mExecutor = new HandlerExecutor(getHandler());
for (UserInfo userInfo : UserManager.get(mContext).getUsers(true)) {
registerUserCallbacks(userInfo.getUserHandle());
@@ -228,12 +239,17 @@
Slog.v(TAG, "broadcast=ACTION_CANCEL_ENABLE_ROLLBACK token=" + token);
}
synchronized (mLock) {
- for (NewRollback rollback : mNewRollbacks) {
- if (rollback.hasToken(token)) {
- rollback.setCancelled();
- return;
+ NewRollback found = null;
+ for (NewRollback newRollback : mNewRollbacks) {
+ if (newRollback.rollback.hasToken(token)) {
+ found = newRollback;
+ break;
}
}
+ if (found != null) {
+ mNewRollbacks.remove(found);
+ found.rollback.delete(mAppDataRollbackHelper);
+ }
}
}
}
@@ -256,6 +272,7 @@
registerTimeChangeReceiver();
}
+ @AnyThread
private void registerUserCallbacks(UserHandle user) {
Context context = getContextAsUser(user);
if (context == null) {
@@ -338,6 +355,7 @@
callerPackageName, statusReceiver));
}
+ @AnyThread
private void registerTimeChangeReceiver() {
final BroadcastReceiver timeChangeIntentReceiver = new BroadcastReceiver() {
@Override
@@ -362,6 +380,7 @@
null /* broadcastPermission */, getHandler());
}
+ @AnyThread
private static long calculateRelativeBootTime() {
return System.currentTimeMillis() - SystemClock.elapsedRealtime();
}
@@ -371,6 +390,7 @@
* The work is done on the current thread. This may be a long running
* operation.
*/
+ @WorkerThread
private void commitRollbackInternal(int rollbackId, List<VersionedPackage> causePackages,
String callerPackageName, IntentSender statusReceiver) {
Slog.i(TAG, "commitRollback id=" + rollbackId + " caller=" + callerPackageName);
@@ -393,7 +413,6 @@
CountDownLatch latch = new CountDownLatch(1);
getHandler().post(() -> {
- updateRollbackLifetimeDurationInMillis();
synchronized (mLock) {
mRollbacks.clear();
mRollbacks.addAll(mRollbackStore.loadRollbacks());
@@ -422,10 +441,14 @@
rollback.delete(mAppDataRollbackHelper);
}
}
- for (NewRollback newRollback : mNewRollbacks) {
+ Iterator<NewRollback> iter2 = mNewRollbacks.iterator();
+ while (iter2.hasNext()) {
+ NewRollback newRollback = iter2.next();
if (newRollback.rollback.includesPackage(packageName)) {
- newRollback.setCancelled();
+ iter2.remove();
+ newRollback.rollback.delete(mAppDataRollbackHelper);
}
+
}
}
}
@@ -440,6 +463,7 @@
});
}
+ @WorkerThread
private void queueSleepIfNeeded() {
if (mSleepDuration.size() == 0) {
return;
@@ -486,6 +510,7 @@
}
}
+ @WorkerThread
private void updateRollbackLifetimeDurationInMillis() {
mRollbackLifetimeDurationInMillis = DeviceConfig.getLong(
DeviceConfig.NAMESPACE_ROLLBACK_BOOT,
@@ -496,12 +521,15 @@
}
}
+ @AnyThread
void onBootCompleted() {
- getHandler().post(() -> updateRollbackLifetimeDurationInMillis());
- // Also posts to handler thread
- scheduleExpiration(0);
+ DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_ROLLBACK_BOOT,
+ mExecutor, properties -> updateRollbackLifetimeDurationInMillis());
getHandler().post(() -> {
+ updateRollbackLifetimeDurationInMillis();
+ runExpiration();
+
// Check to see if any rollback-enabled staged sessions or staged
// rollback sessions been applied.
List<Rollback> enabling = new ArrayList<>();
@@ -564,6 +592,7 @@
* Removes all backups for the package not matching the currently
* installed package version.
*/
+ @WorkerThread
private void onPackageReplaced(String packageName) {
// TODO: Could this end up incorrectly deleting a rollback for a
// package that is about to be installed?
@@ -588,6 +617,7 @@
* Called when a package has been completely removed from the device.
* Removes all backups and rollback history for the given package.
*/
+ @WorkerThread
private void onPackageFullyRemoved(String packageName) {
expireRollbackForPackage(packageName);
}
@@ -599,6 +629,7 @@
* @param status the RollbackManager.STATUS_* code with the failure.
* @param message the failure message.
*/
+ @AnyThread
static void sendFailure(Context context, IntentSender statusReceiver,
@RollbackManager.Status int status, String message) {
Slog.e(TAG, message);
@@ -614,6 +645,7 @@
// Check to see if anything needs expiration, and if so, expire it.
// Schedules future expiration as appropriate.
+ @WorkerThread
private void runExpiration() {
Instant now = Instant.now();
Instant oldest = null;
@@ -649,10 +681,12 @@
* Schedules an expiration check to be run after the given duration in
* milliseconds has gone by.
*/
+ @AnyThread
private void scheduleExpiration(long duration) {
getHandler().postDelayed(() -> runExpiration(), duration);
}
+ @AnyThread
private Handler getHandler() {
return mHandlerThread.getThreadHandler();
}
@@ -660,6 +694,7 @@
// Returns true if <code>session</code> has installFlags and code path
// matching the installFlags and new package code path given to
// enableRollback.
+ @WorkerThread
private boolean sessionMatchesForEnableRollback(PackageInstaller.SessionInfo session,
int installFlags, File newPackageCodePath) {
if (session == null || session.resolvedBaseCodePath == null) {
@@ -674,6 +709,7 @@
return false;
}
+ @AnyThread
private Context getContextAsUser(UserHandle user) {
try {
return mContext.createPackageContextAsUser(mContext.getPackageName(), 0, user);
@@ -693,6 +729,7 @@
* @param token the distinct rollback token sent by package manager.
* @return true if enabling the rollback succeeds, false otherwise.
*/
+ @WorkerThread
private boolean enableRollback(
int installFlags, File newPackageCodePath, @UserIdInt int user, int token) {
if (LOCAL_LOGV) {
@@ -775,11 +812,12 @@
mNewRollbacks.add(newRollback);
}
}
- newRollback.addToken(token);
+ newRollback.rollback.addToken(token);
return enableRollbackForPackageSession(newRollback.rollback, packageSession);
}
+ @WorkerThread
private void removeRollbackForPackageSessionId(int sessionId) {
if (LOCAL_LOGV) {
Slog.v(TAG, "removeRollbackForPackageSessionId=" + sessionId);
@@ -814,6 +852,7 @@
*
* @return true on success, false on failure.
*/
+ @WorkerThread
private boolean enableRollbackForPackageSession(Rollback rollback,
PackageInstaller.SessionInfo session) {
// TODO: Don't attempt to enable rollback for split installs.
@@ -866,9 +905,36 @@
}
ApplicationInfo appInfo = pkgInfo.applicationInfo;
- return rollback.enableForPackage(packageName, newPackage.versionCode,
+ boolean success = rollback.enableForPackage(packageName, newPackage.versionCode,
pkgInfo.getLongVersionCode(), isApex, appInfo.sourceDir,
appInfo.splitSourceDirs, session.rollbackDataPolicy);
+ if (!success) {
+ return success;
+ }
+
+ if (isApex) {
+ // Check if this apex contains apks inside it. If true, then they should be added as
+ // a RollbackPackageInfo into this rollback
+ final PackageManagerInternal pmi = LocalServices.getService(
+ PackageManagerInternal.class);
+ List<String> apksInApex = pmi.getApksInApex(packageName);
+ for (String apkInApex : apksInApex) {
+ // Get information about the currently installed package.
+ final PackageInfo apkPkgInfo;
+ try {
+ apkPkgInfo = getPackageInfo(apkInApex);
+ } catch (PackageManager.NameNotFoundException e) {
+ // TODO: Support rolling back fresh package installs rather than
+ // fail here. Test this case.
+ Slog.e(TAG, apkInApex + " is not installed");
+ return false;
+ }
+ success = rollback.enableForPackageInApex(
+ apkInApex, apkPkgInfo.getLongVersionCode(), session.rollbackDataPolicy);
+ if (!success) return success;
+ }
+ }
+ return true;
}
@Override
@@ -882,12 +948,17 @@
getHandler().post(() -> {
snapshotUserDataInternal(packageName, userIds);
restoreUserDataInternal(packageName, userIds, appId, seInfo);
- final PackageManagerInternal pmi = LocalServices.getService(
- PackageManagerInternal.class);
- pmi.finishPackageInstall(token, false);
+ // When this method is called as part of the install flow, a positive token number is
+ // passed to it. Need to notify the PackageManager when we are done.
+ if (token > 0) {
+ final PackageManagerInternal pmi = LocalServices.getService(
+ PackageManagerInternal.class);
+ pmi.finishPackageInstall(token, false);
+ }
});
}
+ @WorkerThread
private void snapshotUserDataInternal(String packageName, int[] userIds) {
if (LOCAL_LOGV) {
Slog.v(TAG, "snapshotUserData pkg=" + packageName
@@ -907,6 +978,7 @@
}
}
+ @WorkerThread
private void restoreUserDataInternal(
String packageName, int[] userIds, int appId, String seInfo) {
if (LOCAL_LOGV) {
@@ -1021,6 +1093,7 @@
* Returns true if the installer is allowed to enable rollback for the
* given named package, false otherwise.
*/
+ @AnyThread
private boolean enableRollbackAllowed(String installerPackageName, String packageName) {
if (installerPackageName == null) {
return false;
@@ -1043,6 +1116,7 @@
/**
* Returns true is this package is eligible for enabling rollback.
*/
+ @AnyThread
private boolean isRollbackWhitelisted(String packageName) {
// TODO: Remove #isModule when the white list is ready.
return SystemConfig.getInstance().getRollbackWhitelistedPackages().contains(packageName)
@@ -1051,6 +1125,7 @@
/**
* Returns true if the package name is the name of a module.
*/
+ @AnyThread
private boolean isModule(String packageName) {
PackageManager pm = mContext.getPackageManager();
final ModuleInfo moduleInfo;
@@ -1067,6 +1142,7 @@
* Gets the version of the package currently installed.
* Returns -1 if the package is not currently installed.
*/
+ @AnyThread
private long getInstalledPackageVersion(String packageName) {
PackageInfo pkgInfo;
try {
@@ -1083,6 +1159,7 @@
*
* @throws PackageManager.NameNotFoundException if no such package is installed.
*/
+ @AnyThread
private PackageInfo getPackageInfo(String packageName)
throws PackageManager.NameNotFoundException {
PackageManager pm = mContext.getPackageManager();
@@ -1097,6 +1174,7 @@
}
}
+ @WorkerThread
private class SessionCallback extends PackageInstaller.SessionCallback {
@Override
@@ -1149,19 +1227,18 @@
* @return the Rollback instance for a successfully enable-completed rollback,
* or null on error.
*/
+ @WorkerThread
private Rollback completeEnableRollback(NewRollback newRollback) {
Rollback rollback = newRollback.rollback;
if (LOCAL_LOGV) {
Slog.v(TAG, "completeEnableRollback id=" + rollback.info.getRollbackId());
}
- if (newRollback.isCancelled()) {
- Slog.e(TAG, "Rollback has been cancelled by PackageManager");
- rollback.delete(mAppDataRollbackHelper);
- return null;
- }
-
- if (rollback.getPackageCount() != newRollback.getPackageSessionIdCount()) {
+ // We are checking if number of packages (excluding apk-in-apex) we enabled for rollback is
+ // equal to the number of sessions we are installing, to ensure we didn't skip enabling
+ // of any sessions. If we successfully enable an apex, then we can assume we enabled
+ // rollback for the embedded apk-in-apex, if any.
+ if (rollback.getPackageCount(0 /*flags*/) != newRollback.getPackageSessionIdCount()) {
Slog.e(TAG, "Failed to enable rollback for all packages in session.");
rollback.delete(mAppDataRollbackHelper);
return null;
@@ -1185,6 +1262,7 @@
return rollback;
}
+ @WorkerThread
@GuardedBy("rollback.getLock")
private void makeRollbackAvailable(Rollback rollback) {
if (LOCAL_LOGV) {
@@ -1205,6 +1283,7 @@
/*
* Returns the rollback with the given rollbackId, if any.
*/
+ @WorkerThread
private Rollback getRollbackForId(int rollbackId) {
synchronized (mLock) {
for (int i = 0; i < mRollbacks.size(); ++i) {
@@ -1218,6 +1297,7 @@
return null;
}
+ @WorkerThread
@GuardedBy("mLock")
private int allocateRollbackIdLocked() {
int n = 0;
@@ -1247,6 +1327,7 @@
}
}
+ @AnyThread
private void enforceManageRollbacks(@NonNull String message) {
if ((PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission(
Manifest.permission.MANAGE_ROLLBACKS))
@@ -1262,22 +1343,12 @@
public final Rollback rollback;
/**
- * This array holds all of the rollback tokens associated with package sessions included in
- * this rollback.
- */
- @GuardedBy("mNewRollbackLock")
- private final IntArray mTokens = new IntArray();
-
- /**
* Session ids for all packages in the install. For multi-package sessions, this is the list
* of child session ids. For normal sessions, this list is a single element with the normal
* session id.
*/
private final int[] mPackageSessionIds;
- @GuardedBy("mNewRollbackLock")
- private boolean mIsCancelled = false;
-
/**
* The number of sessions in the install which are notified with success by
* {@link PackageInstaller.SessionCallback#onFinished(int, boolean)}.
@@ -1294,52 +1365,6 @@
}
/**
- * Adds a rollback token to be associated with this NewRollback. This may be used to
- * identify which rollback should be cancelled in case {@link PackageManager} sends an
- * {@link Intent#ACTION_CANCEL_ENABLE_ROLLBACK} intent.
- */
- void addToken(int token) {
- synchronized (mNewRollbackLock) {
- mTokens.add(token);
- }
- }
-
- /**
- * Returns true if this NewRollback is associated with the provided {@code token}.
- */
- boolean hasToken(int token) {
- synchronized (mNewRollbackLock) {
- return mTokens.indexOf(token) != -1;
- }
- }
-
- /**
- * Returns true if this NewRollback has been cancelled.
- *
- * <p>Rollback could be invalidated and cancelled if RollbackManager receives
- * {@link Intent#ACTION_CANCEL_ENABLE_ROLLBACK} from {@link PackageManager}.
- *
- * <p>The main underlying assumption here is that if enabling the rollback times out, then
- * {@link PackageManager} will NOT send
- * {@link PackageInstaller.SessionCallback#onFinished(int, boolean)} before it broadcasts
- * {@link Intent#ACTION_CANCEL_ENABLE_ROLLBACK}.
- */
- boolean isCancelled() {
- synchronized (mNewRollbackLock) {
- return mIsCancelled;
- }
- }
-
- /**
- * Sets this NewRollback to be marked as cancelled.
- */
- void setCancelled() {
- synchronized (mNewRollbackLock) {
- mIsCancelled = true;
- }
- }
-
- /**
* Returns true if this NewRollback contains the provided {@code packageSessionId}.
*/
boolean containsSessionId(int packageSessionId) {
@@ -1370,6 +1395,7 @@
}
}
+ @WorkerThread
@GuardedBy("mLock")
private NewRollback createNewRollbackLocked(PackageInstaller.SessionInfo parentSession) {
int rollbackId = allocateRollbackIdLocked();
@@ -1411,6 +1437,7 @@
* Returns null if no NewRollback is found for the given package
* session.
*/
+ @WorkerThread
@GuardedBy("mLock")
NewRollback getNewRollbackForPackageSessionLocked(int packageSessionId) {
// We expect mNewRollbacks to be a very small list; linear search
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index a0ef8cf..6686de9 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -179,10 +179,10 @@
// Use the version of the metadata package that was installed before
// we rolled back for logging purposes.
- VersionedPackage oldModuleMetadataPackage = null;
+ VersionedPackage oldLogPackage = null;
for (PackageRollbackInfo packageRollback : rollback.getPackages()) {
if (packageRollback.getPackageName().equals(moduleMetadataPackageName)) {
- oldModuleMetadataPackage = packageRollback.getVersionRolledBackFrom();
+ oldLogPackage = packageRollback.getVersionRolledBackFrom();
break;
}
}
@@ -194,13 +194,13 @@
return;
}
if (sessionInfo.isStagedSessionApplied()) {
- logEvent(oldModuleMetadataPackage,
+ logEvent(oldLogPackage,
StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_SUCCESS,
WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN, "");
} else if (sessionInfo.isStagedSessionReady()) {
// TODO: What do for staged session ready but not applied
} else {
- logEvent(oldModuleMetadataPackage,
+ logEvent(oldLogPackage,
StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN, "");
}
@@ -213,6 +213,23 @@
if (packageRollback.getVersionRolledBackFrom().equals(failedPackage)) {
return rollback;
}
+ // TODO(b/147666157): Extract version number of apk-in-apex so that we don't have
+ // to rely on complicated reasoning as below
+
+ // Due to b/147666157, for apk in apex, we do not know the version we are rolling
+ // back from. But if a package X is embedded in apex A exclusively (not embedded in
+ // any other apex), which is not guaranteed, then it is sufficient to check only
+ // package names here, as the version of failedPackage and the PackageRollbackInfo
+ // can't be different. If failedPackage has a higher version, then it must have
+ // been updated somehow. There are two ways: it was updated by an update of apex A
+ // or updated directly as apk. In both cases, this rollback would have gotten
+ // expired when onPackageReplaced() was called. Since the rollback exists, it has
+ // same version as failedPackage.
+ if (packageRollback.isApkInApex()
+ && packageRollback.getVersionRolledBackFrom().getPackageName()
+ .equals(failedPackage.getPackageName())) {
+ return rollback;
+ }
}
}
return null;
@@ -245,12 +262,12 @@
}
private BroadcastReceiver listenForStagedSessionReady(RollbackManager rollbackManager,
- int rollbackId, @Nullable VersionedPackage moduleMetadataPackage) {
+ int rollbackId, @Nullable VersionedPackage logPackage) {
BroadcastReceiver sessionUpdatedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
handleStagedSessionChange(rollbackManager,
- rollbackId, this /* BroadcastReceiver */, moduleMetadataPackage);
+ rollbackId, this /* BroadcastReceiver */, logPackage);
}
};
IntentFilter sessionUpdatedFilter =
@@ -260,7 +277,7 @@
}
private void handleStagedSessionChange(RollbackManager rollbackManager, int rollbackId,
- BroadcastReceiver listener, @Nullable VersionedPackage moduleMetadataPackage) {
+ BroadcastReceiver listener, @Nullable VersionedPackage logPackage) {
PackageInstaller packageInstaller =
mContext.getPackageManager().getPackageInstaller();
List<RollbackInfo> recentRollbacks =
@@ -274,16 +291,19 @@
packageInstaller.getSessionInfo(sessionId);
if (sessionInfo.isStagedSessionReady() && markStagedSessionHandled(rollbackId)) {
mContext.unregisterReceiver(listener);
- saveLastStagedRollbackId(rollbackId);
- logEvent(moduleMetadataPackage,
+ if (logPackage != null) {
+ // We save the rollback id so that after reboot, we can log if rollback was
+ // successful or not. If logPackage is null, then there is nothing to log.
+ saveLastStagedRollbackId(rollbackId);
+ }
+ logEvent(logPackage,
StatsLog
.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_BOOT_TRIGGERED,
WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN,
"");
- mContext.getSystemService(PowerManager.class).reboot("Rollback staged install");
} else if (sessionInfo.isStagedSessionFailed()
&& markStagedSessionHandled(rollbackId)) {
- logEvent(moduleMetadataPackage,
+ logEvent(logPackage,
StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_REASON__REASON_UNKNOWN,
"");
@@ -291,6 +311,11 @@
}
}
}
+
+ // Wait for all pending staged sessions to get handled before rebooting.
+ if (isPendingStagedSessionsEmpty()) {
+ mContext.getSystemService(PowerManager.class).reboot("Rollback staged install");
+ }
}
/**
@@ -303,6 +328,16 @@
}
}
+ /**
+ * Returns {@code true} if all pending staged rollback sessions were marked as handled,
+ * {@code false} if there is any left.
+ */
+ private boolean isPendingStagedSessionsEmpty() {
+ synchronized (mPendingStagedRollbackIds) {
+ return mPendingStagedRollbackIds.isEmpty();
+ }
+ }
+
private void saveLastStagedRollbackId(int stagedRollbackId) {
try {
FileOutputStream fos = new FileOutputStream(mLastStagedRollbackIdFile);
@@ -348,12 +383,12 @@
}
}
- private static void logEvent(@Nullable VersionedPackage moduleMetadataPackage, int type,
+ private static void logEvent(@Nullable VersionedPackage logPackage, int type,
int rollbackReason, @NonNull String failingPackageName) {
Slog.i(TAG, "Watchdog event occurred of type: " + rollbackTypeToString(type));
- if (moduleMetadataPackage != null) {
- StatsLog.logWatchdogRollbackOccurred(type, moduleMetadataPackage.getPackageName(),
- moduleMetadataPackage.getVersionCode(), rollbackReason, failingPackageName);
+ if (logPackage != null) {
+ StatsLog.logWatchdogRollbackOccurred(type, logPackage.getPackageName(),
+ logPackage.getVersionCode(), rollbackReason, failingPackageName);
}
}
@@ -414,6 +449,9 @@
reasonToLog, failedPackageToLog);
}
} else {
+ if (rollback.isStaged()) {
+ markStagedSessionHandled(rollback.getRollbackId());
+ }
logEvent(logPackage,
StatsLog.WATCHDOG_ROLLBACK_OCCURRED__ROLLBACK_TYPE__ROLLBACK_FAILURE,
reasonToLog, failedPackageToLog);
@@ -431,6 +469,16 @@
RollbackManager rollbackManager = mContext.getSystemService(RollbackManager.class);
List<RollbackInfo> rollbacks = rollbackManager.getAvailableRollbacks();
+ // Add all rollback ids to mPendingStagedRollbackIds, so that we do not reboot before all
+ // pending staged rollbacks are handled.
+ synchronized (mPendingStagedRollbackIds) {
+ for (RollbackInfo rollback : rollbacks) {
+ if (rollback.isStaged()) {
+ mPendingStagedRollbackIds.add(rollback.getRollbackId());
+ }
+ }
+ }
+
for (RollbackInfo rollback : rollbacks) {
VersionedPackage sample = rollback.getPackages().get(0).getVersionRolledBackFrom();
rollbackPackage(rollback, sample, PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
diff --git a/services/core/java/com/android/server/rollback/RollbackStore.java b/services/core/java/com/android/server/rollback/RollbackStore.java
index df75a29..bbcd0de 100644
--- a/services/core/java/com/android/server/rollback/RollbackStore.java
+++ b/services/core/java/com/android/server/rollback/RollbackStore.java
@@ -341,6 +341,7 @@
json.put("pendingRestores", convertToJsonArray(pendingRestores));
json.put("isApex", info.isApex());
+ json.put("isApkInApex", info.isApkInApex());
// Field is named 'installedUsers' for legacy reasons.
json.put("installedUsers", convertToJsonArray(snapshottedUsers));
@@ -364,6 +365,7 @@
json.getJSONArray("pendingRestores"));
final boolean isApex = json.getBoolean("isApex");
+ final boolean isApkInApex = json.getBoolean("isApkInApex");
// Field is named 'installedUsers' for legacy reasons.
final IntArray snapshottedUsers = convertToIntArray(json.getJSONArray("installedUsers"));
@@ -375,8 +377,8 @@
PackageManager.RollbackDataPolicy.RESTORE);
return new PackageRollbackInfo(versionRolledBackFrom, versionRolledBackTo,
- pendingBackups, pendingRestores, isApex, snapshottedUsers, ceSnapshotInodes,
- rollbackDataPolicy);
+ pendingBackups, pendingRestores, isApex, isApkInApex, snapshottedUsers,
+ ceSnapshotInodes, rollbackDataPolicy);
}
private static JSONArray versionedPackagesToJson(List<VersionedPackage> packages)
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
index 9b22f33..a641f06 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ConversionUtil.java
@@ -21,8 +21,10 @@
import android.hardware.audio.common.V2_0.Uuid;
import android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback;
import android.hardware.soundtrigger.V2_3.ISoundTriggerHw;
+import android.hardware.soundtrigger.V2_3.Properties;
import android.media.audio.common.AudioConfig;
import android.media.audio.common.AudioOffloadInfo;
+import android.media.soundtrigger_middleware.AudioCapabilities;
import android.media.soundtrigger_middleware.ConfidenceLevel;
import android.media.soundtrigger_middleware.ModelParameter;
import android.media.soundtrigger_middleware.ModelParameterRange;
@@ -40,7 +42,6 @@
import android.os.HidlMemoryUtil;
import java.util.regex.Matcher;
-import java.util.regex.Pattern;
/**
* Utilities for type conversion between SoundTrigger HAL types and SoundTriggerMiddleware service
@@ -60,7 +61,8 @@
aidlProperties.maxSoundModels = hidlProperties.maxSoundModels;
aidlProperties.maxKeyPhrases = hidlProperties.maxKeyPhrases;
aidlProperties.maxUsers = hidlProperties.maxUsers;
- aidlProperties.recognitionModes = hidlProperties.recognitionModes;
+ aidlProperties.recognitionModes =
+ hidl2aidlRecognitionModes(hidlProperties.recognitionModes);
aidlProperties.captureTransition = hidlProperties.captureTransition;
aidlProperties.maxBufferMs = hidlProperties.maxBufferMs;
aidlProperties.concurrentCapture = hidlProperties.concurrentCapture;
@@ -69,6 +71,15 @@
return aidlProperties;
}
+ static @NonNull SoundTriggerModuleProperties hidl2aidlProperties(
+ @NonNull Properties hidlProperties) {
+ SoundTriggerModuleProperties aidlProperties = hidl2aidlProperties(hidlProperties.base);
+ aidlProperties.supportedModelArch = hidlProperties.supportedModelArch;
+ aidlProperties.audioCapabilities =
+ hidl2aidlAudioCapabilities(hidlProperties.audioCapabilities);
+ return aidlProperties;
+ }
+
static @NonNull
String hidl2aidlUuid(@NonNull Uuid hidlUuid) {
if (hidlUuid.node == null || hidlUuid.node.length != 6) {
@@ -201,16 +212,17 @@
return hidlModel;
}
- static @NonNull
- ISoundTriggerHw.RecognitionConfig aidl2hidlRecognitionConfig(
+ static @NonNull android.hardware.soundtrigger.V2_3.RecognitionConfig aidl2hidlRecognitionConfig(
@NonNull RecognitionConfig aidlConfig) {
- ISoundTriggerHw.RecognitionConfig hidlConfig = new ISoundTriggerHw.RecognitionConfig();
- hidlConfig.header.captureRequested = aidlConfig.captureRequested;
+ android.hardware.soundtrigger.V2_3.RecognitionConfig hidlConfig =
+ new android.hardware.soundtrigger.V2_3.RecognitionConfig();
+ hidlConfig.base.header.captureRequested = aidlConfig.captureRequested;
for (PhraseRecognitionExtra aidlPhraseExtra : aidlConfig.phraseRecognitionExtras) {
- hidlConfig.header.phrases.add(aidl2hidlPhraseRecognitionExtra(aidlPhraseExtra));
+ hidlConfig.base.header.phrases.add(aidl2hidlPhraseRecognitionExtra(aidlPhraseExtra));
}
- hidlConfig.data = HidlMemoryUtil.byteArrayToHidlMemory(aidlConfig.data,
+ hidlConfig.base.data = HidlMemoryUtil.byteArrayToHidlMemory(aidlConfig.data,
"SoundTrigger RecognitionConfig");
+ hidlConfig.audioCapabilities = aidlConfig.audioCapabilities;
return hidlConfig;
}
@@ -387,4 +399,17 @@
return android.hardware.soundtrigger.V2_3.ModelParameter.INVALID;
}
}
+
+ static int hidl2aidlAudioCapabilities(int hidlCapabilities) {
+ int aidlCapabilities = 0;
+ if ((hidlCapabilities
+ & android.hardware.soundtrigger.V2_3.AudioCapabilities.ECHO_CANCELLATION) != 0) {
+ aidlCapabilities |= AudioCapabilities.ECHO_CANCELLATION;
+ }
+ if ((hidlCapabilities
+ & android.hardware.soundtrigger.V2_3.AudioCapabilities.NOISE_SUPPRESSION) != 0) {
+ aidlCapabilities |= AudioCapabilities.NOISE_SUPPRESSION;
+ }
+ return aidlCapabilities;
+ }
}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/HalFactory.java b/services/core/java/com/android/server/soundtrigger_middleware/HalFactory.java
new file mode 100644
index 0000000..b19e2ed
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/HalFactory.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.soundtrigger_middleware;
+
+import android.hardware.soundtrigger.V2_0.ISoundTriggerHw;
+
+/**
+ * A factory for creating instances of {@link ISoundTriggerHw}.
+ *
+ * @hide
+ */
+public interface HalFactory {
+ /**
+ * @return An instance of {@link ISoundTriggerHw}.
+ */
+ ISoundTriggerHw create();
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java b/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java
index f0a0d83..a42d292 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/Hw2CompatUtil.java
@@ -66,12 +66,25 @@
return model_2_0;
}
- static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig convertRecognitionConfig_2_1_to_2_0(
- android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config) {
+ static android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig convertRecognitionConfig_2_3_to_2_1(
+ android.hardware.soundtrigger.V2_3.RecognitionConfig config) {
+ return config.base;
+ }
+
+ static android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig convertRecognitionConfig_2_3_to_2_0(
+ android.hardware.soundtrigger.V2_3.RecognitionConfig config) {
android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig config_2_0 =
- config.header;
+ config.base.header;
// Note: this mutates the input!
- config_2_0.data = HidlMemoryUtil.hidlMemoryToByteList(config.data);
+ config_2_0.data = HidlMemoryUtil.hidlMemoryToByteList(config.base.data);
return config_2_0;
}
+
+ static android.hardware.soundtrigger.V2_3.Properties convertProperties_2_0_to_2_3(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties properties) {
+ android.hardware.soundtrigger.V2_3.Properties properties_2_3 =
+ new android.hardware.soundtrigger.V2_3.Properties();
+ properties_2_3.base = properties;
+ return properties_2_3;
+ }
}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java
index 81252c9..8b434bd 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerHw2.java
@@ -16,7 +16,6 @@
package com.android.server.soundtrigger_middleware;
-import android.hardware.soundtrigger.V2_3.ISoundTriggerHw;
import android.hardware.soundtrigger.V2_3.ModelParameterRange;
import android.hidl.base.V1_0.IBase;
import android.os.IHwBinder;
@@ -54,9 +53,10 @@
*/
public interface ISoundTriggerHw2 {
/**
- * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#getProperties(android.hardware.soundtrigger.V2_0.ISoundTriggerHw.getPropertiesCallback
+ * @see android.hardware.soundtrigger.V2_3.ISoundTriggerHw#getPropertiesEx(
+ * android.hardware.soundtrigger.V2_3.ISoundTriggerHw.getPropertiesExCallback)
*/
- android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties getProperties();
+ android.hardware.soundtrigger.V2_3.Properties getProperties();
/**
* @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#loadSoundModel_2_1(android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel,
@@ -92,12 +92,12 @@
void stopAllRecognitions();
/**
- * @see android.hardware.soundtrigger.V2_2.ISoundTriggerHw#startRecognition_2_1(int,
- * android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig,
+ * @see android.hardware.soundtrigger.V2_3.ISoundTriggerHw#startRecognition_2_3(int,
+ * android.hardware.soundtrigger.V2_3.RecognitionConfig,
* android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback, int)
*/
void startRecognition(int modelHandle,
- android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config,
+ android.hardware.soundtrigger.V2_3.RecognitionConfig config,
SoundTriggerHw2Compat.Callback callback, int cookie);
/**
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
index 4a852c4..2f087f4 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Compat.java
@@ -112,18 +112,23 @@
}
@Override
- public android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties getProperties() {
+ public android.hardware.soundtrigger.V2_3.Properties getProperties() {
try {
AtomicInteger retval = new AtomicInteger(-1);
- AtomicReference<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.Properties>
+ AtomicReference<android.hardware.soundtrigger.V2_3.Properties>
properties =
new AtomicReference<>();
- as2_0().getProperties(
- (r, p) -> {
- retval.set(r);
- properties.set(p);
- });
- handleHalStatus(retval.get(), "getProperties");
+ try {
+ as2_3().getProperties_2_3(
+ (r, p) -> {
+ retval.set(r);
+ properties.set(p);
+ });
+ } catch (NotSupported e) {
+ // Fall-back to the 2.0 version:
+ return getProperties_2_0();
+ }
+ handleHalStatus(retval.get(), "getProperties_2_3");
return properties.get();
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
@@ -212,16 +217,15 @@
@Override
public void startRecognition(int modelHandle,
- android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config,
+ android.hardware.soundtrigger.V2_3.RecognitionConfig config,
Callback callback, int cookie) {
try {
try {
- int retval = as2_1().startRecognition_2_1(modelHandle, config,
- new SoundTriggerCallback(callback), cookie);
- handleHalStatus(retval, "startRecognition_2_1");
+ int retval = as2_3().startRecognition_2_3(modelHandle, config);
+ handleHalStatus(retval, "startRecognition_2_3");
} catch (NotSupported e) {
// Fall-back to the 2.0 version:
- startRecognition_2_0(modelHandle, config, callback, cookie);
+ startRecognition_2_1(modelHandle, config, callback, cookie);
}
} catch (RemoteException e) {
throw e.rethrowAsRuntimeException();
@@ -312,6 +316,21 @@
return as2_0().interfaceDescriptor();
}
+ private android.hardware.soundtrigger.V2_3.Properties getProperties_2_0()
+ throws RemoteException {
+ AtomicInteger retval = new AtomicInteger(-1);
+ AtomicReference<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.Properties>
+ properties =
+ new AtomicReference<>();
+ as2_0().getProperties(
+ (r, p) -> {
+ retval.set(r);
+ properties.set(p);
+ });
+ handleHalStatus(retval.get(), "getProperties");
+ return Hw2CompatUtil.convertProperties_2_0_to_2_3(properties.get());
+ }
+
private int loadSoundModel_2_0(
android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel soundModel,
Callback callback, int cookie)
@@ -349,13 +368,31 @@
return handle.get();
}
+ private void startRecognition_2_1(int modelHandle,
+ android.hardware.soundtrigger.V2_3.RecognitionConfig config,
+ Callback callback, int cookie) {
+ try {
+ try {
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config_2_1 =
+ Hw2CompatUtil.convertRecognitionConfig_2_3_to_2_1(config);
+ int retval = as2_1().startRecognition_2_1(modelHandle, config_2_1,
+ new SoundTriggerCallback(callback), cookie);
+ handleHalStatus(retval, "startRecognition_2_1");
+ } catch (NotSupported e) {
+ // Fall-back to the 2.0 version:
+ startRecognition_2_0(modelHandle, config, callback, cookie);
+ }
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
private void startRecognition_2_0(int modelHandle,
- android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig config,
+ android.hardware.soundtrigger.V2_3.RecognitionConfig config,
Callback callback, int cookie)
throws RemoteException {
-
android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig config_2_0 =
- Hw2CompatUtil.convertRecognitionConfig_2_1_to_2_0(config);
+ Hw2CompatUtil.convertRecognitionConfig_2_3_to_2_0(config);
int retval = as2_0().startRecognition(modelHandle, config_2_0,
new SoundTriggerCallback(callback), cookie);
handleHalStatus(retval, "startRecognition");
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
index 9d51b65..9f4b09a 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
@@ -78,18 +78,17 @@
}
/**
- * Most generic constructor - gets an array of HAL driver instances.
+ * Constructor - gets an array of HAL driver factories.
*/
- public SoundTriggerMiddlewareImpl(@NonNull ISoundTriggerHw[] halServices,
+ public SoundTriggerMiddlewareImpl(@NonNull HalFactory[] halFactories,
@NonNull AudioSessionProvider audioSessionProvider) {
- List<SoundTriggerModule> modules = new ArrayList<>(halServices.length);
+ List<SoundTriggerModule> modules = new ArrayList<>(halFactories.length);
- for (int i = 0; i < halServices.length; ++i) {
- ISoundTriggerHw service = halServices[i];
+ for (int i = 0; i < halFactories.length; ++i) {
try {
- modules.add(new SoundTriggerModule(service, audioSessionProvider));
+ modules.add(new SoundTriggerModule(halFactories[i], audioSessionProvider));
} catch (Exception e) {
- Log.e(TAG, "Failed to a SoundTriggerModule instance", e);
+ Log.e(TAG, "Failed to add a SoundTriggerModule instance", e);
}
}
@@ -97,11 +96,11 @@
}
/**
- * Convenience constructor - gets a single HAL driver instance.
+ * Convenience constructor - gets a single HAL factory.
*/
- public SoundTriggerMiddlewareImpl(@NonNull ISoundTriggerHw halService,
+ public SoundTriggerMiddlewareImpl(@NonNull HalFactory factory,
@NonNull AudioSessionProvider audioSessionProvider) {
- this(new ISoundTriggerHw[]{halService}, audioSessionProvider);
+ this(new HalFactory[]{factory}, audioSessionProvider);
}
@Override
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
index 987c05f..12f9fd9 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
+import android.content.PermissionChecker;
import android.hardware.soundtrigger.V2_0.ISoundTriggerHw;
import android.media.soundtrigger_middleware.ISoundTriggerCallback;
import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
@@ -32,6 +33,7 @@
import android.media.soundtrigger_middleware.RecognitionStatus;
import android.media.soundtrigger_middleware.SoundModel;
import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+import android.media.soundtrigger_middleware.Status;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.util.Log;
@@ -111,9 +113,9 @@
public class SoundTriggerMiddlewareService extends ISoundTriggerMiddlewareService.Stub {
static private final String TAG = "SoundTriggerMiddlewareService";
- final ISoundTriggerMiddlewareService mDelegate;
- final Context mContext;
- Set<Integer> mModuleHandles;
+ private final ISoundTriggerMiddlewareService mDelegate;
+ private final Context mContext;
+ private Set<Integer> mModuleHandles;
/**
* Constructor for internal use only. Could be exposed for testing purposes in the future.
@@ -223,23 +225,48 @@
}
/**
- * Throws a {@link SecurityException} if caller doesn't have the right permissions to use this
- * service.
+ * Throws a {@link SecurityException} if caller permanently doesn't have the given permission,
+ * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if
+ * caller temporarily doesn't have the right permissions to use this service.
*/
private void checkPermissions() {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.RECORD_AUDIO,
- "Caller must have the android.permission.RECORD_AUDIO permission.");
- mContext.enforceCallingOrSelfPermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD,
- "Caller must have the android.permission.CAPTURE_AUDIO_HOTWORD permission.");
+ enforcePermission(Manifest.permission.RECORD_AUDIO);
+ enforcePermission(Manifest.permission.CAPTURE_AUDIO_HOTWORD);
}
/**
- * Throws a {@link SecurityException} if caller doesn't have the right permissions to preempt
- * active sound trigger sessions.
+ * Throws a {@link SecurityException} if caller permanently doesn't have the given permission,
+ * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if
+ * caller temporarily doesn't have the right permissions to preempt active sound trigger
+ * sessions.
*/
private void checkPreemptPermissions() {
- mContext.enforceCallingOrSelfPermission(Manifest.permission.PREEMPT_SOUND_TRIGGER,
- "Caller must have the android.permission.PREEMPT_SOUND_TRIGGER permission.");
+ enforcePermission(Manifest.permission.PREEMPT_SOUND_TRIGGER);
+ }
+
+ /**
+ * Throws a {@link SecurityException} if caller permanently doesn't have the given permission,
+ * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if
+ * caller temporarily doesn't have the given permission.
+ *
+ * @param permission The permission to check.
+ */
+ private void enforcePermission(String permission) {
+ final int status = PermissionChecker.checkCallingOrSelfPermissionForPreflight(mContext,
+ permission);
+ switch (status) {
+ case PermissionChecker.PERMISSION_GRANTED:
+ return;
+ case PermissionChecker.PERMISSION_DENIED:
+ throw new SecurityException(
+ String.format("Caller must have the %s permission.", permission));
+ case PermissionChecker.PERMISSION_DENIED_APP_OP:
+ throw new ServiceSpecificException(Status.TEMPORARY_PERMISSION_DENIED,
+ String.format("Caller must have the %s permission.", permission));
+ default:
+ throw new InternalServerError(
+ new RuntimeException("Unexpected perimission check result."));
+ }
}
/** State of a sound model. */
@@ -253,7 +280,7 @@
}
/** Activity state. */
- public Activity activityState = Activity.LOADED;
+ Activity activityState = Activity.LOADED;
/**
* A map of known parameter support. A missing key means we don't know yet whether the
@@ -267,7 +294,7 @@
*
* @param modelParam The parameter key.
*/
- public void checkSupported(int modelParam) {
+ void checkSupported(int modelParam) {
if (!parameterSupport.containsKey(modelParam)) {
throw new IllegalStateException("Parameter has not been checked for support.");
}
@@ -284,7 +311,7 @@
* @param modelParam The parameter key.
* @param value The value.
*/
- public void checkSupported(int modelParam, int value) {
+ void checkSupported(int modelParam, int value) {
if (!parameterSupport.containsKey(modelParam)) {
throw new IllegalStateException("Parameter has not been checked for support.");
}
@@ -302,7 +329,7 @@
* @param modelParam The parameter key.
* @param range The parameter value range, or null if not supported.
*/
- public void updateParameterSupport(int modelParam, @Nullable ModelParameterRange range) {
+ void updateParameterSupport(int modelParam, @Nullable ModelParameterRange range) {
parameterSupport.put(modelParam, range);
}
}
@@ -311,27 +338,26 @@
* Entry-point to this module: exposes the module as a {@link SystemService}.
*/
public static final class Lifecycle extends SystemService {
- private SoundTriggerMiddlewareService mService;
-
public Lifecycle(Context context) {
super(context);
}
@Override
public void onStart() {
- ISoundTriggerHw[] services;
- try {
- services = new ISoundTriggerHw[]{ISoundTriggerHw.getService(true)};
- Log.d(TAG, "Connected to default ISoundTriggerHw");
- } catch (Exception e) {
- Log.e(TAG, "Failed to connect to default ISoundTriggerHw", e);
- services = new ISoundTriggerHw[0];
- }
+ HalFactory[] factories = new HalFactory[]{() -> {
+ try {
+ Log.d(TAG, "Connecting to default ISoundTriggerHw");
+ return ISoundTriggerHw.getService(true);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }};
- mService = new SoundTriggerMiddlewareService(
- new SoundTriggerMiddlewareImpl(services, new AudioSessionProviderImpl()),
- getContext());
- publishBinderService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE, mService);
+ publishBinderService(Context.SOUND_TRIGGER_MIDDLEWARE_SERVICE,
+ new SoundTriggerMiddlewareService(
+ new SoundTriggerMiddlewareImpl(factories,
+ new AudioSessionProviderImpl()),
+ getContext()));
}
}
@@ -343,7 +369,7 @@
DeathRecipient {
private final ISoundTriggerCallback mCallback;
private ISoundTriggerModule mDelegate;
- private Map<Integer, ModelState> mLoadedModels = new HashMap<>();
+ private @NonNull Map<Integer, ModelState> mLoadedModels = new HashMap<>();
ModuleService(@NonNull ISoundTriggerCallback callback) {
mCallback = callback;
@@ -653,7 +679,7 @@
} catch (RemoteException e) {
// Dead client will be handled by binderDied() - no need to handle here.
// In any case, client callbacks are considered best effort.
- Log.e(TAG, "Client callback execption.", e);
+ Log.e(TAG, "Client callback exception.", e);
}
}
}
@@ -669,20 +695,33 @@
} catch (RemoteException e) {
// Dead client will be handled by binderDied() - no need to handle here.
// In any case, client callbacks are considered best effort.
- Log.e(TAG, "Client callback execption.", e);
+ Log.e(TAG, "Client callback exception.", e);
}
}
}
@Override
- public void onRecognitionAvailabilityChange(boolean available) throws RemoteException {
+ public void onRecognitionAvailabilityChange(boolean available) {
synchronized (this) {
try {
mCallback.onRecognitionAvailabilityChange(available);
} catch (RemoteException e) {
// Dead client will be handled by binderDied() - no need to handle here.
// In any case, client callbacks are considered best effort.
- Log.e(TAG, "Client callback execption.", e);
+ Log.e(TAG, "Client callback exception.", e);
+ }
+ }
+ }
+
+ @Override
+ public void onModuleDied() {
+ synchronized (this) {
+ try {
+ mCallback.onModuleDied();
+ } catch (RemoteException e) {
+ // Dead client will be handled by binderDied() - no need to handle here.
+ // In any case, client callbacks are considered best effort.
+ Log.e(TAG, "Client callback exception.", e);
}
}
}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
index 81789e1..adf16fa 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerModule.java
@@ -30,7 +30,9 @@
import android.media.soundtrigger_middleware.SoundTriggerModuleProperties;
import android.media.soundtrigger_middleware.Status;
import android.os.IBinder;
+import android.os.IHwBinder;
import android.os.RemoteException;
+import android.os.ServiceSpecificException;
import android.util.Log;
import java.util.HashMap;
@@ -57,6 +59,7 @@
* gracefully handle driver malfunction and such behavior will result in undefined behavior. If this
* service is to used with an untrusted driver, the driver must be wrapped with validation / error
* recovery code.
+ * <li>Recovery from driver death is supported.</li>
* <li>RemoteExceptions thrown by the driver are treated as RuntimeExceptions - they are not
* considered recoverable faults and should not occur in a properly functioning system.
* <li>There is no binder instance associated with this implementation. Do not call asBinder().
@@ -79,27 +82,29 @@
*
* @hide
*/
-class SoundTriggerModule {
+class SoundTriggerModule implements IHwBinder.DeathRecipient {
static private final String TAG = "SoundTriggerModule";
- @NonNull private final ISoundTriggerHw2 mHalService;
+ @NonNull private HalFactory mHalFactory;
+ @NonNull private ISoundTriggerHw2 mHalService;
@NonNull private final SoundTriggerMiddlewareImpl.AudioSessionProvider mAudioSessionProvider;
private final Set<Session> mActiveSessions = new HashSet<>();
private int mNumLoadedModels = 0;
- private SoundTriggerModuleProperties mProperties = null;
+ private SoundTriggerModuleProperties mProperties;
private boolean mRecognitionAvailable;
/**
* Ctor.
*
- * @param halService The underlying HAL driver.
+ * @param halFactory A factory for the underlying HAL driver.
*/
- SoundTriggerModule(@NonNull android.hardware.soundtrigger.V2_0.ISoundTriggerHw halService,
+ SoundTriggerModule(@NonNull HalFactory halFactory,
@NonNull SoundTriggerMiddlewareImpl.AudioSessionProvider audioSessionProvider) {
- assert halService != null;
- mHalService = new SoundTriggerHw2Compat(halService);
+ assert halFactory != null;
+ mHalFactory = halFactory;
mAudioSessionProvider = audioSessionProvider;
- mProperties = ConversionUtil.hidl2aidlProperties(mHalService.getProperties());
+ attachToHal();
+ mProperties = ConversionUtil.hidl2aidlProperties(mHalService.getProperties());
// We conservatively assume that external capture is active until explicitly told otherwise.
mRecognitionAvailable = mProperties.concurrentCapture;
}
@@ -117,7 +122,7 @@
* @return The interface through which this module can be controlled.
*/
synchronized @NonNull
- Session attach(@NonNull ISoundTriggerCallback callback) {
+ ISoundTriggerModule attach(@NonNull ISoundTriggerCallback callback) {
Log.d(TAG, "attach()");
Session session = new Session(callback);
mActiveSessions.add(session);
@@ -145,6 +150,7 @@
*/
synchronized void setExternalCaptureState(boolean active) {
Log.d(TAG, String.format("setExternalCaptureState(active=%b)", active));
+
if (mProperties.concurrentCapture) {
// If we support concurrent capture, we don't care about any of this.
return;
@@ -162,6 +168,23 @@
}
}
+ @Override
+ public synchronized void serviceDied(long cookie) {
+ Log.w(TAG, String.format("Underlying HAL driver died."));
+ for (Session session : mActiveSessions) {
+ session.moduleDied();
+ }
+ attachToHal();
+ }
+
+ /**
+ * Attached to the HAL service via factory.
+ */
+ private void attachToHal() {
+ mHalService = new SoundTriggerHw2Compat(mHalFactory.create());
+ mHalService.linkToDeath(this, 0);
+ }
+
/**
* Remove session from the list of active sessions.
*
@@ -204,7 +227,11 @@
public void detach() {
Log.d(TAG, "detach()");
synchronized (SoundTriggerModule.this) {
+ if (mCallback == null) {
+ return;
+ }
removeSession(this);
+ mCallback = null;
}
}
@@ -212,6 +239,7 @@
public int loadModel(@NonNull SoundModel model) {
Log.d(TAG, String.format("loadModel(model=%s)", model));
synchronized (SoundTriggerModule.this) {
+ checkValid();
if (mNumLoadedModels == mProperties.maxSoundModels) {
throw new RecoverableException(Status.RESOURCE_CONTENTION,
"Maximum number of models loaded.");
@@ -227,6 +255,7 @@
public int loadPhraseModel(@NonNull PhraseSoundModel model) {
Log.d(TAG, String.format("loadPhraseModel(model=%s)", model));
synchronized (SoundTriggerModule.this) {
+ checkValid();
if (mNumLoadedModels == mProperties.maxSoundModels) {
throw new RecoverableException(Status.RESOURCE_CONTENTION,
"Maximum number of models loaded.");
@@ -243,6 +272,7 @@
public void unloadModel(int modelHandle) {
Log.d(TAG, String.format("unloadModel(handle=%d)", modelHandle));
synchronized (SoundTriggerModule.this) {
+ checkValid();
mLoadedModels.get(modelHandle).unload();
--mNumLoadedModels;
}
@@ -253,6 +283,7 @@
Log.d(TAG,
String.format("startRecognition(handle=%d, config=%s)", modelHandle, config));
synchronized (SoundTriggerModule.this) {
+ checkValid();
mLoadedModels.get(modelHandle).startRecognition(config);
}
}
@@ -269,26 +300,28 @@
public void forceRecognitionEvent(int modelHandle) {
Log.d(TAG, String.format("forceRecognitionEvent(handle=%d)", modelHandle));
synchronized (SoundTriggerModule.this) {
+ checkValid();
mLoadedModels.get(modelHandle).forceRecognitionEvent();
}
}
@Override
- public void setModelParameter(int modelHandle, int modelParam, int value)
- throws RemoteException {
+ public void setModelParameter(int modelHandle, int modelParam, int value) {
Log.d(TAG,
String.format("setModelParameter(handle=%d, param=%d, value=%d)", modelHandle,
modelParam, value));
synchronized (SoundTriggerModule.this) {
+ checkValid();
mLoadedModels.get(modelHandle).setParameter(modelParam, value);
}
}
@Override
- public int getModelParameter(int modelHandle, int modelParam) throws RemoteException {
+ public int getModelParameter(int modelHandle, int modelParam) {
Log.d(TAG, String.format("getModelParameter(handle=%d, param=%d)", modelHandle,
modelParam));
synchronized (SoundTriggerModule.this) {
+ checkValid();
return mLoadedModels.get(modelHandle).getParameter(modelParam);
}
}
@@ -299,6 +332,7 @@
Log.d(TAG, String.format("queryModelParameterSupport(handle=%d, param=%d)", modelHandle,
modelParam));
synchronized (SoundTriggerModule.this) {
+ checkValid();
return mLoadedModels.get(modelHandle).queryModelParameterSupport(modelParam);
}
}
@@ -322,6 +356,25 @@
}
}
+ /**
+ * The underlying module HAL is dead.
+ */
+ private void moduleDied() {
+ try {
+ mCallback.onModuleDied();
+ removeSession(this);
+ mCallback = null;
+ } catch (RemoteException e) {
+ e.rethrowAsRuntimeException();
+ }
+ }
+
+ private void checkValid() {
+ if (mCallback == null) {
+ throw new ServiceSpecificException(Status.DEAD_OBJECT);
+ }
+ }
+
@Override
public @NonNull
IBinder asBinder() {
@@ -350,10 +403,6 @@
SoundTriggerModule.this.notifyAll();
}
- private void waitStateChange() throws InterruptedException {
- SoundTriggerModule.this.wait();
- }
-
private int load(@NonNull SoundModel model) {
mModelType = model.type;
ISoundTriggerHw.SoundModel hidlModel = ConversionUtil.aidl2hidlSoundModel(model);
@@ -401,10 +450,10 @@
notifyAbort();
return;
}
- android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig hidlConfig =
+ android.hardware.soundtrigger.V2_3.RecognitionConfig hidlConfig =
ConversionUtil.aidl2hidlRecognitionConfig(config);
- hidlConfig.header.captureDevice = mSession.mDeviceHandle;
- hidlConfig.header.captureHandle = mSession.mIoHandle;
+ hidlConfig.base.header.captureDevice = mSession.mDeviceHandle;
+ hidlConfig.base.header.captureHandle = mSession.mIoHandle;
mHalService.startRecognition(mHandle, hidlConfig, this, 0);
setState(ModelState.ACTIVE);
}
diff --git a/services/core/java/com/android/server/stats/OWNERS b/services/core/java/com/android/server/stats/OWNERS
index 8d7f882..fc7fd22 100644
--- a/services/core/java/com/android/server/stats/OWNERS
+++ b/services/core/java/com/android/server/stats/OWNERS
@@ -1,9 +1,8 @@
-bookatz@google.com
-cjyu@google.com
-dwchen@google.com
+jeffreyhuang@google.com
joeo@google.com
+muhammadq@google.com
+ruchirr@google.com
singhtejinder@google.com
-stlafon@google.com
+tsaichristine@google.com
yaochen@google.com
-yanglu@google.com
-yro@google.com
\ No newline at end of file
+yro@google.com
diff --git a/services/core/java/com/android/server/stats/StatsPullAtomService.java b/services/core/java/com/android/server/stats/StatsPullAtomService.java
new file mode 100644
index 0000000..f78330e
--- /dev/null
+++ b/services/core/java/com/android/server/stats/StatsPullAtomService.java
@@ -0,0 +1,1446 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.stats;
+
+import static android.app.AppOpsManager.OP_FLAGS_ALL_TRUSTED;
+import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
+import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
+import static android.os.Debug.getIonHeapsSizeKb;
+import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
+import static android.os.Process.getUidForPid;
+import static android.os.storage.VolumeInfo.TYPE_PRIVATE;
+import static android.os.storage.VolumeInfo.TYPE_PUBLIC;
+
+import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
+import static com.android.server.stats.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs;
+import static com.android.server.stats.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs;
+import static com.android.server.stats.ProcfsMemoryUtil.forEachPid;
+import static com.android.server.stats.ProcfsMemoryUtil.readCmdlineFromProcfs;
+import static com.android.server.stats.ProcfsMemoryUtil.readMemorySnapshotFromProcfs;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManagerInternal;
+import android.app.AlarmManager;
+import android.app.AlarmManager.OnAlarmListener;
+import android.app.AppOpsManager;
+import android.app.AppOpsManager.HistoricalOps;
+import android.app.AppOpsManager.HistoricalOpsRequest;
+import android.app.AppOpsManager.HistoricalPackageOps;
+import android.app.AppOpsManager.HistoricalUidOps;
+import android.app.INotificationManager;
+import android.app.ProcessMemoryState;
+import android.app.StatsManager;
+import android.app.StatsManager.PullAtomMetadata;
+import android.bluetooth.BluetoothActivityEnergyInfo;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.UidTraffic;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
+import android.content.pm.UserInfo;
+import android.hardware.biometrics.BiometricsProtoEnums;
+import android.hardware.face.FaceManager;
+import android.hardware.fingerprint.FingerprintManager;
+import android.net.ConnectivityManager;
+import android.net.INetworkStatsService;
+import android.net.Network;
+import android.net.NetworkRequest;
+import android.net.NetworkStats;
+import android.net.wifi.WifiManager;
+import android.os.BatteryStats;
+import android.os.BatteryStatsInternal;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.CoolingDevice;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.IPullAtomCallback;
+import android.os.IStatsCompanionService;
+import android.os.IStatsd;
+import android.os.IStoraged;
+import android.os.IThermalEventListener;
+import android.os.IThermalService;
+import android.os.Looper;
+import android.os.ParcelFileDescriptor;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.StatFs;
+import android.os.StatsLogEventWrapper;
+import android.os.SynchronousResultReceiver;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.Temperature;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.connectivity.WifiActivityEnergyInfo;
+import android.os.storage.DiskInfo;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
+import android.provider.Settings;
+import android.stats.storage.StorageEnums;
+import android.telephony.ModemActivityInfo;
+import android.telephony.TelephonyManager;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+import android.util.StatsEvent;
+import android.util.StatsLog;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.procstats.IProcessStats;
+import com.android.internal.app.procstats.ProcessStats;
+import com.android.internal.os.BackgroundThread;
+import com.android.internal.os.BatterySipper;
+import com.android.internal.os.BatteryStatsHelper;
+import com.android.internal.os.BinderCallsStats.ExportedCallStat;
+import com.android.internal.os.KernelCpuSpeedReader;
+import com.android.internal.os.KernelCpuThreadReader;
+import com.android.internal.os.KernelCpuThreadReaderDiff;
+import com.android.internal.os.KernelCpuThreadReaderSettingsObserver;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidActiveTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidClusterTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidFreqTimeReader;
+import com.android.internal.os.KernelCpuUidTimeReader.KernelCpuUidUserSysTimeReader;
+import com.android.internal.os.KernelWakelockReader;
+import com.android.internal.os.KernelWakelockStats;
+import com.android.internal.os.LooperStats;
+import com.android.internal.os.PowerProfile;
+import com.android.internal.os.ProcessCpuTracker;
+import com.android.internal.os.StoragedUidIoStatsReader;
+import com.android.internal.util.DumpUtils;
+import com.android.server.BinderCallsStatsService;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+import com.android.server.SystemServiceManager;
+import com.android.server.am.MemoryStatUtil.MemoryStat;
+import com.android.server.notification.NotificationManagerService;
+import com.android.server.role.RoleManagerInternal;
+import com.android.server.stats.IonMemoryUtil.IonAllocations;
+import com.android.server.stats.ProcfsMemoryUtil.MemorySnapshot;
+import com.android.server.storage.DiskStatsFileLogger;
+import com.android.server.storage.DiskStatsLoggingService;
+
+import com.google.android.collect.Sets;
+
+import libcore.io.IoUtils;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Objects;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * SystemService containing PullAtomCallbacks that are registered with statsd.
+ *
+ * @hide
+ */
+public class StatsPullAtomService extends SystemService {
+ private static final String TAG = "StatsPullAtomService";
+ private static final boolean DEBUG = true;
+
+ private static final String RESULT_RECEIVER_CONTROLLER_KEY = "controller_activity";
+ /**
+ * How long to wait on an individual subsystem to return its stats.
+ */
+ private static final long EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS = 2000;
+
+ private final Object mNetworkStatsLock = new Object();
+ @GuardedBy("mNetworkStatsLock")
+ private INetworkStatsService mNetworkStatsService;
+ private final Object mThermalLock = new Object();
+ @GuardedBy("mThermalLock")
+ private IThermalService mThermalService;
+
+ private final Context mContext;
+ private StatsManager mStatsManager;
+
+ public StatsPullAtomService(Context context) {
+ super(context);
+ mContext = context;
+ }
+
+ @Override
+ public void onStart() {
+ mStatsManager = (StatsManager) mContext.getSystemService(Context.STATS_MANAGER);
+ mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+ mTelephony = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+
+ // Used to initialize the CPU Frequency atom.
+ PowerProfile powerProfile = new PowerProfile(mContext);
+ final int numClusters = powerProfile.getNumCpuClusters();
+ mKernelCpuSpeedReaders = new KernelCpuSpeedReader[numClusters];
+ int firstCpuOfCluster = 0;
+ for (int i = 0; i < numClusters; i++) {
+ final int numSpeedSteps = powerProfile.getNumSpeedStepsInCpuCluster(i);
+ mKernelCpuSpeedReaders[i] = new KernelCpuSpeedReader(firstCpuOfCluster,
+ numSpeedSteps);
+ firstCpuOfCluster += powerProfile.getNumCoresInCpuCluster(i);
+ }
+ }
+
+ @Override
+ public void onBootPhase(int phase) {
+ super.onBootPhase(phase);
+ if (phase == PHASE_SYSTEM_SERVICES_READY) {
+ BackgroundThread.getHandler().post(() -> {
+ registerAllPullers();
+ });
+ }
+ }
+
+ void registerAllPullers() {
+ if (DEBUG) {
+ Slog.d(TAG, "Registering all pullers with statsd");
+ }
+ registerWifiBytesTransfer();
+ registerWifiBytesTransferBackground();
+ registerMobileBytesTransfer();
+ registerMobileBytesTransferBackground();
+ registerBluetoothBytesTransfer();
+ registerKernelWakelock();
+ registerCpuTimePerFreq();
+ registerCpuTimePerUid();
+ registerCpuTimePerUidFreq();
+ registerCpuActiveTime();
+ registerCpuClusterTime();
+ registerWifiActivityInfo();
+ registerModemActivityInfo();
+ registerBluetoothActivityInfo();
+ registerSystemElapsedRealtime();
+ registerSystemUptime();
+ registerRemainingBatteryCapacity();
+ registerFullBatteryCapacity();
+ registerBatteryVoltage();
+ registerBatteryLevel();
+ registerBatteryCycleCount();
+ registerProcessMemoryState();
+ registerProcessMemoryHighWaterMark();
+ registerProcessMemorySnapshot();
+ registerSystemIonHeapSize();
+ registerIonHeapSize();
+ registerProcessSystemIonHeapSize();
+ registerTemperature();
+ registerCoolingDevice();
+ registerBinderCalls();
+ registerBinderCallsExceptions();
+ registerLooperStats();
+ registerDiskStats();
+ registerDirectoryUsage();
+ registerAppSize();
+ registerCategorySize();
+ registerNumFingerprintsEnrolled();
+ registerNumFacesEnrolled();
+ registerProcStats();
+ registerProcStatsPkgProc();
+ registerDiskIO();
+ registerPowerProfile();
+ registerProcessCpuTime();
+ registerCpuTimePerThreadFreq();
+ registerDeviceCalculatedPowerUse();
+ registerDeviceCalculatedPowerBlameUid();
+ registerDeviceCalculatedPowerBlameOther();
+ registerDebugElapsedClock();
+ registerDebugFailingElapsedClock();
+ registerBuildInformation();
+ registerRoleHolder();
+ registerDangerousPermissionState();
+ registerTimeZoneDataInfo();
+ registerExternalStorageInfo();
+ registerAppsOnExternalStorageInfo();
+ registerFaceSettings();
+ registerAppOps();
+ registerNotificationRemoteViews();
+ registerDangerousPermissionState();
+ registerDangerousPermissionStateSampled();
+ }
+
+ private INetworkStatsService getINetworkStatsService() {
+ synchronized (mNetworkStatsLock) {
+ if (mNetworkStatsService == null) {
+ mNetworkStatsService = INetworkStatsService.Stub.asInterface(
+ ServiceManager.getService(Context.NETWORK_STATS_SERVICE));
+ if (mNetworkStatsService != null) {
+ try {
+ mNetworkStatsService.asBinder().linkToDeath(() -> {
+ synchronized (mNetworkStatsLock) {
+ mNetworkStatsService = null;
+ }
+ }, /* flags */ 0);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "linkToDeath with NetworkStatsService failed", e);
+ mNetworkStatsService = null;
+ }
+ }
+
+ }
+ return mNetworkStatsService;
+ }
+ }
+
+ private IThermalService getIThermalService() {
+ synchronized (mThermalLock) {
+ if (mThermalService == null) {
+ mThermalService = IThermalService.Stub.asInterface(
+ ServiceManager.getService(Context.THERMAL_SERVICE));
+ if (mThermalService != null) {
+ try {
+ mThermalService.asBinder().linkToDeath(() -> {
+ synchronized (mThermalLock) {
+ mThermalService = null;
+ }
+ }, /* flags */ 0);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "linkToDeath with thermalService failed", e);
+ mThermalService = null;
+ }
+ }
+ }
+ return mThermalService;
+ }
+ }
+ private void registerWifiBytesTransfer() {
+ int tagId = StatsLog.WIFI_BYTES_TRANSFER;
+ PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+ .setAdditiveFields(new int[] {2, 3, 4, 5})
+ .build();
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ metadata,
+ (atomTag, data) -> pullWifiBytesTransfer(atomTag, data),
+ Executors.newSingleThreadExecutor()
+ );
+ }
+
+ private int pullWifiBytesTransfer(int atomTag, List<StatsEvent> pulledData) {
+ INetworkStatsService networkStatsService = getINetworkStatsService();
+ if (networkStatsService == null) {
+ Slog.e(TAG, "NetworkStats Service is not available!");
+ return StatsManager.PULL_SKIP;
+ }
+ long token = Binder.clearCallingIdentity();
+ try {
+ // TODO: Consider caching the following call to get BatteryStatsInternal.
+ BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
+ String[] ifaces = bs.getWifiIfaces();
+ if (ifaces.length == 0) {
+ return StatsManager.PULL_SKIP;
+ }
+ // Combine all the metrics per Uid into one record.
+ NetworkStats stats = networkStatsService.getDetailedUidStats(ifaces).groupedByUid();
+ addNetworkStats(atomTag, pulledData, stats, false);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Pulling netstats for wifi bytes has error", e);
+ return StatsManager.PULL_SKIP;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private void addNetworkStats(
+ int tag, List<StatsEvent> ret, NetworkStats stats, boolean withFGBG) {
+ int size = stats.size();
+ NetworkStats.Entry entry = new NetworkStats.Entry(); // For recycling
+ for (int j = 0; j < size; j++) {
+ stats.getValues(j, entry);
+ StatsEvent.Builder e = StatsEvent.newBuilder();
+ e.setAtomId(tag);
+ e.writeInt(entry.uid);
+ if (withFGBG) {
+ e.writeInt(entry.set);
+ }
+ e.writeLong(entry.rxBytes);
+ e.writeLong(entry.rxPackets);
+ e.writeLong(entry.txBytes);
+ e.writeLong(entry.txPackets);
+ ret.add(e.build());
+ }
+ }
+
+ /**
+ * Allows rollups per UID but keeping the set (foreground/background) slicing.
+ * Adapted from groupedByUid in frameworks/base/core/java/android/net/NetworkStats.java
+ */
+ private NetworkStats rollupNetworkStatsByFGBG(NetworkStats stats) {
+ final NetworkStats ret = new NetworkStats(stats.getElapsedRealtime(), 1);
+
+ final NetworkStats.Entry entry = new NetworkStats.Entry();
+ entry.iface = NetworkStats.IFACE_ALL;
+ entry.tag = NetworkStats.TAG_NONE;
+ entry.metered = NetworkStats.METERED_ALL;
+ entry.roaming = NetworkStats.ROAMING_ALL;
+
+ int size = stats.size();
+ NetworkStats.Entry recycle = new NetworkStats.Entry(); // Used for retrieving values
+ for (int i = 0; i < size; i++) {
+ stats.getValues(i, recycle);
+
+ // Skip specific tags, since already counted in TAG_NONE
+ if (recycle.tag != NetworkStats.TAG_NONE) continue;
+
+ entry.set = recycle.set; // Allows slicing by background/foreground
+ entry.uid = recycle.uid;
+ entry.rxBytes = recycle.rxBytes;
+ entry.rxPackets = recycle.rxPackets;
+ entry.txBytes = recycle.txBytes;
+ entry.txPackets = recycle.txPackets;
+ // Operations purposefully omitted since we don't use them for statsd.
+ ret.combineValues(entry);
+ }
+ return ret;
+ }
+
+ private void registerWifiBytesTransferBackground() {
+ int tagId = StatsLog.WIFI_BYTES_TRANSFER_BY_FG_BG;
+ PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+ .setAdditiveFields(new int[] {3, 4, 5, 6})
+ .build();
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ metadata,
+ (atomTag, data) -> pullWifiBytesTransferBackground(atomTag, data),
+ Executors.newSingleThreadExecutor()
+ );
+ }
+
+ private int pullWifiBytesTransferBackground(int atomTag, List<StatsEvent> pulledData) {
+ INetworkStatsService networkStatsService = getINetworkStatsService();
+ if (networkStatsService == null) {
+ Slog.e(TAG, "NetworkStats Service is not available!");
+ return StatsManager.PULL_SKIP;
+ }
+ long token = Binder.clearCallingIdentity();
+ try {
+ BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
+ String[] ifaces = bs.getWifiIfaces();
+ if (ifaces.length == 0) {
+ return StatsManager.PULL_SKIP;
+ }
+ NetworkStats stats = rollupNetworkStatsByFGBG(
+ networkStatsService.getDetailedUidStats(ifaces));
+ addNetworkStats(atomTag, pulledData, stats, true);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Pulling netstats for wifi bytes w/ fg/bg has error", e);
+ return StatsManager.PULL_SKIP;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private void registerMobileBytesTransfer() {
+ int tagId = StatsLog.MOBILE_BYTES_TRANSFER;
+ PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+ .setAdditiveFields(new int[] {2, 3, 4, 5})
+ .build();
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ metadata,
+ (atomTag, data) -> pullMobileBytesTransfer(atomTag, data),
+ Executors.newSingleThreadExecutor()
+ );
+ }
+
+ private int pullMobileBytesTransfer(int atomTag, List<StatsEvent> pulledData) {
+ INetworkStatsService networkStatsService = getINetworkStatsService();
+ if (networkStatsService == null) {
+ Slog.e(TAG, "NetworkStats Service is not available!");
+ return StatsManager.PULL_SKIP;
+ }
+ long token = Binder.clearCallingIdentity();
+ try {
+ BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
+ String[] ifaces = bs.getMobileIfaces();
+ if (ifaces.length == 0) {
+ return StatsManager.PULL_SKIP;
+ }
+ // Combine all the metrics per Uid into one record.
+ NetworkStats stats = networkStatsService.getDetailedUidStats(ifaces).groupedByUid();
+ addNetworkStats(atomTag, pulledData, stats, false);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Pulling netstats for mobile bytes has error", e);
+ return StatsManager.PULL_SKIP;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private void registerMobileBytesTransferBackground() {
+ int tagId = StatsLog.MOBILE_BYTES_TRANSFER_BY_FG_BG;
+ PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+ .setAdditiveFields(new int[] {3, 4, 5, 6})
+ .build();
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ metadata,
+ (atomTag, data) -> pullMobileBytesTransferBackground(atomTag, data),
+ Executors.newSingleThreadExecutor()
+ );
+ }
+
+ private int pullMobileBytesTransferBackground(int atomTag, List<StatsEvent> pulledData) {
+ INetworkStatsService networkStatsService = getINetworkStatsService();
+ if (networkStatsService == null) {
+ Slog.e(TAG, "NetworkStats Service is not available!");
+ return StatsManager.PULL_SKIP;
+ }
+ long token = Binder.clearCallingIdentity();
+ try {
+ BatteryStatsInternal bs = LocalServices.getService(BatteryStatsInternal.class);
+ String[] ifaces = bs.getMobileIfaces();
+ if (ifaces.length == 0) {
+ return StatsManager.PULL_SKIP;
+ }
+ NetworkStats stats = rollupNetworkStatsByFGBG(
+ networkStatsService.getDetailedUidStats(ifaces));
+ addNetworkStats(atomTag, pulledData, stats, true);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Pulling netstats for mobile bytes w/ fg/bg has error", e);
+ return StatsManager.PULL_SKIP;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private void registerBluetoothBytesTransfer() {
+ int tagId = StatsLog.BLUETOOTH_BYTES_TRANSFER;
+ PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+ .setAdditiveFields(new int[] {2, 3})
+ .build();
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ metadata,
+ (atomTag, data) -> pullBluetoothBytesTransfer(atomTag, data),
+ Executors.newSingleThreadExecutor()
+ );
+ }
+
+ /**
+ * Helper method to extract the Parcelable controller info from a
+ * SynchronousResultReceiver.
+ */
+ private static <T extends Parcelable> T awaitControllerInfo(
+ @Nullable SynchronousResultReceiver receiver) {
+ if (receiver == null) {
+ return null;
+ }
+
+ try {
+ final SynchronousResultReceiver.Result result =
+ receiver.awaitResult(EXTERNAL_STATS_SYNC_TIMEOUT_MILLIS);
+ if (result.bundle != null) {
+ // This is the final destination for the Bundle.
+ result.bundle.setDefusable(true);
+
+ final T data = result.bundle.getParcelable(RESULT_RECEIVER_CONTROLLER_KEY);
+ if (data != null) {
+ return data;
+ }
+ }
+ Slog.e(TAG, "no controller energy info supplied for " + receiver.getName());
+ } catch (TimeoutException e) {
+ Slog.w(TAG, "timeout reading " + receiver.getName() + " stats");
+ }
+ return null;
+ }
+
+ private synchronized BluetoothActivityEnergyInfo fetchBluetoothData() {
+ // TODO: Investigate whether the synchronized keyword is needed.
+ final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+ if (adapter != null) {
+ SynchronousResultReceiver bluetoothReceiver = new SynchronousResultReceiver(
+ "bluetooth");
+ adapter.requestControllerActivityEnergyInfo(bluetoothReceiver);
+ return awaitControllerInfo(bluetoothReceiver);
+ } else {
+ Slog.e(TAG, "Failed to get bluetooth adapter!");
+ return null;
+ }
+ }
+
+ private int pullBluetoothBytesTransfer(int atomTag, List<StatsEvent> pulledData) {
+ BluetoothActivityEnergyInfo info = fetchBluetoothData();
+ if (info == null || info.getUidTraffic() == null) {
+ return StatsManager.PULL_SKIP;
+ }
+ for (UidTraffic traffic : info.getUidTraffic()) {
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeInt(traffic.getUid())
+ .writeLong(traffic.getRxBytes())
+ .writeLong(traffic.getTxBytes())
+ .build();
+ pulledData.add(e);
+ }
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
+ private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
+
+ private void registerKernelWakelock() {
+ int tagId = StatsLog.KERNEL_WAKELOCK;
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ /* PullAtomMetadata */ null,
+ (atomTag, data) -> pullKernelWakelock(atomTag, data),
+ Executors.newSingleThreadExecutor()
+ );
+ }
+
+ private int pullKernelWakelock(int atomTag, List<StatsEvent> pulledData) {
+ final KernelWakelockStats wakelockStats =
+ mKernelWakelockReader.readKernelWakelockStats(mTmpWakelockStats);
+ for (Map.Entry<String, KernelWakelockStats.Entry> ent : wakelockStats.entrySet()) {
+ String name = ent.getKey();
+ KernelWakelockStats.Entry kws = ent.getValue();
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeString(name)
+ .writeInt(kws.mCount)
+ .writeInt(kws.mVersion)
+ .writeLong(kws.mTotalTime)
+ .build();
+ pulledData.add(e);
+ }
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private KernelCpuSpeedReader[] mKernelCpuSpeedReaders;
+ // Disables throttler on CPU time readers.
+ private KernelCpuUidUserSysTimeReader mCpuUidUserSysTimeReader =
+ new KernelCpuUidUserSysTimeReader(false);
+ private KernelCpuUidFreqTimeReader mCpuUidFreqTimeReader =
+ new KernelCpuUidFreqTimeReader(false);
+ private KernelCpuUidActiveTimeReader mCpuUidActiveTimeReader =
+ new KernelCpuUidActiveTimeReader(false);
+ private KernelCpuUidClusterTimeReader mCpuUidClusterTimeReader =
+ new KernelCpuUidClusterTimeReader(false);
+
+ private void registerCpuTimePerFreq() {
+ int tagId = StatsLog.CPU_TIME_PER_FREQ;
+ PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+ .setAdditiveFields(new int[] {3})
+ .build();
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ metadata,
+ (atomTag, data) -> pullCpuTimePerFreq(atomTag, data),
+ Executors.newSingleThreadExecutor()
+ );
+ }
+
+ private int pullCpuTimePerFreq(int atomTag, List<StatsEvent> pulledData) {
+ for (int cluster = 0; cluster < mKernelCpuSpeedReaders.length; cluster++) {
+ long[] clusterTimeMs = mKernelCpuSpeedReaders[cluster].readAbsolute();
+ if (clusterTimeMs != null) {
+ for (int speed = clusterTimeMs.length - 1; speed >= 0; --speed) {
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeInt(cluster)
+ .writeInt(speed)
+ .writeLong(clusterTimeMs[speed])
+ .build();
+ pulledData.add(e);
+ }
+ }
+ }
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private void registerCpuTimePerUid() {
+ int tagId = StatsLog.CPU_TIME_PER_UID;
+ PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+ .setAdditiveFields(new int[] {2, 3})
+ .build();
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ metadata,
+ (atomTag, data) -> pullCpuTimePerUid(atomTag, data),
+ Executors.newSingleThreadExecutor()
+ );
+ }
+
+ private int pullCpuTimePerUid(int atomTag, List<StatsEvent> pulledData) {
+ mCpuUidUserSysTimeReader.readAbsolute((uid, timesUs) -> {
+ long userTimeUs = timesUs[0], systemTimeUs = timesUs[1];
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeInt(uid)
+ .writeLong(userTimeUs)
+ .writeLong(systemTimeUs)
+ .build();
+ pulledData.add(e);
+ });
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private void registerCpuTimePerUidFreq() {
+ // the throttling is 3sec, handled in
+ // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
+ int tagId = StatsLog.CPU_TIME_PER_UID_FREQ;
+ PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+ .setAdditiveFields(new int[] {4})
+ .build();
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ metadata,
+ (atomTag, data) -> pullCpuTimeperUidFreq(atomTag, data),
+ Executors.newSingleThreadExecutor()
+ );
+ }
+
+ private int pullCpuTimeperUidFreq(int atomTag, List<StatsEvent> pulledData) {
+ mCpuUidFreqTimeReader.readAbsolute((uid, cpuFreqTimeMs) -> {
+ for (int freqIndex = 0; freqIndex < cpuFreqTimeMs.length; ++freqIndex) {
+ if (cpuFreqTimeMs[freqIndex] != 0) {
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeInt(uid)
+ .writeInt(freqIndex)
+ .writeLong(cpuFreqTimeMs[freqIndex])
+ .build();
+ pulledData.add(e);
+ }
+ }
+ });
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private void registerCpuActiveTime() {
+ // the throttling is 3sec, handled in
+ // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
+ int tagId = StatsLog.CPU_ACTIVE_TIME;
+ PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+ .setAdditiveFields(new int[] {2})
+ .build();
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ metadata,
+ (atomTag, data) -> pullCpuActiveTime(atomTag, data),
+ Executors.newSingleThreadExecutor()
+ );
+ }
+
+ private int pullCpuActiveTime(int atomTag, List<StatsEvent> pulledData) {
+ mCpuUidActiveTimeReader.readAbsolute((uid, cpuActiveTimesMs) -> {
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeInt(uid)
+ .writeLong(cpuActiveTimesMs)
+ .build();
+ pulledData.add(e);
+ });
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private void registerCpuClusterTime() {
+ // the throttling is 3sec, handled in
+ // frameworks/base/core/java/com/android/internal/os/KernelCpuProcReader
+ int tagId = StatsLog.CPU_CLUSTER_TIME;
+ PullAtomMetadata metadata = PullAtomMetadata.newBuilder()
+ .setAdditiveFields(new int[] {3})
+ .build();
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ metadata,
+ (atomTag, data) -> pullCpuClusterTime(atomTag, data),
+ Executors.newSingleThreadExecutor()
+ );
+ }
+
+ private int pullCpuClusterTime(int atomTag, List<StatsEvent> pulledData) {
+ mCpuUidClusterTimeReader.readAbsolute((uid, cpuClusterTimesMs) -> {
+ for (int i = 0; i < cpuClusterTimesMs.length; i++) {
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeInt(uid)
+ .writeInt(i)
+ .writeLong(cpuClusterTimesMs[i])
+ .build();
+ pulledData.add(e);
+ }
+ });
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private void registerWifiActivityInfo() {
+ int tagId = StatsLog.WIFI_ACTIVITY_INFO;
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ null, // use default PullAtomMetadata values
+ (atomTag, data) -> pullWifiActivityInfo(atomTag, data),
+ BackgroundThread.getExecutor()
+ );
+ }
+
+ private WifiManager mWifiManager;
+ private TelephonyManager mTelephony;
+
+ private int pullWifiActivityInfo(int atomTag, List<StatsEvent> pulledData) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ SynchronousResultReceiver wifiReceiver = new SynchronousResultReceiver("wifi");
+ mWifiManager.getWifiActivityEnergyInfoAsync(
+ new Executor() {
+ @Override
+ public void execute(Runnable runnable) {
+ // run the listener on the binder thread, if it was run on the main
+ // thread it would deadlock since we would be waiting on ourselves
+ runnable.run();
+ }
+ },
+ info -> {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY, info);
+ wifiReceiver.send(0, bundle);
+ }
+ );
+ final WifiActivityEnergyInfo wifiInfo = awaitControllerInfo(wifiReceiver);
+ if (wifiInfo == null) {
+ return StatsManager.PULL_SKIP;
+ }
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeLong(wifiInfo.getTimeSinceBootMillis())
+ .writeInt(wifiInfo.getStackState())
+ .writeLong(wifiInfo.getControllerTxDurationMillis())
+ .writeLong(wifiInfo.getControllerRxDurationMillis())
+ .writeLong(wifiInfo.getControllerIdleDurationMillis())
+ .writeLong(wifiInfo.getControllerEnergyUsedMicroJoules())
+ .build();
+ pulledData.add(e);
+ } catch (RuntimeException e) {
+ Slog.e(TAG, "failed to getWifiActivityEnergyInfoAsync", e);
+ return StatsManager.PULL_SKIP;
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private void registerModemActivityInfo() {
+ int tagId = StatsLog.MODEM_ACTIVITY_INFO;
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ null, // use default PullAtomMetadata values
+ (atomTag, data) -> pullModemActivityInfo(atomTag, data),
+ BackgroundThread.getExecutor()
+ );
+ }
+
+ private int pullModemActivityInfo(int atomTag, List<StatsEvent> pulledData) {
+ long token = Binder.clearCallingIdentity();
+ try {
+ SynchronousResultReceiver modemReceiver = new SynchronousResultReceiver("telephony");
+ mTelephony.requestModemActivityInfo(modemReceiver);
+ final ModemActivityInfo modemInfo = awaitControllerInfo(modemReceiver);
+ if (modemInfo == null) {
+ return StatsManager.PULL_SKIP;
+ }
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeLong(modemInfo.getTimestamp())
+ .writeLong(modemInfo.getSleepTimeMillis())
+ .writeLong(modemInfo.getIdleTimeMillis())
+ .writeLong(modemInfo.getTransmitPowerInfo().get(0).getTimeInMillis())
+ .writeLong(modemInfo.getTransmitPowerInfo().get(1).getTimeInMillis())
+ .writeLong(modemInfo.getTransmitPowerInfo().get(2).getTimeInMillis())
+ .writeLong(modemInfo.getTransmitPowerInfo().get(3).getTimeInMillis())
+ .writeLong(modemInfo.getTransmitPowerInfo().get(4).getTimeInMillis())
+ .writeLong(modemInfo.getReceiveTimeMillis())
+ .build();
+ pulledData.add(e);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private void registerBluetoothActivityInfo() {
+ int tagId = StatsLog.BLUETOOTH_ACTIVITY_INFO;
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ /* metadata */ null,
+ (atomTag, data) -> pullBluetoothActivityInfo(atomTag, data),
+ Executors.newSingleThreadExecutor()
+ );
+ }
+
+ private int pullBluetoothActivityInfo(int atomTag, List<StatsEvent> pulledData) {
+ BluetoothActivityEnergyInfo info = fetchBluetoothData();
+ if (info == null) {
+ return StatsManager.PULL_SKIP;
+ }
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeLong(info.getTimeStamp())
+ .writeInt(info.getBluetoothStackState())
+ .writeLong(info.getControllerTxTimeMillis())
+ .writeLong(info.getControllerRxTimeMillis())
+ .writeLong(info.getControllerIdleTimeMillis())
+ .writeLong(info.getControllerEnergyUsed())
+ .build();
+ pulledData.add(e);
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private void registerSystemElapsedRealtime() {
+ // No op.
+ }
+
+ private void pullSystemElapsedRealtime() {
+ // No op.
+ }
+
+ private void registerSystemUptime() {
+ int tagId = StatsLog.SYSTEM_UPTIME;
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ null, // use default PullAtomMetadata values
+ (atomTag, data) -> pullSystemUptime(atomTag, data),
+ Executors.newSingleThreadExecutor()
+ );
+ }
+
+ private int pullSystemUptime(int atomTag, List<StatsEvent> pulledData) {
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeLong(SystemClock.elapsedRealtime())
+ .build();
+ pulledData.add(e);
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private void registerRemainingBatteryCapacity() {
+ // No op.
+ }
+
+ private void pullRemainingBatteryCapacity() {
+ // No op.
+ }
+
+ private void registerFullBatteryCapacity() {
+ // No op.
+ }
+
+ private void pullFullBatteryCapacity() {
+ // No op.
+ }
+
+ private void registerBatteryVoltage() {
+ // No op.
+ }
+
+ private void pullBatteryVoltage() {
+ // No op.
+ }
+
+ private void registerBatteryLevel() {
+ // No op.
+ }
+
+ private void pullBatteryLevel() {
+ // No op.
+ }
+
+ private void registerBatteryCycleCount() {
+ // No op.
+ }
+
+ private void pullBatteryCycleCount() {
+ // No op.
+ }
+
+ private void registerProcessMemoryState() {
+ // No op.
+ }
+
+ private void pullProcessMemoryState() {
+ // No op.
+ }
+
+ private void registerProcessMemoryHighWaterMark() {
+ // No op.
+ }
+
+ private void pullProcessMemoryHighWaterMark() {
+ // No op.
+ }
+
+ private void registerProcessMemorySnapshot() {
+ // No op.
+ }
+
+ private void pullProcessMemorySnapshot() {
+ // No op.
+ }
+
+ private void registerSystemIonHeapSize() {
+ // No op.
+ }
+
+ private void pullSystemIonHeapSize() {
+ // No op.
+ }
+
+ private void registerIonHeapSize() {
+ int tagId = StatsLog.ION_HEAP_SIZE;
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ /* PullAtomMetadata */ null,
+ (atomTag, data) -> pullIonHeapSize(atomTag, data),
+ Executors.newSingleThreadExecutor()
+ );
+ }
+
+ private int pullIonHeapSize(int atomTag, List<StatsEvent> pulledData) {
+ int ionHeapSizeInKilobytes = (int) getIonHeapsSizeKb();
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeInt(ionHeapSizeInKilobytes)
+ .build();
+ pulledData.add(e);
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private void registerProcessSystemIonHeapSize() {
+ // No op.
+ }
+
+ private void pullProcessSystemIonHeapSize() {
+ // No op.
+ }
+
+ private void registerTemperature() {
+ // No op.
+ }
+
+ private void pullTemperature() {
+ // No op.
+ }
+
+ private void registerCoolingDevice() {
+ // No op.
+ }
+
+ private void pullCooldownDevice() {
+ // No op.
+ }
+
+ private void registerBinderCalls() {
+ // No op.
+ }
+
+ private void pullBinderCalls() {
+ // No op.
+ }
+
+ private void registerBinderCallsExceptions() {
+ // No op.
+ }
+
+ private void pullBinderCallsExceptions() {
+ // No op.
+ }
+
+ private void registerLooperStats() {
+ // No op.
+ }
+
+ private void pullLooperStats() {
+ // No op.
+ }
+
+ private void registerDiskStats() {
+ // No op.
+ }
+
+ private void pullDiskStats() {
+ // No op.
+ }
+
+ private void registerDirectoryUsage() {
+ // No op.
+ }
+
+ private void pullDirectoryUsage() {
+ // No op.
+ }
+
+ private void registerAppSize() {
+ // No op.
+ }
+
+ private void pullAppSize() {
+ // No op.
+ }
+
+ private void registerCategorySize() {
+ // No op.
+ }
+
+ private void pullCategorySize() {
+ // No op.
+ }
+
+ private void registerNumFingerprintsEnrolled() {
+ // No op.
+ }
+
+ private void pullNumFingerprintsEnrolled() {
+ // No op.
+ }
+
+ private void registerNumFacesEnrolled() {
+ // No op.
+ }
+
+ private void pullNumFacesEnrolled() {
+ // No op.
+ }
+
+ private void registerProcStats() {
+ // No op.
+ }
+
+ private void pullProcStats() {
+ // No op.
+ }
+
+ private void registerProcStatsPkgProc() {
+ // No op.
+ }
+
+ private void pullProcStatsPkgProc() {
+ // No op.
+ }
+
+ private void registerDiskIO() {
+ // No op.
+ }
+
+ private void pullDiskIO() {
+ // No op.
+ }
+
+ private void registerPowerProfile() {
+ int tagId = StatsLog.POWER_PROFILE;
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ /* PullAtomMetadata */ null,
+ (atomTag, data) -> pullPowerProfile(atomTag, data),
+ Executors.newSingleThreadExecutor()
+ );
+ }
+
+ private int pullPowerProfile(int atomTag, List<StatsEvent> pulledData) {
+ PowerProfile powerProfile = new PowerProfile(mContext);
+ ProtoOutputStream proto = new ProtoOutputStream();
+ powerProfile.dumpDebug(proto);
+ proto.flush();
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeByteArray(proto.getBytes())
+ .build();
+ pulledData.add(e);
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private void registerProcessCpuTime() {
+ // No op.
+ }
+
+ private void pullProcessCpuTime() {
+ // No op.
+ }
+
+ private void registerCpuTimePerThreadFreq() {
+ // No op.
+ }
+
+ private void pullCpuTimePerThreadFreq() {
+ // No op.
+ }
+
+ // TODO: move to top of file when all migrations are complete
+ private BatteryStatsHelper mBatteryStatsHelper = null;
+ private static final int MAX_BATTERY_STATS_HELPER_FREQUENCY_MS = 1000;
+ private long mBatteryStatsHelperTimestampMs = -MAX_BATTERY_STATS_HELPER_FREQUENCY_MS;
+ private static final long MILLI_AMP_HR_TO_NANO_AMP_SECS = 1_000_000L * 3600L;
+
+ private BatteryStatsHelper getBatteryStatsHelper() {
+ if (mBatteryStatsHelper == null) {
+ final long callingToken = Binder.clearCallingIdentity();
+ try {
+ // clearCallingIdentity required for BatteryStatsHelper.checkWifiOnly().
+ mBatteryStatsHelper = new BatteryStatsHelper(mContext, false);
+ } finally {
+ Binder.restoreCallingIdentity(callingToken);
+ }
+ mBatteryStatsHelper.create((Bundle) null);
+ }
+ long currentTime = SystemClock.elapsedRealtime();
+ if (currentTime - mBatteryStatsHelperTimestampMs >= MAX_BATTERY_STATS_HELPER_FREQUENCY_MS) {
+ // Load BatteryStats and do all the calculations.
+ mBatteryStatsHelper.refreshStats(BatteryStats.STATS_SINCE_CHARGED, UserHandle.USER_ALL);
+ // Calculations are done so we don't need to save the raw BatteryStats data in RAM.
+ mBatteryStatsHelper.clearStats();
+ mBatteryStatsHelperTimestampMs = currentTime;
+ }
+ return mBatteryStatsHelper;
+ }
+
+ private long milliAmpHrsToNanoAmpSecs(double mAh) {
+ return (long) (mAh * MILLI_AMP_HR_TO_NANO_AMP_SECS + 0.5);
+ }
+
+ private void registerDeviceCalculatedPowerUse() {
+ int tagId = StatsLog.DEVICE_CALCULATED_POWER_USE;
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ null, // use default PullAtomMetadata values
+ (atomTag, data) -> pullDeviceCalculatedPowerUse(atomTag, data),
+ BackgroundThread.getExecutor()
+ );
+ }
+
+ private int pullDeviceCalculatedPowerUse(int atomTag, List<StatsEvent> pulledData) {
+ BatteryStatsHelper bsHelper = getBatteryStatsHelper();
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeLong(milliAmpHrsToNanoAmpSecs(bsHelper.getComputedPower()))
+ .build();
+ pulledData.add(e);
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private void registerDeviceCalculatedPowerBlameUid() {
+ int tagId = StatsLog.DEVICE_CALCULATED_POWER_BLAME_UID;
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ null, // use default PullAtomMetadata values
+ (atomTag, data) -> pullDeviceCalculatedPowerBlameUid(atomTag, data),
+ BackgroundThread.getExecutor()
+ );
+ }
+
+ private int pullDeviceCalculatedPowerBlameUid(int atomTag, List<StatsEvent> pulledData) {
+ final List<BatterySipper> sippers = getBatteryStatsHelper().getUsageList();
+ if (sippers == null) {
+ return StatsManager.PULL_SKIP;
+ }
+
+ for (BatterySipper bs : sippers) {
+ if (bs.drainType != bs.drainType.APP) {
+ continue;
+ }
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeInt(bs.uidObj.getUid())
+ .writeLong(milliAmpHrsToNanoAmpSecs(bs.totalPowerMah))
+ .build();
+ pulledData.add(e);
+ }
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private void registerDeviceCalculatedPowerBlameOther() {
+ int tagId = StatsLog.DEVICE_CALCULATED_POWER_BLAME_OTHER;
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ null, // use default PullAtomMetadata values
+ (atomTag, data) -> pullDeviceCalculatedPowerBlameOther(atomTag, data),
+ BackgroundThread.getExecutor()
+ );
+ }
+
+ private int pullDeviceCalculatedPowerBlameOther(int atomTag, List<StatsEvent> pulledData) {
+ final List<BatterySipper> sippers = getBatteryStatsHelper().getUsageList();
+ if (sippers == null) {
+ return StatsManager.PULL_SKIP;
+ }
+
+ for (BatterySipper bs : sippers) {
+ if (bs.drainType == bs.drainType.APP) {
+ continue; // This is a separate atom; see pullDeviceCalculatedPowerBlameUid().
+ }
+ if (bs.drainType == bs.drainType.USER) {
+ continue; // This is not supported. We purposefully calculate over USER_ALL.
+ }
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeInt(bs.drainType.ordinal())
+ .writeLong(milliAmpHrsToNanoAmpSecs(bs.totalPowerMah))
+ .build();
+ pulledData.add(e);
+ }
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private void registerDebugElapsedClock() {
+ // No op.
+ }
+
+ private void pullDebugElapsedClock() {
+ // No op.
+ }
+
+ private void registerDebugFailingElapsedClock() {
+ // No op.
+ }
+
+ private void pullDebugFailingElapsedClock() {
+ // No op.
+ }
+
+ private void registerBuildInformation() {
+ int tagId = StatsLog.BUILD_INFORMATION;
+ mStatsManager.registerPullAtomCallback(
+ tagId,
+ null, // use default PullAtomMetadata values
+ (atomTag, data) -> pullBuildInformation(atomTag, data),
+ BackgroundThread.getExecutor()
+ );
+ }
+
+ private int pullBuildInformation(int atomTag, List<StatsEvent> pulledData) {
+ StatsEvent e = StatsEvent.newBuilder()
+ .setAtomId(atomTag)
+ .writeString(Build.FINGERPRINT)
+ .writeString(Build.BRAND)
+ .writeString(Build.PRODUCT)
+ .writeString(Build.DEVICE)
+ .writeString(Build.VERSION.RELEASE)
+ .writeString(Build.ID)
+ .writeString(Build.VERSION.INCREMENTAL)
+ .writeString(Build.TYPE)
+ .writeString(Build.TAGS)
+ .build();
+ pulledData.add(e);
+ return StatsManager.PULL_SUCCESS;
+ }
+
+ private void registerRoleHolder() {
+ // No op.
+ }
+
+ private void pullRoleHolder() {
+ // No op.
+ }
+
+ private void registerDangerousPermissionState() {
+ // No op.
+ }
+
+ private void pullDangerousPermissionState() {
+ // No op.
+ }
+
+ private void registerTimeZoneDataInfo() {
+ // No op.
+ }
+
+ private void pullTimeZoneDataInfo() {
+ // No op.
+ }
+
+ private void registerExternalStorageInfo() {
+ // No op.
+ }
+
+ private void pullExternalStorageInfo() {
+ // No op.
+ }
+
+ private void registerAppsOnExternalStorageInfo() {
+ // No op.
+ }
+
+ private void pullAppsOnExternalStorageInfo() {
+ // No op.
+ }
+
+ private void registerFaceSettings() {
+ // No op.
+ }
+
+ private void pullRegisterFaceSettings() {
+ // No op.
+ }
+
+ private void registerAppOps() {
+ // No op.
+ }
+
+ private void pullAppOps() {
+ // No op.
+ }
+
+ private void registerNotificationRemoteViews() {
+ // No op.
+ }
+
+ private void pullNotificationRemoteViews() {
+ // No op.
+ }
+
+ private void registerDangerousPermissionStateSampled() {
+ // No op.
+ }
+
+ private void pullDangerousPermissionStateSampled() {
+ // No op.
+ }
+}
diff --git a/services/core/java/com/android/server/storage/StorageSessionController.java b/services/core/java/com/android/server/storage/StorageSessionController.java
index f4fb93a..baef5d6 100644
--- a/services/core/java/com/android/server/storage/StorageSessionController.java
+++ b/services/core/java/com/android/server/storage/StorageSessionController.java
@@ -40,9 +40,7 @@
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.Preconditions;
-import java.io.FileDescriptor;
import java.util.Objects;
/**
@@ -82,7 +80,7 @@
* @throws ExternalStorageServiceException if the session fails to start
* @throws IllegalStateException if a session has already been created for {@code vol}
*/
- public void onVolumeMount(FileDescriptor deviceFd, VolumeInfo vol)
+ public void onVolumeMount(ParcelFileDescriptor deviceFd, VolumeInfo vol)
throws ExternalStorageServiceException {
if (!shouldHandle(vol)) {
return;
@@ -102,8 +100,8 @@
mConnections.put(userId, connection);
}
Slog.i(TAG, "Creating and starting session with id: " + sessionId);
- connection.startSession(sessionId, new ParcelFileDescriptor(deviceFd),
- vol.getPath().getPath(), vol.getInternalPath().getPath());
+ connection.startSession(sessionId, deviceFd, vol.getPath().getPath(),
+ vol.getInternalPath().getPath());
}
}
@@ -185,7 +183,7 @@
* This call removes all sessions for the user that is being stopped;
* this will make sure that we don't rebind to the service needlessly.
*/
- public void onUserStopping(int userId) throws ExternalStorageServiceException {
+ public void onUserStopping(int userId) {
if (!shouldHandle(null)) {
return;
}
diff --git a/services/core/java/com/android/server/storage/StorageUserConnection.java b/services/core/java/com/android/server/storage/StorageUserConnection.java
index c02ded8..dd18f4e 100644
--- a/services/core/java/com/android/server/storage/StorageUserConnection.java
+++ b/services/core/java/com/android/server/storage/StorageUserConnection.java
@@ -60,7 +60,8 @@
*/
public final class StorageUserConnection {
private static final String TAG = "StorageUserConnection";
- private static final int REMOTE_TIMEOUT_SECONDS = 15;
+
+ public static final int REMOTE_TIMEOUT_SECONDS = 5;
private final Object mLock = new Object();
private final Context mContext;
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index 172367a..b7d6360 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.app.timedetector.ITimeDetectorService;
import android.app.timedetector.ManualTimeSuggestion;
+import android.app.timedetector.NetworkTimeSuggestion;
import android.app.timedetector.PhoneTimeSuggestion;
import android.content.ContentResolver;
import android.content.Context;
@@ -105,6 +106,14 @@
mHandler.post(() -> mTimeDetectorStrategy.suggestManualTime(timeSignal));
}
+ @Override
+ public void suggestNetworkTime(@NonNull NetworkTimeSuggestion timeSignal) {
+ enforceSuggestNetworkTimePermission();
+ Objects.requireNonNull(timeSignal);
+
+ mHandler.post(() -> mTimeDetectorStrategy.suggestNetworkTime(timeSignal));
+ }
+
@VisibleForTesting
public void handleAutoTimeDetectionToggle() {
mHandler.post(mTimeDetectorStrategy::handleAutoTimeDetectionChanged);
@@ -119,10 +128,20 @@
}
private void enforceSuggestPhoneTimePermission() {
- mContext.enforceCallingPermission(android.Manifest.permission.SET_TIME, "set time");
+ mContext.enforceCallingPermission(
+ android.Manifest.permission.SUGGEST_PHONE_TIME_AND_ZONE,
+ "suggest phone time and time zone");
}
private void enforceSuggestManualTimePermission() {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.SET_TIME, "set time");
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE,
+ "suggest manual time and time zone");
+ }
+
+ private void enforceSuggestNetworkTimePermission() {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.SET_TIME,
+ "set time");
}
}
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
index 0a6c2e7..468b806 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
@@ -19,9 +19,9 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.timedetector.ManualTimeSuggestion;
+import android.app.timedetector.NetworkTimeSuggestion;
import android.app.timedetector.PhoneTimeSuggestion;
-import android.content.Intent;
-import android.util.TimestampedValue;
+import android.os.TimestampedValue;
import java.io.PrintWriter;
@@ -61,10 +61,10 @@
/** Acquire a suitable wake lock. Must be followed by {@link #releaseWakeLock()} */
void acquireWakeLock();
- /** Returns the elapsedRealtimeMillis clock value. The WakeLock must be held. */
+ /** Returns the elapsedRealtimeMillis clock value. */
long elapsedRealtimeMillis();
- /** Returns the system clock value. The WakeLock must be held. */
+ /** Returns the system clock value. */
long systemClockMillis();
/** Sets the device system clock. The WakeLock must be held. */
@@ -72,9 +72,6 @@
/** Release the wake lock acquired by a call to {@link #acquireWakeLock()}. */
void releaseWakeLock();
-
- /** Send the supplied intent as a stick broadcast. */
- void sendStickyBroadcast(@NonNull Intent intent);
}
/** Initialize the strategy. */
@@ -86,6 +83,9 @@
/** Process the suggested manually entered time. */
void suggestManualTime(@NonNull ManualTimeSuggestion timeSuggestion);
+ /** Process the suggested time from network sources. */
+ void suggestNetworkTime(@NonNull NetworkTimeSuggestion timeSuggestion);
+
/** Handle the auto-time setting being toggled on or off. */
void handleAutoTimeDetectionChanged();
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java
index 42d59d5..19484db 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyCallbackImpl.java
@@ -20,11 +20,9 @@
import android.app.AlarmManager;
import android.content.ContentResolver;
import android.content.Context;
-import android.content.Intent;
import android.os.PowerManager;
import android.os.SystemClock;
import android.os.SystemProperties;
-import android.os.UserHandle;
import android.provider.Settings;
import android.util.Slog;
@@ -90,13 +88,11 @@
@Override
public long elapsedRealtimeMillis() {
- checkWakeLockHeld();
return SystemClock.elapsedRealtime();
}
@Override
public long systemClockMillis() {
- checkWakeLockHeld();
return System.currentTimeMillis();
}
@@ -112,11 +108,6 @@
mWakeLock.release();
}
- @Override
- public void sendStickyBroadcast(@NonNull Intent intent) {
- mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
- }
-
private void checkWakeLockHeld() {
if (!mWakeLock.isHeld()) {
Slog.wtf(TAG, "WakeLock " + mWakeLock + " not held");
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index c50248d..e95fc4a 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -21,17 +21,17 @@
import android.annotation.Nullable;
import android.app.AlarmManager;
import android.app.timedetector.ManualTimeSuggestion;
+import android.app.timedetector.NetworkTimeSuggestion;
import android.app.timedetector.PhoneTimeSuggestion;
-import android.content.Intent;
-import android.telephony.TelephonyManager;
+import android.os.TimestampedValue;
import android.util.LocalLog;
import android.util.Slog;
-import android.util.TimestampedValue;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.timezonedetector.ArrayMapWithHistory;
+import com.android.server.timezonedetector.ReferenceWithHistory;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -56,11 +56,11 @@
/** Each bucket is this size. All buckets are equally sized. */
@VisibleForTesting
static final int PHONE_BUCKET_SIZE_MILLIS = 60 * 60 * 1000;
- /** Phone suggestions older than this value are considered too old. */
+ /** Phone and network suggestions older than this value are considered too old to be used. */
@VisibleForTesting
- static final long PHONE_MAX_AGE_MILLIS = PHONE_BUCKET_COUNT * PHONE_BUCKET_SIZE_MILLIS;
+ static final long MAX_UTC_TIME_AGE_MILLIS = PHONE_BUCKET_COUNT * PHONE_BUCKET_SIZE_MILLIS;
- @IntDef({ ORIGIN_PHONE, ORIGIN_MANUAL })
+ @IntDef({ ORIGIN_PHONE, ORIGIN_MANUAL, ORIGIN_NETWORK })
@Retention(RetentionPolicy.SOURCE)
public @interface Origin {}
@@ -72,6 +72,10 @@
@Origin
private static final int ORIGIN_MANUAL = 2;
+ /** Used when a time value originated from a network signal. */
+ @Origin
+ private static final int ORIGIN_NETWORK = 3;
+
/**
* CLOCK_PARANOIA: The maximum difference allowed between the expected system clock time and the
* actual system clock time before a warning is logged. Used to help identify situations where
@@ -101,9 +105,13 @@
* will have a small number of telephony devices and phoneIds are assumed to be stable.
*/
@GuardedBy("this")
- private ArrayMapWithHistory<Integer, PhoneTimeSuggestion> mSuggestionByPhoneId =
+ private final ArrayMapWithHistory<Integer, PhoneTimeSuggestion> mSuggestionByPhoneId =
new ArrayMapWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
+ @GuardedBy("this")
+ private final ReferenceWithHistory<NetworkTimeSuggestion> mLastNetworkSuggestion =
+ new ReferenceWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
+
@Override
public void initialize(@NonNull Callback callback) {
mCallback = callback;
@@ -122,6 +130,19 @@
}
@Override
+ public synchronized void suggestNetworkTime(@NonNull NetworkTimeSuggestion timeSuggestion) {
+ if (!validateSuggestionTime(timeSuggestion.getUtcTime(), timeSuggestion)) {
+ return;
+ }
+ mLastNetworkSuggestion.set(timeSuggestion);
+
+ // Now perform auto time detection. The new suggestion may be used to modify the system
+ // clock.
+ String reason = "New network time suggested. timeSuggestion=" + timeSuggestion;
+ doAutoTimeDetection(reason);
+ }
+
+ @Override
public synchronized void suggestPhoneTime(@NonNull PhoneTimeSuggestion timeSuggestion) {
// Empty time suggestion means that telephony network connectivity has been lost.
// The passage of time is relentless, and we don't expect our users to use a time machine,
@@ -167,6 +188,12 @@
ipw.increaseIndent(); // level 1
ipw.println("mLastAutoSystemClockTimeSet=" + mLastAutoSystemClockTimeSet);
+ ipw.println("mCallback.isAutoTimeDetectionEnabled()="
+ + mCallback.isAutoTimeDetectionEnabled());
+ ipw.println("mCallback.elapsedRealtimeMillis()=" + mCallback.elapsedRealtimeMillis());
+ ipw.println("mCallback.systemClockMillis()=" + mCallback.systemClockMillis());
+ ipw.println("mCallback.systemClockUpdateThresholdMillis()="
+ + mCallback.systemClockUpdateThresholdMillis());
ipw.println("Time change log:");
ipw.increaseIndent(); // level 2
@@ -178,6 +205,11 @@
mSuggestionByPhoneId.dump(ipw);
ipw.decreaseIndent(); // level 2
+ ipw.println("Network suggestion history:");
+ ipw.increaseIndent(); // level 2
+ mLastNetworkSuggestion.dump(ipw);
+ ipw.decreaseIndent(); // level 2
+
ipw.decreaseIndent(); // level 1
ipw.flush();
}
@@ -247,23 +279,34 @@
return;
}
+ // Android devices currently prioritize any telephony over network signals. There are
+ // carrier compliance tests that would need to be changed before we could ignore NITZ or
+ // prefer NTP generally. This check is cheap on devices without phone hardware.
PhoneTimeSuggestion bestPhoneSuggestion = findBestPhoneSuggestion();
-
- // Work out what to do with the best suggestion.
- if (bestPhoneSuggestion == null) {
- // There is no good phone suggestion.
- if (DBG) {
- Slog.d(LOG_TAG, "Could not determine time: No best phone suggestion."
- + " detectionReason=" + detectionReason);
- }
+ if (bestPhoneSuggestion != null) {
+ final TimestampedValue<Long> newUtcTime = bestPhoneSuggestion.getUtcTime();
+ String cause = "Found good phone suggestion."
+ + ", bestPhoneSuggestion=" + bestPhoneSuggestion
+ + ", detectionReason=" + detectionReason;
+ setSystemClockIfRequired(ORIGIN_PHONE, newUtcTime, cause);
return;
}
- final TimestampedValue<Long> newUtcTime = bestPhoneSuggestion.getUtcTime();
- String cause = "Found good suggestion."
- + ", bestPhoneSuggestion=" + bestPhoneSuggestion
- + ", detectionReason=" + detectionReason;
- setSystemClockIfRequired(ORIGIN_PHONE, newUtcTime, cause);
+ // There is no good phone suggestion, try network.
+ NetworkTimeSuggestion networkSuggestion = findLatestValidNetworkSuggestion();
+ if (networkSuggestion != null) {
+ final TimestampedValue<Long> newUtcTime = networkSuggestion.getUtcTime();
+ String cause = "Found good network suggestion."
+ + ", networkSuggestion=" + networkSuggestion
+ + ", detectionReason=" + detectionReason;
+ setSystemClockIfRequired(ORIGIN_NETWORK, newUtcTime, cause);
+ return;
+ }
+
+ if (DBG) {
+ Slog.d(LOG_TAG, "Could not determine time: No best phone or network suggestion."
+ + " detectionReason=" + detectionReason);
+ }
}
@GuardedBy("this")
@@ -342,37 +385,50 @@
private static int scorePhoneSuggestion(
long elapsedRealtimeMillis, @NonNull PhoneTimeSuggestion timeSuggestion) {
- // The score is based on the age since receipt. Suggestions are bucketed so two
- // suggestions in the same bucket from different phoneIds are scored the same.
+
+ // Validate first.
TimestampedValue<Long> utcTime = timeSuggestion.getUtcTime();
- long referenceTimeMillis = utcTime.getReferenceTimeMillis();
- if (referenceTimeMillis > elapsedRealtimeMillis) {
- // Future times are ignored. They imply the reference time was wrong, or the elapsed
- // realtime clock has gone backwards, neither of which are supportable situations.
- Slog.w(LOG_TAG, "Existing suggestion found to be in the future. "
+ if (!validateSuggestionUtcTime(elapsedRealtimeMillis, utcTime)) {
+ Slog.w(LOG_TAG, "Existing suggestion found to be invalid "
+ " elapsedRealtimeMillis=" + elapsedRealtimeMillis
+ ", timeSuggestion=" + timeSuggestion);
return PHONE_INVALID_SCORE;
}
- long ageMillis = elapsedRealtimeMillis - referenceTimeMillis;
+ // The score is based on the age since receipt. Suggestions are bucketed so two
+ // suggestions in the same bucket from different phoneIds are scored the same.
+ long ageMillis = elapsedRealtimeMillis - utcTime.getReferenceTimeMillis();
- // Any suggestion > MAX_AGE_MILLIS is treated as too old. Although time is relentless and
- // predictable, the accuracy of the reference time clock may be poor over long periods which
- // would lead to errors creeping in. Also, in edge cases where a bad suggestion has been
- // made and never replaced, it could also mean that the time detection code remains
- // opinionated using a bad invalid suggestion. This caps that edge case at MAX_AGE_MILLIS.
- if (ageMillis > PHONE_MAX_AGE_MILLIS) {
+ // Turn the age into a discrete value: 0 <= bucketIndex < PHONE_BUCKET_COUNT.
+ int bucketIndex = (int) (ageMillis / PHONE_BUCKET_SIZE_MILLIS);
+ if (bucketIndex >= PHONE_BUCKET_COUNT) {
return PHONE_INVALID_SCORE;
}
- // Turn the age into a discrete value: 0 <= bucketIndex < MAX_AGE_HOURS.
- int bucketIndex = (int) (ageMillis / PHONE_BUCKET_SIZE_MILLIS);
-
// We want the lowest bucket index to have the highest score. 0 > score >= BUCKET_COUNT.
return PHONE_BUCKET_COUNT - bucketIndex;
}
+ /** Returns the latest, valid, network suggestion. Returns {@code null} if there isn't one. */
+ @GuardedBy("this")
+ @Nullable
+ private NetworkTimeSuggestion findLatestValidNetworkSuggestion() {
+ NetworkTimeSuggestion networkSuggestion = mLastNetworkSuggestion.get();
+ if (networkSuggestion == null) {
+ // No network suggestions received. This is normal if there's no connectivity.
+ return null;
+ }
+
+ TimestampedValue<Long> utcTime = networkSuggestion.getUtcTime();
+ long elapsedRealTimeMillis = mCallback.elapsedRealtimeMillis();
+ if (!validateSuggestionUtcTime(elapsedRealTimeMillis, utcTime)) {
+ // The latest suggestion is not valid, usually due to its age.
+ return null;
+ }
+
+ return networkSuggestion;
+ }
+
@GuardedBy("this")
private void setSystemClockIfRequired(
@Origin int origin, @NonNull TimestampedValue<Long> time, @NonNull String cause) {
@@ -409,7 +465,7 @@
}
private static boolean isOriginAutomatic(@Origin int origin) {
- return origin == ORIGIN_PHONE;
+ return origin != ORIGIN_MANUAL;
}
@GuardedBy("this")
@@ -477,17 +533,6 @@
} else {
mLastAutoSystemClockTimeSet = null;
}
-
- // Historically, Android has sent a TelephonyManager.ACTION_NETWORK_SET_TIME broadcast only
- // when setting the time using NITZ.
- if (origin == ORIGIN_PHONE) {
- // Send a broadcast that telephony code used to send after setting the clock.
- // TODO Remove this broadcast as soon as there are no remaining listeners.
- Intent intent = new Intent(TelephonyManager.ACTION_NETWORK_SET_TIME);
- intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
- intent.putExtra("time", newSystemClockMillis);
- mCallback.sendStickyBroadcast(intent);
- }
}
/**
@@ -501,6 +546,16 @@
}
/**
+ * Returns the latest valid network suggestion. Not intended for general use: it is used during
+ * tests to check strategy behavior.
+ */
+ @VisibleForTesting
+ @Nullable
+ public NetworkTimeSuggestion findLatestValidNetworkSuggestionForTests() {
+ return findLatestValidNetworkSuggestion();
+ }
+
+ /**
* A method used to inspect state during tests. Not intended for general use.
*/
@VisibleForTesting
@@ -508,4 +563,32 @@
public synchronized PhoneTimeSuggestion getLatestPhoneSuggestion(int phoneId) {
return mSuggestionByPhoneId.get(phoneId);
}
+
+ /**
+ * A method used to inspect state during tests. Not intended for general use.
+ */
+ @VisibleForTesting
+ @Nullable
+ public NetworkTimeSuggestion getLatestNetworkSuggestion() {
+ return mLastNetworkSuggestion.get();
+ }
+
+ private static boolean validateSuggestionUtcTime(
+ long elapsedRealtimeMillis, TimestampedValue<Long> utcTime) {
+ long referenceTimeMillis = utcTime.getReferenceTimeMillis();
+ if (referenceTimeMillis > elapsedRealtimeMillis) {
+ // Future reference times are ignored. They imply the reference time was wrong, or the
+ // elapsed realtime clock used to derive it has gone backwards, neither of which are
+ // supportable situations.
+ return false;
+ }
+
+ // Any suggestion > MAX_AGE_MILLIS is treated as too old. Although time is relentless and
+ // predictable, the accuracy of the reference time clock may be poor over long periods which
+ // would lead to errors creeping in. Also, in edge cases where a bad suggestion has been
+ // made and never replaced, it could also mean that the time detection code remains
+ // opinionated using a bad invalid suggestion. This caps that edge case at MAX_AGE_MILLIS.
+ long ageMillis = elapsedRealtimeMillis - referenceTimeMillis;
+ return ageMillis <= MAX_UTC_TIME_AGE_MILLIS;
+ }
}
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 18ed51a..eb7c5ca 100755
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -127,6 +127,9 @@
// A map from user id to UserState.
private final SparseArray<UserState> mUserStates = new SparseArray<>();
+ // A map from session id to session state saved in userstate
+ private final Map<String, SessionState> mSessionIdToSessionStateMap = new HashMap<>();
+
private final WatchLogHandler mWatchLogHandler;
public TvInputManagerService(Context context) {
@@ -632,7 +635,8 @@
UserState userState = getOrCreateUserStateLocked(userId);
SessionState sessionState = userState.sessionStateMap.get(sessionToken);
if (DEBUG) {
- Slog.d(TAG, "createSessionInternalLocked(inputId=" + sessionState.inputId + ")");
+ Slog.d(TAG, "createSessionInternalLocked(inputId="
+ + sessionState.inputId + ", sessionId=" + sessionState.sessionId + ")");
}
InputChannel[] channels = InputChannel.openInputChannelPair(sessionToken.toString());
@@ -643,9 +647,11 @@
// Create a session. When failed, send a null token immediately.
try {
if (sessionState.isRecordingSession) {
- service.createRecordingSession(callback, sessionState.inputId);
+ service.createRecordingSession(
+ callback, sessionState.inputId, sessionState.sessionId);
} else {
- service.createSession(channels[1], callback, sessionState.inputId);
+ service.createSession(
+ channels[1], callback, sessionState.inputId, sessionState.sessionId);
}
} catch (RemoteException e) {
Slog.e(TAG, "error in createSession", e);
@@ -715,6 +721,8 @@
}
}
+ mSessionIdToSessionStateMap.remove(sessionState.sessionId);
+
ServiceState serviceState = userState.serviceStateMap.get(sessionState.componentName);
if (serviceState != null) {
serviceState.sessionTokens.remove(sessionToken);
@@ -1156,9 +1164,11 @@
public void createSession(final ITvInputClient client, final String inputId,
boolean isRecordingSession, int seq, int userId) {
final int callingUid = Binder.getCallingUid();
- final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+ final int callingPid = Binder.getCallingPid();
+ final int resolvedUserId = resolveCallingUserId(callingPid, callingUid,
userId, "createSession");
final long identity = Binder.clearCallingIdentity();
+ StringBuilder sessionId = new StringBuilder();
try {
synchronized (mLock) {
if (userId != mCurrentUserId && !isRecordingSession) {
@@ -1187,15 +1197,21 @@
return;
}
+ // Create a unique session id with pid, uid and resolved user id
+ sessionId.append(callingUid).append(callingPid).append(resolvedUserId);
+
// Create a new session token and a session state.
IBinder sessionToken = new Binder();
SessionState sessionState = new SessionState(sessionToken, info.getId(),
info.getComponent(), isRecordingSession, client, seq, callingUid,
- resolvedUserId);
+ callingPid, resolvedUserId, sessionId.toString());
// Add them to the global session state map of the current user.
userState.sessionStateMap.put(sessionToken, sessionState);
+ // Map the session id to the sessionStateMap in the user state
+ mSessionIdToSessionStateMap.put(sessionId.toString(), sessionState);
+
// Also, add them to the session state map of the current service.
serviceState.sessionTokens.add(sessionToken);
@@ -1814,8 +1830,8 @@
}
@Override
- public ParcelFileDescriptor openDvbDevice(DvbDeviceInfo info, int device)
- throws RemoteException {
+ public ParcelFileDescriptor openDvbDevice(DvbDeviceInfo info,
+ @TvInputManager.DvbDeviceType int deviceType) throws RemoteException {
if (mContext.checkCallingPermission(android.Manifest.permission.DVB_DEVICE)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException("Requires DVB_DEVICE permission");
@@ -1852,7 +1868,7 @@
final long identity = Binder.clearCallingIdentity();
try {
String deviceFileName;
- switch (device) {
+ switch (deviceType) {
case TvInputManager.DVB_DEVICE_DEMUX:
deviceFileName = String.format(dvbDeviceFound
? "/dev/dvb/adapter%d/demux%d" : "/dev/dvb%d.demux%d",
@@ -1869,14 +1885,14 @@
info.getAdapterId(), info.getDeviceId());
break;
default:
- throw new IllegalArgumentException("Invalid DVB device: " + device);
+ throw new IllegalArgumentException("Invalid DVB device: " + deviceType);
}
try {
// The DVB frontend device only needs to be opened in read/write mode, which
// allows performing tuning operations. The DVB demux and DVR device are enough
// to be opened in read only mode.
return ParcelFileDescriptor.open(new File(deviceFileName),
- TvInputManager.DVB_DEVICE_FRONTEND == device
+ TvInputManager.DVB_DEVICE_FRONTEND == deviceType
? ParcelFileDescriptor.MODE_READ_WRITE
: ParcelFileDescriptor.MODE_READ_ONLY);
} catch (FileNotFoundException e) {
@@ -2003,6 +2019,43 @@
}
@Override
+ public int getClientPid(String sessionId) {
+ ensureTunerResourceAccessPermission();
+ final long identity = Binder.clearCallingIdentity();
+
+ int clientPid = TvInputManager.UNKNOWN_CLIENT_PID;
+ try {
+ synchronized (mLock) {
+ try {
+ clientPid = getClientPidLocked(sessionId);
+ } catch (ClientPidNotFoundException e) {
+ Slog.e(TAG, "error in getClientPid", e);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ return clientPid;
+ }
+
+ private int getClientPidLocked(String sessionId)
+ throws IllegalStateException {
+ if (mSessionIdToSessionStateMap.get(sessionId) == null) {
+ throw new IllegalStateException("Client Pid not found with sessionId "
+ + sessionId);
+ }
+ return mSessionIdToSessionStateMap.get(sessionId).callingPid;
+ }
+
+ private void ensureTunerResourceAccessPermission() {
+ if (mContext.checkCallingPermission(
+ android.Manifest.permission.TUNER_RESOURCE_ACCESS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires TUNER_RESOURCE_ACCESS permission");
+ }
+ }
+
+ @Override
@SuppressWarnings("resource")
protected void dump(FileDescriptor fd, final PrintWriter writer, String[] args) {
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
@@ -2094,9 +2147,11 @@
pw.increaseIndent();
pw.println("inputId: " + session.inputId);
+ pw.println("sessionId: " + session.sessionId);
pw.println("client: " + session.client);
pw.println("seq: " + session.seq);
pw.println("callingUid: " + session.callingUid);
+ pw.println("callingPid: " + session.callingPid);
pw.println("userId: " + session.userId);
pw.println("sessionToken: " + session.sessionToken);
pw.println("session: " + session.session);
@@ -2226,11 +2281,13 @@
private final class SessionState implements IBinder.DeathRecipient {
private final String inputId;
+ private final String sessionId;
private final ComponentName componentName;
private final boolean isRecordingSession;
private final ITvInputClient client;
private final int seq;
private final int callingUid;
+ private final int callingPid;
private final int userId;
private final IBinder sessionToken;
private ITvInputSession session;
@@ -2240,7 +2297,7 @@
private SessionState(IBinder sessionToken, String inputId, ComponentName componentName,
boolean isRecordingSession, ITvInputClient client, int seq, int callingUid,
- int userId) {
+ int callingPid, int userId, String sessionId) {
this.sessionToken = sessionToken;
this.inputId = inputId;
this.componentName = componentName;
@@ -2248,7 +2305,9 @@
this.client = client;
this.seq = seq;
this.callingUid = callingUid;
+ this.callingPid = callingPid;
this.userId = userId;
+ this.sessionId = sessionId;
}
@Override
@@ -2962,4 +3021,10 @@
super(name);
}
}
+
+ private static class ClientPidNotFoundException extends IllegalArgumentException {
+ public ClientPidNotFoundException(String name) {
+ super(name);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/utils/quota/CountQuotaTracker.java b/services/core/java/com/android/server/utils/quota/CountQuotaTracker.java
new file mode 100644
index 0000000..7fe4bf8
--- /dev/null
+++ b/services/core/java/com/android/server/utils/quota/CountQuotaTracker.java
@@ -0,0 +1,802 @@
+/*
+ * 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 com.android.server.utils.quota;
+
+import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
+
+import static com.android.server.utils.quota.Uptc.string;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AlarmManager;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.LongArrayQueue;
+import android.util.Slog;
+import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
+import android.util.quota.CountQuotaTrackerProto;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * Class that tracks whether an app has exceeded its defined count quota.
+ *
+ * Quotas are applied per userId-package-tag combination (UPTC). Tags can be null.
+ *
+ * This tracker tracks the count of instantaneous events.
+ *
+ * Limits are applied according to the category the UPTC is placed in. If a UPTC reaches its limit,
+ * it will be considered out of quota until it is below that limit again. A {@link Category} is a
+ * basic construct to apply different limits to different groups of UPTCs. For example, standby
+ * buckets can be a set of categories, or foreground & background could be two categories. If every
+ * UPTC should have the same limits applied, then only one category is needed
+ * ({@see Category.SINGLE_CATEGORY}).
+ *
+ * Note: all limits are enforced per category unless explicitly stated otherwise.
+ *
+ * Test: atest com.android.server.utils.quota.CountQuotaTrackerTest
+ *
+ * @hide
+ */
+public class CountQuotaTracker extends QuotaTracker {
+ private static final String TAG = CountQuotaTracker.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private static final String ALARM_TAG_CLEANUP = "*" + TAG + ".cleanup*";
+
+ @VisibleForTesting
+ static class ExecutionStats {
+ /**
+ * The time after which this record should be considered invalid (out of date), in the
+ * elapsed realtime timebase.
+ */
+ public long expirationTimeElapsed;
+
+ /** The window size that's used when counting the number of events. */
+ public long windowSizeMs;
+ /** The maximum number of events allowed within the window size. */
+ public int countLimit;
+
+ /** The total number of events that occurred in the window. */
+ public int countInWindow;
+
+ /**
+ * The time after which the app will be under the category quota again. This is only valid
+ * if {@link #countInWindow} >= {@link #countLimit}.
+ */
+ public long inQuotaTimeElapsed;
+
+ @Override
+ public String toString() {
+ return "expirationTime=" + expirationTimeElapsed + ", "
+ + "windowSizeMs=" + windowSizeMs + ", "
+ + "countLimit=" + countLimit + ", "
+ + "countInWindow=" + countInWindow + ", "
+ + "inQuotaTime=" + inQuotaTimeElapsed;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof ExecutionStats) {
+ ExecutionStats other = (ExecutionStats) obj;
+ return this.expirationTimeElapsed == other.expirationTimeElapsed
+ && this.windowSizeMs == other.windowSizeMs
+ && this.countLimit == other.countLimit
+ && this.countInWindow == other.countInWindow
+ && this.inQuotaTimeElapsed == other.inQuotaTimeElapsed;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = 0;
+ result = 31 * result + Long.hashCode(expirationTimeElapsed);
+ result = 31 * result + Long.hashCode(windowSizeMs);
+ result = 31 * result + countLimit;
+ result = 31 * result + countInWindow;
+ result = 31 * result + Long.hashCode(inQuotaTimeElapsed);
+ return result;
+ }
+ }
+
+ /** List of times of all instantaneous events for a UPTC, in chronological order. */
+ // TODO(146148168): introduce a bucketized mode that's more efficient but less accurate
+ @GuardedBy("mLock")
+ private final UptcMap<LongArrayQueue> mEventTimes = new UptcMap<>();
+
+ /** Cached calculation results for each app. */
+ @GuardedBy("mLock")
+ private final UptcMap<ExecutionStats> mExecutionStatsCache = new UptcMap<>();
+
+ private final Handler mHandler;
+
+ @GuardedBy("mLock")
+ private long mNextCleanupTimeElapsed = 0;
+ @GuardedBy("mLock")
+ private final AlarmManager.OnAlarmListener mEventCleanupAlarmListener = () ->
+ CountQuotaTracker.this.mHandler.obtainMessage(MSG_CLEAN_UP_EVENTS).sendToTarget();
+
+ /** The rolling window size for each Category's count limit. */
+ @GuardedBy("mLock")
+ private final ArrayMap<Category, Long> mCategoryCountWindowSizesMs = new ArrayMap<>();
+
+ /**
+ * The maximum count for each Category. For each max value count in the map, the app will
+ * not be allowed any more events within the latest time interval of its rolling window size.
+ *
+ * @see #mCategoryCountWindowSizesMs
+ */
+ @GuardedBy("mLock")
+ private final ArrayMap<Category, Integer> mMaxCategoryCounts = new ArrayMap<>();
+
+ /** The longest period a registered category applies to. */
+ @GuardedBy("mLock")
+ private long mMaxPeriodMs = 0;
+
+ /** Drop any old events. */
+ private static final int MSG_CLEAN_UP_EVENTS = 1;
+
+ public CountQuotaTracker(@NonNull Context context, @NonNull Categorizer categorizer) {
+ this(context, categorizer, new Injector());
+ }
+
+ @VisibleForTesting
+ CountQuotaTracker(@NonNull Context context, @NonNull Categorizer categorizer,
+ Injector injector) {
+ super(context, categorizer, injector);
+
+ mHandler = new CqtHandler(context.getMainLooper());
+ }
+
+ // Exposed API to users.
+
+ /**
+ * Record that an instantaneous event happened.
+ *
+ * @return true if the UPTC is within quota, false otherwise.
+ */
+ public boolean noteEvent(int userId, @NonNull String packageName, @Nullable String tag) {
+ synchronized (mLock) {
+ if (!isEnabledLocked() || isQuotaFreeLocked(userId, packageName)) {
+ return true;
+ }
+ final long nowElapsed = mInjector.getElapsedRealtime();
+
+ final LongArrayQueue times = mEventTimes
+ .getOrCreate(userId, packageName, tag, mCreateLongArrayQueue);
+ times.addLast(nowElapsed);
+ final ExecutionStats stats = getExecutionStatsLocked(userId, packageName, tag);
+ stats.countInWindow++;
+ stats.expirationTimeElapsed = Math.min(stats.expirationTimeElapsed,
+ nowElapsed + stats.windowSizeMs);
+ if (stats.countInWindow == stats.countLimit) {
+ final long windowEdgeElapsed = nowElapsed - stats.windowSizeMs;
+ while (times.size() > 0 && times.peekFirst() < windowEdgeElapsed) {
+ times.removeFirst();
+ }
+ stats.inQuotaTimeElapsed = times.peekFirst() + stats.windowSizeMs;
+ postQuotaStatusChanged(userId, packageName, tag);
+ } else if (stats.countLimit > 9
+ && stats.countInWindow == stats.countLimit * 4 / 5) {
+ // TODO: log high watermark to statsd
+ Slog.w(TAG, string(userId, packageName, tag)
+ + " has reached 80% of it's count limit of " + stats.countLimit);
+ }
+ maybeScheduleCleanupAlarmLocked();
+ return isWithinQuotaLocked(stats);
+ }
+ }
+
+ /**
+ * Set count limit over a rolling time window for the specified category.
+ *
+ * @param category The category these limits apply to.
+ * @param limit The maximum event count an app can have in the rolling window. Must be
+ * nonnegative.
+ * @param timeWindowMs The rolling time window (in milliseconds) to use when checking quota
+ * usage. Must be at least {@value #MIN_WINDOW_SIZE_MS} and no longer than
+ * {@value #MAX_WINDOW_SIZE_MS}
+ */
+ public void setCountLimit(@NonNull Category category, int limit, long timeWindowMs) {
+ if (limit < 0 || timeWindowMs < 0) {
+ throw new IllegalArgumentException("Limit and window size must be nonnegative.");
+ }
+ synchronized (mLock) {
+ final Integer oldLimit = mMaxCategoryCounts.put(category, limit);
+ final long newWindowSizeMs = Math.max(MIN_WINDOW_SIZE_MS,
+ Math.min(timeWindowMs, MAX_WINDOW_SIZE_MS));
+ final Long oldWindowSizeMs = mCategoryCountWindowSizesMs.put(category, newWindowSizeMs);
+ if (oldLimit != null && oldWindowSizeMs != null
+ && oldLimit == limit && oldWindowSizeMs == newWindowSizeMs) {
+ // No change.
+ return;
+ }
+ mDeleteOldEventTimesFunctor.updateMaxPeriod();
+ mMaxPeriodMs = mDeleteOldEventTimesFunctor.mMaxPeriodMs;
+ invalidateAllExecutionStatsLocked();
+ }
+ scheduleQuotaCheck();
+ }
+
+ /**
+ * Gets the count limit for the specified category.
+ */
+ public int getLimit(@NonNull Category category) {
+ synchronized (mLock) {
+ final Integer limit = mMaxCategoryCounts.get(category);
+ if (limit == null) {
+ throw new IllegalArgumentException("Limit for " + category + " not defined");
+ }
+ return limit;
+ }
+ }
+
+ /**
+ * Gets the count time window for the specified category.
+ */
+ public long getWindowSizeMs(@NonNull Category category) {
+ synchronized (mLock) {
+ final Long limitMs = mCategoryCountWindowSizesMs.get(category);
+ if (limitMs == null) {
+ throw new IllegalArgumentException("Limit for " + category + " not defined");
+ }
+ return limitMs;
+ }
+ }
+
+ // Internal implementation.
+
+ @Override
+ @GuardedBy("mLock")
+ void dropEverythingLocked() {
+ mExecutionStatsCache.clear();
+ mEventTimes.clear();
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ @NonNull
+ Handler getHandler() {
+ return mHandler;
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ long getInQuotaTimeElapsedLocked(final int userId, @NonNull final String packageName,
+ @Nullable final String tag) {
+ return getExecutionStatsLocked(userId, packageName, tag).inQuotaTimeElapsed;
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ void handleRemovedAppLocked(String packageName, int uid) {
+ if (packageName == null) {
+ Slog.wtf(TAG, "Told app removed but given null package name.");
+ return;
+ }
+ final int userId = UserHandle.getUserId(uid);
+
+ mEventTimes.delete(userId, packageName);
+ mExecutionStatsCache.delete(userId, packageName);
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ void handleRemovedUserLocked(int userId) {
+ mEventTimes.delete(userId);
+ mExecutionStatsCache.delete(userId);
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ boolean isWithinQuotaLocked(final int userId, @NonNull final String packageName,
+ @Nullable final String tag) {
+ if (!isEnabledLocked()) return true;
+
+ // Quota constraint is not enforced when quota is free.
+ if (isQuotaFreeLocked(userId, packageName)) {
+ return true;
+ }
+
+ return isWithinQuotaLocked(getExecutionStatsLocked(userId, packageName, tag));
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ void maybeUpdateAllQuotaStatusLocked() {
+ final UptcMap<Boolean> doneMap = new UptcMap<>();
+ mEventTimes.forEach((userId, packageName, tag, events) -> {
+ if (!doneMap.contains(userId, packageName, tag)) {
+ maybeUpdateStatusForUptcLocked(userId, packageName, tag);
+ doneMap.add(userId, packageName, tag, Boolean.TRUE);
+ }
+ });
+
+ }
+
+ @Override
+ void maybeUpdateQuotaStatus(final int userId, @NonNull final String packageName,
+ @Nullable final String tag) {
+ synchronized (mLock) {
+ maybeUpdateStatusForUptcLocked(userId, packageName, tag);
+ }
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ void onQuotaFreeChangedLocked(boolean isFree) {
+ // Nothing to do here.
+ }
+
+ @Override
+ @GuardedBy("mLock")
+ void onQuotaFreeChangedLocked(int userId, @NonNull String packageName, boolean isFree) {
+ maybeUpdateStatusForPkgLocked(userId, packageName);
+ }
+
+ @GuardedBy("mLock")
+ private boolean isWithinQuotaLocked(@NonNull final ExecutionStats stats) {
+ return isUnderCountQuotaLocked(stats);
+ }
+
+ @GuardedBy("mLock")
+ private boolean isUnderCountQuotaLocked(@NonNull ExecutionStats stats) {
+ return stats.countInWindow < stats.countLimit;
+ }
+
+ /** Returns the execution stats of the app in the most recent window. */
+ @GuardedBy("mLock")
+ @VisibleForTesting
+ @NonNull
+ ExecutionStats getExecutionStatsLocked(final int userId, @NonNull final String packageName,
+ @Nullable final String tag) {
+ return getExecutionStatsLocked(userId, packageName, tag, true);
+ }
+
+ @GuardedBy("mLock")
+ @NonNull
+ private ExecutionStats getExecutionStatsLocked(final int userId,
+ @NonNull final String packageName, @Nullable String tag,
+ final boolean refreshStatsIfOld) {
+ final ExecutionStats stats =
+ mExecutionStatsCache.getOrCreate(userId, packageName, tag, mCreateExecutionStats);
+ if (refreshStatsIfOld) {
+ final Category category = mCategorizer.getCategory(userId, packageName, tag);
+ final long countWindowSizeMs = mCategoryCountWindowSizesMs.getOrDefault(category,
+ Long.MAX_VALUE);
+ final int countLimit = mMaxCategoryCounts.getOrDefault(category, Integer.MAX_VALUE);
+ if (stats.expirationTimeElapsed <= mInjector.getElapsedRealtime()
+ || stats.windowSizeMs != countWindowSizeMs
+ || stats.countLimit != countLimit) {
+ // The stats are no longer valid.
+ stats.windowSizeMs = countWindowSizeMs;
+ stats.countLimit = countLimit;
+ updateExecutionStatsLocked(userId, packageName, tag, stats);
+ }
+ }
+
+ return stats;
+ }
+
+ @GuardedBy("mLock")
+ @VisibleForTesting
+ void updateExecutionStatsLocked(final int userId, @NonNull final String packageName,
+ @Nullable final String tag, @NonNull ExecutionStats stats) {
+ stats.countInWindow = 0;
+ stats.inQuotaTimeElapsed = 0;
+
+ // This can be used to determine when an app will have enough quota to transition from
+ // out-of-quota to in-quota.
+ final long nowElapsed = mInjector.getElapsedRealtime();
+ stats.expirationTimeElapsed = nowElapsed + mMaxPeriodMs;
+
+ final LongArrayQueue events = mEventTimes.get(userId, packageName, tag);
+ if (events == null) {
+ return;
+ }
+
+ // The minimum time between the start time and the beginning of the events that were
+ // looked at --> how much time the stats will be valid for.
+ long emptyTimeMs = Long.MAX_VALUE - nowElapsed;
+
+ final long eventStartWindowElapsed = nowElapsed - stats.windowSizeMs;
+ for (int i = events.size() - 1; i >= 0; --i) {
+ final long eventTimeElapsed = events.get(i);
+ if (eventTimeElapsed < eventStartWindowElapsed) {
+ // This event happened before the window. No point in going any further.
+ break;
+ }
+ stats.countInWindow++;
+ emptyTimeMs = Math.min(emptyTimeMs, eventTimeElapsed - eventStartWindowElapsed);
+
+ if (stats.countInWindow >= stats.countLimit) {
+ stats.inQuotaTimeElapsed = Math.max(stats.inQuotaTimeElapsed,
+ eventTimeElapsed + stats.windowSizeMs);
+ }
+ }
+
+ stats.expirationTimeElapsed = nowElapsed + emptyTimeMs;
+ }
+
+ /** Invalidate ExecutionStats for all apps. */
+ @GuardedBy("mLock")
+ private void invalidateAllExecutionStatsLocked() {
+ final long nowElapsed = mInjector.getElapsedRealtime();
+ mExecutionStatsCache.forEach((appStats) -> {
+ if (appStats != null) {
+ appStats.expirationTimeElapsed = nowElapsed;
+ }
+ });
+ }
+
+ @GuardedBy("mLock")
+ private void invalidateAllExecutionStatsLocked(final int userId,
+ @NonNull final String packageName) {
+ final ArrayMap<String, ExecutionStats> appStats =
+ mExecutionStatsCache.get(userId, packageName);
+ if (appStats != null) {
+ final long nowElapsed = mInjector.getElapsedRealtime();
+ final int numStats = appStats.size();
+ for (int i = 0; i < numStats; ++i) {
+ final ExecutionStats stats = appStats.valueAt(i);
+ if (stats != null) {
+ stats.expirationTimeElapsed = nowElapsed;
+ }
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void invalidateExecutionStatsLocked(final int userId, @NonNull final String packageName,
+ @Nullable String tag) {
+ final ExecutionStats stats = mExecutionStatsCache.get(userId, packageName, tag);
+ if (stats != null) {
+ stats.expirationTimeElapsed = mInjector.getElapsedRealtime();
+ }
+ }
+
+ private static final class EarliestEventTimeFunctor implements Consumer<LongArrayQueue> {
+ long earliestTimeElapsed = Long.MAX_VALUE;
+
+ @Override
+ public void accept(LongArrayQueue events) {
+ if (events != null && events.size() > 0) {
+ earliestTimeElapsed = Math.min(earliestTimeElapsed, events.get(0));
+ }
+ }
+
+ void reset() {
+ earliestTimeElapsed = Long.MAX_VALUE;
+ }
+ }
+
+ private final EarliestEventTimeFunctor mEarliestEventTimeFunctor =
+ new EarliestEventTimeFunctor();
+
+ /** Schedule a cleanup alarm if necessary and there isn't already one scheduled. */
+ @GuardedBy("mLock")
+ @VisibleForTesting
+ void maybeScheduleCleanupAlarmLocked() {
+ if (mNextCleanupTimeElapsed > mInjector.getElapsedRealtime()) {
+ // There's already an alarm scheduled. Just stick with that one. There's no way we'll
+ // end up scheduling an earlier alarm.
+ if (DEBUG) {
+ Slog.v(TAG, "Not scheduling cleanup since there's already one at "
+ + mNextCleanupTimeElapsed + " (in " + (mNextCleanupTimeElapsed
+ - mInjector.getElapsedRealtime()) + "ms)");
+ }
+ return;
+ }
+
+ mEarliestEventTimeFunctor.reset();
+ mEventTimes.forEach(mEarliestEventTimeFunctor);
+ final long earliestEndElapsed = mEarliestEventTimeFunctor.earliestTimeElapsed;
+ if (earliestEndElapsed == Long.MAX_VALUE) {
+ // Couldn't find a good time to clean up. Maybe this was called after we deleted all
+ // events.
+ if (DEBUG) {
+ Slog.d(TAG, "Didn't find a time to schedule cleanup");
+ }
+ return;
+ }
+
+ // Need to keep events for all apps up to the max period, regardless of their current
+ // category.
+ long nextCleanupElapsed = earliestEndElapsed + mMaxPeriodMs;
+ if (nextCleanupElapsed - mNextCleanupTimeElapsed <= 10 * MINUTE_IN_MILLIS) {
+ // No need to clean up too often. Delay the alarm if the next cleanup would be too soon
+ // after it.
+ nextCleanupElapsed += 10 * MINUTE_IN_MILLIS;
+ }
+ mNextCleanupTimeElapsed = nextCleanupElapsed;
+ scheduleAlarm(AlarmManager.ELAPSED_REALTIME, nextCleanupElapsed, ALARM_TAG_CLEANUP,
+ mEventCleanupAlarmListener);
+ if (DEBUG) {
+ Slog.d(TAG, "Scheduled next cleanup for " + mNextCleanupTimeElapsed);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private boolean maybeUpdateStatusForPkgLocked(final int userId,
+ @NonNull final String packageName) {
+ final UptcMap<Boolean> done = new UptcMap<>();
+
+ if (!mEventTimes.contains(userId, packageName)) {
+ return false;
+ }
+ final ArrayMap<String, LongArrayQueue> events = mEventTimes.get(userId, packageName);
+ if (events == null) {
+ Slog.wtf(TAG,
+ "Events map was null even though mEventTimes said it contained "
+ + string(userId, packageName, null));
+ return false;
+ }
+
+ // Lambdas can't interact with non-final outer variables.
+ final boolean[] changed = {false};
+ events.forEach((tag, eventList) -> {
+ if (!done.contains(userId, packageName, tag)) {
+ changed[0] |= maybeUpdateStatusForUptcLocked(userId, packageName, tag);
+ done.add(userId, packageName, tag, Boolean.TRUE);
+ }
+ });
+
+ return changed[0];
+ }
+
+ /**
+ * Posts that the quota status for the UPTC has changed if it has changed. Avoid calling if
+ * there are no {@link QuotaChangeListener}s registered as the work done will be useless.
+ *
+ * @return true if the in/out quota status changed
+ */
+ @GuardedBy("mLock")
+ private boolean maybeUpdateStatusForUptcLocked(final int userId,
+ @NonNull final String packageName, @Nullable final String tag) {
+ final boolean oldInQuota = isWithinQuotaLocked(
+ getExecutionStatsLocked(userId, packageName, tag, false));
+
+ final boolean newInQuota;
+ if (!isEnabledLocked() || isQuotaFreeLocked(userId, packageName)) {
+ newInQuota = true;
+ } else {
+ newInQuota = isWithinQuotaLocked(
+ getExecutionStatsLocked(userId, packageName, tag, true));
+ }
+
+ if (!newInQuota) {
+ maybeScheduleStartAlarmLocked(userId, packageName, tag);
+ } else {
+ cancelScheduledStartAlarmLocked(userId, packageName, tag);
+ }
+
+ if (oldInQuota != newInQuota) {
+ if (DEBUG) {
+ Slog.d(TAG,
+ "Quota status changed from " + oldInQuota + " to " + newInQuota + " for "
+ + string(userId, packageName, tag));
+ }
+ postQuotaStatusChanged(userId, packageName, tag);
+ return true;
+ }
+
+ return false;
+ }
+
+ private final class DeleteEventTimesFunctor implements Consumer<LongArrayQueue> {
+ private long mMaxPeriodMs;
+
+ @Override
+ public void accept(LongArrayQueue times) {
+ if (times != null) {
+ // Remove everything older than mMaxPeriodMs time ago.
+ while (times.size() > 0
+ && times.peekFirst() <= mInjector.getElapsedRealtime() - mMaxPeriodMs) {
+ times.removeFirst();
+ }
+ }
+ }
+
+ private void updateMaxPeriod() {
+ long maxPeriodMs = 0;
+ for (int i = mCategoryCountWindowSizesMs.size() - 1; i >= 0; --i) {
+ maxPeriodMs = Long.max(maxPeriodMs, mCategoryCountWindowSizesMs.valueAt(i));
+ }
+ mMaxPeriodMs = maxPeriodMs;
+ }
+ }
+
+ private final DeleteEventTimesFunctor mDeleteOldEventTimesFunctor =
+ new DeleteEventTimesFunctor();
+
+ @GuardedBy("mLock")
+ @VisibleForTesting
+ void deleteObsoleteEventsLocked() {
+ mEventTimes.forEach(mDeleteOldEventTimesFunctor);
+ }
+
+ private class CqtHandler extends Handler {
+ CqtHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ synchronized (mLock) {
+ switch (msg.what) {
+ case MSG_CLEAN_UP_EVENTS: {
+ if (DEBUG) {
+ Slog.d(TAG, "Cleaning up events.");
+ }
+ deleteObsoleteEventsLocked();
+ maybeScheduleCleanupAlarmLocked();
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ private Function<Void, LongArrayQueue> mCreateLongArrayQueue = aVoid -> new LongArrayQueue();
+ private Function<Void, ExecutionStats> mCreateExecutionStats = aVoid -> new ExecutionStats();
+
+ //////////////////////// TESTING HELPERS /////////////////////////////
+
+ @VisibleForTesting
+ @Nullable
+ LongArrayQueue getEvents(int userId, String packageName, String tag) {
+ return mEventTimes.get(userId, packageName, tag);
+ }
+
+ //////////////////////////// DATA DUMP //////////////////////////////
+
+ /** Dump state in text format. */
+ public void dump(final IndentingPrintWriter pw) {
+ pw.print(TAG);
+ pw.println(":");
+ pw.increaseIndent();
+
+ synchronized (mLock) {
+ super.dump(pw);
+ pw.println();
+
+ pw.println("Instantaneous events:");
+ pw.increaseIndent();
+ mEventTimes.forEach((userId, pkgName, tag, events) -> {
+ if (events.size() > 0) {
+ pw.print(string(userId, pkgName, tag));
+ pw.println(":");
+ pw.increaseIndent();
+ pw.print(events.get(0));
+ for (int i = 1; i < events.size(); ++i) {
+ pw.print(", ");
+ pw.print(events.get(i));
+ }
+ pw.decreaseIndent();
+ pw.println();
+ }
+ });
+ pw.decreaseIndent();
+
+ pw.println();
+ pw.println("Cached execution stats:");
+ pw.increaseIndent();
+ mExecutionStatsCache.forEach((userId, pkgName, tag, stats) -> {
+ if (stats != null) {
+ pw.print(string(userId, pkgName, tag));
+ pw.println(":");
+ pw.increaseIndent();
+ pw.println(stats);
+ pw.decreaseIndent();
+ }
+ });
+ pw.decreaseIndent();
+
+ pw.println();
+ pw.println("Limits:");
+ pw.increaseIndent();
+ final int numCategories = mCategoryCountWindowSizesMs.size();
+ for (int i = 0; i < numCategories; ++i) {
+ final Category category = mCategoryCountWindowSizesMs.keyAt(i);
+ pw.print(category);
+ pw.print(": ");
+ pw.print(mMaxCategoryCounts.get(category));
+ pw.print(" events in ");
+ pw.println(TimeUtils.formatDuration(mCategoryCountWindowSizesMs.get(category)));
+ }
+ pw.decreaseIndent();
+ }
+ pw.decreaseIndent();
+ }
+
+ /**
+ * Dump state to proto.
+ *
+ * @param proto The ProtoOutputStream to write to.
+ * @param fieldId The field ID of the {@link CountQuotaTrackerProto}.
+ */
+ public void dump(ProtoOutputStream proto, long fieldId) {
+ final long token = proto.start(fieldId);
+
+ synchronized (mLock) {
+ super.dump(proto, CountQuotaTrackerProto.BASE_QUOTA_DATA);
+
+ for (int i = 0; i < mCategoryCountWindowSizesMs.size(); ++i) {
+ final Category category = mCategoryCountWindowSizesMs.keyAt(i);
+ final long clToken = proto.start(CountQuotaTrackerProto.COUNT_LIMIT);
+ category.dumpDebug(proto, CountQuotaTrackerProto.CountLimit.CATEGORY);
+ proto.write(CountQuotaTrackerProto.CountLimit.LIMIT,
+ mMaxCategoryCounts.get(category));
+ proto.write(CountQuotaTrackerProto.CountLimit.WINDOW_SIZE_MS,
+ mCategoryCountWindowSizesMs.get(category));
+ proto.end(clToken);
+ }
+
+ mExecutionStatsCache.forEach((userId, pkgName, tag, stats) -> {
+ final boolean isQuotaFree = isIndividualQuotaFreeLocked(userId, pkgName);
+
+ final long usToken = proto.start(CountQuotaTrackerProto.UPTC_STATS);
+
+ (new Uptc(userId, pkgName, tag))
+ .dumpDebug(proto, CountQuotaTrackerProto.UptcStats.UPTC);
+
+ proto.write(CountQuotaTrackerProto.UptcStats.IS_QUOTA_FREE, isQuotaFree);
+
+ final LongArrayQueue events = mEventTimes.get(userId, pkgName, tag);
+ if (events != null) {
+ for (int j = events.size() - 1; j >= 0; --j) {
+ final long eToken = proto.start(CountQuotaTrackerProto.UptcStats.EVENTS);
+ proto.write(CountQuotaTrackerProto.Event.TIMESTAMP_ELAPSED, events.get(j));
+ proto.end(eToken);
+ }
+ }
+
+ final long statsToken = proto.start(
+ CountQuotaTrackerProto.UptcStats.EXECUTION_STATS);
+ proto.write(
+ CountQuotaTrackerProto.ExecutionStats.EXPIRATION_TIME_ELAPSED,
+ stats.expirationTimeElapsed);
+ proto.write(
+ CountQuotaTrackerProto.ExecutionStats.WINDOW_SIZE_MS,
+ stats.windowSizeMs);
+ proto.write(CountQuotaTrackerProto.ExecutionStats.COUNT_LIMIT, stats.countLimit);
+ proto.write(
+ CountQuotaTrackerProto.ExecutionStats.COUNT_IN_WINDOW,
+ stats.countInWindow);
+ proto.write(
+ CountQuotaTrackerProto.ExecutionStats.IN_QUOTA_TIME_ELAPSED,
+ stats.inQuotaTimeElapsed);
+ proto.end(statsToken);
+
+ proto.end(usToken);
+ });
+
+ proto.end(token);
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/utils/quota/QuotaTracker.java b/services/core/java/com/android/server/utils/quota/QuotaTracker.java
index ef1f426..a8cf9f6 100644
--- a/services/core/java/com/android/server/utils/quota/QuotaTracker.java
+++ b/services/core/java/com/android/server/utils/quota/QuotaTracker.java
@@ -172,6 +172,16 @@
// Exposed API to users.
+ /** Remove all saved events from the tracker. */
+ public void clear() {
+ synchronized (mLock) {
+ mInQuotaAlarmListener.clearLocked();
+ mFreeQuota.clear();
+
+ dropEverythingLocked();
+ }
+ }
+
/**
* @return true if the UPTC is within quota, false otherwise.
* @throws IllegalStateException if given categorizer returns a Category that's not recognized.
@@ -245,10 +255,7 @@
mIsEnabled = enable;
if (!mIsEnabled) {
- mInQuotaAlarmListener.clearLocked();
- mFreeQuota.clear();
-
- dropEverythingLocked();
+ clear();
}
}
}
diff --git a/services/core/java/com/android/server/utils/quota/UptcMap.java b/services/core/java/com/android/server/utils/quota/UptcMap.java
index 7b49913..a3d6ee5 100644
--- a/services/core/java/com/android/server/utils/quota/UptcMap.java
+++ b/services/core/java/com/android/server/utils/quota/UptcMap.java
@@ -22,6 +22,7 @@
import android.util.SparseArrayMap;
import java.util.function.Consumer;
+import java.util.function.Function;
/**
* A SparseArrayMap of ArrayMaps, which is suitable for holding userId-packageName-tag combination
@@ -95,36 +96,36 @@
}
/**
- * Returns the index for which {@link #getUserIdAtIndex(int)} would return the specified userId,
- * or a negative number if the specified userId is not mapped.
+ * Returns the saved object for the given UPTC. If there was no saved object, it will create a
+ * new object using creator, insert it, and return it.
*/
- public int indexOfUserId(int userId) {
- return mData.indexOfKey(userId);
- }
-
- /**
- * Returns the index for which {@link #getPackageNameAtIndex(int, int)} would return the
- * specified userId, or a negative number if the specified userId and packageName are not mapped
- * together.
- */
- public int indexOfUserIdAndPackage(int userId, @NonNull String packageName) {
- return mData.indexOfKey(userId, packageName);
+ @Nullable
+ public T getOrCreate(int userId, @NonNull String packageName, @Nullable String tag,
+ Function<Void, T> creator) {
+ final ArrayMap<String, T> data = mData.get(userId, packageName);
+ if (data == null || !data.containsKey(tag)) {
+ // We've never inserted data for this combination before. Create a new object.
+ final T val = creator.apply(null);
+ add(userId, packageName, tag, val);
+ return val;
+ }
+ return data.get(tag);
}
/** Returns the userId at the given index. */
- public int getUserIdAtIndex(int index) {
+ private int getUserIdAtIndex(int index) {
return mData.keyAt(index);
}
/** Returns the package name at the given index. */
@NonNull
- public String getPackageNameAtIndex(int userIndex, int packageIndex) {
+ private String getPackageNameAtIndex(int userIndex, int packageIndex) {
return mData.keyAt(userIndex, packageIndex);
}
/** Returns the tag at the given index. */
@NonNull
- public String getTagAtIndex(int userIndex, int packageIndex, int tagIndex) {
+ private String getTagAtIndex(int userIndex, int packageIndex, int tagIndex) {
// This structure never inserts a null ArrayMap, so if the indices are valid, valueAt()
// won't return null.
return mData.valueAt(userIndex, packageIndex).keyAt(tagIndex);
@@ -160,4 +161,26 @@
}
});
}
+
+ public void forEach(UptcDataConsumer<T> consumer) {
+ final int uCount = userCount();
+ for (int u = 0; u < uCount; ++u) {
+ final int userId = getUserIdAtIndex(u);
+
+ final int pkgCount = packageCountForUser(userId);
+ for (int p = 0; p < pkgCount; ++p) {
+ final String pkgName = getPackageNameAtIndex(u, p);
+
+ final int tagCount = tagCountForUserAndPackage(userId, pkgName);
+ for (int t = 0; t < tagCount; ++t) {
+ final String tag = getTagAtIndex(u, p, t);
+ consumer.accept(userId, pkgName, tag, get(userId, pkgName, tag));
+ }
+ }
+ }
+ }
+
+ interface UptcDataConsumer<D> {
+ void accept(int userId, @NonNull String packageName, @Nullable String tag, @Nullable D obj);
+ }
}
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 94c2192..23b94bd 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -751,10 +751,10 @@
if (abort) {
launchObserverNotifyActivityLaunchCancelled(info);
} else {
- logAppTransitionFinished(info);
if (info.isInterestingToLoggerAndObserver()) {
launchObserverNotifyActivityLaunchFinished(info, timestampNs);
}
+ logAppTransitionFinished(info);
}
info.mPendingDrawActivities.clear();
mTransitionInfoList.remove(info);
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 8bedeae..3f3408f 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -243,9 +243,9 @@
import android.app.servertransaction.PauseActivityItem;
import android.app.servertransaction.PipModeChangeItem;
import android.app.servertransaction.ResumeActivityItem;
+import android.app.servertransaction.StartActivityItem;
import android.app.servertransaction.StopActivityItem;
import android.app.servertransaction.TopResumedActivityChangeItem;
-import android.app.servertransaction.WindowVisibilityItem;
import android.app.usage.UsageEvents.Event;
import android.content.ComponentName;
import android.content.Intent;
@@ -555,7 +555,7 @@
private RemoteAnimationDefinition mRemoteAnimationDefinition;
- private AnimatingActivityRegistry mAnimatingActivityRegistry;
+ AnimatingActivityRegistry mAnimatingActivityRegistry;
private Task mLastParent;
@@ -1633,12 +1633,7 @@
requestedVrComponent = (aInfo.requestedVrComponent == null) ?
null : ComponentName.unflattenFromString(aInfo.requestedVrComponent);
- lockTaskLaunchMode = aInfo.lockTaskLaunchMode;
- if (info.applicationInfo.isPrivilegedApp()
- && (lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_ALWAYS
- || lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_NEVER)) {
- lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_DEFAULT;
- }
+ lockTaskLaunchMode = getLockTaskLaunchMode(aInfo, options);
if (options != null) {
pendingOptions = options;
@@ -1646,15 +1641,27 @@
if (usageReport != null) {
appTimeTracker = new AppTimeTracker(usageReport);
}
- final boolean useLockTask = pendingOptions.getLockTaskMode();
- if (useLockTask && lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_DEFAULT) {
- lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED;
- }
// Gets launch display id from options. It returns INVALID_DISPLAY if not set.
mHandoverLaunchDisplayId = options.getLaunchDisplayId();
}
}
+ static int getLockTaskLaunchMode(ActivityInfo aInfo, @Nullable ActivityOptions options) {
+ int lockTaskLaunchMode = aInfo.lockTaskLaunchMode;
+ if (aInfo.applicationInfo.isPrivilegedApp()
+ && (lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_ALWAYS
+ || lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_NEVER)) {
+ lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_DEFAULT;
+ }
+ if (options != null) {
+ final boolean useLockTask = options.getLockTaskMode();
+ if (useLockTask && lockTaskLaunchMode == LOCK_TASK_LAUNCH_MODE_DEFAULT) {
+ lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED;
+ }
+ }
+ return lockTaskLaunchMode;
+ }
+
@Override
ActivityRecord asActivityRecord() {
// I am an activity record!
@@ -2130,8 +2137,10 @@
(intent == null || (intent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0);
}
+ @Override
boolean isFocusable() {
- return mRootWindowContainer.isFocusable(this, isAlwaysFocusable());
+ return super.isFocusable()
+ && (getWindowConfiguration().canReceiveKeys() || isAlwaysFocusable());
}
boolean isResizeable() {
@@ -2482,7 +2491,7 @@
// We are finishing the top focused activity and its stack has nothing to be focused so
// the next focusable stack should be focused.
if (mayAdjustTop
- && (stack.topRunningActivity() == null || !stack.isFocusable())) {
+ && (stack.topRunningActivity() == null || !stack.isTopActivityFocusable())) {
if (shouldAdjustGlobalFocus) {
// Move the entire hierarchy to top with updating global top resumed activity
// and focused application if needed.
@@ -3088,7 +3097,7 @@
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
"Removing app %s delayed=%b animation=%s animating=%b", this, delayed,
- getAnimation(), isAnimating(TRANSITION));
+ getAnimation(), isAnimating(TRANSITION | PARENTS));
ProtoLog.v(WM_DEBUG_ADD_REMOVE, "removeAppToken: %s"
+ " delayed=%b Callers=%s", this, delayed, Debug.getCallers(4));
@@ -3100,7 +3109,7 @@
// If this window was animating, then we need to ensure that the app transition notifies
// that animations have completed in DisplayContent.handleAnimatingStoppedAndTransition(),
// so add to that list now
- if (isAnimating(TRANSITION)) {
+ if (isAnimating(TRANSITION | PARENTS)) {
getDisplayContent().mNoAnimationNotifyOnTransitionFinished.add(token);
}
@@ -3436,7 +3445,7 @@
* color mode set to avoid jank in the middle of the transition.
*/
boolean canShowWindows() {
- return allDrawn && !(isAnimating() && hasNonDefaultColorWindow());
+ return allDrawn && !(isAnimating(PARENTS) && hasNonDefaultColorWindow());
}
/**
@@ -4497,7 +4506,8 @@
sleeping = false;
app.postPendingUiCleanMsg(true);
if (reportToClient) {
- makeClientVisible();
+ mClientVisibilityDeferred = false;
+ makeActiveIfNeeded(starting);
} else {
mClientVisibilityDeferred = true;
}
@@ -4511,23 +4521,6 @@
handleAlreadyVisible();
}
- /** Send visibility change message to the client and pause if needed. */
- void makeClientVisible() {
- mClientVisibilityDeferred = false;
- try {
- mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
- WindowVisibilityItem.obtain(true /* showWindow */));
- makeActiveIfNeeded(null /* activeActivity*/);
- if (isState(STOPPING, STOPPED)) {
- // Set state to STARTED in order to have consistent state with client while
- // making an non-active activity visible from stopped.
- setState(STARTED, "makeClientVisible");
- }
- } catch (Exception e) {
- Slog.w(TAG, "Exception thrown sending visibility update: " + intent.getComponent(), e);
- }
- }
-
void makeInvisible() {
if (!mVisibleRequested) {
if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Already invisible: " + this);
@@ -4556,14 +4549,6 @@
switch (getState()) {
case STOPPING:
case STOPPED:
- if (attachedToProcess()) {
- if (DEBUG_VISIBILITY) {
- Slog.v(TAG_VISIBILITY, "Scheduling invisibility: " + this);
- }
- mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(),
- appToken, WindowVisibilityItem.obtain(false /* showWindow */));
- }
-
// Reset the flag indicating that an app can enter picture-in-picture once the
// activity is hidden
supportsEnterPipOnTaskSwitch = false;
@@ -4595,17 +4580,17 @@
boolean makeActiveIfNeeded(ActivityRecord activeActivity) {
if (shouldResumeActivity(activeActivity)) {
if (DEBUG_VISIBILITY) {
- Slog.v("TAG_VISIBILITY", "Resume visible activity, " + this);
+ Slog.v(TAG_VISIBILITY, "Resume visible activity, " + this);
}
return getActivityStack().resumeTopActivityUncheckedLocked(activeActivity /* prev */,
null /* options */);
} else if (shouldPauseActivity(activeActivity)) {
if (DEBUG_VISIBILITY) {
- Slog.v("TAG_VISIBILITY", "Pause visible activity, " + this);
+ Slog.v(TAG_VISIBILITY, "Pause visible activity, " + this);
}
// An activity must be in the {@link PAUSING} state for the system to validate
// the move to {@link PAUSED}.
- setState(PAUSING, "makeVisibleIfNeeded");
+ setState(PAUSING, "makeActiveIfNeeded");
try {
mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
PauseActivityItem.obtain(finishing, false /* userLeaving */,
@@ -4613,6 +4598,17 @@
} catch (Exception e) {
Slog.w(TAG, "Exception thrown sending pause: " + intent.getComponent(), e);
}
+ } else if (shouldStartActivity()) {
+ if (DEBUG_VISIBILITY) {
+ Slog.v(TAG_VISIBILITY, "Start visible activity, " + this);
+ }
+ setState(STARTED, "makeActiveIfNeeded");
+ try {
+ mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
+ StartActivityItem.obtain());
+ } catch (Exception e) {
+ Slog.w(TAG, "Exception thrown sending start: " + intent.getComponent(), e);
+ }
}
return false;
}
@@ -4656,6 +4652,16 @@
}
/**
+ * Check if activity should be moved to STARTED state.
+ * NOTE: This will not check if activity should be made paused or resumed first, so it must only
+ * be called after checking with {@link #shouldResumeActivity(ActivityRecord)}
+ * and {@link #shouldPauseActivity(ActivityRecord)}.
+ */
+ private boolean shouldStartActivity() {
+ return mVisibleRequested && isState(STOPPED);
+ }
+
+ /**
* Check if activity is eligible to be made active (resumed of paused). The activity:
* - should be paused, stopped or stopping
* - should not be the currently active one or launching behind other tasks
@@ -4861,8 +4867,7 @@
void stopIfPossible() {
if (DEBUG_SWITCH) Slog.d(TAG_SWITCH, "Stopping: " + this);
final ActivityStack stack = getActivityStack();
- if ((intent.getFlags() & Intent.FLAG_ACTIVITY_NO_HISTORY) != 0
- || (info.flags & ActivityInfo.FLAG_NO_HISTORY) != 0) {
+ if (isNoHistory()) {
if (!finishing) {
if (!stack.shouldSleepActivities()) {
if (DEBUG_STATES) Slog.d(TAG_STATES, "no-history finish of " + this);
@@ -4891,16 +4896,13 @@
}
setState(STOPPING, "stopIfPossible");
if (DEBUG_VISIBILITY) {
- Slog.v(TAG_VISIBILITY, "Stopping visibleRequested="
- + mVisibleRequested + " for " + this);
- }
- if (!mVisibleRequested) {
- setVisibility(false);
+ Slog.v(TAG_VISIBILITY, "Stopping:" + this);
}
EventLogTags.writeWmStopActivity(
mUserId, System.identityHashCode(this), shortComponentName);
mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
- StopActivityItem.obtain(mVisibleRequested, configChangeFlags));
+ StopActivityItem.obtain(configChangeFlags));
+
if (stack.shouldSleepOrShutDownActivities()) {
setSleeping(true);
}
@@ -5344,13 +5346,13 @@
if (!allDrawn && w.mightAffectAllDrawn()) {
if (DEBUG_VISIBILITY || WM_DEBUG_ORIENTATION.isLogToLogcat()) {
Slog.v(TAG, "Eval win " + w + ": isDrawn=" + w.isDrawnLw()
- + ", isAnimationSet=" + isAnimating(TRANSITION));
+ + ", isAnimationSet=" + isAnimating(TRANSITION | PARENTS));
if (!w.isDrawnLw()) {
Slog.v(TAG, "Not displayed: s=" + winAnimator.mSurfaceController
+ " pv=" + w.isVisibleByPolicy()
+ " mDrawState=" + winAnimator.drawStateToString()
+ " ph=" + w.isParentWindowHidden() + " th=" + mVisibleRequested
- + " a=" + isAnimating(TRANSITION));
+ + " a=" + isAnimating(TRANSITION | PARENTS));
}
}
@@ -5941,7 +5943,11 @@
mAnimationBoundsLayer = createAnimationBoundsLayer(t);
// Crop to stack bounds.
- t.setWindowCrop(mAnimationBoundsLayer, mTmpRect);
+ if (!WindowManagerService.sHierarchicalAnimations) {
+ // For Hierarchical animation, we don't need to set window crop since the leash
+ // surface size has already same as the animating container.
+ t.setWindowCrop(mAnimationBoundsLayer, mTmpRect);
+ }
t.setLayer(mAnimationBoundsLayer, layer);
// Reparent leash to animation bounds layer.
@@ -5951,7 +5957,7 @@
@Override
void prepareSurfaces() {
- final boolean show = isVisible() || isAnimating();
+ final boolean show = isVisible() || isAnimating(PARENTS);
if (mSurfaceControl != null) {
if (show && !mLastSurfaceShowing) {
@@ -5979,7 +5985,7 @@
}
void attachThumbnailAnimation() {
- if (!isAnimating()) {
+ if (!isAnimating(PARENTS)) {
return;
}
final GraphicBuffer thumbnailHeader =
@@ -5989,9 +5995,10 @@
return;
}
clearThumbnail();
+ final Transaction transaction = getAnimatingContainer().getPendingTransaction();
mThumbnail = new WindowContainerThumbnail(mWmService.mSurfaceFactory,
- getPendingTransaction(), this, thumbnailHeader);
- mThumbnail.startAnimation(getPendingTransaction(), loadThumbnailAnimation(thumbnailHeader));
+ transaction, getAnimatingContainer(), thumbnailHeader);
+ mThumbnail.startAnimation(transaction, loadThumbnailAnimation(thumbnailHeader));
}
/**
@@ -5999,7 +6006,7 @@
* {@link android.app.ActivityOptions#ANIM_OPEN_CROSS_PROFILE_APPS} animation.
*/
void attachCrossProfileAppsThumbnailAnimation() {
- if (!isAnimating()) {
+ if (!isAnimating(PARENTS)) {
return;
}
clearThumbnail();
@@ -6018,13 +6025,13 @@
if (thumbnail == null) {
return;
}
+ final Transaction transaction = getAnimatingContainer().getPendingTransaction();
mThumbnail = new WindowContainerThumbnail(mWmService.mSurfaceFactory,
- getPendingTransaction(), this, thumbnail);
+ transaction, getAnimatingContainer(), thumbnail);
final Animation animation =
getDisplayContent().mAppTransition.createCrossProfileAppsThumbnailAnimationLocked(
win.getFrameLw());
- mThumbnail.startAnimation(getPendingTransaction(), animation, new Point(frame.left,
- frame.top));
+ mThumbnail.startAnimation(transaction, animation, new Point(frame.left, frame.top));
}
private Animation loadThumbnailAnimation(GraphicBuffer thumbnailHeader) {
@@ -6094,20 +6101,21 @@
getDisplayContent().mAppTransition.notifyAppTransitionFinishedLocked(token);
scheduleAnimation();
- if (mAtmService.mRootWindowContainer.allResumedActivitiesIdle()
- || mAtmService.mStackSupervisor.isStoppingNoHistoryActivity()) {
- // If all activities are already idle or there is an activity that must be
- // stopped immediately after visible, then we now need to make sure we perform
- // the full stop of this activity. This is because we won't do that while they are still
- // waiting for the animation to finish.
- if (mAtmService.mStackSupervisor.mStoppingActivities.contains(this)) {
+ if (!mStackSupervisor.mStoppingActivities.isEmpty()
+ || !mStackSupervisor.mFinishingActivities.isEmpty()) {
+ if (mRootWindowContainer.allResumedActivitiesIdle()) {
+ // If all activities are already idle then we now need to make sure we perform
+ // the full stop of this activity. This is because we won't do that while they
+ // are still waiting for the animation to finish.
mStackSupervisor.scheduleIdle();
+ } else if (mRootWindowContainer.allResumedActivitiesVisible()) {
+ // If all resumed activities are already visible (and should be drawn, see
+ // updateReportedVisibility ~ nowVisible) but not idle, we still schedule to
+ // process the stopping and finishing activities because the transition is done.
+ // This also avoids if the next activity never reports idle (e.g. animating view),
+ // the previous will need to wait until idle timeout to be stopped or destroyed.
+ mStackSupervisor.scheduleProcessStoppingAndFinishingActivities();
}
- } else {
- // Instead of doing the full stop routine here, let's just hide any activities
- // we now can, and let them stop when the normal idle happens.
- mStackSupervisor.processStoppingActivities(null /* launchedActivity */,
- true /* onlyUpdateVisibility */, true /* unused */, null /* unused */);
}
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
@@ -6355,6 +6363,14 @@
* aspect ratio.
*/
boolean shouldUseSizeCompatMode() {
+ if (inMultiWindowMode() || getWindowConfiguration().hasWindowDecorCaption()) {
+ final ActivityRecord root = task != null ? task.getRootActivity() : null;
+ if (root != null && root != this && !root.shouldUseSizeCompatMode()) {
+ // If the root activity doesn't use size compatibility mode, the activities above
+ // are forced to be the same for consistent visual appearance.
+ return false;
+ }
+ }
return !isResizeable() && (info.isFixedOrientation() || info.hasFixedAspectRatio())
// The configuration of non-standard type should be enforced by system.
&& isActivityTypeStandard()
@@ -7201,7 +7217,7 @@
// {@link ActivityTaskManagerService.activityStopped}).
try {
mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), appToken,
- StopActivityItem.obtain(false /* showWindow */, 0 /* configChanges */));
+ StopActivityItem.obtain(0 /* configChanges */));
} catch (RemoteException e) {
Slog.w(TAG, "Exception thrown during restart " + this, e);
}
@@ -7511,7 +7527,7 @@
super.dumpDebug(proto, WINDOW_TOKEN, logLevel);
proto.write(LAST_SURFACE_SHOWING, mLastSurfaceShowing);
proto.write(IS_WAITING_FOR_TRANSITION_START, isWaitingForTransitionStart());
- proto.write(IS_ANIMATING, isAnimating());
+ proto.write(IS_ANIMATING, isAnimating(PARENTS));
if (mThumbnail != null){
mThumbnail.dumpDebug(proto, THUMBNAIL);
}
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index ef34327..f5fba8e 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -161,6 +161,7 @@
import android.view.Display;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
+import android.view.ITaskOrganizer;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
@@ -784,6 +785,10 @@
null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
null /* tempOtherTaskBounds */, null /* tempOtherTaskInsetBounds */,
PRESERVE_WINDOWS, true /* deferResume */);
+ } else if (overrideWindowingMode != WINDOWING_MODE_PINNED) {
+ // For pinned stack, resize is now part of the {@link WindowContainerTransaction}
+ resize(new Rect(newBounds), null /* tempTaskBounds */,
+ null /* tempTaskInsetBounds */, PRESERVE_WINDOWS, true /* deferResume */);
}
}
if (prevIsAlwaysOnTop != isAlwaysOnTop()) {
@@ -799,6 +804,16 @@
setWindowingMode(windowingMode, false /* animate */, false /* showRecents */,
false /* enteringSplitScreenMode */, false /* deferEnsuringVisibility */,
false /* creating */);
+
+ windowingMode = getWindowingMode();
+ /*
+ * Different windowing modes may be managed by different task organizers. If
+ * getTaskOrganizer returns null, we still call transferToTaskOrganizer to
+ * make sure we clear it.
+ */
+ final ITaskOrganizer org =
+ mWmService.mAtmService.mTaskOrganizerController.getTaskOrganizer(windowingMode);
+ transferToTaskOrganizer(org);
}
/**
@@ -1233,13 +1248,20 @@
}
}
+ @Override
boolean isFocusable() {
+ return super.isFocusable() && !(inSplitScreenPrimaryWindowingMode()
+ && mRootWindowContainer.mIsDockMinimized);
+ }
+
+ boolean isTopActivityFocusable() {
final ActivityRecord r = topRunningActivity();
- return mRootWindowContainer.isFocusable(this, r != null && r.isFocusable());
+ return r != null ? r.isFocusable()
+ : (isFocusable() && getWindowConfiguration().canReceiveKeys());
}
boolean isFocusableAndVisible() {
- return isFocusable() && shouldBeVisible(null /* starting */);
+ return isTopActivityFocusable() && shouldBeVisible(null /* starting */);
}
@Override
@@ -1414,8 +1436,7 @@
else if (DEBUG_PAUSE) Slog.v(TAG_PAUSE, "Start pausing: " + prev);
mPausingActivity = prev;
mLastPausedActivity = prev;
- mLastNoHistoryActivity = (prev.intent.getFlags() & Intent.FLAG_ACTIVITY_NO_HISTORY) != 0
- || (prev.info.flags & ActivityInfo.FLAG_NO_HISTORY) != 0 ? prev : null;
+ mLastNoHistoryActivity = prev.isNoHistory() ? prev : null;
prev.setState(PAUSING, "startPausingLocked");
prev.getTask().touchActiveTime();
clearLaunchTime(prev);
@@ -1647,6 +1668,33 @@
}
/**
+ * Indicate whether the first task in this stack is controlled by a TaskOrganizer. We aren't
+ * expecting to use the TaskOrganizer in multiple task per stack scenarios so checking
+ * the first one is ok.
+ */
+ boolean isControlledByTaskOrganizer() {
+ return getChildCount() > 0 && getTopMostTask().mTaskOrganizer != null;
+ }
+
+ private static void transferSingleTaskToOrganizer(Task tr, ITaskOrganizer organizer) {
+ tr.setTaskOrganizer(organizer);
+ }
+
+ /**
+ * Transfer control of the leashes and IWindowContainers to the given ITaskOrganizer.
+ * This will (or shortly there-after) invoke the taskAppeared callbacks.
+ * If the tasks had a previous TaskOrganizer, setTaskOrganizer will take care of
+ * emitting the taskVanished callbacks.
+ */
+ void transferToTaskOrganizer(ITaskOrganizer organizer) {
+ final PooledConsumer c = PooledLambda.obtainConsumer(
+ ActivityStack::transferSingleTaskToOrganizer,
+ PooledLambda.__(Task.class), organizer);
+ forAllTasks(c);
+ c.recycle();
+ }
+
+ /**
* Returns true if the stack should be visible.
*
* @param starting The currently starting activity or null if there is none.
@@ -3574,6 +3622,15 @@
void animateResizePinnedStack(Rect toBounds, Rect sourceHintBounds, int animationDuration,
boolean fromFullscreen) {
if (!inPinnedWindowingMode()) return;
+
+ /**
+ * TODO(b/146594635): Remove all PIP animation code from WM once SysUI handles animation.
+ * If this PIP Task is controlled by a TaskOrganizer, the animation occurs entirely
+ * on the TaskOrganizer side, so we just hand over the leash without doing any animation.
+ * We have to be careful to not schedule the enter-pip callback as the TaskOrganizer
+ * needs to have flexibility to schedule that at an appropriate point in the animation.
+ */
+ if (isControlledByTaskOrganizer()) return;
if (toBounds == null /* toFullscreen */) {
final Configuration parentConfig = getParent().getConfiguration();
final ActivityRecord top = topRunningNonOverlayTaskActivity();
@@ -4889,6 +4946,18 @@
}
}
+ @Override
+ protected void onAnimationFinished() {
+ super.onAnimationFinished();
+ // TODO(b/142617871): we may need to add animation type parameter on onAnimationFinished to
+ // identify if the callback is for launch animation finish and then calling
+ // activity#onAnimationFinished.
+ final ActivityRecord activity = getTopMostActivity();
+ if (activity != null) {
+ activity.onAnimationFinished();
+ }
+ }
+
/**
* Sets the current picture-in-picture aspect ratio.
*/
diff --git a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
index ed73943..aa90248 100644
--- a/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityStackSupervisor.java
@@ -83,6 +83,7 @@
import static com.android.server.wm.Task.REPARENT_KEEP_STACK_AT_FRONT;
import static com.android.server.wm.Task.REPARENT_LEAVE_STACK_IN_PLACE;
import static com.android.server.wm.Task.REPARENT_MOVE_STACK_TO_FRONT;
+import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
@@ -183,6 +184,7 @@
private static final int RESUME_TOP_ACTIVITY_MSG = FIRST_SUPERVISOR_STACK_MSG + 2;
private static final int SLEEP_TIMEOUT_MSG = FIRST_SUPERVISOR_STACK_MSG + 3;
private static final int LAUNCH_TIMEOUT_MSG = FIRST_SUPERVISOR_STACK_MSG + 4;
+ private static final int PROCESS_STOPPING_AND_FINISHING_MSG = FIRST_SUPERVISOR_STACK_MSG + 5;
private static final int LAUNCH_TASK_BEHIND_COMPLETE = FIRST_SUPERVISOR_STACK_MSG + 12;
private static final int RESTART_ACTIVITY_PROCESS_TIMEOUT_MSG = FIRST_SUPERVISOR_STACK_MSG + 13;
private static final int REPORT_MULTI_WINDOW_MODE_CHANGED_MSG = FIRST_SUPERVISOR_STACK_MSG + 14;
@@ -2068,23 +2070,6 @@
}
/**
- * Returns whether a stopping activity is present that should be stopped after visible, rather
- * than idle.
- * @return {@code true} if such activity is present. {@code false} otherwise.
- */
- boolean isStoppingNoHistoryActivity() {
- // Activities that are marked as nohistory should be stopped immediately after the resumed
- // activity has become visible.
- for (ActivityRecord record : mStoppingActivities) {
- if (record.isNoHistory()) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
* Processes the activities to be stopped or destroyed. This should be called when the resumed
* activities are idle or drawn.
*/
@@ -2092,54 +2077,11 @@
boolean processPausingActivities, String reason) {
// Stop any activities that are scheduled to do so but have been waiting for the transition
// animation to finish.
- processStoppingActivities(launchedActivity, false /* onlyUpdateVisibility */,
- processPausingActivities, reason);
-
- final int numFinishingActivities = mFinishingActivities.size();
- if (numFinishingActivities == 0) {
- return;
- }
-
- // Finish any activities that are scheduled to do so but have been waiting for the next one
- // to start.
- final ArrayList<ActivityRecord> finishingActivities = new ArrayList<>(mFinishingActivities);
- mFinishingActivities.clear();
- for (int i = 0; i < numFinishingActivities; i++) {
- final ActivityRecord r = finishingActivities.get(i);
- if (r.isInHistory()) {
- r.destroyImmediately(true /* removeFromApp */, "finish-" + reason);
- }
- }
- }
-
- /** Stop or destroy the stopping activities if they are not interactive. */
- void processStoppingActivities(ActivityRecord launchedActivity, boolean onlyUpdateVisibility,
- boolean processPausingActivities, String reason) {
- final int numStoppingActivities = mStoppingActivities.size();
- if (numStoppingActivities == 0) {
- return;
- }
ArrayList<ActivityRecord> readyToStopActivities = null;
-
- final boolean nowVisible = mRootWindowContainer.allResumedActivitiesVisible();
- for (int activityNdx = numStoppingActivities - 1; activityNdx >= 0; --activityNdx) {
- final ActivityRecord s = mStoppingActivities.get(activityNdx);
- if (nowVisible && s.finishing) {
-
- // If this activity is finishing, it is sitting on top of
- // everyone else but we now know it is no longer needed...
- // so get rid of it. Otherwise, we need to go through the
- // normal flow and hide it once we determine that it is
- // hidden by the activities in front of it.
- if (DEBUG_STATES) Slog.v(TAG, "Before stopping, can hide: " + s);
- s.setVisibility(false);
- }
- if (onlyUpdateVisibility) {
- continue;
- }
-
- final boolean animating = s.isAnimating(TRANSITION);
- if (DEBUG_STATES) Slog.v(TAG, "Stopping " + s + ": nowVisible=" + nowVisible
+ for (int i = mStoppingActivities.size() - 1; i >= 0; --i) {
+ final ActivityRecord s = mStoppingActivities.get(i);
+ final boolean animating = s.isAnimating(TRANSITION | PARENTS);
+ if (DEBUG_STATES) Slog.v(TAG, "Stopping " + s + ": nowVisible=" + s.nowVisible
+ " animating=" + animating + " finishing=" + s.finishing);
final ActivityStack stack = s.getActivityStack();
@@ -2161,7 +2103,7 @@
}
readyToStopActivities.add(s);
- mStoppingActivities.remove(activityNdx);
+ mStoppingActivities.remove(i);
}
}
@@ -2177,6 +2119,22 @@
}
}
}
+
+ final int numFinishingActivities = mFinishingActivities.size();
+ if (numFinishingActivities == 0) {
+ return;
+ }
+
+ // Finish any activities that are scheduled to do so but have been waiting for the next one
+ // to start.
+ final ArrayList<ActivityRecord> finishingActivities = new ArrayList<>(mFinishingActivities);
+ mFinishingActivities.clear();
+ for (int i = 0; i < numFinishingActivities; i++) {
+ final ActivityRecord r = finishingActivities.get(i);
+ if (r.isInHistory()) {
+ r.destroyImmediately(true /* removeFromApp */, "finish-" + reason);
+ }
+ }
}
void removeHistoryRecords(WindowProcessController app) {
@@ -2403,6 +2361,12 @@
}
}
+ void scheduleProcessStoppingAndFinishingActivities() {
+ if (!mHandler.hasMessages(PROCESS_STOPPING_AND_FINISHING_MSG)) {
+ mHandler.sendEmptyMessage(PROCESS_STOPPING_AND_FINISHING_MSG);
+ }
+ }
+
void removeSleepTimeouts() {
mHandler.removeMessages(SLEEP_TIMEOUT_MSG);
}
@@ -2584,6 +2548,7 @@
final PooledConsumer c = PooledLambda.obtainConsumer(
ActivityRecord::updatePictureInPictureMode,
PooledLambda.__(ActivityRecord.class), targetStackBounds, forceUpdate);
+ task.getStack().setBounds(targetStackBounds);
task.forAllActivities(c);
c.recycle();
}
@@ -2701,6 +2666,10 @@
mLaunchingActivityWakeLock.release();
}
} break;
+ case PROCESS_STOPPING_AND_FINISHING_MSG: {
+ processStoppingAndFinishingActivities(null /* launchedActivity */,
+ false /* processPausingActivities */, "transit");
+ } break;
case LAUNCH_TASK_BEHIND_COMPLETE: {
final ActivityRecord r = ActivityRecord.forTokenLocked((IBinder) msg.obj);
if (r != null) {
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index df97caa..d61d29d 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -52,6 +52,7 @@
import android.os.UserManager;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.BlockedAppActivity;
import com.android.internal.app.HarmfulAppWarningActivity;
import com.android.internal.app.SuspendedAppActivity;
import com.android.internal.app.UnlaunchableAppActivity;
@@ -166,6 +167,9 @@
// no user action can undo this.
return true;
}
+ if (interceptLockTaskModeViolationPackageIfNeeded()) {
+ return true;
+ }
if (interceptHarmfulAppIfNeeded()) {
// If the app has a "harmful app" warning associated with it, we should ask to uninstall
// before issuing the work challenge.
@@ -262,6 +266,25 @@
return true;
}
+ private boolean interceptLockTaskModeViolationPackageIfNeeded() {
+ if (mAInfo == null || mAInfo.applicationInfo == null) {
+ return false;
+ }
+ LockTaskController controller = mService.getLockTaskController();
+ String packageName = mAInfo.applicationInfo.packageName;
+ int lockTaskLaunchMode = ActivityRecord.getLockTaskLaunchMode(mAInfo, mActivityOptions);
+ if (controller.isActivityAllowed(mUserId, packageName, lockTaskLaunchMode)) {
+ return false;
+ }
+ mIntent = BlockedAppActivity.createIntent(mUserId, mAInfo.applicationInfo.packageName);
+ mCallingPid = mRealCallingPid;
+ mCallingUid = mRealCallingUid;
+ mResolvedType = null;
+ mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, mUserId, 0, mRealCallingUid);
+ mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, null /*profilerInfo*/);
+ return true;
+ }
+
private boolean interceptWorkProfileChallengeIfNeeded() {
final Intent interceptingIntent = interceptWithConfirmCredentialsIfNeeded(mAInfo, mUserId);
if (interceptingIntent == null) {
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 61ba15c..6587226 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -1569,7 +1569,7 @@
if (mDoResume) {
final ActivityRecord topTaskActivity =
mStartActivity.getTask().topRunningActivityLocked();
- if (!mTargetStack.isFocusable()
+ if (!mTargetStack.isTopActivityFocusable()
|| (topTaskActivity != null && topTaskActivity.isTaskOverlay()
&& mStartActivity != topTaskActivity)) {
// If the activity is not focusable, we can't resume it, but still would like to
@@ -1588,7 +1588,7 @@
// will not update the focused stack. If starting the new activity now allows the
// task stack to be focusable, then ensure that we now update the focused stack
// accordingly.
- if (mTargetStack.isFocusable()
+ if (mTargetStack.isTopActivityFocusable()
&& !mRootWindowContainer.isTopDisplayFocusedStack(mTargetStack)) {
mTargetStack.moveToFront("startActivityInner");
}
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 76c0e4e..e308f6b 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -28,7 +28,7 @@
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
import static android.Manifest.permission.STOP_APP_SWITCHES;
import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
-import static android.app.ActivityManagerInternal.ALLOW_FULL_ONLY;
+import static android.app.ActivityManagerInternal.ALLOW_NON_FULL;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
import static android.app.ActivityTaskManager.RESIZE_MODE_PRESERVE_WINDOW;
import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
@@ -226,6 +226,7 @@
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
import android.view.IRecentsAnimationRunner;
+import android.view.ITaskOrganizer;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationDefinition;
import android.view.WindowContainerTransaction;
@@ -247,7 +248,6 @@
import com.android.internal.policy.KeyguardDismissCallback;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastPrintWriter;
-import com.android.internal.util.Preconditions;
import com.android.internal.util.function.pooled.PooledConsumer;
import com.android.internal.util.function.pooled.PooledFunction;
import com.android.internal.util.function.pooled.PooledLambda;
@@ -344,6 +344,10 @@
/** This activity is being relaunched due to a free-resize operation. */
public static final int RELAUNCH_REASON_FREE_RESIZE = 2;
+ /** Flag indicating that an applied transaction may have effected lifecycle */
+ private static final int TRANSACT_EFFECTS_CLIENT_CONFIG = 1;
+ private static final int TRANSACT_EFFECTS_LIFECYCLE = 1 << 1;
+
Context mContext;
/**
@@ -662,6 +666,12 @@
private FontScaleSettingObserver mFontScaleSettingObserver;
+ /**
+ * Stores the registration and state of TaskOrganizers in use.
+ */
+ TaskOrganizerController mTaskOrganizerController =
+ new TaskOrganizerController(this, mGlobalLock);
+
private int mDeviceOwnerUid = Process.INVALID_UID;
private final class FontScaleSettingObserver extends ContentObserver {
@@ -1271,6 +1281,14 @@
.execute();
}
+ @Override
+ public final void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode) {
+ enforceCallerIsRecentsOrHasPermission(
+ MANAGE_ACTIVITY_STACKS, "registerTaskOrganizer()");
+ synchronized (mGlobalLock) {
+ mTaskOrganizerController.registerTaskOrganizer(organizer, windowingMode);
+ }
+ }
@Override
public IBinder requestStartActivityPermissionToken(IBinder delegatorToken) {
@@ -1421,7 +1439,7 @@
int handleIncomingUser(int callingPid, int callingUid, int userId, String name) {
return mAmInternal.handleIncomingUser(callingPid, callingUid, userId, false /* allowAll */,
- ALLOW_FULL_ONLY, name, null /* callerPackage */);
+ ALLOW_NON_FULL, name, null /* callerPackage */);
}
@Override
@@ -3285,7 +3303,7 @@
}
}
- private void sanitizeAndApplyConfigChange(ConfigurationContainer container,
+ private int sanitizeAndApplyChange(ConfigurationContainer container,
WindowContainerTransaction.Change change) {
if (!(container instanceof Task || container instanceof ActivityStack)) {
throw new RuntimeException("Invalid token in task transaction");
@@ -3297,12 +3315,22 @@
configMask &= ActivityInfo.CONFIG_WINDOW_CONFIGURATION
| ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
windowMask &= WindowConfiguration.WINDOW_CONFIG_BOUNDS;
- Configuration c = new Configuration(container.getRequestedOverrideConfiguration());
- c.setTo(change.getConfiguration(), configMask, windowMask);
- container.onRequestedOverrideConfigurationChanged(c);
- // TODO(b/145675353): remove the following once we could apply new bounds to the
- // pinned stack together with its children.
- resizePinnedStackIfNeeded(container, configMask, windowMask, c);
+ int effects = 0;
+ if (configMask != 0) {
+ Configuration c = new Configuration(container.getRequestedOverrideConfiguration());
+ c.setTo(change.getConfiguration(), configMask, windowMask);
+ container.onRequestedOverrideConfigurationChanged(c);
+ // TODO(b/145675353): remove the following once we could apply new bounds to the
+ // pinned stack together with its children.
+ resizePinnedStackIfNeeded(container, configMask, windowMask, c);
+ effects |= TRANSACT_EFFECTS_CLIENT_CONFIG;
+ }
+ if ((change.getChangeMask() & WindowContainerTransaction.Change.CHANGE_FOCUSABLE) != 0) {
+ if (container.setFocusable(change.getFocusable())) {
+ effects |= TRANSACT_EFFECTS_LIFECYCLE;
+ }
+ }
+ return effects;
}
private void resizePinnedStackIfNeeded(ConfigurationContainer container, int configMask,
@@ -3319,23 +3347,68 @@
}
}
+ private int applyWindowContainerChange(ConfigurationContainer cc,
+ WindowContainerTransaction.Change c) {
+ int effects = sanitizeAndApplyChange(cc, c);
+
+ Rect enterPipBounds = c.getEnterPipBounds();
+ if (enterPipBounds != null) {
+ Task tr = (Task) cc;
+ mStackSupervisor.updatePictureInPictureMode(tr,
+ enterPipBounds, true);
+ }
+ return effects;
+ }
+
@Override
public void applyContainerTransaction(WindowContainerTransaction t) {
mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "applyContainerTransaction()");
+ if (t == null) {
+ return;
+ }
long ident = Binder.clearCallingIdentity();
try {
- if (t == null) {
- return;
- }
synchronized (mGlobalLock) {
- Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries =
- t.getChanges().entrySet().iterator();
- while (entries.hasNext()) {
- final Map.Entry<IBinder, WindowContainerTransaction.Change> entry =
- entries.next();
- final ConfigurationContainer cc = ConfigurationContainer.RemoteToken.fromBinder(
- entry.getKey()).getContainer();
- sanitizeAndApplyConfigChange(cc, entry.getValue());
+ int effects = 0;
+ deferWindowLayout();
+ try {
+ ArraySet<WindowContainer> haveConfigChanges = new ArraySet<>();
+ Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries =
+ t.getChanges().entrySet().iterator();
+ while (entries.hasNext()) {
+ final Map.Entry<IBinder, WindowContainerTransaction.Change> entry =
+ entries.next();
+ final ConfigurationContainer cc =
+ ConfigurationContainer.RemoteToken.fromBinder(
+ entry.getKey()).getContainer();
+ int containerEffect = applyWindowContainerChange(cc, entry.getValue());
+ effects |= containerEffect;
+ // Lifecycle changes will trigger ensureConfig for everything.
+ if ((effects & TRANSACT_EFFECTS_LIFECYCLE) == 0
+ && (containerEffect & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) {
+ if (cc instanceof WindowContainer) {
+ haveConfigChanges.add((WindowContainer) cc);
+ }
+ }
+ }
+ if ((effects & TRANSACT_EFFECTS_LIFECYCLE) != 0) {
+ // Already calls ensureActivityConfig
+ mRootWindowContainer.ensureActivitiesVisible(null, 0, PRESERVE_WINDOWS);
+ } else if ((effects & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) {
+ final PooledConsumer f = PooledLambda.obtainConsumer(
+ ActivityRecord::ensureActivityConfiguration,
+ PooledLambda.__(ActivityRecord.class), 0,
+ false /* preserveWindow */);
+ try {
+ for (int i = haveConfigChanges.size() - 1; i >= 0; --i) {
+ haveConfigChanges.valueAt(i).forAllActivities(f);
+ }
+ } finally {
+ f.recycle();
+ }
+ }
+ } finally {
+ continueWindowLayout();
}
}
} finally {
@@ -4057,7 +4130,11 @@
throw new IllegalArgumentException("Stack: " + stack
+ " doesn't support animated resize.");
}
- if (animate) {
+ /**
+ * TODO(b/146594635): Remove all PIP animation code from WM
+ * once SysUI handles animation. Don't even try to animate TaskOrganized tasks.
+ */
+ if (animate && !stack.isControlledByTaskOrganizer()) {
stack.animateResizePinnedStack(null /* destBounds */,
null /* sourceHintBounds */, animationDuration,
false /* fromFullscreen */);
@@ -7203,7 +7280,8 @@
ActivityManagerServiceDumpProcessesProto.VR_CONTROLLER);
if (mController != null) {
final long token = proto.start(CONTROLLER);
- proto.write(CONTROLLER, mController.toString());
+ proto.write(ActivityManagerServiceDumpProcessesProto.Controller.CONTROLLER,
+ mController.toString());
proto.write(IS_A_MONKEY, mControllerIsAMonkey);
proto.end(token);
}
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 3a33a3d..014cb76 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -436,9 +436,10 @@
mNextAppTransition = TRANSIT_UNSET;
mNextAppTransitionFlags = 0;
setAppTransitionState(APP_STATE_RUNNING);
- final AnimationAdapter topOpeningAnim = topOpeningApp != null
- ? topOpeningApp.getAnimation()
- : null;
+ final WindowContainer wc =
+ topOpeningApp != null ? topOpeningApp.getAnimatingContainer() : null;
+ final AnimationAdapter topOpeningAnim = wc != null ? wc.getAnimation() : null;
+
int redoLayout = notifyAppTransitionStartingLocked(transit,
topOpeningAnim != null ? topOpeningAnim.getDurationHint() : 0,
topOpeningAnim != null
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index dd3365c..1e60ce8 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -624,6 +624,21 @@
return mFullConfiguration.windowConfiguration.isAlwaysOnTop();
}
+ /**
+ * Returns {@code true} if this container is focusable. Generally, if a parent is not focusable,
+ * this will not be focusable either.
+ */
+ boolean isFocusable() {
+ // TODO(split): Move this to WindowContainer once Split-screen is based on a WindowContainer
+ // like DisplayArea vs. TaskTiles.
+ ConfigurationContainer parent = getParent();
+ return parent == null || parent.isFocusable();
+ }
+
+ boolean setFocusable(boolean focusable) {
+ return false;
+ }
+
boolean hasChild() {
return getChildCount() > 0;
}
@@ -666,4 +681,8 @@
return sb.toString();
}
}
+
+ RemoteToken getRemoteToken() {
+ return mRemoteToken;
+ }
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 67fe149..908c4f1 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -205,6 +205,7 @@
import android.view.DisplayCutout;
import android.view.DisplayInfo;
import android.view.Gravity;
+import android.view.IDisplayWindowInsetsController;
import android.view.ISystemGestureExclusionListener;
import android.view.IWindow;
import android.view.InputChannel;
@@ -218,6 +219,7 @@
import android.view.SurfaceControl.Transaction;
import android.view.SurfaceSession;
import android.view.View;
+import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowManagerPolicyConstants.PointerEventListener;
@@ -570,6 +572,8 @@
*/
WindowState mInputMethodTarget;
+ InsetsControlTarget mInputMethodControlTarget;
+
/** If true hold off on modifying the animation layer of mInputMethodTarget */
boolean mInputMethodTargetWaitingAnim;
@@ -598,6 +602,13 @@
private final float mWindowCornerRadius;
private final SparseArray<ShellRoot> mShellRoots = new SparseArray<>();
+ RemoteInsetsControlTarget mRemoteInsetsControlTarget = null;
+ private final IBinder.DeathRecipient mRemoteInsetsDeath =
+ () -> {
+ synchronized (mWmService.mGlobalLock) {
+ mRemoteInsetsControlTarget = null;
+ }
+ };
private RootWindowContainer mRootWindowContainer;
@@ -772,7 +783,11 @@
// If this is the first layout, we need to initialize the last frames and inset values,
// as otherwise we'd immediately cause an unnecessary resize.
if (firstLayout) {
- w.updateLastFrames();
+ // The client may compute its actual requested size according to the first layout,
+ // so we still request the window to resize if the current frame is empty.
+ if (!w.getFrameLw().isEmpty()) {
+ w.updateLastFrames();
+ }
w.updateLastInsetValues();
w.updateLocationInParentDisplayIfNeeded();
}
@@ -1032,31 +1047,13 @@
// Sets the display content for the children.
onDisplayChanged(this);
- // Add itself as a child to the root container.
- mWmService.mRoot.addChild(this, POSITION_BOTTOM);
-
- // TODO(b/62541591): evaluate whether this is the best spot to declare the
- // {@link DisplayContent} ready for use.
- mDisplayReady = true;
-
- mWmService.mAnimator.addDisplayLocked(mDisplayId);
- mInputMonitor = new InputMonitor(mWmService, mDisplayId);
+ mInputMonitor = new InputMonitor(mWmService, this);
mInsetsStateController = new InsetsStateController(this);
mInsetsPolicy = new InsetsPolicy(mInsetsStateController, this);
- if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Adding display=" + display);
+ if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Creating display=" + display);
mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this);
-
- if (mWmService.mDisplayManagerInternal != null) {
- mWmService.mDisplayManagerInternal
- .setDisplayInfoOverrideFromWindowManager(mDisplayId, getDisplayInfo());
- configureDisplayPolicy();
- }
-
- reconfigureDisplayLocked();
- onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration());
- mWmService.mDisplayNotificationController.dispatchDisplayAdded(this);
}
boolean isReady() {
@@ -1170,6 +1167,22 @@
mShellRoots.remove(windowType);
}
+ void setRemoteInsetsController(IDisplayWindowInsetsController controller) {
+ if (mRemoteInsetsControlTarget != null) {
+ mRemoteInsetsControlTarget.mRemoteInsetsController.asBinder().unlinkToDeath(
+ mRemoteInsetsDeath, 0);
+ mRemoteInsetsControlTarget = null;
+ }
+ if (controller != null) {
+ try {
+ controller.asBinder().linkToDeath(mRemoteInsetsDeath, 0);
+ mRemoteInsetsControlTarget = new RemoteInsetsControlTarget(controller);
+ } catch (RemoteException e) {
+ return;
+ }
+ }
+ }
+
/** Changes the display the input window token is housed on to this one. */
void reParentWindowToken(WindowToken token) {
final DisplayContent prevDc = token.getDisplayContent();
@@ -3360,7 +3373,7 @@
// to look at all windows below the current target that are in this app, finding the
// highest visible one in layering.
WindowState highestTarget = null;
- if (activity.isAnimating(TRANSITION)) {
+ if (activity.isAnimating(PARENTS | TRANSITION)) {
highestTarget = activity.getHighestAnimLayerWindow(curTarget);
}
@@ -3397,6 +3410,14 @@
}
}
+ boolean isImeAttachedToApp() {
+ return (mInputMethodTarget != null && mInputMethodTarget.mActivityRecord != null
+ && mInputMethodTarget.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+ // An activity with override bounds should be letterboxed inside its parent bounds,
+ // so it doesn't fill the screen.
+ && mInputMethodTarget.mActivityRecord.matchParentBounds());
+ }
+
private void setInputMethodTarget(WindowState target, boolean targetWaitingAnim) {
if (target == mInputMethodTarget && mInputMethodTargetWaitingAnim == targetWaitingAnim) {
return;
@@ -3405,7 +3426,8 @@
mInputMethodTarget = target;
mInputMethodTargetWaitingAnim = targetWaitingAnim;
assignWindowLayers(false /* setLayoutNeeded */);
- mInsetsStateController.onImeTargetChanged(target);
+ mInputMethodControlTarget = computeImeControlTarget();
+ mInsetsStateController.onImeTargetChanged(mInputMethodControlTarget);
updateImeParent();
}
@@ -3430,11 +3452,7 @@
// Attach it to app if the target is part of an app and such app is covering the entire
// screen. If it's not covering the entire screen the IME might extend beyond the apps
// bounds.
- if (mInputMethodTarget != null && mInputMethodTarget.mActivityRecord != null
- && mInputMethodTarget.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
- // An activity with override bounds should be letterboxed inside its parent bounds,
- // so it doesn't fill the screen.
- && mInputMethodTarget.mActivityRecord.matchParentBounds()) {
+ if (isImeAttachedToApp()) {
return mInputMethodTarget.mActivityRecord.getSurfaceControl();
}
@@ -3442,6 +3460,19 @@
return mWindowContainers.getSurfaceControl();
}
+ /**
+ * Computes which control-target the IME should be attached to.
+ */
+ @VisibleForTesting
+ InsetsControlTarget computeImeControlTarget() {
+ if (!isImeAttachedToApp() && mRemoteInsetsControlTarget != null) {
+ return mRemoteInsetsControlTarget;
+ }
+
+ // Otherwise, we just use the ime target
+ return mInputMethodTarget;
+ }
+
void setLayoutNeeded() {
if (DEBUG_LAYOUT) Slog.w(TAG_WM, "setLayoutNeeded: callers=" + Debug.getCallers(3));
mLayoutNeeded = true;
@@ -4802,10 +4833,8 @@
} else {
final int count = mChildren.size();
for (int i = 0; i < count; i++) {
- Slog.d(TAG, "child " + mChildren.get(i));
final WindowContainer child = mChildren.get(i);
if (skipTraverseChild(child)) {
- Slog.d(TAG, "child skipped");
continue;
}
@@ -4994,6 +5023,24 @@
// we create the root surfaces explicitly rather than chaining
// up as the default implementation in onParentChanged does. So we
// explicitly do NOT call super here.
+
+ if (!isReady()) {
+ // TODO(b/62541591): evaluate whether this is the best spot to declare the
+ // {@link DisplayContent} ready for use.
+ mDisplayReady = true;
+
+ mWmService.mAnimator.addDisplayLocked(mDisplayId);
+
+ if (mWmService.mDisplayManagerInternal != null) {
+ mWmService.mDisplayManagerInternal
+ .setDisplayInfoOverrideFromWindowManager(mDisplayId, getDisplayInfo());
+ configureDisplayPolicy();
+ }
+
+ reconfigureDisplayLocked();
+ onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration());
+ mWmService.mDisplayNotificationController.dispatchDisplayAdded(this);
+ }
}
@Override
@@ -5868,7 +5915,7 @@
final ActivityRecord resumedActivity = stack.getResumedActivity();
if (resumedActivity != null
&& (stack.getVisibility(resuming) != STACK_VISIBILITY_VISIBLE
- || !stack.isFocusable())) {
+ || !stack.isTopActivityFocusable())) {
if (DEBUG_STATES) Slog.d(TAG_STATES, "pauseBackStacks: stack=" + stack
+ " mResumedActivity=" + resumedActivity);
someActivityPaused |= stack.startPausingLocked(userLeaving, false /* uiSleeping*/,
@@ -6191,7 +6238,7 @@
for (int i = getStackCount() - 1; i >= 0; --i) {
final ActivityStack stack = getStackAt(i);
// Only consider focusable stacks other than the current focused one.
- if (stack == focusedStack || !stack.isFocusable()) {
+ if (stack == focusedStack || !stack.isTopActivityFocusable()) {
continue;
}
topRunning = stack.topRunningActivity();
@@ -6686,4 +6733,50 @@
Context getDisplayUiContext() {
return mDisplayPolicy.getSystemUiContext();
}
+
+ class RemoteInsetsControlTarget implements InsetsControlTarget {
+ private final IDisplayWindowInsetsController mRemoteInsetsController;
+
+ RemoteInsetsControlTarget(IDisplayWindowInsetsController controller) {
+ mRemoteInsetsController = controller;
+ }
+
+ void notifyInsetsChanged() {
+ try {
+ mRemoteInsetsController.insetsChanged(
+ getInsetsStateController().getRawInsetsState());
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to deliver inset state change", e);
+ }
+ }
+
+ @Override
+ public void notifyInsetsControlChanged() {
+ final InsetsStateController stateController = getInsetsStateController();
+ try {
+ mRemoteInsetsController.insetsControlChanged(stateController.getRawInsetsState(),
+ stateController.getControlsForDispatch(this));
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to deliver inset state change", e);
+ }
+ }
+
+ @Override
+ public void showInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme) {
+ try {
+ mRemoteInsetsController.showInsets(types, fromIme);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to deliver showInsets", e);
+ }
+ }
+
+ @Override
+ public void hideInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme) {
+ try {
+ mRemoteInsetsController.hideInsets(types, fromIme);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Failed to deliver showInsets", e);
+ }
+ }
+ }
}
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 694a73d..9c62e99 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -2061,7 +2061,8 @@
cf.set(displayFrames.mRestricted);
}
applyStableConstraints(sysUiFl, fl, cf, displayFrames);
- if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
+ if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE
+ && adjust != SOFT_INPUT_ADJUST_NOTHING) {
vf.set(displayFrames.mCurrent);
} else {
vf.set(cf);
@@ -2138,7 +2139,8 @@
applyStableConstraints(sysUiFl, fl, cf, displayFrames);
- if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
+ if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE
+ && adjust != SOFT_INPUT_ADJUST_NOTHING) {
vf.set(displayFrames.mCurrent);
} else {
vf.set(cf);
@@ -2179,7 +2181,8 @@
cf.set(displayFrames.mContent);
df.set(displayFrames.mContent);
}
- if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
+ if (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_NONE
+ && adjust != SOFT_INPUT_ADJUST_NOTHING) {
vf.set(displayFrames.mCurrent);
} else {
vf.set(cf);
@@ -2192,10 +2195,13 @@
final boolean attachedInParent = attached != null && !layoutInScreen;
final boolean requestedFullscreen = (fl & FLAG_FULLSCREEN) != 0
|| (requestedSysUiFl & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0
- || !win.getClientInsetsState().getSource(ITYPE_STATUS_BAR).isVisible();
+ || (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_FULL
+ && !win.getRequestedInsetsState().getSource(ITYPE_STATUS_BAR).isVisible());
final boolean requestedHideNavigation =
(requestedSysUiFl & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0
- || !win.getClientInsetsState().getSource(ITYPE_NAVIGATION_BAR).isVisible();
+ || (ViewRootImpl.sNewInsetsMode == NEW_INSETS_MODE_FULL
+ && !win.getRequestedInsetsState().getSource(ITYPE_NAVIGATION_BAR)
+ .isVisible());
// TYPE_BASE_APPLICATION windows are never considered floating here because they don't get
// cropped / shifted to the displayFrame in WindowState.
diff --git a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
index 55f5e28..9d985d7 100644
--- a/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
+++ b/services/core/java/com/android/server/wm/EnsureActivitiesVisibleHelper.java
@@ -75,16 +75,18 @@
// activities are actually behind other fullscreen activities, but still required
// to be visible (such as performing Recents animation).
final boolean resumeTopActivity = mTop != null && !mTop.mLaunchTaskBehind
- && mContiner.isFocusable() && mContiner.isInStackLocked(starting) == null;
+ && mContiner.isTopActivityFocusable()
+ && mContiner.isInStackLocked(starting) == null;
final PooledConsumer f = PooledLambda.obtainConsumer(
EnsureActivitiesVisibleHelper::setActivityVisibilityState, this,
- PooledLambda.__(ActivityRecord.class), resumeTopActivity);
+ PooledLambda.__(ActivityRecord.class), starting, resumeTopActivity);
mContiner.forAllActivities(f);
f.recycle();
}
- private void setActivityVisibilityState(ActivityRecord r, final boolean resumeTopActivity) {
+ private void setActivityVisibilityState(ActivityRecord r, ActivityRecord starting,
+ final boolean resumeTopActivity) {
final boolean isTop = r == mTop;
if (mAboveTop && !isTop) {
return;
@@ -129,7 +131,8 @@
"Skipping: already visible at " + r);
if (r.mClientVisibilityDeferred && mNotifyClients) {
- r.makeClientVisible();
+ r.makeActiveIfNeeded(r.mClientVisibilityDeferred ? null : starting);
+ r.mClientVisibilityDeferred = false;
}
r.handleAlreadyVisible();
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 05ede21..fb97ecf 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -69,7 +69,7 @@
mShowImeRunner = () -> {
// Target should still be the same.
if (isImeTargetFromDisplayContentAndImeSame()) {
- mDisplayContent.mInputMethodTarget.showInsets(
+ mDisplayContent.mInputMethodControlTarget.showInsets(
WindowInsets.Type.ime(), true /* fromIme */);
}
abortShowImePostLayout();
diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java
index 1f9f883..5b892f8 100644
--- a/services/core/java/com/android/server/wm/InputManagerCallback.java
+++ b/services/core/java/com/android/server/wm/InputManagerCallback.java
@@ -10,23 +10,40 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.H.ON_POINTER_DOWN_OUTSIDE_FOCUS;
+import android.os.Build;
import android.os.Debug;
import android.os.IBinder;
+import android.os.Process;
import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.ArrayMap;
import android.util.Slog;
import android.view.IWindow;
import android.view.InputApplicationHandle;
import android.view.KeyEvent;
import android.view.WindowManager;
+import com.android.server.am.ActivityManagerService;
import com.android.server.input.InputManagerService;
import com.android.server.wm.EmbeddedWindowController.EmbeddedWindow;
+import java.io.File;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
final class InputManagerCallback implements InputManagerService.WindowManagerCallbacks {
private static final String TAG = TAG_WITH_CLASS_NAME ? "InputManagerCallback" : TAG_WM;
+
+ /** Prevent spamming the traces because pre-dump cannot aware duplicated ANR. */
+ private static final long PRE_DUMP_MIN_INTERVAL_MS = TimeUnit.SECONDS.toMillis(20);
+ /** The timeout to detect if a monitor is held for a while. */
+ private static final long PRE_DUMP_MONITOR_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(1);
+ /** The last time pre-dump was executed. */
+ private volatile long mLastPreDumpTimeMs;
+
private final WindowManagerService mService;
// Set to true when the first input device configuration change notification
@@ -77,6 +94,77 @@
}
/**
+ * Pre-dump stack trace if the locks of activity manager or window manager (they may be locked
+ * in the path of reporting ANR) cannot be acquired in time. That provides the stack traces
+ * before the real blocking symptom has gone.
+ * <p>
+ * Do not hold the {@link WindowManagerGlobalLock} while calling this method.
+ */
+ private void preDumpIfLockTooSlow() {
+ if (!Build.IS_DEBUGGABLE) {
+ return;
+ }
+ final long now = SystemClock.uptimeMillis();
+ if (mLastPreDumpTimeMs > 0 && now - mLastPreDumpTimeMs < PRE_DUMP_MIN_INTERVAL_MS) {
+ return;
+ }
+
+ final boolean[] shouldDumpSf = { true };
+ final ArrayMap<String, Runnable> monitors = new ArrayMap<>(2);
+ monitors.put(TAG_WM, mService::monitor);
+ monitors.put("ActivityManager", mService.mAmInternal::monitor);
+ final CountDownLatch latch = new CountDownLatch(monitors.size());
+ // The pre-dump will execute if one of the monitors doesn't complete within the timeout.
+ for (int i = 0; i < monitors.size(); i++) {
+ final String name = monitors.keyAt(i);
+ final Runnable monitor = monitors.valueAt(i);
+ // Always create new thread to avoid noise of existing threads. Suppose here won't
+ // create too many threads because it means that watchdog will be triggered first.
+ new Thread() {
+ @Override
+ public void run() {
+ monitor.run();
+ latch.countDown();
+ final long elapsed = SystemClock.uptimeMillis() - now;
+ if (elapsed > PRE_DUMP_MONITOR_TIMEOUT_MS) {
+ Slog.i(TAG_WM, "Pre-dump acquired " + name + " in " + elapsed + "ms");
+ } else if (TAG_WM.equals(name)) {
+ // Window manager is the main client of SurfaceFlinger. If window manager
+ // is responsive, the stack traces of SurfaceFlinger may not be important.
+ shouldDumpSf[0] = false;
+ }
+ };
+ }.start();
+ }
+ try {
+ if (latch.await(PRE_DUMP_MONITOR_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ return;
+ }
+ } catch (InterruptedException ignored) { }
+ mLastPreDumpTimeMs = now;
+ Slog.i(TAG_WM, "Pre-dump for unresponsive");
+
+ final ArrayList<Integer> firstPids = new ArrayList<>(1);
+ firstPids.add(ActivityManagerService.MY_PID);
+ ArrayList<Integer> nativePids = null;
+ final int[] pids = shouldDumpSf[0]
+ ? Process.getPidsForCommands(new String[] { "/system/bin/surfaceflinger" })
+ : null;
+ if (pids != null) {
+ nativePids = new ArrayList<>(1);
+ for (int pid : pids) {
+ nativePids.add(pid);
+ }
+ }
+
+ final File tracesFile = ActivityManagerService.dumpStackTraces(firstPids,
+ null /* processCpuTracker */, null /* lastPids */, nativePids);
+ if (tracesFile != null) {
+ tracesFile.renameTo(new File(tracesFile.getParent(), tracesFile.getName() + "_pre"));
+ }
+ }
+
+ /**
* Notifies the window manager about an application that is not responding.
* Returns a new timeout to continue waiting in nanoseconds, or 0 to abort dispatch.
*
@@ -89,6 +177,9 @@
WindowState windowState = null;
boolean aboveSystem = false;
int windowPid = INVALID_PID;
+
+ preDumpIfLockTooSlow();
+
//TODO(b/141764879) Limit scope of wm lock when input calls notifyANR
synchronized (mService.mGlobalLock) {
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index a8ff500..091f66c 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -151,10 +151,10 @@
private final UpdateInputWindows mUpdateInputWindows = new UpdateInputWindows();
- public InputMonitor(WindowManagerService service, int displayId) {
+ InputMonitor(WindowManagerService service, DisplayContent displayContent) {
mService = service;
- mDisplayContent = mService.mRoot.getDisplayContent(displayId);
- mDisplayId = displayId;
+ mDisplayContent = displayContent;
+ mDisplayId = displayContent.getDisplayId();
mInputTransaction = mService.mTransactionFactory.get();
mHandler = mService.mAnimationHandler;
mUpdateInputForAllWindowsConsumer = new UpdateInputForAllWindowsConsumer();
@@ -279,6 +279,19 @@
// we avoid reintroducing this concept by just choosing one of them here.
inputWindowHandle.surfaceInset = child.getAttrs().surfaceInsets.left;
+ /**
+ * If the window is in a TaskManaged by a TaskOrganizer then most cropping
+ * will be applied using the SurfaceControl hierarchy from the Organizer.
+ * This means we need to make sure that these changes in crop are reflected
+ * in the input windows, and so ensure this flag is set so that
+ * the input crop always reflects the surface hierarchy.
+ * we may have some issues with modal-windows, but I guess we can
+ * cross that bridge when we come to implementing full-screen TaskOrg
+ */
+ if (child.getTask() != null && child.getTask().isControlledByTaskOrganizer()) {
+ inputWindowHandle.replaceTouchableRegionWithCrop(null /* Use this surfaces crop */);
+ }
+
if (child.mGlobalScale != 1) {
// If we are scaling the window, input coordinates need
// to be inversely scaled to map from what is on screen
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index af84836..a008963 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -66,10 +66,11 @@
}
mStatusBar.setVisible(focusedWin == null
|| focusedWin != getStatusControlTarget(focusedWin)
- || focusedWin.getClientInsetsState().getSource(ITYPE_STATUS_BAR).isVisible());
+ || focusedWin.getRequestedInsetsState().getSource(ITYPE_STATUS_BAR).isVisible());
mNavBar.setVisible(focusedWin == null
|| focusedWin != getNavControlTarget(focusedWin)
- || focusedWin.getClientInsetsState().getSource(ITYPE_NAVIGATION_BAR).isVisible());
+ || focusedWin.getRequestedInsetsState().getSource(ITYPE_NAVIGATION_BAR)
+ .isVisible());
}
boolean isHidden(@InternalInsetsType int type) {
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 184e7d6..5a591ec 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -70,6 +70,9 @@
*/
private boolean mServerVisible;
+ private boolean mSeamlessRotating;
+ private long mFinishSeamlessRotateFrameNumber = -1;
+
private final boolean mControllable;
InsetsSourceProvider(InsetsSource source, InsetsStateController stateController,
@@ -126,6 +129,7 @@
if (win == null) {
setServerVisible(false);
mSource.setFrame(new Rect());
+ mSource.setVisibleFrame(null);
} else if (mControllable) {
mWin.setControllableInsetProvider(this);
if (mControlTarget != null) {
@@ -157,6 +161,15 @@
mTmpRect.inset(mWin.mGivenContentInsets);
}
mSource.setFrame(mTmpRect);
+
+ if (mWin.mGivenVisibleInsets.left != 0 || mWin.mGivenVisibleInsets.top != 0
+ || mWin.mGivenVisibleInsets.right != 0 || mWin.mGivenVisibleInsets.bottom != 0) {
+ mTmpRect.set(mWin.getFrameLw());
+ mTmpRect.inset(mWin.mGivenVisibleInsets);
+ mSource.setVisibleFrame(mTmpRect);
+ } else {
+ mSource.setVisibleFrame(null);
+ }
}
/**
@@ -170,7 +183,9 @@
updateSourceFrame();
if (mControl != null) {
final Rect frame = mWin.getWindowFrames().mFrame;
- if (mControl.setSurfacePosition(frame.left, frame.top)) {
+ if (mControl.setSurfacePosition(frame.left, frame.top) && mControlTarget != null) {
+ // The leash has been stale, we need to create a new one for the client.
+ updateControlForTarget(mControlTarget, true /* force */);
mStateController.notifyControlChanged(mControlTarget);
}
}
@@ -189,6 +204,11 @@
}
void updateControlForTarget(@Nullable InsetsControlTarget target, boolean force) {
+ if (mSeamlessRotating) {
+ // We are un-rotating the window against the display rotation. We don't want the target
+ // to control the window for now.
+ return;
+ }
if (mWin == null) {
mControlTarget = target;
return;
@@ -203,14 +223,42 @@
}
mAdapter = new ControlAdapter();
setClientVisible(InsetsState.getDefaultVisibility(mSource.getType()));
- mWin.startAnimation(mDisplayContent.getPendingTransaction(), mAdapter,
- !mClientVisible /* hidden */);
+ final Transaction t = mDisplayContent.getPendingTransaction();
+ mWin.startAnimation(t, mAdapter, !mClientVisible /* hidden */);
+ final SurfaceControl leash = mAdapter.mCapturedLeash;
+ final long frameNumber = mFinishSeamlessRotateFrameNumber;
+ mFinishSeamlessRotateFrameNumber = -1;
+ if (frameNumber >= 0 && mWin.mHasSurface && leash != null) {
+ // We just finished the seamless rotation. We don't want to change the position or the
+ // window crop of the surface controls (including the leash) until the client finishes
+ // drawing the new frame of the new orientation. Although we cannot defer the reparent
+ // operation, it is fine, because reparent won't cause any visual effect.
+ final SurfaceControl barrier = mWin.mWinAnimator.mSurfaceController.mSurfaceControl;
+ t.deferTransactionUntil(mWin.getSurfaceControl(), barrier, frameNumber);
+ t.deferTransactionUntil(leash, barrier, frameNumber);
+ }
mControlTarget = target;
- mControl = new InsetsSourceControl(mSource.getType(), mAdapter.mCapturedLeash,
+ mControl = new InsetsSourceControl(mSource.getType(), leash,
new Point(mWin.getWindowFrames().mFrame.left, mWin.getWindowFrames().mFrame.top));
}
- boolean onInsetsModified(WindowState caller, InsetsSource modifiedSource) {
+ void startSeamlessRotation() {
+ if (!mSeamlessRotating) {
+ mSeamlessRotating = true;
+
+ // This will revoke the leash and clear the control target.
+ mWin.cancelAnimation();
+ }
+ }
+
+ void finishSeamlessRotation(boolean timeout) {
+ if (mSeamlessRotating) {
+ mSeamlessRotating = false;
+ mFinishSeamlessRotateFrameNumber = timeout ? -1 : mWin.getFrameNumber();
+ }
+ }
+
+ boolean onInsetsModified(InsetsControlTarget caller, InsetsSource modifiedSource) {
if (mControlTarget != caller || modifiedSource.isVisible() == mClientVisible) {
return false;
}
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 1526074..b2234d1 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -91,6 +91,10 @@
return state;
}
+ InsetsState getRawInsetsState() {
+ return mState;
+ }
+
@Nullable InsetsSourceControl[] getControlsForDispatch(InsetsControlTarget target) {
ArrayList<Integer> controlled = mControlTargetTypeMap.get(target);
if (controlled == null) {
@@ -144,7 +148,7 @@
getImeSourceProvider().onPostInsetsDispatched();
}
- void onInsetsModified(WindowState windowState, InsetsState state) {
+ void onInsetsModified(InsetsControlTarget windowState, InsetsState state) {
boolean changed = false;
for (int i = state.getSourcesCount() - 1; i >= 0; i--) {
final InsetsSource source = state.sourceAt(i);
@@ -199,7 +203,7 @@
if (target == previous) {
return;
}
- final InsetsSourceProvider provider = getSourceProvider(type);
+ final InsetsSourceProvider provider = mProviders.get(type);
if (provider == null) {
return;
}
@@ -207,6 +211,7 @@
return;
}
provider.updateControlForTarget(target, false /* force */);
+ target = provider.getControlTarget();
if (previous != null) {
removeFromControlMaps(previous, type, false /* fake */);
mPendingControlChanged.add(previous);
@@ -296,6 +301,9 @@
void notifyInsetsChanged() {
mDisplayContent.forAllWindows(mDispatchInsetsChanged, true /* traverseTopToBottom */);
+ if (mDisplayContent.mRemoteInsetsControlTarget != null) {
+ mDisplayContent.mRemoteInsetsControlTarget.notifyInsetsChanged();
+ }
}
void dump(String prefix, PrintWriter pw) {
diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java
index 02413bb..33b0453 100644
--- a/services/core/java/com/android/server/wm/LockTaskController.java
+++ b/services/core/java/com/android/server/wm/LockTaskController.java
@@ -23,6 +23,8 @@
import static android.content.Context.DEVICE_POLICY_SERVICE;
import static android.content.Context.STATUS_BAR_SERVICE;
import static android.content.Intent.ACTION_CALL_EMERGENCY;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
import static android.os.UserHandle.USER_ALL;
import static android.os.UserHandle.USER_CURRENT;
import static android.telecom.TelecomManager.EMERGENCY_DIALER_COMPONENT;
@@ -339,6 +341,20 @@
& DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD) != 0;
}
+ boolean isActivityAllowed(int userId, String packageName, int lockTaskLaunchMode) {
+ if (mLockTaskModeState != LOCK_TASK_MODE_LOCKED) {
+ return true;
+ }
+ switch (lockTaskLaunchMode) {
+ case LOCK_TASK_LAUNCH_MODE_ALWAYS:
+ return true;
+ case LOCK_TASK_LAUNCH_MODE_NEVER:
+ return false;
+ default:
+ }
+ return isPackageWhitelisted(userId, packageName);
+ }
+
private boolean isEmergencyCallTask(Task task) {
final Intent intent = task.intent;
if (intent == null) {
diff --git a/services/core/java/com/android/server/wm/RefreshRatePolicy.java b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
index e0a7b18..2cb7d5a 100644
--- a/services/core/java/com/android/server/wm/RefreshRatePolicy.java
+++ b/services/core/java/com/android/server/wm/RefreshRatePolicy.java
@@ -33,6 +33,27 @@
private final HighRefreshRateBlacklist mHighRefreshRateBlacklist;
private final WindowManagerService mWmService;
+ /**
+ * The following constants represent priority of the window. SF uses this information when
+ * deciding which window has a priority when deciding about the refresh rate of the screen.
+ * Priority 0 is considered the highest priority. -1 means that the priority is unset.
+ */
+ static final int LAYER_PRIORITY_UNSET = -1;
+ /** Windows that are in focus and voted for the preferred mode ID have the highest priority. */
+ static final int LAYER_PRIORITY_FOCUSED_WITH_MODE = 0;
+ /**
+ * This is a default priority for all windows that are in focus, but have not requested a
+ * specific mode ID.
+ */
+ static final int LAYER_PRIORITY_FOCUSED_WITHOUT_MODE = 1;
+ /**
+ * Windows that are not in focus, but voted for a specific mode ID should be
+ * acknowledged by SF. For example, there are two applications in a split screen.
+ * One voted for a given mode ID, and the second one doesn't care. Even though the
+ * second one might be in focus, we can honor the mode ID of the first one.
+ */
+ static final int LAYER_PRIORITY_NOT_FOCUSED_WITH_MODE = 2;
+
RefreshRatePolicy(WindowManagerService wmService, DisplayInfo displayInfo,
HighRefreshRateBlacklist blacklist) {
mLowRefreshRateId = findLowRefreshRateModeId(displayInfo);
@@ -92,4 +113,28 @@
}
return 0;
}
+
+ /**
+ * Calculate the priority based on whether the window is in focus and whether the application
+ * voted for a specific refresh rate.
+ *
+ * TODO(b/144307188): This is a very basic algorithm version. Explore other signals that might
+ * be useful in edge cases when we are deciding which layer should get priority when deciding
+ * about the refresh rate.
+ */
+ int calculatePriority(WindowState w) {
+ boolean isFocused = w.isFocused();
+ int preferredModeId = getPreferredModeId(w);
+
+ if (!isFocused && preferredModeId > 0) {
+ return LAYER_PRIORITY_NOT_FOCUSED_WITH_MODE;
+ }
+ if (isFocused && preferredModeId == 0) {
+ return LAYER_PRIORITY_FOCUSED_WITHOUT_MODE;
+ }
+ if (isFocused && preferredModeId > 0) {
+ return LAYER_PRIORITY_FOCUSED_WITH_MODE;
+ }
+ return LAYER_PRIORITY_UNSET;
+ }
}
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index 0b9be1a..6f7eeab 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -378,9 +378,10 @@
int getMode() {
final DisplayContent dc = mWindowContainer.getDisplayContent();
- if (dc.mOpeningApps.contains(mWindowContainer)) {
+ final ActivityRecord topActivity = mWindowContainer.getTopMostActivity();
+ if (dc.mOpeningApps.contains(topActivity)) {
return RemoteAnimationTarget.MODE_OPENING;
- } else if (dc.mChangingApps.contains(mWindowContainer)) {
+ } else if (dc.mChangingApps.contains(topActivity)) {
return RemoteAnimationTarget.MODE_CHANGING;
} else {
return RemoteAnimationTarget.MODE_CLOSING;
diff --git a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
index a5c90a1..e310fc1 100644
--- a/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
+++ b/services/core/java/com/android/server/wm/ResetTargetTaskHelper.java
@@ -91,6 +91,9 @@
}
private boolean processActivity(ActivityRecord r, boolean isTargetTask) {
+ // End processing if we have reached the root.
+ if (r == mRoot) return true;
+
mAllActivities.add(r);
final int flags = r.info.flags;
final boolean finishOnTaskLaunch =
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 704ab67..2f726e9 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1377,6 +1377,7 @@
for (int displayNdx = 0; displayNdx < displays.length; ++displayNdx) {
final Display display = displays[displayNdx];
final DisplayContent displayContent = new DisplayContent(display, this);
+ addChild(displayContent, POSITION_BOTTOM);
if (displayContent.mDisplayId == DEFAULT_DISPLAY) {
mDefaultDisplay = displayContent;
}
@@ -1445,6 +1446,7 @@
}
// The display hasn't been added to ActivityManager yet, create a new record now.
displayContent = new DisplayContent(display, this);
+ addChild(displayContent, POSITION_BOTTOM);
return displayContent;
}
@@ -1858,14 +1860,6 @@
return null;
}
- boolean isFocusable(ConfigurationContainer container, boolean alwaysFocusable) {
- if (container.inSplitScreenPrimaryWindowingMode() && mIsDockMinimized) {
- return false;
- }
-
- return container.getWindowConfiguration().canReceiveKeys() || alwaysFocusable;
- }
-
boolean isTopDisplayFocusedStack(ActivityStack stack) {
return stack != null && stack == getTopDisplayFocusedStack();
}
@@ -2166,12 +2160,18 @@
mService.continueWindowLayout();
}
+ // TODO(b/146594635): Remove all PIP animation code from WM once SysUI handles animation.
// Notify the pinned stack controller to prepare the PiP animation, expect callback
- // delivered from SystemUI to WM to start the animation.
- final PinnedStackController pinnedStackController =
+ // delivered from SystemUI to WM to start the animation. Unless we are using
+ // the TaskOrganizer in which case the animation will be entirely handled
+ // on that side.
+ if (mService.mTaskOrganizerController.getTaskOrganizer(WINDOWING_MODE_PINNED)
+ == null) {
+ final PinnedStackController pinnedStackController =
display.mDisplayContent.getPinnedStackController();
- pinnedStackController.prepareAnimation(sourceHintBounds, aspectRatio,
- null /* stackBounds */);
+ pinnedStackController.prepareAnimation(sourceHintBounds, aspectRatio,
+ null /* stackBounds */);
+ }
// TODO: revisit the following statement after the animation is moved from WM to SysUI.
// Update the visibility of all activities after the they have been reparented to the new
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 5198602..3b349b8 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -463,7 +463,7 @@
final WindowState windowState = mService.windowForClientLocked(this, window,
false /* throwOnError */);
if (windowState != null) {
- windowState.setClientInsetsState(state);
+ windowState.updateRequestedInsetsState(state);
windowState.getDisplayContent().getInsetsPolicy().onInsetsModified(
windowState, state);
}
diff --git a/services/core/java/com/android/server/wm/SurfaceAnimator.java b/services/core/java/com/android/server/wm/SurfaceAnimator.java
index 976730e..5286a6e 100644
--- a/services/core/java/com/android/server/wm/SurfaceAnimator.java
+++ b/services/core/java/com/android/server/wm/SurfaceAnimator.java
@@ -322,12 +322,16 @@
if (DEBUG_ANIM) Slog.i(TAG, "Reparenting to leash");
final SurfaceControl.Builder builder = mAnimatable.makeAnimationLeash()
.setParent(mAnimatable.getAnimationLeashParent())
+ .setHidden(hidden)
.setName(surface + " - animation-leash");
final SurfaceControl leash = builder.build();
t.setWindowCrop(leash, width, height);
+
+ // TODO: rely on builder.setHidden(hidden) instead of show and setAlpha when b/138459974 is
+ // fixed.
t.show(leash);
- // TODO: change this back to use show instead of alpha when b/138459974 is fixed.
t.setAlpha(leash, hidden ? 0 : 1);
+
t.reparent(surface, leash);
return leash;
}
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 914b95c..5cb7091 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -94,6 +94,7 @@
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STACK;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM;
import static java.lang.Integer.MAX_VALUE;
@@ -116,9 +117,11 @@
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.res.Configuration;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.Debug;
import android.os.IBinder;
+import android.os.Parcel;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
@@ -129,6 +132,7 @@
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.DisplayInfo;
+import android.view.ITaskOrganizer;
import android.view.RemoteAnimationTarget;
import android.view.Surface;
import android.view.SurfaceControl;
@@ -424,6 +428,14 @@
}
/**
+ * The TaskOrganizer which is delegated presentation of this task. If set the Task will
+ * emit an IWindowContainer (allowing access to it's SurfaceControl leash) to the organizers
+ * taskAppeared callback, and emit a taskRemoved callback when the Task is vanished.
+ */
+ ITaskOrganizer mTaskOrganizer;
+
+
+ /**
* Don't use constructor directly. Use {@link #create(ActivityTaskManagerService, int,
* ActivityInfo, Intent, TaskDescription)} instead.
*/
@@ -444,6 +456,22 @@
_voiceSession, _voiceInteractor, stack);
}
+ class TaskToken extends RemoteToken {
+ TaskToken(ConfigurationContainer container) {
+ super(container);
+ }
+
+ @Override
+ public SurfaceControl getLeash() {
+ // We need to copy the SurfaceControl instead of returning the original
+ // because the Parcel FLAGS PARCELABLE_WRITE_RETURN_VALUE cause SurfaceControls
+ // to release themselves.
+ SurfaceControl sc = new SurfaceControl();
+ sc.copyFrom(getSurfaceControl());
+ return sc;
+ }
+ }
+
/** Don't use constructor directly. This is only used by XML parser. */
Task(ActivityTaskManagerService atmService, int _taskId, Intent _intent,
Intent _affinityIntent, String _affinity, String _rootAffinity,
@@ -468,7 +496,7 @@
mTaskDescription = _lastTaskDescription;
// Tasks have no set orientation value (including SCREEN_ORIENTATION_UNSPECIFIED).
setOrientation(SCREEN_ORIENTATION_UNSET);
- mRemoteToken = new RemoteToken(this);
+ mRemoteToken = new TaskToken(this);
affinityIntent = _affinityIntent;
affinity = _affinity;
rootAffinity = _rootAffinity;
@@ -2178,6 +2206,10 @@
void removeImmediately() {
if (DEBUG_STACK) Slog.i(TAG, "removeTask: removing taskId=" + mTaskId);
EventLogTags.writeWmTaskRemoved(mTaskId, "removeTask");
+
+ // If applicable let the TaskOrganizer know the Task is vanishing.
+ setTaskOrganizer(null);
+
super.removeImmediately();
}
@@ -2555,7 +2587,23 @@
return getAppAnimationLayer(ANIMATION_LAYER_HOME);
}
+ @Override
+ Rect getAnimationBounds(int appStackClipMode) {
+ // TODO(b/131661052): we should remove appStackClipMode with hierarchical animations.
+ if (appStackClipMode == STACK_CLIP_BEFORE_ANIM && getStack() != null) {
+ // Using the stack bounds here effectively applies the clipping before animation.
+ return getStack().getBounds();
+ }
+ return super.getAnimationBounds(appStackClipMode);
+ }
+
boolean shouldAnimate() {
+ /**
+ * Animations are handled by the TaskOrganizer implementation.
+ */
+ if (isControlledByTaskOrganizer()) {
+ return false;
+ }
// Don't animate while the task runs recents animation but only if we are in the mode
// where we cancel with deferred screenshot, which means that the controller has
// transformed the task.
@@ -2568,6 +2616,18 @@
}
@Override
+ protected void onAnimationFinished() {
+ super.onAnimationFinished();
+ // TODO(b/142617871): we may need to add animation type parameter on onAnimationFinished to
+ // identify if the callback is for launch animation finish and then calling
+ // activity#onAnimationFinished.
+ final ActivityRecord activity = getTopMostActivity();
+ if (activity != null) {
+ activity.onAnimationFinished();
+ }
+ }
+
+ @Override
SurfaceControl.Builder makeSurface() {
return super.makeSurface().setMetadata(METADATA_TASK_ID, mTaskId);
}
@@ -2594,7 +2654,7 @@
@Override
RemoteAnimationTarget createRemoteAnimationTarget(
RemoteAnimationController.RemoteAnimationRecord record) {
- final ActivityRecord activity = getTopVisibleActivity();
+ final ActivityRecord activity = getTopMostActivity();
return activity != null ? activity.createRemoteAnimationTarget(record) : null;
}
@@ -2721,7 +2781,13 @@
getDimBounds(mTmpDimBoundsRect);
// Bounds need to be relative, as the dim layer is a child.
- mTmpDimBoundsRect.offsetTo(0, 0);
+ if (inFreeformWindowingMode()) {
+ getBounds(mTmpRect);
+ mTmpDimBoundsRect.offsetTo(mTmpDimBoundsRect.left - mTmpRect.left,
+ mTmpDimBoundsRect.top - mTmpRect.top);
+ } else {
+ mTmpDimBoundsRect.offsetTo(0, 0);
+ }
if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) {
scheduleAnimation();
}
@@ -3415,4 +3481,91 @@
XmlUtils.skipCurrentTag(in);
}
}
+
+ boolean isControlledByTaskOrganizer() {
+ return mTaskOrganizer != null;
+ }
+
+ @Override
+ protected void reparentSurfaceControl(SurfaceControl.Transaction t, SurfaceControl newParent) {
+ /**
+ * Avoid yanking back control from the TaskOrganizer, which has presumably reparented the
+ * Surface in to its own hierarchy.
+ */
+ if (isControlledByTaskOrganizer()) {
+ return;
+ }
+ super.reparentSurfaceControl(t, newParent);
+ }
+
+ private void sendTaskAppeared() {
+ if (mSurfaceControl != null && mTaskOrganizer != null) {
+ mAtmService.mTaskOrganizerController.onTaskAppeared(mTaskOrganizer, this);
+ }
+ }
+
+ private void sendTaskVanished() {
+ if (mTaskOrganizer != null) {
+ mAtmService.mTaskOrganizerController.onTaskVanished(mTaskOrganizer, this);
+ }
+ }
+
+ void setTaskOrganizer(ITaskOrganizer organizer) {
+ // Let the old organizer know it has lost control.
+ if (mTaskOrganizer != null) {
+ sendTaskVanished();
+ }
+ mTaskOrganizer = organizer;
+ sendTaskAppeared();
+ }
+
+ // Called on Binder death.
+ void taskOrganizerDied() {
+ mTaskOrganizer = null;
+ }
+
+ @Override
+ void setSurfaceControl(SurfaceControl sc) {
+ super.setSurfaceControl(sc);
+ // If the TaskOrganizer was set before we created the SurfaceControl, we need to
+ // emit the callbacks now.
+ sendTaskAppeared();
+ }
+
+ @Override
+ public void updateSurfacePosition() {
+ // Avoid fighting with the TaskOrganizer over Surface position.
+ if (isControlledByTaskOrganizer()) {
+ getPendingTransaction().setPosition(mSurfaceControl, 0, 0);
+ scheduleAnimation();
+ return;
+ } else {
+ super.updateSurfacePosition();
+ }
+ }
+
+ @Override
+ void getRelativeDisplayedPosition(Point outPos) {
+ // In addition to updateSurfacePosition, we keep other code that sets
+ // position from fighting with the TaskOrganizer
+ if (isControlledByTaskOrganizer()) {
+ outPos.set(0, 0);
+ return;
+ }
+ super.getRelativeDisplayedPosition(outPos);
+ }
+
+ @Override
+ public void setWindowingMode(int windowingMode) {
+ super.setWindowingMode(windowingMode);
+ windowingMode = getWindowingMode();
+ /*
+ * Different windowing modes may be managed by different task organizers. If
+ * getTaskOrganizer returns null, we still call transferToTaskOrganizer to
+ * make sure we clear it.
+ */
+ final ITaskOrganizer org =
+ mAtmService.mTaskOrganizerController.getTaskOrganizer(windowingMode);
+ setTaskOrganizer(org);
+ }
}
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
new file mode 100644
index 0000000..283be40
--- /dev/null
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -0,0 +1,167 @@
+/*
+ * 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 com.android.server.wm;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.view.ITaskOrganizer;
+import android.view.SurfaceControl;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * Stores the TaskOrganizers associated with a given windowing mode and
+ * their associated state.
+ */
+class TaskOrganizerController {
+ private static final String TAG = "TaskOrganizerController";
+
+ private WindowManagerGlobalLock mGlobalLock;
+
+ private class DeathRecipient implements IBinder.DeathRecipient {
+ int mWindowingMode;
+ ITaskOrganizer mTaskOrganizer;
+
+ DeathRecipient(ITaskOrganizer organizer, int windowingMode) {
+ mTaskOrganizer = organizer;
+ mWindowingMode = windowingMode;
+ }
+
+ @Override
+ public void binderDied() {
+ synchronized (mGlobalLock) {
+ final TaskOrganizerState state = mTaskOrganizerStates.get(mTaskOrganizer);
+ for (int i = 0; i < state.mOrganizedTasks.size(); i++) {
+ state.mOrganizedTasks.get(i).taskOrganizerDied();
+ }
+ mTaskOrganizerStates.remove(mTaskOrganizer);
+ if (mTaskOrganizersForWindowingMode.get(mWindowingMode) == mTaskOrganizer) {
+ mTaskOrganizersForWindowingMode.remove(mWindowingMode);
+ }
+ }
+ }
+ };
+
+ class TaskOrganizerState {
+ ITaskOrganizer mOrganizer;
+ DeathRecipient mDeathRecipient;
+
+ ArrayList<Task> mOrganizedTasks = new ArrayList<>();
+
+ void addTask(Task t) {
+ mOrganizedTasks.add(t);
+ }
+
+ void removeTask(Task t) {
+ mOrganizedTasks.remove(t);
+ }
+
+ TaskOrganizerState(ITaskOrganizer organizer, DeathRecipient deathRecipient) {
+ mOrganizer = organizer;
+ mDeathRecipient = deathRecipient;
+ }
+ };
+
+
+ final HashMap<Integer, TaskOrganizerState> mTaskOrganizersForWindowingMode = new HashMap();
+ final HashMap<ITaskOrganizer, TaskOrganizerState> mTaskOrganizerStates = new HashMap();
+
+ final HashMap<Integer, ITaskOrganizer> mTaskOrganizersByPendingSyncId = new HashMap();
+
+ final ActivityTaskManagerService mService;
+
+ TaskOrganizerController(ActivityTaskManagerService atm, WindowManagerGlobalLock lock) {
+ mService = atm;
+ mGlobalLock = lock;
+ }
+
+ private void clearIfNeeded(int windowingMode) {
+ final TaskOrganizerState oldState = mTaskOrganizersForWindowingMode.get(windowingMode);
+ if (oldState != null) {
+ oldState.mOrganizer.asBinder().unlinkToDeath(oldState.mDeathRecipient, 0);
+ }
+ }
+
+ /**
+ * Register a TaskOrganizer to manage tasks as they enter the given windowing mode.
+ * If there was already a TaskOrganizer for this windowing mode it will be evicted
+ * and receive taskVanished callbacks in the process.
+ */
+ void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode) {
+ if (windowingMode != WINDOWING_MODE_PINNED) {
+ throw new UnsupportedOperationException(
+ "As of now only Pinned windowing mode is supported for registerTaskOrganizer");
+
+ }
+ clearIfNeeded(windowingMode);
+ DeathRecipient dr = new DeathRecipient(organizer, windowingMode);
+ try {
+ organizer.asBinder().linkToDeath(dr, 0);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "TaskOrganizer failed to register death recipient");
+ }
+
+ final TaskOrganizerState state = new TaskOrganizerState(organizer, dr);
+ mTaskOrganizersForWindowingMode.put(windowingMode, state);
+
+ mTaskOrganizerStates.put(organizer, state);
+ }
+
+ ITaskOrganizer getTaskOrganizer(int windowingMode) {
+ final TaskOrganizerState state = mTaskOrganizersForWindowingMode.get(windowingMode);
+ if (state == null) {
+ return null;
+ }
+ return state.mOrganizer;
+ }
+
+ private void sendTaskAppeared(ITaskOrganizer organizer, Task task) {
+ try {
+ organizer.taskAppeared(task.getRemoteToken(), task.getTaskInfo());
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception sending taskAppeared callback" + e);
+ }
+ }
+
+ private void sendTaskVanished(ITaskOrganizer organizer, Task task) {
+ try {
+ organizer.taskVanished(task.getRemoteToken());
+ } catch (Exception e) {
+ Slog.e(TAG, "Exception sending taskVanished callback" + e);
+ }
+ }
+
+ void onTaskAppeared(ITaskOrganizer organizer, Task task) {
+ TaskOrganizerState state = mTaskOrganizerStates.get(organizer);
+
+ state.addTask(task);
+ sendTaskAppeared(organizer, task);
+ }
+
+ void onTaskVanished(ITaskOrganizer organizer, Task task) {
+ final TaskOrganizerState state = mTaskOrganizerStates.get(organizer);
+ sendTaskVanished(organizer, task);
+
+ // This could trigger TaskAppeared for other tasks in the same stack so make sure
+ // we do this AFTER sending taskVanished.
+ state.removeTask(task);
+ }
+}
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index e4744db..4cb5de4 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -470,7 +470,7 @@
final LayoutParams attrs = mainWindow.getAttrs();
final SystemBarBackgroundPainter decorPainter = new SystemBarBackgroundPainter(attrs.flags,
attrs.privateFlags, attrs.systemUiVisibility, task.getTaskDescription(),
- mFullSnapshotScale, mainWindow.getClientInsetsState());
+ mFullSnapshotScale, mainWindow.getRequestedInsetsState());
final int width = (int) (task.getBounds().width() * mFullSnapshotScale);
final int height = (int) (task.getBounds().height() * mFullSnapshotScale);
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
index d36a5d4..38a7000 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotPersister.java
@@ -53,7 +53,7 @@
private static final String SNAPSHOTS_DIRNAME = "snapshots";
private static final String REDUCED_POSTFIX = "_reduced";
private static final float REDUCED_SCALE = .5f;
- private static final float LOW_RAM_REDUCED_SCALE = .6f;
+ private static final float LOW_RAM_REDUCED_SCALE = .8f;
static final boolean DISABLE_FULL_SIZED_BITMAPS = ActivityManager.isLowRamDeviceStatic();
private static final long DELAY_MS = 100;
private static final int QUALITY = 95;
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
index 5b458d8..e2a21a9 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotSurface.java
@@ -222,7 +222,7 @@
final TaskSnapshotSurface snapshotSurface = new TaskSnapshotSurface(service, window,
surfaceControl, snapshot, layoutParams.getTitle(), taskDescription, sysUiVis,
windowFlags, windowPrivateFlags, taskBounds,
- currentOrientation, topFullscreenOpaqueWindow.getClientInsetsState());
+ currentOrientation, topFullscreenOpaqueWindow.getRequestedInsetsState());
window.setOuter(snapshotSurface);
try {
session.relayout(window, window.mSeq, layoutParams, -1, -1, View.VISIBLE, 0, -1,
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 6ff4b2e..137d122 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -121,7 +121,7 @@
mFindResults.resetTopWallpaper = true;
if (w.mActivityRecord != null && !w.mActivityRecord.isVisible()
- && !w.mActivityRecord.isAnimating(TRANSITION)) {
+ && !w.mActivityRecord.isAnimating(TRANSITION | PARENTS)) {
// If this window's app token is hidden and not animating, it is of no interest to us.
if (DEBUG_WALLPAPER) Slog.v(TAG, "Skipping hidden and not animating token: " + w);
@@ -139,7 +139,7 @@
}
final boolean keyguardGoingAwayWithWallpaper = (w.mActivityRecord != null
- && w.mActivityRecord.isAnimating(TRANSITION)
+ && w.mActivityRecord.isAnimating(TRANSITION | PARENTS)
&& AppTransition.isKeyguardGoingAwayTransit(w.mActivityRecord.getTransit())
&& (w.mActivityRecord.getTransitFlags()
& TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER) != 0);
@@ -162,9 +162,11 @@
final RecentsAnimationController recentsAnimationController =
mService.getRecentsAnimationController();
- final boolean animationWallpaper = w.mActivityRecord != null
- && w.mActivityRecord.getAnimation() != null
- && w.mActivityRecord.getAnimation().getShowWallpaper();
+ final WindowContainer animatingContainer =
+ w.mActivityRecord != null ? w.mActivityRecord.getAnimatingContainer() : null;
+ final boolean animationWallpaper = animatingContainer != null
+ && animatingContainer.getAnimation() != null
+ && animatingContainer.getAnimation().getShowWallpaper();
final boolean hasWallpaper = (w.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0
|| animationWallpaper;
final boolean isRecentsTransitionTarget = (recentsAnimationController != null
@@ -228,14 +230,14 @@
if (DEBUG_WALLPAPER) Slog.v(TAG, "Wallpaper vis: target " + wallpaperTarget + ", obscured="
+ (wallpaperTarget != null ? Boolean.toString(wallpaperTarget.mObscured) : "??")
+ " animating=" + ((wallpaperTarget != null && wallpaperTarget.mActivityRecord != null)
- ? wallpaperTarget.mActivityRecord.isAnimating(TRANSITION) : null)
+ ? wallpaperTarget.mActivityRecord.isAnimating(TRANSITION | PARENTS) : null)
+ " prev=" + mPrevWallpaperTarget
+ " recentsAnimationWallpaperVisible=" + isAnimatingWithRecentsComponent);
return (wallpaperTarget != null
&& (!wallpaperTarget.mObscured
|| isAnimatingWithRecentsComponent
|| (wallpaperTarget.mActivityRecord != null
- && wallpaperTarget.mActivityRecord.isAnimating(TRANSITION))))
+ && wallpaperTarget.mActivityRecord.isAnimating(TRANSITION | PARENTS))))
|| mPrevWallpaperTarget != null;
}
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 5daf567..3b2d519 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -42,6 +42,7 @@
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.logWithStack;
+import static com.android.server.wm.WindowManagerService.sHierarchicalAnimations;
import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM;
import android.annotation.CallSuper;
@@ -246,6 +247,8 @@
private MagnificationSpec mLastMagnificationSpec;
+ private boolean mIsFocusable = true;
+
WindowContainer(WindowManagerService wms) {
mWmService = wms;
mPendingTransaction = wms.mTransactionFactory.get();
@@ -342,7 +345,7 @@
if (mSurfaceControl == null) {
// If we don't yet have a surface, but we now have a parent, we should
// build a surface.
- mSurfaceControl = makeSurface().build();
+ setSurfaceControl(makeSurface().build());
getPendingTransaction().show(mSurfaceControl);
updateSurfacePosition();
} else {
@@ -496,7 +499,7 @@
mParent.getPendingTransaction().merge(getPendingTransaction());
}
- mSurfaceControl = null;
+ setSurfaceControl(null);
mLastSurfacePosition.set(0, 0);
scheduleAnimation();
}
@@ -785,7 +788,7 @@
* {@code ActivityRecord#isAnimating(TRANSITION)}, {@code false} otherwise.
*/
boolean isAppTransitioning() {
- return getActivity(app -> app.isAnimating(TRANSITION)) != null;
+ return getActivity(app -> app.isAnimating(PARENTS | TRANSITION)) != null;
}
/**
@@ -850,6 +853,21 @@
return false;
}
+ @Override
+ boolean isFocusable() {
+ return super.isFocusable() && mIsFocusable;
+ }
+
+ /** Set whether this container or its children can be focusable */
+ @Override
+ boolean setFocusable(boolean focusable) {
+ if (mIsFocusable == focusable) {
+ return false;
+ }
+ mIsFocusable = focusable;
+ return true;
+ }
+
/**
* @return Whether this child is on top of the window hierarchy.
*/
@@ -1855,7 +1873,7 @@
// TODO: Remove this and use #getBounds() instead once we set an app transition animation
// on TaskStack.
Rect getAnimationBounds(int appStackClipMode) {
- return getBounds();
+ return getDisplayedBounds();
}
/**
@@ -1895,7 +1913,7 @@
if (adapter != null) {
startAnimation(getPendingTransaction(), adapter, !isVisible());
if (adapter.getShowWallpaper()) {
- mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
+ getDisplayContent().pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
}
if (thumbnailAdapter != null) {
mThumbnail.startAnimation(
@@ -1929,7 +1947,11 @@
// Separate position and size for use in animators.
mTmpRect.set(getAnimationBounds(appStackClipMode));
- mTmpPoint.set(mTmpRect.left, mTmpRect.top);
+ if (sHierarchicalAnimations) {
+ getRelativeDisplayedPosition(mTmpPoint);
+ } else {
+ mTmpPoint.set(mTmpRect.left, mTmpRect.top);
+ }
mTmpRect.offsetTo(0, 0);
final RemoteAnimationController controller =
@@ -2040,7 +2062,8 @@
}
boolean okToDisplay() {
- return mDisplayContent != null && mDisplayContent.okToDisplay();
+ final DisplayContent dc = getDisplayContent();
+ return dc != null && dc.okToDisplay();
}
boolean okToAnimate() {
@@ -2048,7 +2071,8 @@
}
boolean okToAnimate(boolean ignoreFrozen) {
- return mDisplayContent != null && mDisplayContent.okToAnimate(ignoreFrozen);
+ final DisplayContent dc = getDisplayContent();
+ return dc != null && dc.okToAnimate(ignoreFrozen);
}
@Override
@@ -2090,6 +2114,21 @@
}
/**
+ * @return The {@link WindowContainer} which is running an animation.
+ *
+ * It traverses from the current container to its parents recursively. If nothing is animating,
+ * it will return {@code null}.
+ */
+ @Nullable
+ WindowContainer getAnimatingContainer() {
+ if (isAnimating()) {
+ return this;
+ }
+ final WindowContainer parent = getParent();
+ return (parent != null) ? parent.getAnimatingContainer() : null;
+ }
+
+ /**
* @see SurfaceAnimator#startDelayingAnimationStart
*/
void startDelayingAnimationStart() {
@@ -2192,4 +2231,8 @@
}
return mParent.getDimmer();
}
+
+ void setSurfaceControl(SurfaceControl sc) {
+ mSurfaceControl = sc;
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 223e9b9..e3b593e9 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -205,6 +205,7 @@
import android.view.Gravity;
import android.view.IAppTransitionAnimationSpecsFuture;
import android.view.IDisplayFoldListener;
+import android.view.IDisplayWindowInsetsController;
import android.view.IDisplayWindowListener;
import android.view.IDisplayWindowRotationController;
import android.view.IDockedStackListener;
@@ -2768,6 +2769,7 @@
true /* includingParents */);
}
}
+ syncInputTransactions();
}
/**
@@ -3727,6 +3729,48 @@
}
@Override
+ public void setDisplayWindowInsetsController(
+ int displayId, IDisplayWindowInsetsController insetsController) {
+ if (mContext.checkCallingOrSelfPermission(MANAGE_APP_TOKENS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Must hold permission " + MANAGE_APP_TOKENS);
+ }
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ final DisplayContent dc = mRoot.getDisplayContent(displayId);
+ if (dc == null) {
+ return;
+ }
+ dc.setRemoteInsetsController(insetsController);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
+ @Override
+ public void modifyDisplayWindowInsets(int displayId, InsetsState state) {
+ if (mContext.checkCallingOrSelfPermission(MANAGE_APP_TOKENS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Must hold permission " + MANAGE_APP_TOKENS);
+ }
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ final DisplayContent dc = mRoot.getDisplayContent(displayId);
+ if (dc == null || dc.mRemoteInsetsControlTarget == null) {
+ return;
+ }
+ dc.getInsetsStateController().onInsetsModified(
+ dc.mRemoteInsetsControlTarget, state);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
+ @Override
public int watchRotation(IRotationWatcher watcher, int displayId) {
final DisplayContent displayContent;
synchronized (mGlobalLock) {
@@ -4558,13 +4602,13 @@
if (newFocus != null) {
ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Gaining focus: %s", newFocus);
- newFocus.reportFocusChangedSerialized(true, mInTouchMode);
+ newFocus.reportFocusChangedSerialized(true);
notifyFocusChanged();
}
if (lastFocus != null) {
ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Losing focus: %s", lastFocus);
- lastFocus.reportFocusChangedSerialized(false, mInTouchMode);
+ lastFocus.reportFocusChangedSerialized(false);
}
break;
}
@@ -4582,7 +4626,7 @@
for (int i = 0; i < N; i++) {
ProtoLog.i(WM_DEBUG_FOCUS_LIGHT, "Losing delayed focus: %s",
losers.get(i));
- losers.get(i).reportFocusChangedSerialized(false, mInTouchMode);
+ losers.get(i).reportFocusChangedSerialized(false);
}
break;
}
@@ -7314,7 +7358,8 @@
// If there was a pending IME show(), reset it as IME has been
// requested to be hidden.
dc.getInsetsStateController().getImeSourceProvider().abortShowImePostLayout();
- dc.mInputMethodTarget.hideInsets(WindowInsets.Type.ime(), true /* fromIme */);
+ dc.mInputMethodControlTarget.hideInsets(WindowInsets.Type.ime(),
+ true /* fromIme */);
}
}
}
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 2ac1f39..36e9273 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -210,6 +210,7 @@
import android.view.InputEvent;
import android.view.InputEventReceiver;
import android.view.InputWindowHandle;
+import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.Surface.Rotation;
import android.view.SurfaceControl;
@@ -650,17 +651,36 @@
private boolean mIsDimming = false;
private @Nullable InsetsSourceProvider mControllableInsetProvider;
- private InsetsState mClientInsetsState;
+ private InsetsState mRequestedInsetsState;
private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f;
private KeyInterceptionInfo mKeyInterceptionInfo;
- InsetsState getClientInsetsState() {
- return mClientInsetsState;
+ /**
+ * This information is passed to SurfaceFlinger to decide which window should have a priority
+ * when deciding about the refresh rate of the display. All windows have the lowest priority by
+ * default. The variable is cached, so we do not send too many updates to SF.
+ */
+ int mFrameRateSelectionPriority = RefreshRatePolicy.LAYER_PRIORITY_UNSET;
+
+ /**
+ * @return The insets state as requested by the client, i.e. the dispatched insets state
+ * for which the visibilities are overridden with what the client requested.
+ */
+ InsetsState getRequestedInsetsState() {
+ return mRequestedInsetsState;
}
- void setClientInsetsState(InsetsState state) {
- mClientInsetsState = state;
+ /**
+ * @see #getRequestedInsetsState()
+ */
+ void updateRequestedInsetsState(InsetsState state) {
+
+ // Only update the sources the client is actually controlling.
+ for (int i = state.getSourcesCount() - 1; i >= 0; i--) {
+ final InsetsSource source = state.sourceAt(i);
+ mRequestedInsetsState.addSource(source);
+ }
}
void seamlesslyRotateIfAllowed(Transaction transaction, @Rotation int oldRotation,
@@ -675,6 +695,9 @@
}
if (mForceSeamlesslyRotate || requested) {
+ if (mControllableInsetProvider != null) {
+ mControllableInsetProvider.startSeamlessRotation();
+ }
mPendingSeamlessRotate = new SeamlessRotator(oldRotation, rotation, getDisplayInfo());
mPendingSeamlessRotate.unrotate(transaction, this);
getDisplayContent().getDisplayRotation().markForSeamlessRotation(this,
@@ -689,6 +712,9 @@
mPendingSeamlessRotate = null;
getDisplayContent().getDisplayRotation().markForSeamlessRotation(this,
false /* seamlesslyRotated */);
+ if (mControllableInsetProvider != null) {
+ mControllableInsetProvider.finishSeamlessRotation(timeout);
+ }
}
}
@@ -779,8 +805,9 @@
mSeq = seq;
mPowerManagerWrapper = powerManagerWrapper;
mForceSeamlesslyRotate = token.mRoundedCornerOverlay;
- mClientInsetsState =
- getDisplayContent().getInsetsPolicy().getInsetsForDispatch(this);
+ mRequestedInsetsState = new InsetsState(
+ getDisplayContent().getInsetsPolicy().getInsetsForDispatch(this),
+ true /* copySources */);
if (DEBUG) {
Slog.v(TAG, "Window " + this + " client=" + c.asBinder()
+ " token=" + token + " (" + mAttrs.token + ")" + " params=" + a);
@@ -971,8 +998,11 @@
final int layoutXDiff;
final int layoutYDiff;
final WindowState imeWin = mWmService.mRoot.getCurrentInputMethodWindow();
+ final boolean isInputMethodAdjustTarget = windowsAreFloating
+ ? dc.mInputMethodTarget != null && task == dc.mInputMethodTarget.getTask()
+ : isInputMethodTarget();
final boolean isImeTarget =
- imeWin != null && imeWin.isVisibleNow() && isInputMethodTarget();
+ imeWin != null && imeWin.isVisibleNow() && isInputMethodAdjustTarget;
if (isFullscreenAndFillsDisplay || layoutInParentFrame()) {
// We use the parent frame as the containing frame for fullscreen and child windows
mWindowFrames.mContainingFrame.set(mWindowFrames.mParentFrame);
@@ -1003,7 +1033,8 @@
final int distanceToTop = Math.max(mWindowFrames.mContainingFrame.top
- mWindowFrames.mContentFrame.top, 0);
int offs = Math.min(bottomOverlap, distanceToTop);
- mWindowFrames.mContainingFrame.top -= offs;
+ mWindowFrames.mContainingFrame.offset(0, -offs);
+ mInsetFrame.offset(0, -offs);
}
} else if (!inPinnedWindowingMode() && mWindowFrames.mContainingFrame.bottom
> mWindowFrames.mParentFrame.bottom) {
@@ -1573,7 +1604,7 @@
// TODO: Can we consolidate this with #isVisible() or have a more appropriate name for this?
boolean isWinVisibleLw() {
return (mActivityRecord == null || mActivityRecord.mVisibleRequested
- || mActivityRecord.isAnimating(TRANSITION)) && isVisible();
+ || mActivityRecord.isAnimating(TRANSITION | PARENTS)) && isVisible();
}
/**
@@ -1799,7 +1830,7 @@
// Starting window that's exiting will be removed when the animation finishes.
// Mark all relevant flags for that onExitAnimationDone will proceed all the way
// to actually remove it.
- if (!visible && isVisibleNow && mActivityRecord.isAnimating(TRANSITION)) {
+ if (!visible && isVisibleNow && mActivityRecord.isAnimating(PARENTS | TRANSITION)) {
mAnimatingExit = true;
mRemoveOnExit = true;
mWindowRemovalAllowed = true;
@@ -2068,7 +2099,7 @@
this, mWinAnimator.mSurfaceController, mAnimatingExit, mRemoveOnExit,
mHasSurface, mWinAnimator.getShown(),
isAnimating(TRANSITION | PARENTS),
- mActivityRecord != null && mActivityRecord.isAnimating(TRANSITION),
+ mActivityRecord != null && mActivityRecord.isAnimating(PARENTS | TRANSITION),
mWillReplaceWindow,
mWmService.mDisplayFrozen, Debug.getCallers(6));
@@ -3315,11 +3346,7 @@
* Report a focus change. Must be called with no locks held, and consistently
* from the same serialized thread (such as dispatched from a handler).
*/
- void reportFocusChangedSerialized(boolean focused, boolean inTouchMode) {
- try {
- mClient.windowFocusChanged(focused, inTouchMode);
- } catch (RemoteException e) {
- }
+ void reportFocusChangedSerialized(boolean focused) {
if (mFocusCallbacks != null) {
final int N = mFocusCallbacks.beginBroadcast();
for (int i=0; i<N; i++) {
@@ -4287,7 +4314,7 @@
+ " tok.visible=" + (mActivityRecord != null && mActivityRecord.isVisible())
+ " animating=" + isAnimating(TRANSITION | PARENTS)
+ " tok animating="
- + (mActivityRecord != null && mActivityRecord.isAnimating(TRANSITION))
+ + (mActivityRecord != null && mActivityRecord.isAnimating(TRANSITION | PARENTS))
+ " Callers=" + Debug.getCallers(4));
}
}
@@ -5145,6 +5172,24 @@
}
}
+
+ /**
+ * Notifies SF about the priority of the window, if it changed. SF then uses this information
+ * to decide which window's desired rendering rate should have a priority when deciding about
+ * the refresh rate of the screen. Priority
+ * {@link RefreshRatePolicy#LAYER_PRIORITY_FOCUSED_WITH_MODE} is considered the highest.
+ */
+ @VisibleForTesting
+ void updateFrameRateSelectionPriorityIfNeeded() {
+ final int priority = getDisplayContent().getDisplayPolicy().getRefreshRatePolicy()
+ .calculatePriority(this);
+ if (mFrameRateSelectionPriority != priority) {
+ mFrameRateSelectionPriority = priority;
+ getPendingTransaction().setFrameRateSelectionPriority(mSurfaceControl,
+ mFrameRateSelectionPriority);
+ }
+ }
+
@Override
void prepareSurfaces() {
final Dimmer dimmer = getDimmer();
@@ -5153,6 +5198,8 @@
applyDims(dimmer);
}
updateSurfacePosition();
+ // Send information to SufaceFlinger about the priority of the current window.
+ updateFrameRateSelectionPriorityIfNeeded();
mWinAnimator.prepareSurfaceLocked(true);
super.prepareSurfaces();
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 1a11766..486616d 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1398,7 +1398,7 @@
mWin.getDisplayContent().adjustForImeIfNeeded();
}
- return mWin.isAnimating(TRANSITION | PARENTS);
+ return mWin.isAnimating(PARENTS);
}
void dumpDebug(ProtoOutputStream proto, long fieldId) {
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index fee29db..77d814e 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -13,6 +13,7 @@
],
srcs: [
+ ":graphicsstats_proto",
"BroadcastRadio/JavaRef.cpp",
"BroadcastRadio/NativeCallbackThread.cpp",
"BroadcastRadio/BroadcastRadioService.cpp",
@@ -50,7 +51,7 @@
"com_android_server_VibratorService.cpp",
"com_android_server_PersistentDataBlockService.cpp",
"com_android_server_GraphicsStatsService.cpp",
- "com_android_server_am_AppCompactor.cpp",
+ "com_android_server_am_CachedAppOptimizer.cpp",
"com_android_server_am_LowMemDetector.cpp",
"com_android_server_incremental_IncrementalManagerService.cpp",
"onload.cpp",
@@ -103,6 +104,11 @@
"libinputflinger",
"libinputflinger_base",
"libinputservice",
+ "libprotobuf-cpp-lite",
+ "libprotoutil",
+ "libstatspull",
+ "libstatssocket",
+ "libstatslog",
"libschedulerservicehidl",
"libsensorservice",
"libsensorservicehidl",
@@ -138,6 +144,7 @@
"android.hardware.thermal@1.0",
"android.hardware.tv.cec@1.0",
"android.hardware.tv.input@1.0",
+ "android.hardware.vibrator-cpp",
"android.hardware.vibrator@1.0",
"android.hardware.vibrator@1.1",
"android.hardware.vibrator@1.2",
@@ -148,7 +155,6 @@
"android.system.suspend@1.0",
"service.incremental",
"suspend_control_aidl_interface-cpp",
- "vintf-vibrator-cpp",
],
static_libs: [
diff --git a/services/core/jni/com_android_server_GraphicsStatsService.cpp b/services/core/jni/com_android_server_GraphicsStatsService.cpp
index d1d253b..7644ade 100644
--- a/services/core/jni/com_android_server_GraphicsStatsService.cpp
+++ b/services/core/jni/com_android_server_GraphicsStatsService.cpp
@@ -23,6 +23,16 @@
#include <nativehelper/ScopedUtfChars.h>
#include <JankTracker.h>
#include <service/GraphicsStatsService.h>
+#include <stats_pull_atom_callback.h>
+#include <stats_event.h>
+#include <statslog.h>
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
+#include <android/util/ProtoOutputStream.h>
+#include "android/graphics/Utils.h"
+#include "core_jni_helpers.h"
+#include "protos/graphicsstats.pb.h"
+#include <cstring>
+#include <memory>
namespace android {
@@ -77,6 +87,20 @@
GraphicsStatsService::finishDump(dump);
}
+static jlong finishDumpInMemory(JNIEnv* env, jobject, jlong dumpPtr) {
+ GraphicsStatsService::Dump* dump = reinterpret_cast<GraphicsStatsService::Dump*>(dumpPtr);
+ std::vector<uint8_t>* result = new std::vector<uint8_t>();
+ GraphicsStatsService::finishDumpInMemory(dump,
+ [](void* buffer, int bufferOffset, int bufferSize, int totalSize, void* param1, void* param2) {
+ std::vector<uint8_t>* outBuffer = reinterpret_cast<std::vector<uint8_t>*>(param2);
+ if (outBuffer->size() < totalSize) {
+ outBuffer->resize(totalSize);
+ }
+ std::memcpy(outBuffer->data() + bufferOffset, buffer, bufferSize);
+ }, env, result);
+ return reinterpret_cast<jlong>(result);
+}
+
static void saveBuffer(JNIEnv* env, jobject clazz, jstring jpath, jstring jpackage,
jlong versionCode, jlong startTime, jlong endTime, jbyteArray jdata) {
ScopedByteArrayRO buffer(env, jdata);
@@ -93,19 +117,174 @@
GraphicsStatsService::saveBuffer(path, package, versionCode, startTime, endTime, data);
}
+static jobject gGraphicsStatsServiceObject = nullptr;
+static jmethodID gGraphicsStatsService_pullGraphicsStatsMethodID;
+
+static JNIEnv* getJNIEnv() {
+ JavaVM* vm = AndroidRuntime::getJavaVM();
+ JNIEnv* env = nullptr;
+ if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ if (vm->AttachCurrentThreadAsDaemon(&env, nullptr) != JNI_OK) {
+ LOG_ALWAYS_FATAL("Failed to AttachCurrentThread!");
+ }
+ }
+ return env;
+}
+
+using namespace google::protobuf;
+
+// Field ids taken from FrameTimingHistogram message in atoms.proto
+#define TIME_MILLIS_BUCKETS_FIELD_NUMBER 1
+#define FRAME_COUNTS_FIELD_NUMBER 2
+
+static void writeCpuHistogram(stats_event* event,
+ const uirenderer::protos::GraphicsStatsProto& stat) {
+ util::ProtoOutputStream proto;
+ for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) {
+ auto& bucket = stat.histogram(bucketIndex);
+ proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED |
+ TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */,
+ (int)bucket.render_millis());
+ }
+ for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) {
+ auto& bucket = stat.histogram(bucketIndex);
+ proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED |
+ FRAME_COUNTS_FIELD_NUMBER /* field id */,
+ (long long)bucket.frame_count());
+ }
+ std::vector<uint8_t> outVector;
+ proto.serializeToVector(&outVector);
+ stats_event_write_byte_array(event, outVector.data(), outVector.size());
+}
+
+static void writeGpuHistogram(stats_event* event,
+ const uirenderer::protos::GraphicsStatsProto& stat) {
+ util::ProtoOutputStream proto;
+ for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) {
+ auto& bucket = stat.gpu_histogram(bucketIndex);
+ proto.write(android::util::FIELD_TYPE_INT32 | android::util::FIELD_COUNT_REPEATED |
+ TIME_MILLIS_BUCKETS_FIELD_NUMBER /* field id */,
+ (int)bucket.render_millis());
+ }
+ for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) {
+ auto& bucket = stat.gpu_histogram(bucketIndex);
+ proto.write(android::util::FIELD_TYPE_INT64 | android::util::FIELD_COUNT_REPEATED |
+ FRAME_COUNTS_FIELD_NUMBER /* field id */,
+ (long long)bucket.frame_count());
+ }
+ std::vector<uint8_t> outVector;
+ proto.serializeToVector(&outVector);
+ stats_event_write_byte_array(event, outVector.data(), outVector.size());
+}
+
+// graphicsStatsPullCallback is invoked by statsd service to pull GRAPHICS_STATS atom.
+static status_pull_atom_return_t graphicsStatsPullCallback(int32_t atom_tag,
+ pulled_stats_event_list* data,
+ void* cookie) {
+ JNIEnv* env = getJNIEnv();
+ if (!env) {
+ return false;
+ }
+ if (gGraphicsStatsServiceObject == nullptr) {
+ ALOGE("Failed to get graphicsstats service");
+ return STATS_PULL_SKIP;
+ }
+
+ for (bool lastFullDay : {true, false}) {
+ jlong jdata = (jlong) env->CallLongMethod(
+ gGraphicsStatsServiceObject,
+ gGraphicsStatsService_pullGraphicsStatsMethodID,
+ (jboolean)(lastFullDay ? JNI_TRUE : JNI_FALSE));
+ if (env->ExceptionCheck()) {
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ ALOGE("Failed to invoke graphicsstats service");
+ return STATS_PULL_SKIP;
+ }
+ if (!jdata) {
+ // null means data is not available for that day.
+ continue;
+ }
+ android::uirenderer::protos::GraphicsStatsServiceDumpProto serviceDump;
+ std::vector<uint8_t>* buffer = reinterpret_cast<std::vector<uint8_t>*>(jdata);
+ std::unique_ptr<std::vector<uint8_t>> bufferRelease(buffer);
+ int dataSize = buffer->size();
+ if (!dataSize) {
+ // Data is not available for that day.
+ continue;
+ }
+ io::ArrayInputStream input{buffer->data(), dataSize};
+ bool success = serviceDump.ParseFromZeroCopyStream(&input);
+ if (!success) {
+ ALOGW("Parse failed on GraphicsStatsPuller error='%s' dataSize='%d'",
+ serviceDump.InitializationErrorString().c_str(), dataSize);
+ return STATS_PULL_SKIP;
+ }
+
+ for (int stat_index = 0; stat_index < serviceDump.stats_size(); stat_index++) {
+ auto& stat = serviceDump.stats(stat_index);
+ stats_event* event = add_stats_event_to_pull_data(data);
+ stats_event_set_atom_id(event, android::util::GRAPHICS_STATS);
+ stats_event_write_string8(event, stat.package_name().c_str());
+ stats_event_write_int64(event, (int64_t)stat.version_code());
+ stats_event_write_int64(event, (int64_t)stat.stats_start());
+ stats_event_write_int64(event, (int64_t)stat.stats_end());
+ stats_event_write_int32(event, (int32_t)stat.pipeline());
+ stats_event_write_int32(event, (int32_t)stat.summary().total_frames());
+ stats_event_write_int32(event, (int32_t)stat.summary().missed_vsync_count());
+ stats_event_write_int32(event, (int32_t)stat.summary().high_input_latency_count());
+ stats_event_write_int32(event, (int32_t)stat.summary().slow_ui_thread_count());
+ stats_event_write_int32(event, (int32_t)stat.summary().slow_bitmap_upload_count());
+ stats_event_write_int32(event, (int32_t)stat.summary().slow_draw_count());
+ stats_event_write_int32(event, (int32_t)stat.summary().missed_deadline_count());
+ writeCpuHistogram(event, stat);
+ writeGpuHistogram(event, stat);
+ // TODO: fill in UI mainline module version, when the feature is available.
+ stats_event_write_int64(event, (int64_t)0);
+ stats_event_write_bool(event, !lastFullDay);
+ stats_event_build(event);
+ }
+ }
+ return STATS_PULL_SUCCESS;
+}
+
+// Register a puller for GRAPHICS_STATS atom with the statsd service.
+static void nativeInit(JNIEnv* env, jobject javaObject) {
+ gGraphicsStatsServiceObject = env->NewGlobalRef(javaObject);
+ pull_atom_metadata metadata = {.cool_down_ns = 10 * 1000000, // 10 milliseconds
+ .timeout_ns = 2 * NS_PER_SEC, // 2 seconds
+ .additive_fields = nullptr,
+ .additive_fields_size = 0};
+ register_stats_pull_atom_callback(android::util::GRAPHICS_STATS, &graphicsStatsPullCallback,
+ &metadata, nullptr);
+}
+
+static void nativeDestructor(JNIEnv* env, jobject javaObject) {
+ //TODO: Unregister the puller callback when a new API is available.
+ env->DeleteGlobalRef(gGraphicsStatsServiceObject);
+ gGraphicsStatsServiceObject = nullptr;
+}
+
static const JNINativeMethod sMethods[] = {
{ "nGetAshmemSize", "()I", (void*) getAshmemSize },
{ "nCreateDump", "(IZ)J", (void*) createDump },
{ "nAddToDump", "(JLjava/lang/String;Ljava/lang/String;JJJ[B)V", (void*) addToDump },
{ "nAddToDump", "(JLjava/lang/String;)V", (void*) addFileToDump },
{ "nFinishDump", "(J)V", (void*) finishDump },
+ { "nFinishDumpInMemory", "(J)J", (void*) finishDumpInMemory },
{ "nSaveBuffer", "(Ljava/lang/String;Ljava/lang/String;JJJ[B)V", (void*) saveBuffer },
+ { "nativeInit", "()V", (void*) nativeInit },
+ { "nativeDestructor", "()V", (void*)nativeDestructor }
};
int register_android_server_GraphicsStatsService(JNIEnv* env)
{
+ jclass graphicsStatsService_class = FindClassOrDie(env,
+ "com/android/server/GraphicsStatsService");
+ gGraphicsStatsService_pullGraphicsStatsMethodID = GetMethodIDOrDie(env,
+ graphicsStatsService_class, "pullGraphicsStats", "(Z)J");
return jniRegisterNativeMethods(env, "com/android/server/GraphicsStatsService",
sMethods, NELEM(sMethods));
}
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/services/core/jni/com_android_server_am_AppCompactor.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
similarity index 89%
rename from services/core/jni/com_android_server_am_AppCompactor.cpp
rename to services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
index de6aa8b..6a6da0e 100644
--- a/services/core/jni/com_android_server_am_AppCompactor.cpp
+++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define LOG_TAG "AppCompactor"
+#define LOG_TAG "CachedAppOptimizer"
//#define LOG_NDEBUG 0
#include <dirent.h>
@@ -42,7 +42,7 @@
// or potentially some mainline modules. The only process that should definitely
// not be compacted is system_server, since compacting system_server around the
// time of BOOT_COMPLETE could result in perceptible issues.
-static void com_android_server_am_AppCompactor_compactSystem(JNIEnv *, jobject) {
+static void com_android_server_am_CachedAppOptimizer_compactSystem(JNIEnv *, jobject) {
std::unique_ptr<DIR, decltype(&closedir)> proc(opendir("/proc"), closedir);
struct dirent* current;
while ((current = readdir(proc.get()))) {
@@ -76,12 +76,12 @@
static const JNINativeMethod sMethods[] = {
/* name, signature, funcPtr */
- {"compactSystem", "()V", (void*)com_android_server_am_AppCompactor_compactSystem},
+ {"compactSystem", "()V", (void*)com_android_server_am_CachedAppOptimizer_compactSystem},
};
-int register_android_server_am_AppCompactor(JNIEnv* env)
+int register_android_server_am_CachedAppOptimizer(JNIEnv* env)
{
- return jniRegisterNativeMethods(env, "com/android/server/am/AppCompactor",
+ return jniRegisterNativeMethods(env, "com/android/server/am/CachedAppOptimizer",
sMethods, NELEM(sMethods));
}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 2e8e5e7..c0891d7 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -1757,6 +1757,12 @@
return im->getInputManager()->getReader()->canDispatchToDisplay(deviceId, displayId);
}
+static void nativeNotifyPortAssociationsChanged(JNIEnv* env, jclass /* clazz */, jlong ptr) {
+ NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
+ im->getInputManager()->getReader()->requestRefreshConfiguration(
+ InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+}
+
// ----------------------------------------------------------------------------
static const JNINativeMethod gInputManagerMethods[] = {
@@ -1842,6 +1848,8 @@
(void*) nativeSetCustomPointerIcon },
{ "nativeCanDispatchToDisplay", "(JII)Z",
(void*) nativeCanDispatchToDisplay },
+ { "nativeNotifyPortAssociationsChanged", "(J)V",
+ (void*) nativeNotifyPortAssociationsChanged },
};
#define FIND_CLASS(var, className) \
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 6504e31..00436bb 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -129,7 +129,6 @@
using android::hardware::hidl_string;
using android::hardware::hidl_death_recipient;
-using android::hardware::gnss::V1_0::GnssConstellationType;
using android::hardware::gnss::V1_0::GnssLocationFlags;
using android::hardware::gnss::V1_0::IAGnssRilCallback;
using android::hardware::gnss::V1_0::IGnssGeofenceCallback;
@@ -149,6 +148,8 @@
using android::hidl::base::V1_0::IBase;
+using GnssConstellationType_V1_0 = android::hardware::gnss::V1_0::GnssConstellationType;
+using GnssConstellationType_V2_0 = android::hardware::gnss::V2_0::GnssConstellationType;
using GnssLocation_V1_0 = android::hardware::gnss::V1_0::GnssLocation;
using GnssLocation_V2_0 = android::hardware::gnss::V2_0::GnssLocation;
using IGnss_V1_0 = android::hardware::gnss::V1_0::IGnss;
@@ -161,6 +162,7 @@
using IGnssConfiguration_V1_0 = android::hardware::gnss::V1_0::IGnssConfiguration;
using IGnssConfiguration_V1_1 = android::hardware::gnss::V1_1::IGnssConfiguration;
using IGnssConfiguration_V2_0 = android::hardware::gnss::V2_0::IGnssConfiguration;
+using IGnssConfiguration_V2_1 = android::hardware::gnss::V2_1::IGnssConfiguration;
using IGnssDebug_V1_0 = android::hardware::gnss::V1_0::IGnssDebug;
using IGnssDebug_V2_0 = android::hardware::gnss::V2_0::IGnssDebug;
using IGnssMeasurement_V1_0 = android::hardware::gnss::V1_0::IGnssMeasurement;
@@ -202,6 +204,7 @@
// Must match the value from GnssMeasurement.java
static const uint32_t ADR_STATE_HALF_CYCLE_REPORTED = (1<<4);
+static const uint32_t SVID_FLAGS_HAS_BASEBAND_CN0 = (1<<4);
sp<GnssDeathRecipient> gnssHalDeathRecipient = nullptr;
sp<IGnss_V1_0> gnssHal = nullptr;
@@ -221,6 +224,7 @@
sp<IGnssConfiguration_V1_0> gnssConfigurationIface = nullptr;
sp<IGnssConfiguration_V1_1> gnssConfigurationIface_V1_1 = nullptr;
sp<IGnssConfiguration_V2_0> gnssConfigurationIface_V2_0 = nullptr;
+sp<IGnssConfiguration_V2_1> gnssConfigurationIface_V2_1 = nullptr;
sp<IGnssNi> gnssNiIface = nullptr;
sp<IGnssMeasurement_V1_0> gnssMeasurementIface = nullptr;
sp<IGnssMeasurement_V1_1> gnssMeasurementIface_V1_1 = nullptr;
@@ -631,6 +635,16 @@
template<class T>
Return<void> gnssSvStatusCbImpl(const T& svStatus);
+ template<class T>
+ uint32_t getHasBasebandCn0DbHzFlag(const T& svStatus) {
+ return 0;
+ }
+
+ template<class T>
+ double getBasebandCn0DbHz(const T& svStatus, size_t i) {
+ return 0.0;
+ }
+
uint32_t getGnssSvInfoListSize(const IGnssCallback_V1_0::GnssSvStatus& svStatus) {
return svStatus.numSvs;
}
@@ -655,8 +669,6 @@
const IGnssCallback_V1_0::GnssSvInfo& getGnssSvInfoOfIndex(
const hidl_vec<IGnssCallback_V2_1::GnssSvInfo>& svInfoList, size_t i) {
- // TODO(b/144850155): fill baseband CN0 after it's available in Java object.
- ALOGD("getGnssSvInfoOfIndex %d: baseband C/N0: %f", (int) i, svInfoList[i].basebandCN0DbHz);
return svInfoList[i].v2_0.v1_0;
}
@@ -718,6 +730,18 @@
return Void();
}
+template<>
+uint32_t GnssCallback::getHasBasebandCn0DbHzFlag(const hidl_vec<IGnssCallback_V2_1::GnssSvInfo>&
+ svStatus) {
+ return SVID_FLAGS_HAS_BASEBAND_CN0;
+}
+
+template<>
+double GnssCallback::getBasebandCn0DbHz(const hidl_vec<IGnssCallback_V2_1::GnssSvInfo>& svInfoList,
+ size_t i) {
+ return svInfoList[i].basebandCN0DbHz;
+}
+
template<class T>
Return<void> GnssCallback::gnssSvStatusCbImpl(const T& svStatus) {
JNIEnv* env = getJniEnv();
@@ -755,8 +779,8 @@
elev[i] = info.elevationDegrees;
azim[i] = info.azimuthDegrees;
carrierFreq[i] = info.carrierFrequencyHz;
- // TODO(b/144850155): fill svidWithFlags with hasBasebandCn0DbHz based on HAL versions
- basebandCn0s[i] = 0.0;
+ svidWithFlags[i] |= getHasBasebandCn0DbHzFlag(svStatus);
+ basebandCn0s[i] = getBasebandCn0DbHz(svStatus, i);
}
env->ReleaseIntArrayElements(svidWithFlagArray, svidWithFlags, 0);
@@ -1182,8 +1206,8 @@
const IGnssMeasurementCallback_V2_1::GnssMeasurement* measurement_V2_1,
JavaObject& object) {
translateSingleGnssMeasurement(&(measurement_V2_1->v2_0), object);
- // TODO(b/144850155): fill baseband CN0 after it's available in Java object
- ALOGD("baseband CN0DbHz = %f\n", measurement_V2_1->basebandCN0DbHz);
+
+ SET(BasebandCn0DbHz, measurement_V2_1->basebandCN0DbHz);
}
template<class T>
@@ -1888,7 +1912,17 @@
gnssNiIface = gnssNi;
}
- if (gnssHal_V2_0 != nullptr) {
+ if (gnssHal_V2_1 != nullptr) {
+ auto gnssConfiguration = gnssHal_V2_1->getExtensionGnssConfiguration_2_1();
+ if (!gnssConfiguration.isOk()) {
+ ALOGD("Unable to get a handle to GnssConfiguration_V2_1");
+ } else {
+ gnssConfigurationIface_V2_1 = gnssConfiguration;
+ gnssConfigurationIface_V2_0 = gnssConfigurationIface_V2_1;
+ gnssConfigurationIface_V1_1 = gnssConfigurationIface_V2_1;
+ gnssConfigurationIface = gnssConfigurationIface_V2_1;
+ }
+ } else if (gnssHal_V2_0 != nullptr) {
auto gnssConfiguration = gnssHal_V2_0->getExtensionGnssConfiguration_2_0();
if (!gnssConfiguration.isOk()) {
ALOGD("Unable to get a handle to GnssConfiguration_V2_0");
@@ -1962,7 +1996,11 @@
static jobject android_location_GnssConfiguration_get_gnss_configuration_version(
JNIEnv* env, jclass /* jclazz */) {
jint major, minor;
- if (gnssConfigurationIface_V2_0 != nullptr) {
+ if (gnssConfigurationIface_V2_1 != nullptr) {
+ major = 2;
+ minor = 1;
+ }
+ else if (gnssConfigurationIface_V2_0 != nullptr) {
major = 2;
minor = 0;
} else if (gnssConfigurationIface_V1_1 != nullptr) {
@@ -2768,7 +2806,7 @@
SingleSatCorrection singleSatCorrection = {
.singleSatCorrectionFlags = corrFlags,
- .constellation = static_cast<GnssConstellationType>(constType),
+ .constellation = static_cast<GnssConstellationType_V1_0>(constType),
.svid = static_cast<uint16_t>(satId),
.carrierFrequencyHz = carrierFreqHz,
.probSatIsLos = probSatIsLos,
@@ -2863,8 +2901,8 @@
static jboolean android_location_GnssConfiguration_set_supl_es(JNIEnv*,
jobject,
jint suplEs) {
- if (gnssConfigurationIface_V2_0 != nullptr) {
- ALOGI("Config parameter SUPL_ES is deprecated in IGnssConfiguration.hal version 2.0.");
+ if (gnssConfigurationIface_V2_0 != nullptr || gnssConfigurationIface_V2_1 != nullptr) {
+ ALOGI("Config parameter SUPL_ES is deprecated in IGnssConfiguration.hal version 2.0 and higher.");
return JNI_FALSE;
}
@@ -2892,7 +2930,7 @@
static jboolean android_location_GnssConfiguration_set_gps_lock(JNIEnv*,
jobject,
jint gpsLock) {
- if (gnssConfigurationIface_V2_0 != nullptr) {
+ if (gnssConfigurationIface_V2_0 != nullptr || gnssConfigurationIface_V2_1 != nullptr) {
ALOGI("Config parameter GPS_LOCK is deprecated in IGnssConfiguration.hal version 2.0.");
return JNI_FALSE;
}
@@ -2932,7 +2970,7 @@
static jboolean android_location_GnssConfiguration_set_satellite_blacklist(
JNIEnv* env, jobject, jintArray constellations, jintArray sv_ids) {
- if (gnssConfigurationIface_V1_1 == nullptr) {
+ if (gnssConfigurationIface_V1_1 == nullptr && gnssConfigurationIface_V2_1 == nullptr) {
ALOGI("IGnssConfiguration interface does not support satellite blacklist.");
return JNI_FALSE;
}
@@ -2955,11 +2993,24 @@
return JNI_FALSE;
}
+ if (gnssConfigurationIface_V2_1 != nullptr) {
+ hidl_vec<IGnssConfiguration_V2_1::BlacklistedSource> sources;
+ sources.resize(length);
+
+ for (int i = 0; i < length; i++) {
+ sources[i].constellation = static_cast<GnssConstellationType_V2_0>(constellation_array[i]);
+ sources[i].svid = sv_id_array[i];
+ }
+
+ auto result = gnssConfigurationIface_V2_1->setBlacklist_2_1(sources);
+ return checkHidlReturn(result, "IGnssConfiguration_V2_1 setBlacklist_2_1() failed.");
+ }
+
hidl_vec<IGnssConfiguration_V1_1::BlacklistedSource> sources;
sources.resize(length);
for (int i = 0; i < length; i++) {
- sources[i].constellation = static_cast<GnssConstellationType>(constellation_array[i]);
+ sources[i].constellation = static_cast<GnssConstellationType_V1_0>(constellation_array[i]);
sources[i].svid = sv_id_array[i];
}
diff --git a/services/core/jni/com_android_server_net_NetworkStatsService.cpp b/services/core/jni/com_android_server_net_NetworkStatsService.cpp
index 4696dd0..0275f3e 100644
--- a/services/core/jni/com_android_server_net_NetworkStatsService.cpp
+++ b/services/core/jni/com_android_server_net_NetworkStatsService.cpp
@@ -33,7 +33,6 @@
#include "bpf/BpfUtils.h"
#include "netdbpf/BpfNetworkStats.h"
-using android::bpf::Stats;
using android::bpf::bpfGetUidStats;
using android::bpf::bpfGetIfaceStats;
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index c0a6e4e..19fa062 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -54,7 +54,7 @@
int register_android_server_net_NetworkStatsFactory(JNIEnv* env);
int register_android_server_net_NetworkStatsService(JNIEnv* env);
int register_android_server_security_VerityUtils(JNIEnv* env);
-int register_android_server_am_AppCompactor(JNIEnv* env);
+int register_android_server_am_CachedAppOptimizer(JNIEnv* env);
int register_android_server_am_LowMemDetector(JNIEnv* env);
int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
JNIEnv* env);
@@ -106,7 +106,7 @@
register_android_server_net_NetworkStatsFactory(env);
register_android_server_net_NetworkStatsService(env);
register_android_server_security_VerityUtils(env);
- register_android_server_am_AppCompactor(env);
+ register_android_server_am_CachedAppOptimizer(env);
register_android_server_am_LowMemDetector(env);
register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
env);
diff --git a/services/devicepolicy/Android.bp b/services/devicepolicy/Android.bp
index 380ee94..cdbe77a 100644
--- a/services/devicepolicy/Android.bp
+++ b/services/devicepolicy/Android.bp
@@ -18,8 +18,3 @@
"compat-changeid-annotation-processor",
],
}
-
-platform_compat_config {
- name: "services-devicepolicy-platform-compat-config",
- src: ":services.devicepolicy",
-}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index 5a1d552..8641059 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -64,4 +64,8 @@
}
public void setLocationEnabled(ComponentName who, boolean locationEnabled) {}
+
+ public boolean isOrganizationOwnedDeviceWithManagedProfile() {
+ return false;
+ }
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index c275ccc..b8b0dbf 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -23,7 +23,6 @@
import static android.app.admin.DeviceAdminReceiver.EXTRA_TRANSFER_OWNERSHIP_ADMIN_EXTRAS_BUNDLE;
import static android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_USER;
import static android.app.admin.DevicePolicyManager.CODE_ACCOUNTS_NOT_EMPTY;
-import static android.app.admin.DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED;
import static android.app.admin.DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE;
import static android.app.admin.DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED;
import static android.app.admin.DevicePolicyManager.CODE_HAS_DEVICE_OWNER;
@@ -129,6 +128,7 @@
import android.app.admin.DevicePolicyManager.PasswordComplexity;
import android.app.admin.DevicePolicyManagerInternal;
import android.app.admin.DeviceStateCache;
+import android.app.admin.FactoryResetProtectionPolicy;
import android.app.admin.NetworkEvent;
import android.app.admin.PasswordMetrics;
import android.app.admin.PasswordPolicy;
@@ -268,6 +268,7 @@
import com.android.internal.widget.PasswordValidationError;
import com.android.server.LocalServices;
import com.android.server.LockGuard;
+import com.android.server.PersistentDataBlockManagerInternal;
import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemService;
import com.android.server.devicepolicy.DevicePolicyManagerService.ActiveAdmin.TrustAgentInfo;
@@ -367,6 +368,8 @@
private static final String TAG_TRANSFER_OWNERSHIP_BUNDLE = "transfer-ownership-bundle";
+ private static final String TAG_PROTECTED_PACKAGES = "protected-packages";
+
private static final int REQUEST_EXPIRE_PASSWORD = 5571;
private static final long MS_PER_DAY = TimeUnit.DAYS.toMillis(1);
@@ -742,6 +745,9 @@
// This is the list of component allowed to start lock task mode.
List<String> mLockTaskPackages = new ArrayList<>();
+ // List of packages protected by device owner
+ List<String> mProtectedPackages = new ArrayList<>();
+
// Bitfield of feature flags to be enabled during LockTask mode.
// We default on the power button menu, in order to be consistent with pre-P behaviour.
int mLockTaskFeatures = DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS;
@@ -1001,6 +1007,8 @@
private static final String TAG_CROSS_PROFILE_CALENDAR_PACKAGES_NULL =
"cross-profile-calendar-packages-null";
private static final String TAG_CROSS_PROFILE_PACKAGES = "cross-profile-packages";
+ private static final String TAG_FACTORY_RESET_PROTECTION_POLICY =
+ "factory_reset_protection_policy";
DeviceAdminInfo info;
@@ -1011,6 +1019,9 @@
@NonNull
PasswordPolicy mPasswordPolicy = new PasswordPolicy();
+ @Nullable
+ FactoryResetProtectionPolicy mFactoryResetProtectionPolicy = null;
+
static final long DEF_MAXIMUM_TIME_TO_UNLOCK = 0;
long maximumTimeToUnlock = DEF_MAXIMUM_TIME_TO_UNLOCK;
@@ -1084,7 +1095,7 @@
String globalProxySpec = null;
String globalProxyExclusionList = null;
- ArrayMap<String, TrustAgentInfo> trustAgentInfos = new ArrayMap<>();
+ @NonNull ArrayMap<String, TrustAgentInfo> trustAgentInfos = new ArrayMap<>();
List<String> crossProfileWidgetProviders;
@@ -1346,6 +1357,11 @@
mCrossProfileCalendarPackages);
}
writePackageListToXml(out, TAG_CROSS_PROFILE_PACKAGES, mCrossProfilePackages);
+ if (mFactoryResetProtectionPolicy != null) {
+ out.startTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY);
+ mFactoryResetProtectionPolicy.writeToXml(out);
+ out.endTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY);
+ }
}
void writeTextToXml(XmlSerializer out, String tag, String text) throws IOException {
@@ -1579,6 +1595,9 @@
mCrossProfileCalendarPackages = null;
} else if (TAG_CROSS_PROFILE_PACKAGES.equals(tag)) {
mCrossProfilePackages = readPackageList(parser, tag);
+ } else if (TAG_FACTORY_RESET_PROTECTION_POLICY.equals(tag)) {
+ mFactoryResetProtectionPolicy = FactoryResetProtectionPolicy.readFromXml(
+ parser);
} else {
Slog.w(LOG_TAG, "Unknown admin tag: " + tag);
XmlUtils.skipCurrentTag(parser);
@@ -1631,6 +1650,7 @@
}
}
+ @NonNull
private ArrayMap<String, TrustAgentInfo> getAllTrustAgentInfos(
XmlPullParser parser, String tag) throws XmlPullParserException, IOException {
int outerDepthDAM = parser.getDepth();
@@ -2031,6 +2051,10 @@
return IAudioService.Stub.asInterface(ServiceManager.getService(Context.AUDIO_SERVICE));
}
+ PersistentDataBlockManagerInternal getPersistentDataBlockManagerInternal() {
+ return LocalServices.getService(PersistentDataBlockManagerInternal.class);
+ }
+
LockSettingsInternal getLockSettingsInternal() {
return LocalServices.getService(LockSettingsInternal.class);
}
@@ -2411,11 +2435,133 @@
migrateUserRestrictionsIfNecessaryLocked();
// TODO PO may not have a class name either due to b/17652534. Address that too.
-
updateDeviceOwnerLocked();
}
}
+ /**
+ * Checks if the device is in COMP mode, and if so migrates it to managed profile on a
+ * corporate owned device.
+ */
+ @GuardedBy("getLockObject()")
+ private void maybeMigrateToProfileOnOrganizationOwnedDeviceLocked() {
+ logIfVerbose("Checking whether we need to migrate COMP ");
+ final int doUserId = mOwners.getDeviceOwnerUserId();
+ if (doUserId == UserHandle.USER_NULL) {
+ logIfVerbose("No DO found, skipping migration.");
+ return;
+ }
+
+ final List<UserInfo> profiles = mUserManager.getProfiles(doUserId);
+ if (profiles.size() != 2) {
+ if (profiles.size() == 1) {
+ logIfVerbose("Profile not found, skipping migration.");
+ } else {
+ Slog.wtf(LOG_TAG, "Found " + profiles.size() + " profiles, skipping migration");
+ }
+ return;
+ }
+
+ final int poUserId = getManagedUserId(doUserId);
+ if (poUserId < 0) {
+ Slog.wtf(LOG_TAG, "Found DO and a profile, but it is not managed, skipping migration");
+ return;
+ }
+
+ final ActiveAdmin doAdmin = getDeviceOwnerAdminLocked();
+ final ActiveAdmin poAdmin = getProfileOwnerAdminLocked(poUserId);
+ if (doAdmin == null || poAdmin == null) {
+ Slog.wtf(LOG_TAG, "Failed to get either PO or DO admin, aborting migration.");
+ return;
+ }
+
+ final ComponentName doAdminComponent = mOwners.getDeviceOwnerComponent();
+ final ComponentName poAdminComponent = mOwners.getProfileOwnerComponent(poUserId);
+ if (doAdminComponent == null || poAdminComponent == null) {
+ Slog.wtf(LOG_TAG, "Cannot find PO or DO component name, aborting migration.");
+ return;
+ }
+ if (!doAdminComponent.getPackageName().equals(poAdminComponent.getPackageName())) {
+ Slog.e(LOG_TAG, "DO and PO are different packages, aborting migration.");
+ return;
+ }
+
+ Slog.i(LOG_TAG, String.format(
+ "Migrating COMP to PO on a corp owned device; primary user: %d; profile: %d",
+ doUserId, poUserId));
+
+ Slog.i(LOG_TAG, "Giving the PO additional power...");
+ markProfileOwnerOnOrganizationOwnedDeviceUncheckedLocked(poAdminComponent, poUserId);
+ Slog.i(LOG_TAG, "Migrating DO policies to PO...");
+ moveDoPoliciesToProfileParentAdmin(doAdmin, poAdmin.getParentActiveAdmin());
+ saveSettingsLocked(poUserId);
+ Slog.i(LOG_TAG, "Clearing the DO...");
+ final ComponentName doAdminReceiver = doAdmin.info.getComponent();
+ clearDeviceOwnerLocked(doAdmin, doUserId);
+ // TODO(b/143516163): If we have a power cut here, we might leave active admin. Consider if
+ // it is worth the complexity to make it more robust.
+ Slog.i(LOG_TAG, "Removing admin artifacts...");
+ // TODO(b/143516163): Clean up application restrictions in UserManager.
+ removeAdminArtifacts(doAdminReceiver, doUserId);
+ Slog.i(LOG_TAG, "Migration complete.");
+
+ // Note: KeyChain keys are not removed and will remain accessible for the apps that have
+ // been given grants to use them.
+ }
+
+ private void moveDoPoliciesToProfileParentAdmin(ActiveAdmin doAdmin, ActiveAdmin parentAdmin) {
+ // The following policies can be already controlled via parent instance, skip if so.
+ if (parentAdmin.mPasswordPolicy.quality == PASSWORD_QUALITY_UNSPECIFIED) {
+ parentAdmin.mPasswordPolicy = doAdmin.mPasswordPolicy;
+ }
+ if (parentAdmin.passwordHistoryLength == ActiveAdmin.DEF_PASSWORD_HISTORY_LENGTH) {
+ parentAdmin.passwordHistoryLength = doAdmin.passwordHistoryLength;
+ }
+ if (parentAdmin.passwordExpirationTimeout == ActiveAdmin.DEF_PASSWORD_HISTORY_LENGTH) {
+ parentAdmin.passwordExpirationTimeout = doAdmin.passwordExpirationTimeout;
+ }
+ if (parentAdmin.maximumFailedPasswordsForWipe
+ == ActiveAdmin.DEF_MAXIMUM_FAILED_PASSWORDS_FOR_WIPE) {
+ parentAdmin.maximumFailedPasswordsForWipe = doAdmin.maximumFailedPasswordsForWipe;
+ }
+ if (parentAdmin.maximumTimeToUnlock == ActiveAdmin.DEF_MAXIMUM_TIME_TO_UNLOCK) {
+ parentAdmin.maximumTimeToUnlock = doAdmin.maximumTimeToUnlock;
+ }
+ if (parentAdmin.strongAuthUnlockTimeout
+ == DevicePolicyManager.DEFAULT_STRONG_AUTH_TIMEOUT_MS) {
+ parentAdmin.strongAuthUnlockTimeout = doAdmin.strongAuthUnlockTimeout;
+ }
+ parentAdmin.disabledKeyguardFeatures |=
+ doAdmin.disabledKeyguardFeatures & PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
+
+ parentAdmin.trustAgentInfos.putAll(doAdmin.trustAgentInfos);
+
+ // The following policies weren't available to PO, but will be available after migration.
+ parentAdmin.disableCamera = doAdmin.disableCamera;
+
+ // TODO(b/143516163): Uncomment once corresponding APIs are available via parent instance.
+ // parentAdmin.disableScreenCapture = doAdmin.disableScreenCapture;
+ // parentAdmin.accountTypesWithManagementDisabled.addAll(
+ // doAdmin.accountTypesWithManagementDisabled);
+
+ moveDoUserRestrictionsToCopeParent(doAdmin, parentAdmin);
+
+ // TODO(b/143516163): migrate network and security logging state, currently they are
+ // turned off when DO is removed.
+ }
+
+ private void moveDoUserRestrictionsToCopeParent(ActiveAdmin doAdmin, ActiveAdmin parentAdmin) {
+ if (doAdmin.userRestrictions == null) {
+ return;
+ }
+ for (final String restriction : doAdmin.userRestrictions.keySet()) {
+ if (UserRestrictionsUtils.canProfileOwnerOfOrganizationOwnedDeviceChange(restriction)) {
+ parentAdmin.userRestrictions.putBoolean(
+ restriction, doAdmin.userRestrictions.getBoolean(restriction));
+ }
+ }
+ }
+
/** Apply default restrictions that haven't been applied to profile owners yet. */
private void maybeSetDefaultProfileOwnerUserRestrictions() {
synchronized (getLockObject()) {
@@ -3245,6 +3391,13 @@
out.endTag(null, TAG_OWNER_INSTALLED_CA_CERT);
}
+ for (int i = 0, size = policy.mProtectedPackages.size(); i < size; i++) {
+ String packageName = policy.mProtectedPackages.get(i);
+ out.startTag(null, TAG_PROTECTED_PACKAGES);
+ out.attribute(null, ATTR_NAME, packageName);
+ out.endTag(null, TAG_PROTECTED_PACKAGES);
+ }
+
out.endTag(null, "policies");
out.endDocument();
@@ -3358,6 +3511,7 @@
policy.mAdminMap.clear();
policy.mAffiliationIds.clear();
policy.mOwnerInstalledCaCerts.clear();
+ policy.mProtectedPackages.clear();
while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
@@ -3455,6 +3609,8 @@
policy.mCurrentInputMethodSet = true;
} else if (TAG_OWNER_INSTALLED_CA_CERT.equals(tag)) {
policy.mOwnerInstalledCaCerts.add(parser.getAttributeValue(null, ATTR_ALIAS));
+ } else if (TAG_PROTECTED_PACKAGES.equals(tag)) {
+ policy.mProtectedPackages.add(parser.getAttributeValue(null, ATTR_NAME));
} else {
Slog.w(LOG_TAG, "Unknown tag: " + tag);
XmlUtils.skipCurrentTag(parser);
@@ -3486,6 +3642,7 @@
updateMaximumTimeToLockLocked(userHandle);
updateLockTaskPackagesLocked(policy.mLockTaskPackages, userHandle);
updateLockTaskFeaturesLocked(policy.mLockTaskFeatures, userHandle);
+ updateProtectedPackagesLocked(policy.mProtectedPackages);
if (policy.mStatusBarDisabled) {
setStatusBarDisabledInternal(policy.mStatusBarDisabled, userHandle);
}
@@ -3511,6 +3668,10 @@
}
}
+ private void updateProtectedPackagesLocked(List<String> packages) {
+ mInjector.getPackageManagerInternal().setDeviceOwnerProtectedPackages(packages);
+ }
+
private void updateLockTaskFeaturesLocked(int flags, int userId) {
long ident = mInjector.binderClearCallingIdentity();
try {
@@ -3587,6 +3748,9 @@
break;
case SystemService.PHASE_ACTIVITY_MANAGER_READY:
maybeStartSecurityLogMonitorOnActivityManagerReady();
+ synchronized (getLockObject()) {
+ maybeMigrateToProfileOnOrganizationOwnedDeviceLocked();
+ }
break;
case SystemService.PHASE_BOOT_COMPLETED:
ensureDeviceOwnerUserStarted(); // TODO Consider better place to do this.
@@ -4079,6 +4243,12 @@
if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_USER, userHandle)) {
mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, false, userHandle);
}
+ // When a device owner is set, the system automatically restricts adding a managed profile.
+ // Remove this restriction when the device owner is cleared.
+ if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, userHandle)) {
+ mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, false,
+ userHandle);
+ }
}
/**
@@ -5807,8 +5977,9 @@
// Make sure that the caller is the profile owner or delegate.
enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
DELEGATION_CERT_INSTALL);
- // Verify that the profile owner was granted access to Device IDs.
- if (canProfileOwnerAccessDeviceIds(userId)) {
+ // Verify that the managed profile is on an organization-owned device and as such
+ // the profile owner can access Device IDs.
+ if (isProfileOwnerOfOrganizationOwnedDevice(userId)) {
return;
}
throw new SecurityException(
@@ -6715,6 +6886,67 @@
}
@Override
+ public void setFactoryResetProtectionPolicy(ComponentName who,
+ @Nullable FactoryResetProtectionPolicy policy) {
+ if (!mHasFeature) {
+ return;
+ }
+ Preconditions.checkNotNull(who, "ComponentName is null");
+
+ final int frpManagementAgentUid = getFrpManagementAgentUidOrThrow();
+ final int userId = mInjector.userHandleGetCallingUserId();
+ synchronized (getLockObject()) {
+ ActiveAdmin admin = getActiveAdminForCallerLocked(
+ who, DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER);
+ admin.mFactoryResetProtectionPolicy = policy;
+ saveSettingsLocked(userId);
+ }
+
+ mInjector.binderWithCleanCallingIdentity(() -> mContext.sendBroadcastAsUser(
+ new Intent(DevicePolicyManager.ACTION_RESET_PROTECTION_POLICY_CHANGED),
+ UserHandle.getUserHandleForUid(frpManagementAgentUid)));
+
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_FACTORY_RESET_PROTECTION)
+ .setAdmin(who)
+ .write();
+ }
+
+ @Override
+ public FactoryResetProtectionPolicy getFactoryResetProtectionPolicy(
+ @Nullable ComponentName who) {
+ if (!mHasFeature) {
+ return null;
+ }
+
+ final int frpManagementAgentUid = getFrpManagementAgentUidOrThrow();
+ ActiveAdmin admin;
+ synchronized (getLockObject()) {
+ if (who == null) {
+ if ((frpManagementAgentUid != mInjector.binderGetCallingUid())) {
+ throw new SecurityException(
+ "Must be called by the FRP management agent on device");
+ }
+ admin = getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(
+ UserHandle.getUserId(frpManagementAgentUid));
+ } else {
+ admin = getActiveAdminForCallerLocked(
+ who, DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER);
+ }
+ }
+ return admin != null ? admin.mFactoryResetProtectionPolicy : null;
+ }
+
+ private int getFrpManagementAgentUidOrThrow() {
+ PersistentDataBlockManagerInternal pdb = mInjector.getPersistentDataBlockManagerInternal();
+ if ((pdb == null) || (pdb.getAllowedUid() == -1)) {
+ throw new UnsupportedOperationException(
+ "The persistent data block service is not supported on this device");
+ }
+ return pdb.getAllowedUid();
+ }
+
+ @Override
public void getRemoveWarning(ComponentName comp, final RemoteCallback result, int userHandle) {
if (!mHasFeature) {
return;
@@ -7350,8 +7582,7 @@
return;
}
Objects.requireNonNull(who, "ComponentName is null");
- // TODO (b/145286957) Refactor security checks
- enforceDeviceOwnerOrProfileOwnerOnUser0OrProfileOwnerOrganizationOwned();
+ enforceProfileOwnerOnUser0OrProfileOwnerOrganizationOwned();
mInjector.binderWithCleanCallingIdentity(() ->
mInjector.settingsGlobalPutInt(Settings.Global.AUTO_TIME, enabled ? 1 : 0));
@@ -7372,7 +7603,7 @@
return false;
}
Objects.requireNonNull(who, "ComponentName is null");
- enforceDeviceOwnerOrProfileOwnerOnUser0OrProfileOwnerOrganizationOwned();
+ enforceProfileOwnerOnUser0OrProfileOwnerOrganizationOwned();
return mInjector.settingsGlobalGetInt(Global.AUTO_TIME, 0) > 0;
}
@@ -7386,8 +7617,7 @@
return;
}
Objects.requireNonNull(who, "ComponentName is null");
- // TODO (b/145286957) Refactor security checks
- enforceDeviceOwnerOrProfileOwnerOnUser0OrProfileOwnerOrganizationOwned();
+ enforceProfileOwnerOnUser0OrProfileOwnerOrganizationOwned();
mInjector.binderWithCleanCallingIdentity(() ->
mInjector.settingsGlobalPutInt(Global.AUTO_TIME_ZONE, enabled ? 1 : 0));
@@ -7408,7 +7638,7 @@
return false;
}
Objects.requireNonNull(who, "ComponentName is null");
- enforceDeviceOwnerOrProfileOwnerOnUser0OrProfileOwnerOrganizationOwned();
+ enforceProfileOwnerOnUser0OrProfileOwnerOrganizationOwned();
return mInjector.settingsGlobalGetInt(Global.AUTO_TIME_ZONE, 0) > 0;
}
@@ -7725,7 +7955,13 @@
}
}
// Tell the user manager that the restrictions have changed.
- pushUserRestrictions(parent ? getProfileParentId(userHandle) : userHandle);
+ final int affectedUserId = parent ? getProfileParentId(userHandle) : userHandle;
+ pushUserRestrictions(affectedUserId);
+
+ if (SecurityLog.isLoggingEnabled()) {
+ SecurityLog.writeEvent(SecurityLog.TAG_CAMERA_POLICY_SET,
+ who.getPackageName(), userHandle, affectedUserId, disabled ? 1 : 0);
+ }
DevicePolicyEventLogger
.createEvent(DevicePolicyEnums.SET_CAMERA_DISABLED)
.setAdmin(who)
@@ -7949,10 +8185,19 @@
updateDeviceOwnerLocked();
setDeviceOwnerSystemPropertyLocked();
- // TODO Send to system too?
- mInjector.binderWithCleanCallingIdentity(
- () -> sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED,
- userId));
+ mInjector.binderWithCleanCallingIdentity(() -> {
+ // Restrict adding a managed profile when a device owner is set on the device.
+ // That is to prevent the co-existence of a managed profile and a device owner
+ // on the same device.
+ // Instead, the device may be provisioned with an organization-owned managed
+ // profile, such that the admin on that managed profile has extended management
+ // capabilities that can affect the entire device (but not access private data
+ // on the primary profile).
+ mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, true,
+ UserHandle.of(userId));
+ // TODO Send to system too?
+ sendOwnerChangedBroadcast(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED, userId);
+ });
mDeviceAdminServiceController.startServiceForOwner(
admin.getPackageName(), userId, "set-device-owner");
@@ -8005,7 +8250,7 @@
}
}
- private boolean canProfileOwnerAccessDeviceIds(int userId) {
+ private boolean isProfileOwnerOfOrganizationOwnedDevice(int userId) {
synchronized (getLockObject()) {
return mOwners.isProfileOwnerOfOrganizationOwnedDevice(userId);
}
@@ -8028,7 +8273,7 @@
}
private boolean isProfileOwnerOfOrganizationOwnedDevice(ComponentName who, int userId) {
- return isProfileOwner(who, userId) && canProfileOwnerAccessDeviceIds(userId);
+ return isProfileOwner(who, userId) && isProfileOwnerOfOrganizationOwnedDevice(userId);
}
@Override
@@ -8104,6 +8349,14 @@
return null;
}
+ ActiveAdmin getDeviceOwnerOrProfileOwnerOfOrganizationOwnedDeviceLocked(int userId) {
+ ActiveAdmin admin = getDeviceOwnerAdminLocked();
+ if (admin == null) {
+ admin = getProfileOwnerOfOrganizationOwnedDeviceLocked(userId);
+ }
+ return admin;
+ }
+
@Override
public void clearDeviceOwner(String packageName) {
Objects.requireNonNull(packageName, "packageName is null");
@@ -8207,6 +8460,17 @@
throw new IllegalArgumentException("Not active admin: " + who);
}
+ final int parentUserId = getProfileParentId(userHandle);
+ // When trying to set a profile owner on a new user, it may be that this user is
+ // a profile - but it may not be a managed profile if there's a restriction on the
+ // parent to add managed profiles (e.g. if the device has a device owner).
+ if (parentUserId != userHandle && mUserManager.hasUserRestriction(
+ UserManager.DISALLOW_ADD_MANAGED_PROFILE,
+ UserHandle.of(parentUserId))) {
+ Slog.i(LOG_TAG, "Cannot set profile owner because of restriction.");
+ return false;
+ }
+
if (isAdb()) {
// Log profile owner provisioning was started using adb.
MetricsLogger.action(mContext, PROVISIONING_ENTRY_POINT_ADB, LOG_TAG_PROFILE_OWNER);
@@ -8342,6 +8606,8 @@
policy.mLockTaskPackages.clear();
updateLockTaskPackagesLocked(policy.mLockTaskPackages, userId);
policy.mLockTaskFeatures = DevicePolicyManager.LOCK_TASK_FEATURE_NONE;
+ policy.mProtectedPackages.clear();
+ updateProtectedPackagesLocked(policy.mProtectedPackages);
saveSettingsLocked(userId);
try {
@@ -8524,7 +8790,6 @@
if (!mHasFeature) {
return null;
}
-
synchronized (getLockObject()) {
return mOwners.getProfileOwnerComponent(userHandle);
}
@@ -8555,7 +8820,7 @@
for (UserInfo userInfo : mUserManager.getProfiles(userHandle)) {
if (userInfo.isManagedProfile()) {
if (getProfileOwner(userInfo.id) != null
- && canProfileOwnerAccessDeviceIds(userInfo.id)) {
+ && isProfileOwnerOfOrganizationOwnedDevice(userInfo.id)) {
ComponentName who = getProfileOwner(userInfo.id);
return getActiveAdminUncheckedLocked(who, userInfo.id);
}
@@ -8579,6 +8844,23 @@
}
@Override
+ public boolean isOrganizationOwnedDeviceWithManagedProfile() {
+ if (!mHasFeature) {
+ return false;
+ }
+
+ return mInjector.binderWithCleanCallingIdentity(() -> {
+ for (UserInfo ui : mUserManager.getUsers()) {
+ if (ui.isManagedProfile() && isProfileOwnerOfOrganizationOwnedDevice(ui.id)) {
+ return true;
+ }
+ }
+
+ return false;
+ });
+ }
+
+ @Override
public boolean checkDeviceIdentifierAccess(String packageName, int pid, int uid) {
ensureCallerIdentityMatchesIfNotSystem(packageName, pid, uid);
@@ -8606,7 +8888,7 @@
final boolean isCallerProfileOwnerOrDelegate = profileOwner != null
&& (profileOwner.getPackageName().equals(packageName)
|| isCallerDelegate(packageName, uid, DELEGATION_CERT_INSTALL));
- if (isCallerProfileOwnerOrDelegate && canProfileOwnerAccessDeviceIds(userId)) {
+ if (isCallerProfileOwnerOrDelegate && isProfileOwnerOfOrganizationOwnedDevice(userId)) {
return true;
}
//TODO(b/130844684): Temporarily allow profile owner on non-organization-owned devices
@@ -8819,6 +9101,26 @@
}
}
+ private void enforceAcrossUsersPermissions() {
+ if (isCallerWithSystemUid() || mInjector.binderGetCallingUid() == Process.ROOT_UID) {
+ return;
+ }
+ if (mContext.checkCallingPermission(permission.INTERACT_ACROSS_PROFILES)
+ == PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
+ if (mContext.checkCallingPermission(permission.INTERACT_ACROSS_USERS)
+ == PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
+ if (mContext.checkCallingPermission(permission.INTERACT_ACROSS_USERS_FULL)
+ == PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
+ throw new SecurityException("Calling user does not have INTERACT_ACROSS_PROFILES or"
+ + "INTERACT_ACROSS_USERS or INTERACT_ACROSS_USERS_FULL permissions");
+ }
+
private void enforceFullCrossUsersPermission(int userHandle) {
enforceSystemUserOrPermissionIfCrossUser(userHandle,
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
@@ -8882,23 +9184,22 @@
"Only profile owner, device owner and system may call this method.");
}
- private ActiveAdmin enforceDeviceOwnerOrProfileOwnerOnUser0OrProfileOwnerOrganizationOwned() {
+ private void enforceProfileOwnerOnUser0OrProfileOwnerOrganizationOwned() {
synchronized (getLockObject()) {
- // Check if there is a device owner
- ActiveAdmin deviceOwner = getActiveAdminWithPolicyForUidLocked(null,
- DeviceAdminInfo.USES_POLICY_DEVICE_OWNER, mInjector.binderGetCallingUid());
- if (deviceOwner != null) return deviceOwner;
+ // Check if there is a device owner or profile owner of an organization-owned device
+ ActiveAdmin owner = getActiveAdminWithPolicyForUidLocked(null,
+ DeviceAdminInfo.USES_POLICY_ORGANIZATION_OWNED_PROFILE_OWNER,
+ mInjector.binderGetCallingUid());
+ if (owner != null) {
+ return;
+ }
- ActiveAdmin profileOwner = getActiveAdminWithPolicyForUidLocked(null,
+ // Checks whether the caller is a profile owner on user 0 rather than
+ // checking whether the active admin is on user 0
+ owner = getActiveAdminWithPolicyForUidLocked(null,
DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, mInjector.binderGetCallingUid());
-
- // Check if there is a profile owner of an organization owned device
- if (isProfileOwnerOfOrganizationOwnedDevice(profileOwner)) return profileOwner;
-
- // Check if there is a profile owner called on user 0
- if (profileOwner != null) {
- enforceCallerSystemUserHandle();
- return profileOwner;
+ if (owner != null && owner.getUserHandle().isSystem()) {
+ return;
}
}
throw new SecurityException("No active admin found");
@@ -9039,6 +9340,10 @@
pw.increaseIndent();
pw.print("mPasswordOwner="); pw.println(policy.mPasswordOwner);
pw.decreaseIndent();
+ pw.println();
+ pw.increaseIndent();
+ pw.print("mProtectedPackages="); pw.println(policy.mProtectedPackages);
+ pw.decreaseIndent();
}
}
@@ -11653,6 +11958,11 @@
return mStateCache;
}
+ @Override
+ public List<String> getAllCrossProfilePackages() {
+ return DevicePolicyManagerService.this.getAllCrossProfilePackages();
+ }
+
}
private Intent createShowAdminSupportIntent(ComponentName admin, int userId) {
@@ -12137,6 +12447,7 @@
case DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE:
return checkManagedProfileProvisioningPreCondition(packageName, callingUserId);
case DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE:
+ case DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE:
return checkDeviceOwnerProvisioningPreCondition(callingUserId);
case DevicePolicyManager.ACTION_PROVISION_MANAGED_USER:
return checkManagedUserProvisioningPreCondition(callingUserId);
@@ -12223,25 +12534,41 @@
final long ident = mInjector.binderClearCallingIdentity();
try {
final UserHandle callingUserHandle = UserHandle.of(callingUserId);
- final ComponentName ownerAdmin = getOwnerComponent(packageName, callingUserId);
- if (mUserManager.hasUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE,
- callingUserHandle)) {
- // An admin can initiate provisioning if it has set the restriction.
- if (ownerAdmin == null || isAdminAffectedByRestriction(ownerAdmin,
- UserManager.DISALLOW_ADD_MANAGED_PROFILE, callingUserId)) {
- return CODE_ADD_MANAGED_PROFILE_DISALLOWED;
- }
+ final boolean hasDeviceOwner;
+ synchronized (getLockObject()) {
+ hasDeviceOwner = getDeviceOwnerAdminLocked() != null;
}
- boolean canRemoveProfile = true;
- if (mUserManager.hasUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
- callingUserHandle)) {
- // We can remove a profile if the admin itself has set the restriction.
- if (ownerAdmin == null || isAdminAffectedByRestriction(ownerAdmin,
- UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
- callingUserId)) {
- canRemoveProfile = false;
- }
+
+ final boolean addingProfileRestricted = mUserManager.hasUserRestriction(
+ UserManager.DISALLOW_ADD_MANAGED_PROFILE, callingUserHandle);
+
+ UserInfo parentUser = mUserManager.getProfileParent(callingUserId);
+ final boolean addingProfileRestrictedOnParent = (parentUser != null)
+ && mUserManager.hasUserRestriction(
+ UserManager.DISALLOW_ADD_MANAGED_PROFILE,
+ UserHandle.of(parentUser.id));
+
+ Slog.i(LOG_TAG, String.format(
+ "When checking for managed profile provisioning: Has device owner? %b, adding"
+ + " profile restricted? %b, adding profile restricted on parent? %b",
+ hasDeviceOwner, addingProfileRestricted, addingProfileRestrictedOnParent));
+
+ // If there's a device owner, the restriction on adding a managed profile must be set
+ // somewhere.
+ if (hasDeviceOwner && !addingProfileRestricted && !addingProfileRestrictedOnParent) {
+ Slog.wtf(LOG_TAG, "Has a device owner but no restriction on adding a profile.");
}
+
+ // Do not allow adding a managed profile if there's a restriction, either on the current
+ // user or its parent user.
+ if (addingProfileRestricted || addingProfileRestrictedOnParent) {
+ return CODE_CANNOT_ADD_MANAGED_PROFILE;
+ }
+ // If there's a restriction on removing the managed profile then we have to take it
+ // into account when checking whether more profiles can be added.
+ boolean canRemoveProfile =
+ !mUserManager.hasUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE,
+ callingUserHandle);
if (!mUserManager.canAddMoreManagedProfiles(callingUserId, canRemoveProfile)) {
return CODE_CANNOT_ADD_MANAGED_PROFILE;
}
@@ -12718,37 +13045,43 @@
// Grant access under lock.
synchronized (getLockObject()) {
- // Sanity check: Make sure that the user has a profile owner and that the specified
- // component is the profile owner of that user.
- if (!isProfileOwner(who, userId)) {
- throw new IllegalArgumentException(String.format(
- "Component %s is not a Profile Owner of user %d",
- who.flattenToString(), userId));
- }
+ markProfileOwnerOnOrganizationOwnedDeviceUncheckedLocked(who, userId);
+ }
+ }
- Slog.i(LOG_TAG, String.format(
- "Marking %s as profile owner on organization-owned device for user %d",
+ @GuardedBy("getLockObject()")
+ private void markProfileOwnerOnOrganizationOwnedDeviceUncheckedLocked(
+ ComponentName who, int userId) {
+ // Sanity check: Make sure that the user has a profile owner and that the specified
+ // component is the profile owner of that user.
+ if (!isProfileOwner(who, userId)) {
+ throw new IllegalArgumentException(String.format(
+ "Component %s is not a Profile Owner of user %d",
who.flattenToString(), userId));
+ }
- // First, set restriction on removing the profile.
- mInjector.binderWithCleanCallingIdentity(() -> {
- // Clear restriction as user.
- UserHandle parentUser = mUserManager.getProfileParent(UserHandle.of(userId));
- if (!parentUser.isSystem()) {
- throw new IllegalStateException(
- String.format("Only the profile owner of a managed profile on the"
+ Slog.i(LOG_TAG, String.format(
+ "Marking %s as profile owner on organization-owned device for user %d",
+ who.flattenToString(), userId));
+
+ // First, set restriction on removing the profile.
+ mInjector.binderWithCleanCallingIdentity(() -> {
+ // Clear restriction as user.
+ final UserHandle parentUser = mUserManager.getProfileParent(UserHandle.of(userId));
+ if (!parentUser.isSystem()) {
+ throw new IllegalStateException(
+ String.format("Only the profile owner of a managed profile on the"
+ " primary user can be granted access to device identifiers, not"
+ " on user %d", parentUser.getIdentifier()));
- }
+ }
- mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, true,
- parentUser);
- });
+ mUserManager.setUserRestriction(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE, true,
+ parentUser);
+ });
- // markProfileOwnerOfOrganizationOwnedDevice will trigger writing of the profile owner
- // data, no need to do it manually.
- mOwners.markProfileOwnerOfOrganizationOwnedDevice(userId);
- }
+ // markProfileOwnerOfOrganizationOwnedDevice will trigger writing of the profile owner
+ // data, no need to do it manually.
+ mOwners.markProfileOwnerOfOrganizationOwnedDevice(userId);
}
private void pushMeteredDisabledPackagesLocked(int userId) {
@@ -14496,6 +14829,53 @@
}
@Override
+ public List<String> getAllCrossProfilePackages() {
+ if (!mHasFeature) {
+ return Collections.emptyList();
+ }
+ enforceAcrossUsersPermissions();
+
+ synchronized (getLockObject()) {
+ final List<ActiveAdmin> admins = getProfileOwnerAdminsForCurrentProfileGroup();
+ final List<String> packages = getCrossProfilePackagesForAdmins(admins);
+
+ packages.addAll(getDefaultCrossProfilePackages());
+
+ return packages;
+ }
+ }
+
+ private List<String> getCrossProfilePackagesForAdmins(List<ActiveAdmin> admins) {
+ final List<String> packages = new ArrayList<>();
+ for (int i = 0; i < admins.size(); i++) {
+ packages.addAll(admins.get(i).mCrossProfilePackages);
+ }
+ return packages;
+ }
+
+ private List<String> getDefaultCrossProfilePackages() {
+ return Arrays.asList(mContext.getResources()
+ .getStringArray(R.array.cross_profile_apps));
+ }
+
+ private List<ActiveAdmin> getProfileOwnerAdminsForCurrentProfileGroup() {
+ synchronized (getLockObject()) {
+ final List<ActiveAdmin> admins = new ArrayList<>();
+ int[] users = mUserManager.getProfileIdsWithDisabled(UserHandle.getCallingUserId());
+ for (int i = 0; i < users.length; i++) {
+ final ComponentName componentName = getProfileOwner(users[i]);
+ if (componentName != null) {
+ ActiveAdmin admin = getActiveAdminUncheckedLocked(componentName, users[i]);
+ if (admin != null) {
+ admins.add(admin);
+ }
+ }
+ }
+ return admins;
+ }
+ }
+
+ @Override
public boolean isManagedKiosk() {
if (!mHasFeature) {
return false;
@@ -14627,4 +15007,48 @@
return DevicePolicyConstants.loadFromString(
mInjector.settingsGlobalGetString(Global.DEVICE_POLICY_CONSTANTS));
}
+
+ @Override
+ public void setProtectedPackages(ComponentName who, List<String> packages) {
+ Preconditions.checkNotNull(who, "ComponentName is null");
+ Preconditions.checkNotNull(packages, "packages is null");
+
+ enforceDeviceOwner(who);
+ synchronized (getLockObject()) {
+ final int userHandle = mInjector.userHandleGetCallingUserId();
+ setProtectedPackagesLocked(userHandle, packages);
+ DevicePolicyEventLogger
+ .createEvent(DevicePolicyEnums.SET_PACKAGES_PROTECTED)
+ .setAdmin(who)
+ .setStrings(packages.toArray(new String[packages.size()]))
+ .write();
+ }
+ }
+
+ private void setProtectedPackagesLocked(int userHandle, List<String> packages) {
+ final DevicePolicyData policy = getUserData(userHandle);
+ policy.mProtectedPackages = packages;
+
+ // Store the settings persistently.
+ saveSettingsLocked(userHandle);
+ updateProtectedPackagesLocked(packages);
+ }
+
+ @Override
+ public List<String> getProtectedPackages(ComponentName who) {
+ Preconditions.checkNotNull(who, "ComponentName is null");
+
+ enforceDeviceOwner(who);
+ final int userHandle = mInjector.binderGetCallingUserHandle().getIdentifier();
+ synchronized (getLockObject()) {
+ final List<String> packages = getUserData(userHandle).mProtectedPackages;
+ return packages == null ? Collections.EMPTY_LIST : packages;
+ }
+ }
+
+ private void logIfVerbose(String message) {
+ if (VERBOSE_LOG) {
+ Slog.d(LOG_TAG, message);
+ }
+ }
}
diff --git a/services/incremental/Android.bp b/services/incremental/Android.bp
index 2661925..ddf4dd5 100644
--- a/services/incremental/Android.bp
+++ b/services/incremental/Android.bp
@@ -69,7 +69,6 @@
name: "service.incremental",
defaults: [
"service.incremental-defaults",
- "linux_bionic_supported",
],
export_include_dirs: ["include/",],
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index cfe1318..3dee913 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -71,11 +71,11 @@
import android.util.DisplayMetrics;
import android.util.EventLog;
import android.util.Slog;
+import android.util.StatsLog;
import android.view.WindowManager;
import android.view.contentcapture.ContentCaptureManager;
import com.android.internal.R;
-import com.android.internal.logging.MetricsLogger;
import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.BinderInternal;
import com.android.internal.util.ConcurrentUtils;
@@ -126,6 +126,7 @@
import com.android.server.os.BugreportManagerService;
import com.android.server.os.DeviceIdentifiersPolicyService;
import com.android.server.os.SchedulingPolicyService;
+import com.android.server.people.PeopleService;
import com.android.server.pm.BackgroundDexOptService;
import com.android.server.pm.CrossProfileAppsService;
import com.android.server.pm.DataLoaderManagerService;
@@ -212,8 +213,12 @@
"com.android.server.print.PrintManagerService";
private static final String COMPANION_DEVICE_MANAGER_SERVICE_CLASS =
"com.android.server.companion.CompanionDeviceManagerService";
+ private static final String STATS_COMPANION_APEX_PATH =
+ "/apex/com.android.os.statsd/javalib/service-statsd.jar";
private static final String STATS_COMPANION_LIFECYCLE_CLASS =
"com.android.server.stats.StatsCompanion$Lifecycle";
+ private static final String STATS_PULL_ATOM_SERVICE_CLASS =
+ "com.android.server.stats.StatsPullAtomService";
private static final String USB_SERVICE_CLASS =
"com.android.server.usb.UsbService$Lifecycle";
private static final String MIDI_SERVICE_CLASS =
@@ -438,10 +443,12 @@
// Here we go!
Slog.i(TAG, "Entered the Android system server!");
- int uptimeMillis = (int) SystemClock.elapsedRealtime();
+ final long uptimeMillis = SystemClock.elapsedRealtime();
EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_SYSTEM_RUN, uptimeMillis);
if (!mRuntimeRestart) {
- MetricsLogger.histogram(null, "boot_system_server_init", uptimeMillis);
+ StatsLog.write(StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED,
+ StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__SYSTEM_SERVER_INIT_START,
+ uptimeMillis);
}
// In case the runtime switched since last boot (such as when
@@ -550,10 +557,12 @@
StrictMode.initVmDefaults(null);
if (!mRuntimeRestart && !isFirstBootOrUpgrade()) {
- int uptimeMillis = (int) SystemClock.elapsedRealtime();
- MetricsLogger.histogram(null, "boot_system_server_ready", uptimeMillis);
- final int MAX_UPTIME_MILLIS = 60 * 1000;
- if (uptimeMillis > MAX_UPTIME_MILLIS) {
+ final long uptimeMillis = SystemClock.elapsedRealtime();
+ StatsLog.write(StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED,
+ StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__SYSTEM_SERVER_READY,
+ uptimeMillis);
+ final long maxUptimeMillis = 60 * 1000;
+ if (uptimeMillis > maxUptimeMillis) {
Slog.wtf(SYSTEM_SERVER_TIMING_TAG,
"SystemServer init took too long. uptimeMillis=" + uptimeMillis);
}
@@ -748,7 +757,8 @@
// Now that we have the bare essentials of the OS up and running, take
// note that we just booted, which might send out a rescue party if
// we're stuck in a runtime restart loop.
- RescueParty.noteBoot(mSystemContext);
+ RescueParty.registerHealthObserver(mSystemContext);
+ PackageWatchdog.getInstance(mSystemContext).noteBoot();
// Manages LEDs and display backlight so we need it to bring up the display.
t.traceBegin("StartLightsService");
@@ -785,8 +795,9 @@
// Start the package manager.
if (!mRuntimeRestart) {
- MetricsLogger.histogram(null, "boot_package_manager_init_start",
- (int) SystemClock.elapsedRealtime());
+ StatsLog.write(StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED,
+ StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__PACKAGE_MANAGER_INIT_START,
+ SystemClock.elapsedRealtime());
}
t.traceBegin("StartPackageManagerService");
@@ -802,8 +813,9 @@
mPackageManager = mSystemContext.getPackageManager();
t.traceEnd();
if (!mRuntimeRestart && !isFirstBootOrUpgrade()) {
- MetricsLogger.histogram(null, "boot_package_manager_init_ready",
- (int) SystemClock.elapsedRealtime());
+ StatsLog.write(StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME_REPORTED,
+ StatsLog.BOOT_TIME_EVENT_ELAPSED_TIME__EVENT__PACKAGE_MANAGER_INIT_READY,
+ SystemClock.elapsedRealtime());
}
// Manages A/B OTA dexopting. This is a bootstrap service as we need it to rename
// A/B artifacts after boot, before anything else might touch/need them.
@@ -1915,6 +1927,10 @@
t.traceBegin("StartCrossProfileAppsService");
mSystemServiceManager.startService(CrossProfileAppsService.class);
t.traceEnd();
+
+ t.traceBegin("StartPeopleService");
+ mSystemServiceManager.startService(PeopleService.class);
+ t.traceEnd();
}
if (!isWatch) {
@@ -1972,7 +1988,13 @@
// Statsd helper
t.traceBegin("StartStatsCompanion");
- mSystemServiceManager.startService(STATS_COMPANION_LIFECYCLE_CLASS);
+ mSystemServiceManager.startServiceFromJar(
+ STATS_COMPANION_LIFECYCLE_CLASS, STATS_COMPANION_APEX_PATH);
+ t.traceEnd();
+
+ // Statsd pulled atoms
+ t.traceBegin("StartStatsPullAtomService");
+ mSystemServiceManager.startService(STATS_PULL_ATOM_SERVICE_CLASS);
t.traceEnd();
// Incidentd and dumpstated helper
diff --git a/services/net/Android.bp b/services/net/Android.bp
index 9c7cfc1..cf84bdf 100644
--- a/services/net/Android.bp
+++ b/services/net/Android.bp
@@ -23,7 +23,6 @@
name: "services-tethering-shared-srcs",
srcs: [
":framework-annotations",
- "java/android/net/util/NetdService.java",
"java/android/net/util/NetworkConstants.java",
],
visibility: ["//frameworks/base/packages/Tethering"],
diff --git a/services/net/java/android/net/ip/IpClientCallbacks.java b/services/net/java/android/net/ip/IpClientCallbacks.java
index 61cd88a..c93e5c5 100644
--- a/services/net/java/android/net/ip/IpClientCallbacks.java
+++ b/services/net/java/android/net/ip/IpClientCallbacks.java
@@ -17,6 +17,7 @@
package android.net.ip;
import android.net.DhcpResults;
+import android.net.DhcpResultsParcelable;
import android.net.Layer2PacketParcelable;
import android.net.LinkProperties;
@@ -69,6 +70,18 @@
public void onNewDhcpResults(DhcpResults dhcpResults) {}
/**
+ * Callback called when new DHCP results are available.
+ *
+ * <p>This is purely advisory and not an indication of provisioning success or failure. This is
+ * only here for callers that want to expose DHCPv4 results to other APIs
+ * (e.g., WifiInfo#setInetAddress).
+ *
+ * <p>DHCPv4 or static IPv4 configuration failure or success can be determined by whether or not
+ * the passed-in DhcpResults object is null.
+ */
+ public void onNewDhcpResults(DhcpResultsParcelable dhcpResults) {}
+
+ /**
* Indicates that provisioning was successful.
*/
public void onProvisioningSuccess(LinkProperties newLp) {}
diff --git a/services/net/java/android/net/ip/IpClientUtil.java b/services/net/java/android/net/ip/IpClientUtil.java
index 4d60e62..7f723b1 100644
--- a/services/net/java/android/net/ip/IpClientUtil.java
+++ b/services/net/java/android/net/ip/IpClientUtil.java
@@ -119,6 +119,7 @@
@Override
public void onNewDhcpResults(DhcpResultsParcelable dhcpResults) {
mCb.onNewDhcpResults(fromStableParcelable(dhcpResults));
+ mCb.onNewDhcpResults(dhcpResults);
}
@Override
diff --git a/services/people/Android.bp b/services/people/Android.bp
new file mode 100644
index 0000000..d64097a
--- /dev/null
+++ b/services/people/Android.bp
@@ -0,0 +1,5 @@
+java_library_static {
+ name: "services.people",
+ srcs: ["java/**/*.java"],
+ libs: ["services.core"],
+}
diff --git a/services/people/java/com/android/server/people/PeopleService.java b/services/people/java/com/android/server/people/PeopleService.java
new file mode 100644
index 0000000..9569c6e
--- /dev/null
+++ b/services/people/java/com/android/server/people/PeopleService.java
@@ -0,0 +1,150 @@
+/*
+ * 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 com.android.server.people;
+
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionSessionId;
+import android.app.prediction.AppTarget;
+import android.app.prediction.AppTargetEvent;
+import android.app.prediction.IPredictionCallback;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.SystemService;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+/**
+ * A service that manages the people and conversations provided by apps.
+ */
+public class PeopleService extends SystemService {
+
+ private static final String TAG = "PeopleService";
+
+ /**
+ * Initializes the system service.
+ *
+ * @param context The system server context.
+ */
+ public PeopleService(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onStart() {
+ publishLocalService(PeopleServiceInternal.class, new LocalService());
+ }
+
+ @VisibleForTesting
+ final class LocalService extends PeopleServiceInternal {
+
+ private Map<AppPredictionSessionId, SessionInfo> mSessions = new ArrayMap<>();
+
+ @Override
+ public void onCreatePredictionSession(AppPredictionContext context,
+ AppPredictionSessionId sessionId) {
+ mSessions.put(sessionId, new SessionInfo(context));
+ }
+
+ @Override
+ public void notifyAppTargetEvent(AppPredictionSessionId sessionId, AppTargetEvent event) {
+ runForSession(sessionId,
+ sessionInfo -> sessionInfo.getPredictor().onAppTargetEvent(event));
+ }
+
+ @Override
+ public void notifyLaunchLocationShown(AppPredictionSessionId sessionId,
+ String launchLocation, ParceledListSlice targetIds) {
+ runForSession(sessionId,
+ sessionInfo -> sessionInfo.getPredictor().onLaunchLocationShown(
+ launchLocation, targetIds.getList()));
+ }
+
+ @Override
+ public void sortAppTargets(AppPredictionSessionId sessionId, ParceledListSlice targets,
+ IPredictionCallback callback) {
+ runForSession(sessionId,
+ sessionInfo -> sessionInfo.getPredictor().onSortAppTargets(
+ targets.getList(),
+ targetList -> invokePredictionCallback(callback, targetList)));
+ }
+
+ @Override
+ public void registerPredictionUpdates(AppPredictionSessionId sessionId,
+ IPredictionCallback callback) {
+ runForSession(sessionId, sessionInfo -> sessionInfo.addCallback(callback));
+ }
+
+ @Override
+ public void unregisterPredictionUpdates(AppPredictionSessionId sessionId,
+ IPredictionCallback callback) {
+ runForSession(sessionId, sessionInfo -> sessionInfo.removeCallback(callback));
+ }
+
+ @Override
+ public void requestPredictionUpdate(AppPredictionSessionId sessionId) {
+ runForSession(sessionId,
+ sessionInfo -> sessionInfo.getPredictor().onRequestPredictionUpdate());
+ }
+
+ @Override
+ public void onDestroyPredictionSession(AppPredictionSessionId sessionId) {
+ runForSession(sessionId, sessionInfo -> {
+ sessionInfo.onDestroy();
+ mSessions.remove(sessionId);
+ });
+ }
+
+ @VisibleForTesting
+ SessionInfo getSessionInfo(AppPredictionSessionId sessionId) {
+ return mSessions.get(sessionId);
+ }
+
+ private void runForSession(AppPredictionSessionId sessionId, Consumer<SessionInfo> method) {
+ SessionInfo sessionInfo = mSessions.get(sessionId);
+ if (sessionInfo == null) {
+ Slog.e(TAG, "Failed to find the session: " + sessionId);
+ return;
+ }
+ method.accept(sessionInfo);
+ }
+
+ private void invokePredictionCallback(IPredictionCallback callback,
+ List<AppTarget> targets) {
+ try {
+ callback.onResult(new ParceledListSlice<>(targets));
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to calling callback" + e);
+ }
+ }
+
+ @Override
+ public byte[] backupConversationInfos(int userId) {
+ return new byte[0];
+ }
+
+ @Override
+ public void restoreConversationInfos(int userId, String key, byte[] payload) {
+ }
+ }
+}
diff --git a/services/people/java/com/android/server/people/SessionInfo.java b/services/people/java/com/android/server/people/SessionInfo.java
new file mode 100644
index 0000000..df7cedf
--- /dev/null
+++ b/services/people/java/com/android/server/people/SessionInfo.java
@@ -0,0 +1,72 @@
+/*
+ * 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 com.android.server.people;
+
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppTarget;
+import android.app.prediction.IPredictionCallback;
+import android.content.pm.ParceledListSlice;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.server.people.prediction.ConversationPredictor;
+
+import java.util.List;
+
+/** Manages the information and callbacks in an app prediction request session. */
+class SessionInfo {
+
+ private static final String TAG = "SessionInfo";
+
+ private final ConversationPredictor mConversationPredictor;
+ private final RemoteCallbackList<IPredictionCallback> mCallbacks =
+ new RemoteCallbackList<>();
+
+ SessionInfo(AppPredictionContext predictionContext) {
+ mConversationPredictor = new ConversationPredictor(predictionContext,
+ this::updatePredictions);
+ }
+
+ void addCallback(IPredictionCallback callback) {
+ mCallbacks.register(callback);
+ }
+
+ void removeCallback(IPredictionCallback callback) {
+ mCallbacks.unregister(callback);
+ }
+
+ ConversationPredictor getPredictor() {
+ return mConversationPredictor;
+ }
+
+ void onDestroy() {
+ mCallbacks.kill();
+ }
+
+ private void updatePredictions(List<AppTarget> targets) {
+ int callbackCount = mCallbacks.beginBroadcast();
+ for (int i = 0; i < callbackCount; i++) {
+ try {
+ mCallbacks.getBroadcastItem(i).onResult(new ParceledListSlice<>(targets));
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Failed to calling callback" + e);
+ }
+ }
+ mCallbacks.finishBroadcast();
+ }
+}
diff --git a/services/people/java/com/android/server/people/prediction/ConversationPredictor.java b/services/people/java/com/android/server/people/prediction/ConversationPredictor.java
new file mode 100644
index 0000000..de71d29
--- /dev/null
+++ b/services/people/java/com/android/server/people/prediction/ConversationPredictor.java
@@ -0,0 +1,85 @@
+/*
+ * 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 com.android.server.people.prediction;
+
+import android.annotation.MainThread;
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppTarget;
+import android.app.prediction.AppTargetEvent;
+import android.app.prediction.AppTargetId;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.function.Consumer;
+
+/**
+ * Predictor that predicts the conversations or apps the user is most likely to open.
+ */
+public class ConversationPredictor {
+
+ private final AppPredictionContext mPredictionContext;
+ private final Consumer<List<AppTarget>> mUpdatePredictionsMethod;
+ private final ExecutorService mCallbackExecutor;
+
+ public ConversationPredictor(AppPredictionContext predictionContext,
+ Consumer<List<AppTarget>> updatePredictionsMethod) {
+ mPredictionContext = predictionContext;
+ mUpdatePredictionsMethod = updatePredictionsMethod;
+ mCallbackExecutor = Executors.newSingleThreadExecutor();
+ }
+
+ /**
+ * Called by the client app to indicate a target launch.
+ */
+ @MainThread
+ public void onAppTargetEvent(AppTargetEvent event) {
+ }
+
+ /**
+ * Called by the client app to indicate a particular location has been shown to the user.
+ */
+ @MainThread
+ public void onLaunchLocationShown(String launchLocation, List<AppTargetId> targetIds) {
+ }
+
+ /**
+ * Called by the client app to request sorting of the provided targets based on the prediction
+ * ranking.
+ */
+ @MainThread
+ public void onSortAppTargets(List<AppTarget> targets, Consumer<List<AppTarget>> callback) {
+ mCallbackExecutor.execute(() -> callback.accept(targets));
+ }
+
+ /**
+ * Called by the client app to request target predictions.
+ */
+ @MainThread
+ public void onRequestPredictionUpdate() {
+ List<AppTarget> targets = new ArrayList<>();
+ mCallbackExecutor.execute(() -> mUpdatePredictionsMethod.accept(targets));
+ }
+
+ @VisibleForTesting
+ public Consumer<List<AppTarget>> getUpdatePredictionsMethod() {
+ return mUpdatePredictionsMethod;
+ }
+}
diff --git a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
index 8632ca4..8b2f15c 100644
--- a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
+++ b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java
@@ -81,7 +81,10 @@
import org.robolectric.shadows.ShadowPackageManager;
import java.io.File;
+import java.io.FileDescriptor;
import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
import java.util.List;
/**
@@ -1238,13 +1241,49 @@
assertThat(service.getAncestralSerialNumber()).isEqualTo(testSerialNumber2);
}
+ /**
+ * Test that {@link UserBackupManagerService#dump()} for system user does not prefix dump with
+ * "User 0:".
+ */
+ @Test
+ public void testDump_forSystemUser_DoesNotHaveUserPrefix() throws Exception {
+ mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+ UserBackupManagerService service =
+ BackupManagerServiceTestUtils.createUserBackupManagerServiceAndRunTasks(
+ UserHandle.USER_SYSTEM,
+ mContext,
+ mBackupThread,
+ mBaseStateDir,
+ mDataDir,
+ mTransportManager);
+
+ StringWriter dump = new StringWriter();
+ service.dump(new FileDescriptor(), new PrintWriter(dump), new String[0]);
+
+ assertThat(dump.toString()).startsWith("Backup Manager is ");
+ }
+
+ /**
+ * Test that {@link UserBackupManagerService#dump()} for non-system user prefixes dump with
+ * "User <userid>:".
+ */
+ @Test
+ public void testDump_forNonSystemUser_HasUserPrefix() throws Exception {
+ mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+ UserBackupManagerService service = createUserBackupManagerServiceAndRunTasks();
+
+ StringWriter dump = new StringWriter();
+ service.dump(new FileDescriptor(), new PrintWriter(dump), new String[0]);
+
+ assertThat(dump.toString()).startsWith("User " + USER_ID + ":" + "Backup Manager is ");
+ }
+
private File createTestFile() throws IOException {
File testFile = new File(mContext.getFilesDir(), "test");
testFile.createNewFile();
return testFile;
}
-
/**
* We can't mock the void method {@link #schedule(Context, long, BackupManagerConstants)} so we
* extend {@link ShadowKeyValueBackupJob} and throw an exception at the end of the method.
diff --git a/services/robotests/src/com/android/server/location/LocationRequestStatisticsTest.java b/services/robotests/src/com/android/server/location/LocationRequestStatisticsTest.java
new file mode 100644
index 0000000..4cbdbd17
--- /dev/null
+++ b/services/robotests/src/com/android/server/location/LocationRequestStatisticsTest.java
@@ -0,0 +1,74 @@
+/*
+ * 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.location;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+
+import com.android.internal.util.IndentingPrintWriter;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * Unit tests for {@link LocationRequestStatistics}.
+ */
+@RunWith(RobolectricTestRunner.class)
+@Presubmit
+public class LocationRequestStatisticsTest {
+
+ /**
+ * Check adding and removing requests & strings
+ */
+ @Test
+ public void testRequestSummary() {
+ LocationRequestStatistics.RequestSummary summary =
+ new LocationRequestStatistics.RequestSummary(
+ "com.example", "gps", 1000);
+ StringWriter stringWriter = new StringWriter();
+ summary.dump(new IndentingPrintWriter(new PrintWriter(stringWriter), " "), 1234);
+ assertThat(stringWriter.toString()).startsWith("At");
+
+ StringWriter stringWriterRemove = new StringWriter();
+ summary = new LocationRequestStatistics.RequestSummary(
+ "com.example", "gps",
+ LocationRequestStatistics.RequestSummary.REQUEST_ENDED_INTERVAL);
+ summary.dump(new IndentingPrintWriter(new PrintWriter(stringWriterRemove), " "), 2345);
+ assertThat(stringWriterRemove.toString()).contains("-");
+ }
+
+ /**
+ * Check summary list size capping
+ */
+ @Test
+ public void testSummaryList() {
+ LocationRequestStatistics statistics = new LocationRequestStatistics();
+ statistics.history.addRequest("com.example", "gps", 1000);
+ assertThat(statistics.history.mList.size()).isEqualTo(1);
+ // Try (not) to overflow
+ for (int i = 0; i < LocationRequestStatistics.RequestSummaryLimitedHistory.MAX_SIZE; i++) {
+ statistics.history.addRequest("com.example", "gps", 1000);
+ }
+ assertThat(statistics.history.mList.size()).isEqualTo(
+ LocationRequestStatistics.RequestSummaryLimitedHistory.MAX_SIZE);
+ }
+}
diff --git a/services/robotests/src/com/android/server/location/NtpTimeHelperTest.java b/services/robotests/src/com/android/server/location/NtpTimeHelperTest.java
index a8a258f..9c5d4ad 100644
--- a/services/robotests/src/com/android/server/location/NtpTimeHelperTest.java
+++ b/services/robotests/src/com/android/server/location/NtpTimeHelperTest.java
@@ -3,6 +3,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import android.os.Looper;
import android.os.SystemClock;
@@ -52,8 +53,10 @@
@Test
public void handleInjectNtpTime_cachedAgeLow_injectTime() throws InterruptedException {
- doReturn(NtpTimeHelper.NTP_INTERVAL - 1).when(mMockNtpTrustedTime).getCacheAge();
- doReturn(MOCK_NTP_TIME).when(mMockNtpTrustedTime).getCachedNtpTime();
+ NtpTrustedTime.TimeResult result = mock(NtpTrustedTime.TimeResult.class);
+ doReturn(NtpTimeHelper.NTP_INTERVAL - 1).when(result).getAgeMillis();
+ doReturn(MOCK_NTP_TIME).when(result).getTimeMillis();
+ doReturn(result).when(mMockNtpTrustedTime).getCachedTimeResult();
mNtpTimeHelper.retrieveAndInjectNtpTime();
@@ -64,7 +67,9 @@
@Test
public void handleInjectNtpTime_injectTimeFailed_injectTimeDelayed()
throws InterruptedException {
- doReturn(NtpTimeHelper.NTP_INTERVAL + 1).when(mMockNtpTrustedTime).getCacheAge();
+ NtpTrustedTime.TimeResult result1 = mock(NtpTrustedTime.TimeResult.class);
+ doReturn(NtpTimeHelper.NTP_INTERVAL + 1).when(result1).getAgeMillis();
+ doReturn(result1).when(mMockNtpTrustedTime).getCachedTimeResult();
doReturn(false).when(mMockNtpTrustedTime).forceRefresh();
mNtpTimeHelper.retrieveAndInjectNtpTime();
@@ -72,8 +77,10 @@
assertThat(mCountDownLatch.await(2, TimeUnit.SECONDS)).isFalse();
doReturn(true).when(mMockNtpTrustedTime).forceRefresh();
- doReturn(1L).when(mMockNtpTrustedTime).getCacheAge();
- doReturn(MOCK_NTP_TIME).when(mMockNtpTrustedTime).getCachedNtpTime();
+ NtpTrustedTime.TimeResult result2 = mock(NtpTrustedTime.TimeResult.class);
+ doReturn(1L).when(result2).getAgeMillis();
+ doReturn(MOCK_NTP_TIME).when(result2).getTimeMillis();
+ doReturn(result2).when(mMockNtpTrustedTime).getCachedTimeResult();
SystemClock.sleep(NtpTimeHelper.RETRY_INTERVAL);
waitForTasksToBePostedOnHandlerAndRunThem();
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 96fedf9..3d9f11f 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -21,6 +21,7 @@
"services.core",
"services.net",
"service-jobscheduler",
+ "service-permission",
"androidx.test.runner",
"mockito-target-extended-minus-junit4",
"platform-test-annotations",
diff --git a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
index 556f96a..6a5de84 100644
--- a/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/AlarmManagerServiceTest.java
@@ -345,8 +345,8 @@
}
/**
- * Lowers quotas to make testing feasible.
- * Careful while calling as this will replace any existing settings for the calling test.
+ * Lowers quotas to make testing feasible. Careful while calling as this will replace any
+ * existing settings for the calling test.
*/
private void setTestableQuotas() {
final StringBuilder constantsBuilder = new StringBuilder();
@@ -981,6 +981,25 @@
assertEquals(0, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
}
+ @Test
+ public void alarmCountOnListenerBinderDied() {
+ final int numAlarms = 10;
+ final IAlarmListener[] listeners = new IAlarmListener[numAlarms];
+ for (int i = 0; i < numAlarms; i++) {
+ listeners[i] = new IAlarmListener.Stub() {
+ @Override
+ public void doAlarm(IAlarmCompleteListener callback) throws RemoteException {
+ }
+ };
+ setTestAlarmWithListener(ELAPSED_REALTIME_WAKEUP, mNowElapsedTest + i, listeners[i]);
+ }
+ assertEquals(numAlarms, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
+ for (int i = 0; i < numAlarms; i++) {
+ mService.mListenerDeathRecipient.binderDied(listeners[i].asBinder());
+ assertEquals(numAlarms - i - 1, mService.mAlarmsPerUid.get(TEST_CALLING_UID));
+ }
+ }
+
@After
public void tearDown() {
if (mMockingSession != null) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index 0d6020c..c3602f8 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -24,6 +24,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.RescueParty.LEVEL_FACTORY_RESET;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -32,6 +33,7 @@
import android.content.ContentResolver;
import android.content.Context;
+import android.content.pm.VersionedPackage;
import android.os.RecoverySystem;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -39,6 +41,8 @@
import android.provider.Settings;
import com.android.dx.mockito.inline.extended.ExtendedMockito;
+import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
+import com.android.server.RescueParty.RescuePartyObserver;
import com.android.server.am.SettingsToPropertiesMapper;
import com.android.server.utils.FlagNamespaceUtils;
@@ -57,13 +61,15 @@
* Test RescueParty.
*/
public class RescuePartyTest {
- private static final int PERSISTENT_APP_UID = 12;
private static final long CURRENT_NETWORK_TIME_MILLIS = 0L;
private static final String FAKE_NATIVE_NAMESPACE1 = "native1";
private static final String FAKE_NATIVE_NAMESPACE2 = "native2";
private static final String[] FAKE_RESET_NATIVE_NAMESPACES =
{FAKE_NATIVE_NAMESPACE1, FAKE_NATIVE_NAMESPACE2};
+ private static VersionedPackage sFailingPackage = new VersionedPackage("com.package.name", 1);
+ private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue";
+
private MockitoSession mSession;
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
@@ -139,7 +145,6 @@
doReturn(CURRENT_NETWORK_TIME_MILLIS).when(() -> RescueParty.getElapsedRealtime());
- RescueParty.resetAllThresholds();
FlagNamespaceUtils.resetKnownResetNamespacesFlagCounterForTest();
SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL,
@@ -155,132 +160,63 @@
@Test
public void testBootLoopDetectionWithExecutionForAllRescueLevels() {
- noteBoot(RescueParty.TRIGGER_COUNT);
+ noteBoot();
verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS,
SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
- noteBoot(RescueParty.TRIGGER_COUNT);
+ noteBoot();
verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES);
assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES,
SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
- noteBoot(RescueParty.TRIGGER_COUNT);
+ noteBoot();
verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS);
assertEquals(RescueParty.LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS,
SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
- noteBoot(RescueParty.TRIGGER_COUNT);
+ noteBoot();
verify(() -> RecoverySystem.rebootPromptAndWipeUserData(mMockContext, RescueParty.TAG));
- assertEquals(RescueParty.LEVEL_FACTORY_RESET,
+ assertEquals(LEVEL_FACTORY_RESET,
SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
}
@Test
public void testPersistentAppCrashDetectionWithExecutionForAllRescueLevels() {
- notePersistentAppCrash(RescueParty.TRIGGER_COUNT);
+ notePersistentAppCrash();
verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS,
SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
- notePersistentAppCrash(RescueParty.TRIGGER_COUNT);
+ notePersistentAppCrash();
verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES);
assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES,
SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
- notePersistentAppCrash(RescueParty.TRIGGER_COUNT);
+ notePersistentAppCrash();
verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS);
assertEquals(RescueParty.LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS,
SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
- notePersistentAppCrash(RescueParty.TRIGGER_COUNT);
+ notePersistentAppCrash();
verify(() -> RecoverySystem.rebootPromptAndWipeUserData(mMockContext, RescueParty.TAG));
- assertEquals(RescueParty.LEVEL_FACTORY_RESET,
- SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
- }
-
- @Test
- public void testBootLoopDetectionWithWrongInterval() {
- noteBoot(RescueParty.TRIGGER_COUNT - 1);
-
- // last boot is just outside of the boot loop detection window
- doReturn(CURRENT_NETWORK_TIME_MILLIS + RescueParty.BOOT_TRIGGER_WINDOW_MILLIS + 1).when(
- () -> RescueParty.getElapsedRealtime());
- noteBoot(/*numTimes=*/1);
-
- assertEquals(RescueParty.LEVEL_NONE,
- SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
- }
-
- @Test
- public void testPersistentAppCrashDetectionWithWrongInterval() {
- notePersistentAppCrash(RescueParty.TRIGGER_COUNT - 1);
-
- // last persistent app crash is just outside of the boot loop detection window
- doReturn(CURRENT_NETWORK_TIME_MILLIS
- + RescueParty.PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS + 1)
- .when(() -> RescueParty.getElapsedRealtime());
- notePersistentAppCrash(/*numTimes=*/1);
-
- assertEquals(RescueParty.LEVEL_NONE,
- SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
- }
-
- @Test
- public void testBootLoopDetectionWithProperInterval() {
- noteBoot(RescueParty.TRIGGER_COUNT - 1);
-
- // last boot is just inside of the boot loop detection window
- doReturn(CURRENT_NETWORK_TIME_MILLIS + RescueParty.BOOT_TRIGGER_WINDOW_MILLIS).when(
- () -> RescueParty.getElapsedRealtime());
- noteBoot(/*numTimes=*/1);
-
- verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
- assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS,
- SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
- }
-
- @Test
- public void testPersistentAppCrashDetectionWithProperInterval() {
- notePersistentAppCrash(RescueParty.TRIGGER_COUNT - 1);
-
- // last persistent app crash is just inside of the boot loop detection window
- doReturn(CURRENT_NETWORK_TIME_MILLIS
- + RescueParty.PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS)
- .when(() -> RescueParty.getElapsedRealtime());
- notePersistentAppCrash(/*numTimes=*/1);
-
- verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
- assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS,
- SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
- }
-
- @Test
- public void testBootLoopDetectionWithWrongTriggerCount() {
- noteBoot(RescueParty.TRIGGER_COUNT - 1);
- assertEquals(RescueParty.LEVEL_NONE,
- SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
- }
-
- @Test
- public void testPersistentAppCrashDetectionWithWrongTriggerCount() {
- notePersistentAppCrash(RescueParty.TRIGGER_COUNT - 1);
- assertEquals(RescueParty.LEVEL_NONE,
+ assertEquals(LEVEL_FACTORY_RESET,
SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
}
@Test
public void testIsAttemptingFactoryReset() {
- noteBoot(RescueParty.TRIGGER_COUNT * 4);
-
+ for (int i = 0; i < LEVEL_FACTORY_RESET; i++) {
+ noteBoot();
+ }
verify(() -> RecoverySystem.rebootPromptAndWipeUserData(mMockContext, RescueParty.TAG));
assertTrue(RescueParty.isAttemptingFactoryReset());
}
@@ -319,6 +255,77 @@
FAKE_NATIVE_NAMESPACE2, /*makeDefault=*/true));
}
+ @Test
+ public void testExplicitlyEnablingAndDisablingRescue() {
+ SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false));
+ SystemProperties.set(PROP_DISABLE_RESCUE, Boolean.toString(true));
+ assertEquals(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage,
+ PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING), false);
+
+ SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true));
+ assertTrue(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage,
+ PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING));
+ }
+
+ @Test
+ public void testHealthCheckLevels() {
+ RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
+
+ // Ensure that no action is taken for cases where the failure reason is unknown
+ SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(
+ LEVEL_FACTORY_RESET));
+ assertEquals(observer.onHealthCheckFailed(null, PackageWatchdog.FAILURE_REASON_UNKNOWN),
+ PackageHealthObserverImpact.USER_IMPACT_NONE);
+
+ /*
+ For the following cases, ensure that the returned user impact corresponds with the user
+ impact of the next available rescue level, not the current one.
+ */
+ SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(
+ RescueParty.LEVEL_NONE));
+ assertEquals(observer.onHealthCheckFailed(null,
+ PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING),
+ PackageHealthObserverImpact.USER_IMPACT_LOW);
+
+ SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(
+ RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS));
+ assertEquals(observer.onHealthCheckFailed(null,
+ PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING),
+ PackageHealthObserverImpact.USER_IMPACT_LOW);
+
+
+ SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(
+ RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES));
+ assertEquals(observer.onHealthCheckFailed(null,
+ PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING),
+ PackageHealthObserverImpact.USER_IMPACT_HIGH);
+
+
+ SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(
+ RescueParty.LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS));
+ assertEquals(observer.onHealthCheckFailed(null,
+ PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING),
+ PackageHealthObserverImpact.USER_IMPACT_HIGH);
+
+
+ SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(
+ LEVEL_FACTORY_RESET));
+ assertEquals(observer.onHealthCheckFailed(null,
+ PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING),
+ PackageHealthObserverImpact.USER_IMPACT_HIGH);
+ }
+
+ @Test
+ public void testRescueLevelIncrementsWhenExecuted() {
+ RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
+ SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(
+ RescueParty.LEVEL_NONE));
+ observer.execute(sFailingPackage,
+ PackageWatchdog.FAILURE_REASON_APP_CRASH);
+ assertEquals(SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, -1),
+ RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS);
+ }
+
private void verifySettingsResets(int resetMode) {
verify(() -> Settings.Global.resetToDefaultsAsUser(mMockContentResolver, null,
resetMode, UserHandle.USER_SYSTEM));
@@ -326,15 +333,12 @@
eq(resetMode), anyInt()));
}
- private void noteBoot(int numTimes) {
- for (int i = 0; i < numTimes; i++) {
- RescueParty.noteBoot(mMockContext);
- }
+ private void noteBoot() {
+ RescuePartyObserver.getInstance(mMockContext).executeBootLoopMitigation();
}
- private void notePersistentAppCrash(int numTimes) {
- for (int i = 0; i < numTimes; i++) {
- RescueParty.noteAppCrash(mMockContext, PERSISTENT_APP_UID);
- }
+ private void notePersistentAppCrash() {
+ RescuePartyObserver.getInstance(mMockContext).execute(new VersionedPackage(
+ "com.package.name", 1), PackageWatchdog.FAILURE_REASON_UNKNOWN);
}
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AppCompactorTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AppCompactorTest.java
deleted file mode 100644
index 48e459f..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/am/AppCompactorTest.java
+++ /dev/null
@@ -1,680 +0,0 @@
-/*
- * 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 com.android.server.am;
-
-import static com.android.server.am.ActivityManagerService.Injector;
-import static com.android.server.am.AppCompactor.compactActionIntToString;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.content.Context;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Process;
-import android.platform.test.annotations.Presubmit;
-import android.provider.DeviceConfig;
-import android.text.TextUtils;
-
-import androidx.test.platform.app.InstrumentationRegistry;
-
-import com.android.server.ServiceThread;
-import com.android.server.appop.AppOpsService;
-import com.android.server.testables.TestableDeviceConfig;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
-
-import java.io.File;
-import java.util.HashSet;
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-/**
- * Tests for {@link AppCompactor}.
- *
- * Build/Install/Run:
- * atest FrameworksMockingServicesTests:AppCompactorTest
- */
-@Presubmit
-@RunWith(MockitoJUnitRunner.class)
-public final class AppCompactorTest {
-
- private ServiceThread mThread;
-
- @Mock
- private AppOpsService mAppOpsService;
- private AppCompactor mCompactorUnderTest;
- private HandlerThread mHandlerThread;
- private Handler mHandler;
- private CountDownLatch mCountDown;
-
- @Rule
- public TestableDeviceConfig.TestableDeviceConfigRule
- mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
-
- @Before
- public void setUp() {
- mHandlerThread = new HandlerThread("");
- mHandlerThread.start();
- mHandler = new Handler(mHandlerThread.getLooper());
-
- mThread = new ServiceThread("TestServiceThread", Process.THREAD_PRIORITY_DEFAULT,
- true /* allowIo */);
- mThread.start();
-
- ActivityManagerService ams = new ActivityManagerService(
- new TestInjector(InstrumentationRegistry.getInstrumentation().getContext()),
- mThread);
- mCompactorUnderTest = new AppCompactor(ams,
- new AppCompactor.PropertyChangedCallbackForTest() {
- @Override
- public void onPropertyChanged() {
- if (mCountDown != null) {
- mCountDown.countDown();
- }
- }
- });
- }
-
- @After
- public void tearDown() {
- mHandlerThread.quit();
- mThread.quit();
- mCountDown = null;
- }
-
- @Test
- public void init_setsDefaults() {
- mCompactorUnderTest.init();
- assertThat(mCompactorUnderTest.useCompaction()).isEqualTo(
- AppCompactor.DEFAULT_USE_COMPACTION);
- assertThat(mCompactorUnderTest.mCompactActionSome).isEqualTo(
- compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_1));
- assertThat(mCompactorUnderTest.mCompactActionFull).isEqualTo(
- compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_2));
- assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_1);
- assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_2);
- assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
- assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
- assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo(
- AppCompactor.DEFAULT_STATSD_SAMPLE_RATE);
- assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
- assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
- assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
- assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
- assertThat(mCompactorUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);
-
- Set<Integer> expected = new HashSet<>();
- for (String s : TextUtils.split(AppCompactor.DEFAULT_COMPACT_PROC_STATE_THROTTLE, ",")) {
- expected.add(Integer.parseInt(s));
- }
- assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected);
- }
-
- @Test
- public void init_withDeviceConfigSetsParameters() {
- // When the DeviceConfig already has a flag value stored (note this test will need to
- // change if the default value changes from false).
- assertThat(AppCompactor.DEFAULT_USE_COMPACTION).isFalse();
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_USE_COMPACTION, "true", false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_ACTION_1,
- Integer.toString((AppCompactor.DEFAULT_COMPACT_ACTION_1 + 1 % 4) + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_ACTION_2,
- Integer.toString((AppCompactor.DEFAULT_COMPACT_ACTION_2 + 1 % 4) + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_1,
- Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_2,
- Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_3,
- Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_4,
- Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_5,
- Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_5 + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_6,
- Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_6 + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE,
- Float.toString(AppCompactor.DEFAULT_STATSD_SAMPLE_RATE + 0.1f), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_FULL_RSS_THROTTLE_KB,
- Long.toString(AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB,
- Long.toString(AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "1,2,3", false);
-
- // Then calling init will read and set that flag.
- mCompactorUnderTest.init();
- assertThat(mCompactorUnderTest.useCompaction()).isTrue();
- assertThat(mCompactorUnderTest.mCompactionThread.isAlive()).isTrue();
-
- assertThat(mCompactorUnderTest.mCompactActionSome).isEqualTo(
- compactActionIntToString((AppCompactor.DEFAULT_COMPACT_ACTION_1 + 1 % 4) + 1));
- assertThat(mCompactorUnderTest.mCompactActionFull).isEqualTo(
- compactActionIntToString((AppCompactor.DEFAULT_COMPACT_ACTION_2 + 1 % 4) + 1));
- assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1);
- assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1);
- assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1);
- assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1);
- assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_5 + 1);
- assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_6 + 1);
- assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo(
- AppCompactor.DEFAULT_STATSD_SAMPLE_RATE + 0.1f);
- assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_5 + 1);
- assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_6 + 1);
- assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1);
- assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactly(1, 2, 3);
- }
-
- @Test
- public void useCompaction_listensToDeviceConfigChanges() throws InterruptedException {
- assertThat(mCompactorUnderTest.useCompaction()).isEqualTo(
- AppCompactor.DEFAULT_USE_COMPACTION);
- // When we call init and change some the flag value...
- mCompactorUnderTest.init();
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_USE_COMPACTION, "true", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then that new flag value is updated in the implementation.
- assertThat(mCompactorUnderTest.useCompaction()).isTrue();
- assertThat(mCompactorUnderTest.mCompactionThread.isAlive()).isTrue();
-
- // And again, setting the flag the other way.
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_USE_COMPACTION, "false", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.useCompaction()).isFalse();
- }
-
- @Test
- public void useCompaction_listensToDeviceConfigChangesBadValues() throws InterruptedException {
- assertThat(mCompactorUnderTest.useCompaction()).isEqualTo(
- AppCompactor.DEFAULT_USE_COMPACTION);
- mCompactorUnderTest.init();
-
- // When we push an invalid flag value...
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_USE_COMPACTION, "foobar", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then we set the default.
- assertThat(mCompactorUnderTest.useCompaction()).isEqualTo(
- AppCompactor.DEFAULT_USE_COMPACTION);
- }
-
- @Test
- public void compactAction_listensToDeviceConfigChanges() throws InterruptedException {
- mCompactorUnderTest.init();
-
- // When we override new values for the compaction action with reasonable values...
-
- // There are four possible values for compactAction[Some|Full].
- for (int i = 1; i < 5; i++) {
- mCountDown = new CountDownLatch(2);
- int expectedSome = (AppCompactor.DEFAULT_COMPACT_ACTION_1 + i) % 4 + 1;
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_ACTION_1, Integer.toString(expectedSome), false);
- int expectedFull = (AppCompactor.DEFAULT_COMPACT_ACTION_2 + i) % 4 + 1;
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_ACTION_2, Integer.toString(expectedFull), false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then the updates are reflected in the flags.
- assertThat(mCompactorUnderTest.mCompactActionSome).isEqualTo(
- compactActionIntToString(expectedSome));
- assertThat(mCompactorUnderTest.mCompactActionFull).isEqualTo(
- compactActionIntToString(expectedFull));
- }
- }
-
- @Test
- public void compactAction_listensToDeviceConfigChangesBadValues() throws InterruptedException {
- mCompactorUnderTest.init();
-
- // When we override new values for the compaction action with bad values ...
- mCountDown = new CountDownLatch(2);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_ACTION_1, "foo", false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_ACTION_2, "foo", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then the default values are reflected in the flag
- assertThat(mCompactorUnderTest.mCompactActionSome).isEqualTo(
- compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_1));
- assertThat(mCompactorUnderTest.mCompactActionFull).isEqualTo(
- compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_2));
-
- mCountDown = new CountDownLatch(2);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_ACTION_1, "", false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_ACTION_2, "", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- assertThat(mCompactorUnderTest.mCompactActionSome).isEqualTo(
- compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_1));
- assertThat(mCompactorUnderTest.mCompactActionFull).isEqualTo(
- compactActionIntToString(AppCompactor.DEFAULT_COMPACT_ACTION_2));
- }
-
- @Test
- public void compactThrottle_listensToDeviceConfigChanges() throws InterruptedException {
- mCompactorUnderTest.init();
-
- // When we override new reasonable throttle values after init...
- mCountDown = new CountDownLatch(6);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_1,
- Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_2,
- Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_3,
- Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_4,
- Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_5,
- Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_5 + 1), false);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_6,
- Long.toString(AppCompactor.DEFAULT_COMPACT_THROTTLE_6 + 1), false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then those flags values are reflected in the compactor.
- assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_1 + 1);
- assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_2 + 1);
- assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_3 + 1);
- assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_4 + 1);
- assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_5 + 1);
- assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_6 + 1);
- }
-
- @Test
- public void compactThrottle_listensToDeviceConfigChangesBadValues()
- throws InterruptedException {
- mCompactorUnderTest.init();
-
- // When one of the throttles is overridden with a bad value...
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_1, "foo", false);
- // Then all the throttles have the defaults set.
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_1);
- assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_2);
- assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
- assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
- assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
- assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
-
- // Repeat for each of the throttle keys.
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_2, "foo", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_1);
- assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_2);
- assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
- assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
- assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
- assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
-
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_3, "foo", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_1);
- assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_2);
- assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
- assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
- assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
- assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
-
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_4, "foo", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_1);
- assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_2);
- assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
- assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
- assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
- assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
-
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_5, "foo", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_1);
- assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_2);
- assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
- assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
- assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
- assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
-
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_THROTTLE_6, "foo", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mCompactThrottleSomeSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_1);
- assertThat(mCompactorUnderTest.mCompactThrottleSomeFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_2);
- assertThat(mCompactorUnderTest.mCompactThrottleFullSome).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_3);
- assertThat(mCompactorUnderTest.mCompactThrottleFullFull).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_4);
- assertThat(mCompactorUnderTest.mCompactThrottleBFGS).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_5);
- assertThat(mCompactorUnderTest.mCompactThrottlePersistent).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_THROTTLE_6);
- }
-
- @Test
- public void statsdSampleRate_listensToDeviceConfigChanges() throws InterruptedException {
- mCompactorUnderTest.init();
-
- // When we override mStatsdSampleRate with a reasonable value ...
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE,
- Float.toString(AppCompactor.DEFAULT_STATSD_SAMPLE_RATE + 0.1f), false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then that override is reflected in the compactor.
- assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo(
- AppCompactor.DEFAULT_STATSD_SAMPLE_RATE + 0.1f);
- }
-
- @Test
- public void statsdSampleRate_listensToDeviceConfigChangesBadValues()
- throws InterruptedException {
- mCompactorUnderTest.init();
-
- // When we override mStatsdSampleRate with an unreasonable value ...
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE, "foo", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then that override is reflected in the compactor.
- assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo(
- AppCompactor.DEFAULT_STATSD_SAMPLE_RATE);
- }
-
- @Test
- public void statsdSampleRate_listensToDeviceConfigChangesOutOfRangeValues()
- throws InterruptedException {
- mCompactorUnderTest.init();
-
- // When we override mStatsdSampleRate with an value outside of [0..1]...
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE,
- Float.toString(-1.0f), false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then the values is capped in the range.
- assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo(0.0f);
-
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_STATSD_SAMPLE_RATE,
- Float.toString(1.01f), false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then the values is capped in the range.
- assertThat(mCompactorUnderTest.mStatsdSampleRate).isEqualTo(1.0f);
- }
-
- @Test
- public void fullCompactionRssThrottleKb_listensToDeviceConfigChanges()
- throws InterruptedException {
- mCompactorUnderTest.init();
-
- // When we override mStatsdSampleRate with a reasonable value ...
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_FULL_RSS_THROTTLE_KB,
- Long.toString(AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1), false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then that override is reflected in the compactor.
- assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1);
- }
-
- @Test
- public void fullCompactionRssThrottleKb_listensToDeviceConfigChangesBadValues()
- throws InterruptedException {
- mCompactorUnderTest.init();
-
- // When we override mStatsdSampleRate with an unreasonable value ...
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_FULL_RSS_THROTTLE_KB, "foo", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then that override is reflected in the compactor.
- assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
-
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_FULL_RSS_THROTTLE_KB, "-100", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then that override is reflected in the compactor.
- assertThat(mCompactorUnderTest.mFullAnonRssThrottleKb).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
- }
-
- @Test
- public void fullCompactionDeltaRssThrottleKb_listensToDeviceConfigChanges()
- throws InterruptedException {
- mCompactorUnderTest.init();
-
- // When we override mStatsdSampleRate with a reasonable value ...
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB,
- Long.toString(AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1), false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then that override is reflected in the compactor.
- assertThat(mCompactorUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1);
- }
-
- @Test
- public void fullCompactionDeltaRssThrottleKb_listensToDeviceConfigChangesBadValues()
- throws InterruptedException {
- mCompactorUnderTest.init();
-
- // When we override mStatsdSampleRate with an unreasonable value ...
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, "foo", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then that override is reflected in the compactor.
- assertThat(mCompactorUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);
-
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, "-100", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
-
- // Then that override is reflected in the compactor.
- assertThat(mCompactorUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
- AppCompactor.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);
- }
-
- @Test
- public void procStateThrottle_listensToDeviceConfigChanges()
- throws InterruptedException {
- mCompactorUnderTest.init();
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "1,2,3", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactly(1, 2, 3);
-
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mProcStateThrottle).isEmpty();
- }
-
- @Test
- public void procStateThrottle_listensToDeviceConfigChangesBadValues()
- throws InterruptedException {
- mCompactorUnderTest.init();
-
- Set<Integer> expected = new HashSet<>();
- for (String s : TextUtils.split(AppCompactor.DEFAULT_COMPACT_PROC_STATE_THROTTLE, ",")) {
- expected.add(Integer.parseInt(s));
- }
-
- // Not numbers
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "foo", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected);
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "1,foo", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected);
-
- // Empty splits
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, ",", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected);
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, ",,3", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected);
- mCountDown = new CountDownLatch(1);
- DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
- AppCompactor.KEY_COMPACT_PROC_STATE_THROTTLE, "1,,3", false);
- assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
- assertThat(mCompactorUnderTest.mProcStateThrottle).containsExactlyElementsIn(expected);
- }
-
- private class TestInjector extends Injector {
-
- TestInjector(Context context) {
- super(context);
- }
-
- @Override
- public AppOpsService getAppOpsService(File file, Handler handler) {
- return mAppOpsService;
- }
-
- @Override
- public Handler getUiHandler(ActivityManagerService service) {
- return mHandler;
- }
- }
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
new file mode 100644
index 0000000..f037692
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
@@ -0,0 +1,692 @@
+/*
+ * 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 com.android.server.am;
+
+import static com.android.server.am.ActivityManagerService.Injector;
+import static com.android.server.am.CachedAppOptimizer.compactActionIntToString;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.platform.test.annotations.Presubmit;
+import android.provider.DeviceConfig;
+import android.text.TextUtils;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.server.ServiceThread;
+import com.android.server.appop.AppOpsService;
+import com.android.server.testables.TestableDeviceConfig;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.io.File;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for {@link CachedAppOptimizer}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksMockingServicesTests:CachedAppOptimizerTest
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner.class)
+public final class CachedAppOptimizerTest {
+
+ private ServiceThread mThread;
+
+ @Mock
+ private AppOpsService mAppOpsService;
+ private CachedAppOptimizer mCachedAppOptimizerUnderTest;
+ private HandlerThread mHandlerThread;
+ private Handler mHandler;
+ private CountDownLatch mCountDown;
+
+ @Rule
+ public TestableDeviceConfig.TestableDeviceConfigRule
+ mDeviceConfigRule = new TestableDeviceConfig.TestableDeviceConfigRule();
+
+ @Before
+ public void setUp() {
+ mHandlerThread = new HandlerThread("");
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
+
+ mThread = new ServiceThread("TestServiceThread", Process.THREAD_PRIORITY_DEFAULT,
+ true /* allowIo */);
+ mThread.start();
+
+ ActivityManagerService ams = new ActivityManagerService(
+ new TestInjector(InstrumentationRegistry.getInstrumentation().getContext()),
+ mThread);
+ mCachedAppOptimizerUnderTest = new CachedAppOptimizer(ams,
+ new CachedAppOptimizer.PropertyChangedCallbackForTest() {
+ @Override
+ public void onPropertyChanged() {
+ if (mCountDown != null) {
+ mCountDown.countDown();
+ }
+ }
+ });
+ }
+
+ @After
+ public void tearDown() {
+ mHandlerThread.quit();
+ mThread.quit();
+ mCountDown = null;
+ }
+
+ @Test
+ public void init_setsDefaults() {
+ mCachedAppOptimizerUnderTest.init();
+ assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isEqualTo(
+ CachedAppOptimizer.DEFAULT_USE_COMPACTION);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome).isEqualTo(
+ compactActionIntToString(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1));
+ assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull).isEqualTo(
+ compactActionIntToString(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2));
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
+ assertThat(mCachedAppOptimizerUnderTest.mStatsdSampleRate).isEqualTo(
+ CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE);
+ assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
+ assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
+ assertThat(mCachedAppOptimizerUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);
+
+ Set<Integer> expected = new HashSet<>();
+ for (String s : TextUtils.split(
+ CachedAppOptimizer.DEFAULT_COMPACT_PROC_STATE_THROTTLE, ",")) {
+ expected.add(Integer.parseInt(s));
+ }
+ assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle)
+ .containsExactlyElementsIn(expected);
+ }
+
+ @Test
+ public void init_withDeviceConfigSetsParameters() {
+ // When the DeviceConfig already has a flag value stored (note this test will need to
+ // change if the default value changes from false).
+ assertThat(CachedAppOptimizer.DEFAULT_USE_COMPACTION).isFalse();
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_USE_COMPACTION, "true", false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_ACTION_1,
+ Integer.toString((CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1 + 1 % 4) + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_ACTION_2,
+ Integer.toString((CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2 + 1 % 4) + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_1,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_2,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_3,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_4,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_5,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_6,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_STATSD_SAMPLE_RATE,
+ Float.toString(CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE + 0.1f), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_FULL_RSS_THROTTLE_KB,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB,
+ Long.toString(
+ CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, "1,2,3", false);
+
+ // Then calling init will read and set that flag.
+ mCachedAppOptimizerUnderTest.init();
+ assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mCachedAppOptimizerThread.isAlive()).isTrue();
+
+ assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome).isEqualTo(
+ compactActionIntToString(
+ (CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1 + 1 % 4) + 1));
+ assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull).isEqualTo(
+ compactActionIntToString(
+ (CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2 + 1 % 4) + 1));
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mStatsdSampleRate).isEqualTo(
+ CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE + 0.1f);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle).containsExactly(1, 2, 3);
+ }
+
+ @Test
+ public void useCompaction_listensToDeviceConfigChanges() throws InterruptedException {
+ assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isEqualTo(
+ CachedAppOptimizer.DEFAULT_USE_COMPACTION);
+ // When we call init and change some the flag value...
+ mCachedAppOptimizerUnderTest.init();
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_USE_COMPACTION, "true", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then that new flag value is updated in the implementation.
+ assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mCachedAppOptimizerThread.isAlive()).isTrue();
+
+ // And again, setting the flag the other way.
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_USE_COMPACTION, "false", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isFalse();
+ }
+
+ @Test
+ public void useCompaction_listensToDeviceConfigChangesBadValues() throws InterruptedException {
+ assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isEqualTo(
+ CachedAppOptimizer.DEFAULT_USE_COMPACTION);
+ mCachedAppOptimizerUnderTest.init();
+
+ // When we push an invalid flag value...
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_USE_COMPACTION, "foobar", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then we set the default.
+ assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isEqualTo(
+ CachedAppOptimizer.DEFAULT_USE_COMPACTION);
+ }
+
+ @Test
+ public void compactAction_listensToDeviceConfigChanges() throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+
+ // When we override new values for the compaction action with reasonable values...
+
+ // There are four possible values for compactAction[Some|Full].
+ for (int i = 1; i < 5; i++) {
+ mCountDown = new CountDownLatch(2);
+ int expectedSome = (CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1 + i) % 4 + 1;
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_ACTION_1, Integer.toString(expectedSome), false);
+ int expectedFull = (CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2 + i) % 4 + 1;
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_ACTION_2, Integer.toString(expectedFull), false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then the updates are reflected in the flags.
+ assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome).isEqualTo(
+ compactActionIntToString(expectedSome));
+ assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull).isEqualTo(
+ compactActionIntToString(expectedFull));
+ }
+ }
+
+ @Test
+ public void compactAction_listensToDeviceConfigChangesBadValues() throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+
+ // When we override new values for the compaction action with bad values ...
+ mCountDown = new CountDownLatch(2);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_ACTION_1, "foo", false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_ACTION_2, "foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then the default values are reflected in the flag
+ assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome).isEqualTo(
+ compactActionIntToString(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1));
+ assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull).isEqualTo(
+ compactActionIntToString(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2));
+
+ mCountDown = new CountDownLatch(2);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_ACTION_1, "", false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_ACTION_2, "", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome).isEqualTo(
+ compactActionIntToString(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1));
+ assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull).isEqualTo(
+ compactActionIntToString(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2));
+ }
+
+ @Test
+ public void compactThrottle_listensToDeviceConfigChanges() throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+
+ // When we override new reasonable throttle values after init...
+ mCountDown = new CountDownLatch(6);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_1,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_2,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_3,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_4,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_5,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1), false);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_6,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1), false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then those flags values are reflected in the compactor.
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5 + 1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6 + 1);
+ }
+
+ @Test
+ public void compactThrottle_listensToDeviceConfigChangesBadValues()
+ throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+
+ // When one of the throttles is overridden with a bad value...
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_1, "foo", false);
+ // Then all the throttles have the defaults set.
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
+
+ // Repeat for each of the throttle keys.
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_2, "foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
+
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_3, "foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
+
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_4, "foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
+
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_5, "foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
+
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_THROTTLE_6, "foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_2);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullSome).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_3);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleFullFull).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_4);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleBFGS).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_5);
+ assertThat(mCachedAppOptimizerUnderTest.mCompactThrottlePersistent).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_6);
+ }
+
+ @Test
+ public void statsdSampleRate_listensToDeviceConfigChanges() throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+
+ // When we override mStatsdSampleRate with a reasonable value ...
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_STATSD_SAMPLE_RATE,
+ Float.toString(CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE + 0.1f), false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then that override is reflected in the compactor.
+ assertThat(mCachedAppOptimizerUnderTest.mStatsdSampleRate).isEqualTo(
+ CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE + 0.1f);
+ }
+
+ @Test
+ public void statsdSampleRate_listensToDeviceConfigChangesBadValues()
+ throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+
+ // When we override mStatsdSampleRate with an unreasonable value ...
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_STATSD_SAMPLE_RATE, "foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then that override is reflected in the compactor.
+ assertThat(mCachedAppOptimizerUnderTest.mStatsdSampleRate).isEqualTo(
+ CachedAppOptimizer.DEFAULT_STATSD_SAMPLE_RATE);
+ }
+
+ @Test
+ public void statsdSampleRate_listensToDeviceConfigChangesOutOfRangeValues()
+ throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+
+ // When we override mStatsdSampleRate with an value outside of [0..1]...
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_STATSD_SAMPLE_RATE,
+ Float.toString(-1.0f), false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then the values is capped in the range.
+ assertThat(mCachedAppOptimizerUnderTest.mStatsdSampleRate).isEqualTo(0.0f);
+
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_STATSD_SAMPLE_RATE,
+ Float.toString(1.01f), false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then the values is capped in the range.
+ assertThat(mCachedAppOptimizerUnderTest.mStatsdSampleRate).isEqualTo(1.0f);
+ }
+
+ @Test
+ public void fullCompactionRssThrottleKb_listensToDeviceConfigChanges()
+ throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+
+ // When we override mStatsdSampleRate with a reasonable value ...
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_FULL_RSS_THROTTLE_KB,
+ Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1), false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then that override is reflected in the compactor.
+ assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB + 1);
+ }
+
+ @Test
+ public void fullCompactionRssThrottleKb_listensToDeviceConfigChangesBadValues()
+ throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+
+ // When we override mStatsdSampleRate with an unreasonable value ...
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_FULL_RSS_THROTTLE_KB, "foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then that override is reflected in the compactor.
+ assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
+
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_FULL_RSS_THROTTLE_KB, "-100", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then that override is reflected in the compactor.
+ assertThat(mCachedAppOptimizerUnderTest.mFullAnonRssThrottleKb).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_FULL_RSS_THROTTLE_KB);
+ }
+
+ @Test
+ public void fullCompactionDeltaRssThrottleKb_listensToDeviceConfigChanges()
+ throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+
+ // When we override mStatsdSampleRate with a reasonable value ...
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB,
+ Long.toString(
+ CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1), false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then that override is reflected in the compactor.
+ assertThat(mCachedAppOptimizerUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB + 1);
+ }
+
+ @Test
+ public void fullCompactionDeltaRssThrottleKb_listensToDeviceConfigChangesBadValues()
+ throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+
+ // When we override mStatsdSampleRate with an unreasonable value ...
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, "foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then that override is reflected in the compactor.
+ assertThat(mCachedAppOptimizerUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);
+
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_FULL_DELTA_RSS_THROTTLE_KB, "-100", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+
+ // Then that override is reflected in the compactor.
+ assertThat(mCachedAppOptimizerUnderTest.mFullDeltaRssThrottleKb).isEqualTo(
+ CachedAppOptimizer.DEFAULT_COMPACT_FULL_DELTA_RSS_THROTTLE_KB);
+ }
+
+ @Test
+ public void procStateThrottle_listensToDeviceConfigChanges()
+ throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, "1,2,3", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle).containsExactly(1, 2, 3);
+
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, "", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle).isEmpty();
+ }
+
+ @Test
+ public void procStateThrottle_listensToDeviceConfigChangesBadValues()
+ throws InterruptedException {
+ mCachedAppOptimizerUnderTest.init();
+
+ Set<Integer> expected = new HashSet<>();
+ for (String s : TextUtils.split(
+ CachedAppOptimizer.DEFAULT_COMPACT_PROC_STATE_THROTTLE, ",")) {
+ expected.add(Integer.parseInt(s));
+ }
+
+ // Not numbers
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, "foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle)
+ .containsExactlyElementsIn(expected);
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, "1,foo", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle)
+ .containsExactlyElementsIn(expected);
+
+ // Empty splits
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, ",", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle)
+ .containsExactlyElementsIn(expected);
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, ",,3", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle)
+ .containsExactlyElementsIn(expected);
+ mCountDown = new CountDownLatch(1);
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+ CachedAppOptimizer.KEY_COMPACT_PROC_STATE_THROTTLE, "1,,3", false);
+ assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue();
+ assertThat(mCachedAppOptimizerUnderTest.mProcStateThrottle)
+ .containsExactlyElementsIn(expected);
+ }
+
+ private class TestInjector extends Injector {
+
+ TestInjector(Context context) {
+ super(context);
+ }
+
+ @Override
+ public AppOpsService getAppOpsService(File file, Handler handler) {
+ return mAppOpsService;
+ }
+
+ @Override
+ public Handler getUiHandler(ActivityManagerService service) {
+ return mHandler;
+ }
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
index 0504d14..067f23a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/appop/AppOpsServiceTest.java
@@ -16,6 +16,8 @@
package com.android.server.appop;
import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+import static android.app.AppOpsManager.FILTER_BY_PACKAGE_NAME;
+import static android.app.AppOpsManager.FILTER_BY_UID;
import static android.app.AppOpsManager.MODE_ALLOWED;
import static android.app.AppOpsManager.MODE_ERRORED;
import static android.app.AppOpsManager.MODE_FOREGROUND;
@@ -287,7 +289,7 @@
mAppOpsService.noteOperation(OP_READ_SMS, mMyUid, sMyPackageName, null, false, null);
AppOpsManager.HistoricalOps historicalOps = new AppOpsManager.HistoricalOps(0, 15000);
- historicalOps.increaseAccessCount(OP_READ_SMS, mMyUid, sMyPackageName,
+ historicalOps.increaseAccessCount(OP_READ_SMS, mMyUid, sMyPackageName, null,
AppOpsManager.UID_STATE_PERSISTENT, 0, 1);
mAppOpsService.addHistoricalOps(historicalOps);
@@ -300,8 +302,8 @@
});
// First, do a fetch to ensure it's written
- mAppOpsService.getHistoricalOps(mMyUid, sMyPackageName, null, 0, Long.MAX_VALUE, 0,
- callback);
+ mAppOpsService.getHistoricalOps(mMyUid, sMyPackageName, null, null,
+ FILTER_BY_UID | FILTER_BY_PACKAGE_NAME, 0, Long.MAX_VALUE, 0, callback);
latchRef.get().await(5, TimeUnit.SECONDS);
assertThat(latchRef.get().getCount()).isEqualTo(0);
@@ -312,8 +314,8 @@
latchRef.set(new CountDownLatch(1));
- mAppOpsService.getHistoricalOps(mMyUid, sMyPackageName, null, 0, Long.MAX_VALUE, 0,
- callback);
+ mAppOpsService.getHistoricalOps(mMyUid, sMyPackageName, null, null,
+ FILTER_BY_UID | FILTER_BY_PACKAGE_NAME, 0, Long.MAX_VALUE, 0, callback);
latchRef.get().await(5, TimeUnit.SECONDS);
assertThat(latchRef.get().getCount()).isEqualTo(0);
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index f2e118d..e0e374b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -50,6 +50,7 @@
import android.os.BatteryManagerInternal;
import android.os.Looper;
import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.SystemClock;
import com.android.server.AppStateTracker;
@@ -95,6 +96,7 @@
.initMocks(this)
.strictness(Strictness.LENIENT)
.mockStatic(LocalServices.class)
+ .mockStatic(ServiceManager.class)
.startMocking();
// Called in JobSchedulerService constructor.
diff --git a/services/tests/mockingservicestests/src/com/android/server/location/UserInfoStoreTest.java b/services/tests/mockingservicestests/src/com/android/server/location/UserInfoStoreTest.java
new file mode 100644
index 0000000..06fb102
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/location/UserInfoStoreTest.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.location;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.UserInfo;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.dx.mockito.inline.extended.StaticMockitoSession;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.quality.Strictness;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class UserInfoStoreTest {
+
+ private static final int USER1_ID = 1;
+ private static final int USER1_MANAGED_ID = 11;
+ private static final int[] USER1_PROFILES = new int[]{USER1_ID, USER1_MANAGED_ID};
+ private static final int USER2_ID = 2;
+ private static final int USER2_MANAGED_ID = 12;
+ private static final int[] USER2_PROFILES = new int[]{USER2_ID, USER2_MANAGED_ID};
+
+ @Mock private Context mContext;
+ @Mock private UserManager mUserManager;
+
+ private StaticMockitoSession mMockingSession;
+ private List<BroadcastReceiver> mBroadcastReceivers = new ArrayList<>();
+
+ private UserInfoStore mStore;
+
+ @Before
+ public void setUp() {
+ mMockingSession = mockitoSession()
+ .initMocks(this)
+ .spyStatic(ActivityManager.class)
+ .strictness(Strictness.WARN)
+ .startMocking();
+
+ doReturn(mUserManager).when(mContext).getSystemService(UserManager.class);
+ doAnswer(invocation -> {
+ mBroadcastReceivers.add(invocation.getArgument(0));
+ return null;
+ }).when(mContext).registerReceiverAsUser(any(BroadcastReceiver.class), any(
+ UserHandle.class), any(IntentFilter.class), isNull(), any(Handler.class));
+ doReturn(USER1_PROFILES).when(mUserManager).getProfileIdsWithDisabled(USER1_ID);
+ doReturn(USER2_PROFILES).when(mUserManager).getProfileIdsWithDisabled(USER2_ID);
+ doReturn(new UserInfo(USER1_ID, "", 0)).when(mUserManager).getProfileParent(
+ USER1_MANAGED_ID);
+ doReturn(new UserInfo(USER2_ID, "", 0)).when(mUserManager).getProfileParent(
+ USER2_MANAGED_ID);
+
+ doReturn(USER1_ID).when(ActivityManager::getCurrentUser);
+
+ mStore = new UserInfoStore(mContext);
+ mStore.onSystemReady();
+ }
+
+ @After
+ public void tearDown() {
+ if (mMockingSession != null) {
+ mMockingSession.finishMocking();
+ }
+ }
+
+ private void switchUser(int userId) {
+ doReturn(userId).when(ActivityManager::getCurrentUser);
+ Intent intent = new Intent(Intent.ACTION_USER_SWITCHED).putExtra(Intent.EXTRA_USER_HANDLE,
+ userId);
+ for (BroadcastReceiver broadcastReceiver : mBroadcastReceivers) {
+ broadcastReceiver.onReceive(mContext, intent);
+ }
+ }
+
+ @Test
+ public void testListeners() {
+ UserInfoStore.UserChangedListener listener = mock(UserInfoStore.UserChangedListener.class);
+ mStore.addListener(listener);
+
+ switchUser(USER1_ID);
+ verify(listener, never()).onUserChanged(anyInt(), anyInt());
+
+ switchUser(USER2_ID);
+ verify(listener).onUserChanged(USER1_ID, USER2_ID);
+
+ switchUser(USER1_ID);
+ verify(listener).onUserChanged(USER2_ID, USER1_ID);
+ }
+
+ @Test
+ public void testCurrentUser() {
+ assertThat(mStore.getCurrentUserId()).isEqualTo(USER1_ID);
+
+ switchUser(USER2_ID);
+
+ assertThat(mStore.getCurrentUserId()).isEqualTo(USER2_ID);
+
+ switchUser(USER1_ID);
+
+ assertThat(mStore.getCurrentUserId()).isEqualTo(USER1_ID);
+ }
+
+ @Test
+ public void testIsCurrentUserOrProfile() {
+ assertThat(mStore.isCurrentUserOrProfile(USER1_ID)).isTrue();
+ assertThat(mStore.isCurrentUserOrProfile(USER1_MANAGED_ID)).isTrue();
+ assertThat(mStore.isCurrentUserOrProfile(USER2_ID)).isFalse();
+ assertThat(mStore.isCurrentUserOrProfile(USER2_MANAGED_ID)).isFalse();
+
+ switchUser(USER2_ID);
+
+ assertThat(mStore.isCurrentUserOrProfile(USER1_ID)).isFalse();
+ assertThat(mStore.isCurrentUserOrProfile(USER2_ID)).isTrue();
+ assertThat(mStore.isCurrentUserOrProfile(USER1_MANAGED_ID)).isFalse();
+ assertThat(mStore.isCurrentUserOrProfile(USER2_MANAGED_ID)).isTrue();
+ }
+
+ @Test
+ public void testGetParentUserId() {
+ assertThat(mStore.getParentUserId(USER1_ID)).isEqualTo(USER1_ID);
+ assertThat(mStore.getParentUserId(USER1_MANAGED_ID)).isEqualTo(USER1_ID);
+ assertThat(mStore.getParentUserId(USER2_ID)).isEqualTo(USER2_ID);
+ assertThat(mStore.getParentUserId(USER2_MANAGED_ID)).isEqualTo(USER2_ID);
+
+ switchUser(USER2_ID);
+
+ assertThat(mStore.getParentUserId(USER1_ID)).isEqualTo(USER1_ID);
+ assertThat(mStore.getParentUserId(USER2_ID)).isEqualTo(USER2_ID);
+ assertThat(mStore.getParentUserId(USER1_MANAGED_ID)).isEqualTo(USER1_ID);
+ assertThat(mStore.getParentUserId(USER2_MANAGED_ID)).isEqualTo(USER2_ID);
+ }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/utils/quota/CountQuotaTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/utils/quota/CountQuotaTrackerTest.java
new file mode 100644
index 0000000..80aec73
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/utils/quota/CountQuotaTrackerTest.java
@@ -0,0 +1,848 @@
+/*
+ * 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 com.android.server.utils.quota;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.utils.quota.Category.SINGLE_CATEGORY;
+import static com.android.server.utils.quota.QuotaTracker.MAX_WINDOW_SIZE_MS;
+import static com.android.server.utils.quota.QuotaTracker.MIN_WINDOW_SIZE_MS;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.app.AlarmManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.LongArrayQueue;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.server.LocalServices;
+import com.android.server.utils.quota.CountQuotaTracker.ExecutionStats;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+/**
+ * Tests for {@link CountQuotaTracker}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class CountQuotaTrackerTest {
+ private static final long SECOND_IN_MILLIS = 1000L;
+ private static final long MINUTE_IN_MILLIS = 60 * SECOND_IN_MILLIS;
+ private static final long HOUR_IN_MILLIS = 60 * MINUTE_IN_MILLIS;
+ private static final String TAG_CLEANUP = "*CountQuotaTracker.cleanup*";
+ private static final String TAG_QUOTA_CHECK = "*QuotaTracker.quota_check*";
+ private static final String TEST_PACKAGE = "com.android.frameworks.mockingservicestests";
+ private static final String TEST_TAG = "testing";
+ private static final int TEST_UID = 10987;
+ private static final int TEST_USER_ID = 0;
+
+ /** A {@link Category} to represent the ACTIVE standby bucket. */
+ private static final Category ACTIVE_BUCKET_CATEGORY = new Category("ACTIVE");
+
+ /** A {@link Category} to represent the WORKING_SET standby bucket. */
+ private static final Category WORKING_SET_BUCKET_CATEGORY = new Category("WORKING_SET");
+
+ /** A {@link Category} to represent the FREQUENT standby bucket. */
+ private static final Category FREQUENT_BUCKET_CATEGORY = new Category("FREQUENT");
+
+ /** A {@link Category} to represent the RARE standby bucket. */
+ private static final Category RARE_BUCKET_CATEGORY = new Category("RARE");
+
+ private CountQuotaTracker mQuotaTracker;
+ private final CategorizerForTest mCategorizer = new CategorizerForTest();
+ private final InjectorForTest mInjector = new InjectorForTest();
+ private final TestQuotaChangeListener mQuotaChangeListener = new TestQuotaChangeListener();
+ private BroadcastReceiver mReceiver;
+ private MockitoSession mMockingSession;
+ @Mock
+ private AlarmManager mAlarmManager;
+ @Mock
+ private Context mContext;
+
+ static class CategorizerForTest implements Categorizer {
+ private Category mCategoryToUse = SINGLE_CATEGORY;
+
+ @Override
+ public Category getCategory(int userId,
+ String packageName, String tag) {
+ return mCategoryToUse;
+ }
+ }
+
+ private static class InjectorForTest extends QuotaTracker.Injector {
+ private long mElapsedTime = SystemClock.elapsedRealtime();
+
+ @Override
+ long getElapsedRealtime() {
+ return mElapsedTime;
+ }
+
+ @Override
+ boolean isAlarmManagerReady() {
+ return true;
+ }
+ }
+
+ private static class TestQuotaChangeListener implements QuotaChangeListener {
+
+ @Override
+ public void onQuotaStateChanged(int userId, String packageName, String tag) {
+
+ }
+ }
+
+ @Before
+ public void setUp() {
+ mMockingSession = mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.LENIENT)
+ .mockStatic(LocalServices.class)
+ .startMocking();
+
+ when(mContext.getMainLooper()).thenReturn(Looper.getMainLooper());
+ when(mContext.getSystemService(AlarmManager.class)).thenReturn(mAlarmManager);
+
+ // Freeze the clocks at 24 hours after this moment in time. Several tests create sessions
+ // in the past, and QuotaController sometimes floors values at 0, so if the test time
+ // causes sessions with negative timestamps, they will fail.
+ advanceElapsedClock(24 * HOUR_IN_MILLIS);
+
+ // Initialize real objects.
+ // Capture the listeners.
+ ArgumentCaptor<BroadcastReceiver> receiverCaptor =
+ ArgumentCaptor.forClass(BroadcastReceiver.class);
+ mQuotaTracker = new CountQuotaTracker(mContext, mCategorizer, mInjector);
+ mQuotaTracker.setEnabled(true);
+ mQuotaTracker.setQuotaFree(false);
+ mQuotaTracker.registerQuotaChangeListener(mQuotaChangeListener);
+ verify(mContext, atLeastOnce()).registerReceiverAsUser(
+ receiverCaptor.capture(), eq(UserHandle.ALL), any(), any(), any());
+ mReceiver = receiverCaptor.getValue();
+ }
+
+ @After
+ public void tearDown() {
+ if (mMockingSession != null) {
+ mMockingSession.finishMocking();
+ }
+ }
+
+ /**
+ * Returns true if the two {@link LongArrayQueue}s have the same size and the same elements in
+ * the same order.
+ */
+ private static boolean longArrayQueueEquals(LongArrayQueue queue1, LongArrayQueue queue2) {
+ if (queue1 == queue2) {
+ return true;
+ } else if (queue1 == null || queue2 == null) {
+ return false;
+ }
+ if (queue1.size() == queue2.size()) {
+ for (int i = 0; i < queue1.size(); ++i) {
+ if (queue1.get(i) != queue2.get(i)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private void advanceElapsedClock(long incrementMs) {
+ mInjector.mElapsedTime += incrementMs;
+ }
+
+ private void logEvents(int count) {
+ logEvents(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, count);
+ }
+
+ private void logEvents(int userId, String pkgName, String tag, int count) {
+ for (int i = 0; i < count; ++i) {
+ mQuotaTracker.noteEvent(userId, pkgName, tag);
+ }
+ }
+
+ private void logEventAt(long timeElapsed) {
+ logEventAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, timeElapsed);
+ }
+
+ private void logEventAt(int userId, String pkgName, String tag, long timeElapsed) {
+ long now = mInjector.getElapsedRealtime();
+ mInjector.mElapsedTime = timeElapsed;
+ mQuotaTracker.noteEvent(userId, pkgName, tag);
+ mInjector.mElapsedTime = now;
+ }
+
+ private void logEventsAt(int userId, String pkgName, String tag, long timeElapsed, int count) {
+ for (int i = 0; i < count; ++i) {
+ logEventAt(userId, pkgName, tag, timeElapsed);
+ }
+ }
+
+ @Test
+ public void testDeleteObsoleteEventsLocked() {
+ // Count window size should only apply to event list.
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 7, 2 * HOUR_IN_MILLIS);
+
+ final long now = mInjector.getElapsedRealtime();
+
+ logEventAt(now - 6 * HOUR_IN_MILLIS);
+ logEventAt(now - 5 * HOUR_IN_MILLIS);
+ logEventAt(now - 4 * HOUR_IN_MILLIS);
+ logEventAt(now - 3 * HOUR_IN_MILLIS);
+ logEventAt(now - 2 * HOUR_IN_MILLIS);
+ logEventAt(now - HOUR_IN_MILLIS);
+ logEventAt(now - 1);
+
+ LongArrayQueue expectedEvents = new LongArrayQueue();
+ expectedEvents.addLast(now - HOUR_IN_MILLIS);
+ expectedEvents.addLast(now - 1);
+
+ mQuotaTracker.deleteObsoleteEventsLocked();
+
+ LongArrayQueue remainingEvents = mQuotaTracker.getEvents(TEST_USER_ID, TEST_PACKAGE,
+ TEST_TAG);
+ assertTrue(longArrayQueueEquals(expectedEvents, remainingEvents));
+ }
+
+ @Test
+ public void testAppRemoval() {
+ final long now = mInjector.getElapsedRealtime();
+ logEventAt(TEST_USER_ID, "com.android.test.remove", "tag1", now - (6 * HOUR_IN_MILLIS));
+ logEventAt(TEST_USER_ID, "com.android.test.remove", "tag2",
+ now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS));
+ logEventAt(TEST_USER_ID, "com.android.test.remove", "tag3", now - (HOUR_IN_MILLIS));
+ // Test that another app isn't affected.
+ LongArrayQueue expected1 = new LongArrayQueue();
+ expected1.addLast(now - 10 * MINUTE_IN_MILLIS);
+ LongArrayQueue expected2 = new LongArrayQueue();
+ expected2.addLast(now - 70 * MINUTE_IN_MILLIS);
+ logEventAt(TEST_USER_ID, "com.android.test.stay", "tag1", now - 10 * MINUTE_IN_MILLIS);
+ logEventAt(TEST_USER_ID, "com.android.test.stay", "tag2", now - 70 * MINUTE_IN_MILLIS);
+
+ Intent removal = new Intent(Intent.ACTION_PACKAGE_FULLY_REMOVED,
+ Uri.fromParts("package", "com.android.test.remove", null));
+ removal.putExtra(Intent.EXTRA_UID, TEST_UID);
+ mReceiver.onReceive(mContext, removal);
+ assertNull(
+ mQuotaTracker.getEvents(TEST_USER_ID, "com.android.test.remove", "tag1"));
+ assertNull(
+ mQuotaTracker.getEvents(TEST_USER_ID, "com.android.test.remove", "tag2"));
+ assertNull(
+ mQuotaTracker.getEvents(TEST_USER_ID, "com.android.test.remove", "tag3"));
+ assertTrue(longArrayQueueEquals(expected1,
+ mQuotaTracker.getEvents(TEST_USER_ID, "com.android.test.stay", "tag1")));
+ assertTrue(longArrayQueueEquals(expected2,
+ mQuotaTracker.getEvents(TEST_USER_ID, "com.android.test.stay", "tag2")));
+ }
+
+ @Test
+ public void testUserRemoval() {
+ final long now = mInjector.getElapsedRealtime();
+ logEventAt(TEST_USER_ID, TEST_PACKAGE, "tag1", now - (6 * HOUR_IN_MILLIS));
+ logEventAt(TEST_USER_ID, TEST_PACKAGE, "tag2",
+ now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS));
+ logEventAt(TEST_USER_ID, TEST_PACKAGE, "tag3", now - (HOUR_IN_MILLIS));
+ // Test that another user isn't affected.
+ LongArrayQueue expected = new LongArrayQueue();
+ expected.addLast(now - (70 * MINUTE_IN_MILLIS));
+ expected.addLast(now - (10 * MINUTE_IN_MILLIS));
+ logEventAt(10, TEST_PACKAGE, "tag4", now - (70 * MINUTE_IN_MILLIS));
+ logEventAt(10, TEST_PACKAGE, "tag4", now - 10 * MINUTE_IN_MILLIS);
+
+ Intent removal = new Intent(Intent.ACTION_USER_REMOVED);
+ removal.putExtra(Intent.EXTRA_USER_HANDLE, TEST_USER_ID);
+ mReceiver.onReceive(mContext, removal);
+ assertNull(mQuotaTracker.getEvents(TEST_USER_ID, TEST_PACKAGE, "tag1"));
+ assertNull(mQuotaTracker.getEvents(TEST_USER_ID, TEST_PACKAGE, "tag2"));
+ assertNull(mQuotaTracker.getEvents(TEST_USER_ID, TEST_PACKAGE, "tag3"));
+ longArrayQueueEquals(expected, mQuotaTracker.getEvents(10, TEST_PACKAGE, "tag4"));
+ }
+
+ @Test
+ public void testUpdateExecutionStatsLocked_NoTimer() {
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 3, 24 * HOUR_IN_MILLIS);
+ final long now = mInjector.getElapsedRealtime();
+
+ // Added in chronological order.
+ logEventAt(now - 4 * HOUR_IN_MILLIS);
+ logEventAt(now - HOUR_IN_MILLIS);
+ logEventAt(now - 5 * MINUTE_IN_MILLIS);
+ logEventAt(now - MINUTE_IN_MILLIS);
+
+ // Test an app that hasn't had any activity.
+ ExecutionStats expectedStats = new ExecutionStats();
+ ExecutionStats inputStats = new ExecutionStats();
+
+ inputStats.windowSizeMs = expectedStats.windowSizeMs = 12 * HOUR_IN_MILLIS;
+ inputStats.countLimit = expectedStats.countLimit = 3;
+ // Invalid time is now +24 hours since there are no sessions at all for the app.
+ expectedStats.expirationTimeElapsed = now + 24 * HOUR_IN_MILLIS;
+ mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, "com.android.test.not.run", TEST_TAG,
+ inputStats);
+ assertEquals(expectedStats, inputStats);
+
+ // Now test app that has had activity.
+
+ inputStats.windowSizeMs = expectedStats.windowSizeMs = MINUTE_IN_MILLIS;
+ // Invalid time is now since there was an event exactly windowSizeMs ago.
+ expectedStats.expirationTimeElapsed = now;
+ expectedStats.countInWindow = 1;
+ mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
+ assertEquals(expectedStats, inputStats);
+
+ inputStats.windowSizeMs = expectedStats.windowSizeMs = 3 * MINUTE_IN_MILLIS;
+ expectedStats.expirationTimeElapsed = now + 2 * MINUTE_IN_MILLIS;
+ expectedStats.countInWindow = 1;
+ mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
+ assertEquals(expectedStats, inputStats);
+
+ inputStats.windowSizeMs = expectedStats.windowSizeMs = 4 * MINUTE_IN_MILLIS;
+ expectedStats.expirationTimeElapsed = now + 3 * MINUTE_IN_MILLIS;
+ expectedStats.countInWindow = 1;
+ mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
+ assertEquals(expectedStats, inputStats);
+
+ inputStats.windowSizeMs = expectedStats.windowSizeMs = 49 * MINUTE_IN_MILLIS;
+ // Invalid time is now +44 minutes since the earliest session in the window is now-5
+ // minutes.
+ expectedStats.expirationTimeElapsed = now + 44 * MINUTE_IN_MILLIS;
+ expectedStats.countInWindow = 2;
+ mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
+ assertEquals(expectedStats, inputStats);
+
+ inputStats.windowSizeMs = expectedStats.windowSizeMs = 50 * MINUTE_IN_MILLIS;
+ expectedStats.expirationTimeElapsed = now + 45 * MINUTE_IN_MILLIS;
+ expectedStats.countInWindow = 2;
+ mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
+ assertEquals(expectedStats, inputStats);
+
+ inputStats.windowSizeMs = expectedStats.windowSizeMs = HOUR_IN_MILLIS;
+ // Invalid time is now since the event is at the very edge of the window
+ // cutoff time.
+ expectedStats.expirationTimeElapsed = now;
+ expectedStats.countInWindow = 3;
+ // App is at event count limit but the oldest session is at the edge of the window, so
+ // in quota time is now.
+ expectedStats.inQuotaTimeElapsed = now;
+ mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
+ assertEquals(expectedStats, inputStats);
+
+ inputStats.windowSizeMs = expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
+ expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
+ expectedStats.countInWindow = 3;
+ expectedStats.inQuotaTimeElapsed = now + HOUR_IN_MILLIS;
+ mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
+ assertEquals(expectedStats, inputStats);
+
+ inputStats.windowSizeMs = expectedStats.windowSizeMs = 5 * HOUR_IN_MILLIS;
+ expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
+ expectedStats.countInWindow = 4;
+ expectedStats.inQuotaTimeElapsed = now + 4 * HOUR_IN_MILLIS;
+ mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
+ assertEquals(expectedStats, inputStats);
+
+ inputStats.windowSizeMs = expectedStats.windowSizeMs = 6 * HOUR_IN_MILLIS;
+ expectedStats.expirationTimeElapsed = now + 2 * HOUR_IN_MILLIS;
+ expectedStats.countInWindow = 4;
+ expectedStats.inQuotaTimeElapsed = now + 5 * HOUR_IN_MILLIS;
+ mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, inputStats);
+ assertEquals(expectedStats, inputStats);
+ }
+
+ /**
+ * Tests that getExecutionStatsLocked returns the correct stats.
+ */
+ @Test
+ public void testGetExecutionStatsLocked_Values() {
+ // The handler could cause changes to the cached stats, so prevent it from operating in
+ // this test.
+ Handler handler = mQuotaTracker.getHandler();
+ spyOn(handler);
+ doNothing().when(handler).handleMessage(any());
+
+ mQuotaTracker.setCountLimit(RARE_BUCKET_CATEGORY, 3, 24 * HOUR_IN_MILLIS);
+ mQuotaTracker.setCountLimit(FREQUENT_BUCKET_CATEGORY, 4, 8 * HOUR_IN_MILLIS);
+ mQuotaTracker.setCountLimit(WORKING_SET_BUCKET_CATEGORY, 9, 2 * HOUR_IN_MILLIS);
+ mQuotaTracker.setCountLimit(ACTIVE_BUCKET_CATEGORY, 10, 10 * MINUTE_IN_MILLIS);
+
+ final long now = mInjector.getElapsedRealtime();
+
+ logEventAt(now - 23 * HOUR_IN_MILLIS);
+ logEventAt(now - 7 * HOUR_IN_MILLIS);
+ logEventAt(now - 5 * HOUR_IN_MILLIS);
+ logEventAt(now - 2 * HOUR_IN_MILLIS);
+ logEventAt(now - 5 * MINUTE_IN_MILLIS);
+
+ ExecutionStats expectedStats = new ExecutionStats();
+
+ // Active
+ expectedStats.expirationTimeElapsed = now + 5 * MINUTE_IN_MILLIS;
+ expectedStats.windowSizeMs = 10 * MINUTE_IN_MILLIS;
+ expectedStats.countLimit = 10;
+ expectedStats.countInWindow = 1;
+ mCategorizer.mCategoryToUse = ACTIVE_BUCKET_CATEGORY;
+ assertEquals(expectedStats,
+ mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+
+ // Working
+ expectedStats.expirationTimeElapsed = now;
+ expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
+ expectedStats.countLimit = 9;
+ expectedStats.countInWindow = 2;
+ mCategorizer.mCategoryToUse = WORKING_SET_BUCKET_CATEGORY;
+ assertEquals(expectedStats,
+ mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+
+ // Frequent
+ expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
+ expectedStats.windowSizeMs = 8 * HOUR_IN_MILLIS;
+ expectedStats.countLimit = 4;
+ expectedStats.countInWindow = 4;
+ expectedStats.inQuotaTimeElapsed = now + HOUR_IN_MILLIS;
+ mCategorizer.mCategoryToUse = FREQUENT_BUCKET_CATEGORY;
+ assertEquals(expectedStats,
+ mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+
+ // Rare
+ expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS;
+ expectedStats.windowSizeMs = 24 * HOUR_IN_MILLIS;
+ expectedStats.countLimit = 3;
+ expectedStats.countInWindow = 5;
+ expectedStats.inQuotaTimeElapsed = now + 19 * HOUR_IN_MILLIS;
+ mCategorizer.mCategoryToUse = RARE_BUCKET_CATEGORY;
+ assertEquals(expectedStats,
+ mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+ }
+
+ /**
+ * Tests that getExecutionStatsLocked returns the correct stats soon after device startup.
+ */
+ @Test
+ public void testGetExecutionStatsLocked_Values_BeginningOfTime() {
+ // Set time to 3 minutes after boot.
+ mInjector.mElapsedTime = 3 * MINUTE_IN_MILLIS;
+
+ logEventAt(30_000);
+ logEventAt(MINUTE_IN_MILLIS);
+ logEventAt(2 * MINUTE_IN_MILLIS);
+
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS);
+
+ ExecutionStats expectedStats = new ExecutionStats();
+
+ expectedStats.windowSizeMs = 2 * HOUR_IN_MILLIS;
+ expectedStats.countLimit = 10;
+ expectedStats.countInWindow = 3;
+ expectedStats.expirationTimeElapsed = 2 * HOUR_IN_MILLIS + 30_000;
+ assertEquals(expectedStats,
+ mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+ }
+
+ @Test
+ public void testisWithinQuota_GlobalQuotaFree() {
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 0, 2 * HOUR_IN_MILLIS);
+ mQuotaTracker.setQuotaFree(true);
+ assertTrue(mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, null));
+ assertTrue(mQuotaTracker.isWithinQuota(TEST_USER_ID, "com.android.random.app", null));
+ }
+
+ @Test
+ public void testisWithinQuota_UptcQuotaFree() {
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 0, 2 * HOUR_IN_MILLIS);
+ mQuotaTracker.setQuotaFree(TEST_USER_ID, TEST_PACKAGE, true);
+ assertTrue(mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, null));
+ assertFalse(
+ mQuotaTracker.isWithinQuota(TEST_USER_ID, "com.android.random.app", null));
+ }
+
+ @Test
+ public void testisWithinQuota_UnderCount() {
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS);
+ logEvents(5);
+ assertTrue(mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+ }
+
+ @Test
+ public void testisWithinQuota_OverCount() {
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 25, HOUR_IN_MILLIS);
+ logEvents(TEST_USER_ID, "com.android.test.spam", TEST_TAG, 30);
+ assertFalse(mQuotaTracker.isWithinQuota(TEST_USER_ID, "com.android.test.spam", TEST_TAG));
+ }
+
+ @Test
+ public void testisWithinQuota_EqualsCount() {
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 25, HOUR_IN_MILLIS);
+ logEvents(25);
+ assertFalse(mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+ }
+
+ @Test
+ public void testisWithinQuota_DifferentCategories() {
+ mQuotaTracker.setCountLimit(RARE_BUCKET_CATEGORY, 3, 24 * HOUR_IN_MILLIS);
+ mQuotaTracker.setCountLimit(FREQUENT_BUCKET_CATEGORY, 4, 24 * HOUR_IN_MILLIS);
+ mQuotaTracker.setCountLimit(WORKING_SET_BUCKET_CATEGORY, 5, 24 * HOUR_IN_MILLIS);
+ mQuotaTracker.setCountLimit(ACTIVE_BUCKET_CATEGORY, 6, 24 * HOUR_IN_MILLIS);
+
+ for (int i = 0; i < 7; ++i) {
+ logEvents(1);
+
+ mCategorizer.mCategoryToUse = RARE_BUCKET_CATEGORY;
+ assertEquals("Rare has incorrect quota status with " + (i + 1) + " events",
+ i < 2,
+ mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+ mCategorizer.mCategoryToUse = FREQUENT_BUCKET_CATEGORY;
+ assertEquals("Frequent has incorrect quota status with " + (i + 1) + " events",
+ i < 3,
+ mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+ mCategorizer.mCategoryToUse = WORKING_SET_BUCKET_CATEGORY;
+ assertEquals("Working has incorrect quota status with " + (i + 1) + " events",
+ i < 4,
+ mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+ mCategorizer.mCategoryToUse = ACTIVE_BUCKET_CATEGORY;
+ assertEquals("Active has incorrect quota status with " + (i + 1) + " events",
+ i < 5,
+ mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+ }
+ }
+
+ @Test
+ public void testMaybeScheduleCleanupAlarmLocked() {
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 5, 24 * HOUR_IN_MILLIS);
+
+ // No sessions saved yet.
+ mQuotaTracker.maybeScheduleCleanupAlarmLocked();
+ verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_CLEANUP), any(), any());
+
+ // Test with only one timing session saved.
+ final long now = mInjector.getElapsedRealtime();
+ logEventAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 6 * HOUR_IN_MILLIS);
+ mQuotaTracker.maybeScheduleCleanupAlarmLocked();
+ verify(mAlarmManager, timeout(1000).times(1))
+ .set(anyInt(), eq(now + 18 * HOUR_IN_MILLIS), eq(TAG_CLEANUP), any(), any());
+
+ // Test with new (more recent) timing sessions saved. AlarmManger shouldn't be called again.
+ logEventAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 3 * HOUR_IN_MILLIS);
+ logEventAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - HOUR_IN_MILLIS);
+ mQuotaTracker.maybeScheduleCleanupAlarmLocked();
+ verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(now + 18 * HOUR_IN_MILLIS), eq(TAG_CLEANUP), any(), any());
+ }
+
+ /**
+ * Tests that maybeScheduleStartAlarm schedules an alarm for the right time.
+ */
+ @Test
+ public void testMaybeScheduleStartAlarmLocked() {
+ // logEvent calls maybeScheduleCleanupAlarmLocked which interferes with these tests
+ // because it schedules an alarm too. Prevent it from doing so.
+ spyOn(mQuotaTracker);
+ doNothing().when(mQuotaTracker).maybeScheduleCleanupAlarmLocked();
+
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 8 * HOUR_IN_MILLIS);
+
+ // No sessions saved yet.
+ mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+ // Test with timing sessions out of window.
+ final long now = mInjector.getElapsedRealtime();
+ logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 10 * HOUR_IN_MILLIS, 20);
+ mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+ // Test with timing sessions in window but still in quota.
+ final long start = now - (6 * HOUR_IN_MILLIS);
+ final long expectedAlarmTime = start + 8 * HOUR_IN_MILLIS;
+ logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, start, 5);
+ mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+ // Add some more sessions, but still in quota.
+ logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 3 * HOUR_IN_MILLIS, 1);
+ logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - HOUR_IN_MILLIS, 3);
+ mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ verify(mAlarmManager, never()).set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+
+ // Test when out of quota.
+ logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - HOUR_IN_MILLIS, 1);
+ mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ verify(mAlarmManager, timeout(1000).times(1))
+ .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+
+ // Alarm already scheduled, so make sure it's not scheduled again.
+ mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ verify(mAlarmManager, times(1))
+ .set(anyInt(), eq(expectedAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+ }
+
+ /** Tests that the start alarm is properly rescheduled if the app's category is changed. */
+ @Test
+ public void testMaybeScheduleStartAlarmLocked_CategoryChange() {
+ // logEvent calls maybeScheduleCleanupAlarmLocked which interferes with these tests
+ // because it schedules an alarm too. Prevent it from doing so.
+ spyOn(mQuotaTracker);
+ doNothing().when(mQuotaTracker).maybeScheduleCleanupAlarmLocked();
+
+ mQuotaTracker.setCountLimit(RARE_BUCKET_CATEGORY, 10, 24 * HOUR_IN_MILLIS);
+ mQuotaTracker.setCountLimit(FREQUENT_BUCKET_CATEGORY, 10, 8 * HOUR_IN_MILLIS);
+ mQuotaTracker.setCountLimit(WORKING_SET_BUCKET_CATEGORY, 10, 2 * HOUR_IN_MILLIS);
+ mQuotaTracker.setCountLimit(ACTIVE_BUCKET_CATEGORY, 10, 10 * MINUTE_IN_MILLIS);
+
+ final long now = mInjector.getElapsedRealtime();
+
+ // Affects rare bucket
+ logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 12 * HOUR_IN_MILLIS, 9);
+ // Affects frequent and rare buckets
+ logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 4 * HOUR_IN_MILLIS, 4);
+ // Affects working, frequent, and rare buckets
+ final long outOfQuotaTime = now - HOUR_IN_MILLIS;
+ logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, outOfQuotaTime, 7);
+ // Affects all buckets
+ logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 5 * MINUTE_IN_MILLIS, 3);
+
+ InOrder inOrder = inOrder(mAlarmManager);
+
+ // Start in ACTIVE bucket.
+ mCategorizer.mCategoryToUse = ACTIVE_BUCKET_CATEGORY;
+ mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ inOrder.verify(mAlarmManager, never())
+ .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ inOrder.verify(mAlarmManager, never()).cancel(any(AlarmManager.OnAlarmListener.class));
+
+ // And down from there.
+ final long expectedWorkingAlarmTime = outOfQuotaTime + (2 * HOUR_IN_MILLIS);
+ mCategorizer.mCategoryToUse = WORKING_SET_BUCKET_CATEGORY;
+ mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ inOrder.verify(mAlarmManager, timeout(1000).times(1))
+ .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+
+ final long expectedFrequentAlarmTime = outOfQuotaTime + (8 * HOUR_IN_MILLIS);
+ mCategorizer.mCategoryToUse = FREQUENT_BUCKET_CATEGORY;
+ mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ inOrder.verify(mAlarmManager, timeout(1000).times(1))
+ .set(anyInt(), eq(expectedFrequentAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+
+ final long expectedRareAlarmTime = outOfQuotaTime + (24 * HOUR_IN_MILLIS);
+ mCategorizer.mCategoryToUse = RARE_BUCKET_CATEGORY;
+ mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ inOrder.verify(mAlarmManager, timeout(1000).times(1))
+ .set(anyInt(), eq(expectedRareAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+
+ // And back up again.
+ mCategorizer.mCategoryToUse = FREQUENT_BUCKET_CATEGORY;
+ mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ inOrder.verify(mAlarmManager, timeout(1000).times(1))
+ .set(anyInt(), eq(expectedFrequentAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+
+ mCategorizer.mCategoryToUse = WORKING_SET_BUCKET_CATEGORY;
+ mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ inOrder.verify(mAlarmManager, timeout(1000).times(1))
+ .set(anyInt(), eq(expectedWorkingAlarmTime), eq(TAG_QUOTA_CHECK), any(), any());
+
+ mCategorizer.mCategoryToUse = ACTIVE_BUCKET_CATEGORY;
+ mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ inOrder.verify(mAlarmManager, timeout(1000).times(1))
+ .cancel(any(AlarmManager.OnAlarmListener.class));
+ inOrder.verify(mAlarmManager, timeout(1000).times(0))
+ .set(anyInt(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+ }
+
+ @Test
+ public void testConstantsUpdating_ValidValues() {
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 0, 60_000);
+ assertEquals(0, mQuotaTracker.getLimit(SINGLE_CATEGORY));
+ assertEquals(60_000, mQuotaTracker.getWindowSizeMs(SINGLE_CATEGORY));
+ }
+
+ @Test
+ public void testConstantsUpdating_InvalidValues() {
+ // Test negatives.
+ try {
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, -1, 5000);
+ fail("Negative count limit didn't throw an exception");
+ } catch (IllegalArgumentException e) {
+ // Success
+ }
+ try {
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 1, -1);
+ fail("Negative count window size didn't throw an exception");
+ } catch (IllegalArgumentException e) {
+ // Success
+ }
+
+ // Test window sizes too low.
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 0, 1);
+ assertEquals(MIN_WINDOW_SIZE_MS, mQuotaTracker.getWindowSizeMs(SINGLE_CATEGORY));
+
+ // Test window sizes too high.
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 0, 365 * 24 * HOUR_IN_MILLIS);
+ assertEquals(MAX_WINDOW_SIZE_MS, mQuotaTracker.getWindowSizeMs(SINGLE_CATEGORY));
+ }
+
+ /** Tests that events aren't counted when global quota is free. */
+ @Test
+ public void testLogEvent_GlobalQuotaFree() {
+ mQuotaTracker.setQuotaFree(true);
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS);
+
+ ExecutionStats stats =
+ mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ assertEquals(0, stats.countInWindow);
+
+ for (int i = 0; i < 10; ++i) {
+ mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+
+ mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats);
+ assertEquals(0, stats.countInWindow);
+ }
+ }
+
+ /**
+ * Tests that events are counted when global quota is not free.
+ */
+ @Test
+ public void testLogEvent_GlobalQuotaNotFree() {
+ mQuotaTracker.setQuotaFree(false);
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS);
+
+ ExecutionStats stats =
+ mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ assertEquals(0, stats.countInWindow);
+
+ for (int i = 0; i < 10; ++i) {
+ mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+
+ mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats);
+ assertEquals(i + 1, stats.countInWindow);
+ }
+ }
+
+ /** Tests that events aren't counted when the uptc quota is free. */
+ @Test
+ public void testLogEvent_UptcQuotaFree() {
+ mQuotaTracker.setQuotaFree(TEST_USER_ID, TEST_PACKAGE, true);
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS);
+
+ ExecutionStats stats =
+ mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ assertEquals(0, stats.countInWindow);
+
+ for (int i = 0; i < 10; ++i) {
+ mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+
+ mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats);
+ assertEquals(0, stats.countInWindow);
+ }
+ }
+
+ /**
+ * Tests that events are counted when UPTC quota is not free.
+ */
+ @Test
+ public void testLogEvent_UptcQuotaNotFree() {
+ mQuotaTracker.setQuotaFree(TEST_USER_ID, TEST_PACKAGE, false);
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS);
+
+ ExecutionStats stats =
+ mQuotaTracker.getExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ assertEquals(0, stats.countInWindow);
+
+ for (int i = 0; i < 10; ++i) {
+ mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ advanceElapsedClock(10 * SECOND_IN_MILLIS);
+
+ mQuotaTracker.updateExecutionStatsLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, stats);
+ assertEquals(i + 1, stats.countInWindow);
+ }
+ }
+
+ /**
+ * Tests that QuotaChangeListeners are notified when a UPTC reaches its count quota.
+ */
+ @Test
+ public void testTracking_OutOfQuota() {
+ spyOn(mQuotaChangeListener);
+
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 10, 2 * HOUR_IN_MILLIS);
+ logEvents(9);
+
+ mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+
+ // Wait for some extra time to allow for processing.
+ verify(mQuotaChangeListener, timeout(3 * SECOND_IN_MILLIS).times(1))
+ .onQuotaStateChanged(eq(TEST_USER_ID), eq(TEST_PACKAGE), eq(TEST_TAG));
+ assertFalse(mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+ }
+
+ /**
+ * Tests that QuotaChangeListeners are not incorrectly notified after a UPTC event is logged
+ * quota times.
+ */
+ @Test
+ public void testTracking_InQuota() {
+ spyOn(mQuotaChangeListener);
+
+ mQuotaTracker.setCountLimit(SINGLE_CATEGORY, 5, MINUTE_IN_MILLIS);
+
+ // Log an event once per minute. This is well below the quota, so listeners should not be
+ // notified.
+ for (int i = 0; i < 10; i++) {
+ advanceElapsedClock(MINUTE_IN_MILLIS);
+ mQuotaTracker.noteEvent(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
+ }
+
+ // Wait for some extra time to allow for processing.
+ verify(mQuotaChangeListener, timeout(3 * SECOND_IN_MILLIS).times(0))
+ .onQuotaStateChanged(eq(TEST_USER_ID), eq(TEST_PACKAGE), eq(TEST_TAG));
+ assertTrue(mQuotaTracker.isWithinQuota(TEST_USER_ID, TEST_PACKAGE, TEST_TAG));
+ }
+}
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 015e574f2..556f636 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -26,6 +26,7 @@
"services.core",
"services.devicepolicy",
"services.net",
+ "services.people",
"services.usage",
"guava",
"androidx.test.core",
@@ -43,6 +44,11 @@
"servicestests-utils",
"service-appsearch",
"service-jobscheduler",
+ "service-permission",
+ // TODO: remove once Android migrates to JUnit 4.12,
+ // which provides assertThrows
+ "testng",
+
],
aidl: {
diff --git a/services/tests/servicestests/res/raw/comp_device_owner.xml b/services/tests/servicestests/res/raw/comp_device_owner.xml
new file mode 100644
index 0000000..0a10242
--- /dev/null
+++ b/services/tests/servicestests/res/raw/comp_device_owner.xml
@@ -0,0 +1,8 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<root>
+ <device-owner package="com.android.frameworks.servicestests"
+ name=""
+ component="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1"
+ userRestrictionsMigrated="true" />
+ <device-owner-context userId="0" />
+</root>
diff --git a/services/tests/servicestests/res/raw/comp_policies_primary.xml b/services/tests/servicestests/res/raw/comp_policies_primary.xml
new file mode 100644
index 0000000..1e1a0ef
--- /dev/null
+++ b/services/tests/servicestests/res/raw/comp_policies_primary.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<policies setup-complete="true" provisioning-state="3">
+ <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1">
+ <policies flags="991"/>
+ <password-history-length value="33" />
+ </admin>
+</policies>
diff --git a/services/tests/servicestests/res/raw/comp_policies_profile_another_package.xml b/services/tests/servicestests/res/raw/comp_policies_profile_another_package.xml
new file mode 100644
index 0000000..141315e
--- /dev/null
+++ b/services/tests/servicestests/res/raw/comp_policies_profile_another_package.xml
@@ -0,0 +1,6 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<policies setup-complete="true" provisioning-state="3">
+ <admin name="com.another.package.name/whatever.random.class">
+ <policies flags="991"/>
+ </admin>
+</policies>
diff --git a/services/tests/servicestests/res/raw/comp_policies_profile_same_package.xml b/services/tests/servicestests/res/raw/comp_policies_profile_same_package.xml
new file mode 100644
index 0000000..c874dcc
--- /dev/null
+++ b/services/tests/servicestests/res/raw/comp_policies_profile_same_package.xml
@@ -0,0 +1,6 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<policies setup-complete="true" provisioning-state="3">
+ <admin name="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1">
+ <policies flags="991"/>
+ </admin>
+</policies>
diff --git a/services/tests/servicestests/res/raw/comp_profile_owner_another_package.xml b/services/tests/servicestests/res/raw/comp_profile_owner_another_package.xml
new file mode 100644
index 0000000..d65ba78
--- /dev/null
+++ b/services/tests/servicestests/res/raw/comp_profile_owner_another_package.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<root>
+ <profile-owner package="com.another.package.name"
+ name="com.another.package.name"
+ component="com.another.package.name/whatever.random.class"
+ userRestrictionsMigrated="true"/>
+</root>
diff --git a/services/tests/servicestests/res/raw/comp_profile_owner_same_package.xml b/services/tests/servicestests/res/raw/comp_profile_owner_same_package.xml
new file mode 100644
index 0000000..7f98c91c
--- /dev/null
+++ b/services/tests/servicestests/res/raw/comp_profile_owner_same_package.xml
@@ -0,0 +1,7 @@
+<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
+<root>
+ <profile-owner package="com.android.frameworks.servicestests"
+ name="com.android.frameworks.servicestests"
+ component="com.android.frameworks.servicestests/com.android.server.devicepolicy.DummyDeviceAdmins$Admin1"
+ userRestrictionsMigrated="true"/>
+</root>
diff --git a/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java
index e9c5ce7..d5483ff 100644
--- a/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/CountryDetectorServiceTest.java
@@ -19,8 +19,11 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.when;
import android.content.Context;
+import android.content.res.Resources;
import android.location.Country;
import android.location.CountryListener;
import android.location.ICountryListener;
@@ -31,6 +34,10 @@
import androidx.test.core.app.ApplicationProvider;
+import com.android.internal.R;
+import com.android.server.location.ComprehensiveCountryDetector;
+import com.android.server.location.CustomCountryDetectorTestClass;
+
import com.google.common.truth.Expect;
import org.junit.Before;
@@ -38,12 +45,18 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class CountryDetectorServiceTest {
+ private static final String VALID_CUSTOM_TEST_CLASS =
+ "com.android.server.location.CustomCountryDetectorTestClass";
+ private static final String INVALID_CUSTOM_TEST_CLASS =
+ "com.android.server.location.MissingCountryDetectorTestClass";
+
private static class CountryListenerTester extends ICountryListener.Stub {
private Country mCountry;
@@ -83,12 +96,11 @@
}
}
- @Rule
- public final Expect expect = Expect.create();
- @Spy
- private Context mContext = ApplicationProvider.getApplicationContext();
- @Spy
- private Handler mHandler = new Handler(Looper.myLooper());
+ @Rule public final Expect expect = Expect.create();
+ @Spy private Context mContext = ApplicationProvider.getApplicationContext();
+ @Spy private Handler mHandler = new Handler(Looper.myLooper());
+ @Mock private Resources mResources;
+
private CountryDetectorServiceTester mCountryDetectorService;
@BeforeClass
@@ -108,10 +120,12 @@
message.getCallback().run();
return true;
}).when(mHandler).sendMessageAtTime(any(Message.class), anyLong());
+
+ doReturn(mResources).when(mContext).getResources();
}
@Test
- public void countryListener_add_successful() throws RemoteException {
+ public void addCountryListener_validListener_listenerAdded() throws RemoteException {
CountryListenerTester countryListener = new CountryListenerTester();
mCountryDetectorService.systemRunning();
@@ -122,7 +136,7 @@
}
@Test
- public void countryListener_remove_successful() throws RemoteException {
+ public void removeCountryListener_validListener_listenerRemoved() throws RemoteException {
CountryListenerTester countryListener = new CountryListenerTester();
mCountryDetectorService.systemRunning();
@@ -133,8 +147,31 @@
expect.that(mCountryDetectorService.isListenerSet()).isFalse();
}
+ @Test(expected = RemoteException.class)
+ public void addCountryListener_serviceNotReady_throwsException() throws RemoteException {
+ CountryListenerTester countryListener = new CountryListenerTester();
+
+ expect.that(mCountryDetectorService.isSystemReady()).isFalse();
+ mCountryDetectorService.addCountryListener(countryListener);
+ }
+
+ @Test(expected = RemoteException.class)
+ public void removeCountryListener_serviceNotReady_throwsException() throws RemoteException {
+ CountryListenerTester countryListener = new CountryListenerTester();
+
+ expect.that(mCountryDetectorService.isSystemReady()).isFalse();
+ mCountryDetectorService.removeCountryListener(countryListener);
+ }
+
@Test
- public void countryListener_notify_successful() throws RemoteException {
+ public void detectCountry_serviceNotReady_returnNull() {
+ expect.that(mCountryDetectorService.isSystemReady()).isFalse();
+
+ expect.that(mCountryDetectorService.detectCountry()).isNull();
+ }
+
+ @Test
+ public void notifyReceivers_twoListenersRegistered_bothNotified() throws RemoteException {
CountryListenerTester countryListenerA = new CountryListenerTester();
CountryListenerTester countryListenerB = new CountryListenerTester();
Country country = new Country("US", Country.COUNTRY_SOURCE_NETWORK);
@@ -151,4 +188,26 @@
expect.that(countryListenerA.getCountry().equalsIgnoreSource(country)).isTrue();
expect.that(countryListenerB.getCountry().equalsIgnoreSource(country)).isTrue();
}
+
+ @Test
+ public void initialize_deviceWithCustomDetector_useCustomDetectorClass() {
+ when(mResources.getString(R.string.config_customCountryDetector))
+ .thenReturn(VALID_CUSTOM_TEST_CLASS);
+
+ mCountryDetectorService.initialize();
+
+ expect.that(mCountryDetectorService.getCountryDetector())
+ .isInstanceOf(CustomCountryDetectorTestClass.class);
+ }
+
+ @Test
+ public void initialize_deviceWithInvalidCustomDetector_useDefaultDetector() {
+ when(mResources.getString(R.string.config_customCountryDetector))
+ .thenReturn(INVALID_CUSTOM_TEST_CLASS);
+
+ mCountryDetectorService.initialize();
+
+ expect.that(mCountryDetectorService.getCountryDetector())
+ .isInstanceOf(ComprehensiveCountryDetector.class);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/DynamicSystemServiceTest.java b/services/tests/servicestests/src/com/android/server/DynamicSystemServiceTest.java
index 50437b4..d367f71 100644
--- a/services/tests/servicestests/src/com/android/server/DynamicSystemServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/DynamicSystemServiceTest.java
@@ -36,7 +36,7 @@
public void test1() {
assertTrue("dynamic_system service available", mService != null);
try {
- mService.startInstallation();
+ mService.startInstallation("dsu");
fail("DynamicSystemService did not throw SecurityException as expected");
} catch (SecurityException e) {
// expected
diff --git a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
index 1829fb7..e609adc 100644
--- a/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/NetworkScoreServiceTest.java
@@ -16,7 +16,7 @@
package com.android.server;
-import static android.net.NetworkScoreManager.CACHE_FILTER_NONE;
+import static android.net.NetworkScoreManager.SCORE_FILTER_NONE;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
@@ -29,6 +29,7 @@
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
@@ -56,7 +57,6 @@
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
-import android.net.wifi.WifiSsid;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
@@ -182,8 +182,8 @@
}
private ScanResult createScanResult(String ssid, String bssid) {
- ScanResult result = new ScanResult();
- result.wifiSsid = WifiSsid.createFromAsciiEncoded(ssid);
+ ScanResult result = mock(ScanResult.class);
+ result.SSID = ssid;
result.BSSID = bssid;
return result;
}
@@ -306,7 +306,7 @@
bindToScorer(true /*callerIsScorer*/);
mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI,
- mNetworkScoreCache, CACHE_FILTER_NONE);
+ mNetworkScoreCache, SCORE_FILTER_NONE);
mNetworkScoreService.updateScores(new ScoredNetwork[]{SCORED_NETWORK});
@@ -321,9 +321,9 @@
bindToScorer(true /*callerIsScorer*/);
mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI,
- mNetworkScoreCache, CACHE_FILTER_NONE);
+ mNetworkScoreCache, SCORE_FILTER_NONE);
mNetworkScoreService.registerNetworkScoreCache(
- NetworkKey.TYPE_WIFI, mNetworkScoreCache2, CACHE_FILTER_NONE);
+ NetworkKey.TYPE_WIFI, mNetworkScoreCache2, SCORE_FILTER_NONE);
// updateScores should update both caches
mNetworkScoreService.updateScores(new ScoredNetwork[]{SCORED_NETWORK});
@@ -378,7 +378,7 @@
bindToScorer(true /*callerIsScorer*/);
mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache,
- CACHE_FILTER_NONE);
+ SCORE_FILTER_NONE);
mNetworkScoreService.clearScores();
verify(mNetworkScoreCache).clearScores();
@@ -392,7 +392,7 @@
.thenReturn(PackageManager.PERMISSION_GRANTED);
mNetworkScoreService.registerNetworkScoreCache(NetworkKey.TYPE_WIFI, mNetworkScoreCache,
- CACHE_FILTER_NONE);
+ SCORE_FILTER_NONE);
mNetworkScoreService.clearScores();
verify(mNetworkScoreCache).clearScores();
@@ -472,7 +472,7 @@
try {
mNetworkScoreService.registerNetworkScoreCache(
- NetworkKey.TYPE_WIFI, mNetworkScoreCache, CACHE_FILTER_NONE);
+ NetworkKey.TYPE_WIFI, mNetworkScoreCache, SCORE_FILTER_NONE);
fail("SecurityException expected");
} catch (SecurityException e) {
// expected
@@ -615,7 +615,7 @@
new ArrayList<>(scoredNetworkList),
NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter);
- consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_NONE);
+ consumer.accept(mNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_NONE);
verify(mNetworkScoreCache).updateScores(scoredNetworkList);
verifyZeroInteractions(mCurrentNetworkFilter, mScanResultsFilter);
@@ -656,7 +656,7 @@
Collections.emptyList(),
NetworkKey.TYPE_WIFI, mCurrentNetworkFilter, mScanResultsFilter);
- consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_NONE);
+ consumer.accept(mNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_NONE);
verifyZeroInteractions(mNetworkScoreCache, mCurrentNetworkFilter, mScanResultsFilter);
}
@@ -673,7 +673,7 @@
List<ScoredNetwork> filteredList = new ArrayList<>(scoredNetworkList);
filteredList.remove(SCORED_NETWORK);
when(mCurrentNetworkFilter.apply(scoredNetworkList)).thenReturn(filteredList);
- consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_CURRENT_NETWORK);
+ consumer.accept(mNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_CURRENT_NETWORK);
verify(mNetworkScoreCache).updateScores(filteredList);
verifyZeroInteractions(mScanResultsFilter);
@@ -691,7 +691,7 @@
List<ScoredNetwork> filteredList = new ArrayList<>(scoredNetworkList);
filteredList.remove(SCORED_NETWORK);
when(mScanResultsFilter.apply(scoredNetworkList)).thenReturn(filteredList);
- consumer.accept(mNetworkScoreCache, NetworkScoreManager.CACHE_FILTER_SCAN_RESULTS);
+ consumer.accept(mNetworkScoreCache, NetworkScoreManager.SCORE_FILTER_SCAN_RESULTS);
verify(mNetworkScoreCache).updateScores(filteredList);
verifyZeroInteractions(mCurrentNetworkFilter);
@@ -794,7 +794,7 @@
@Test
public void testScanResultsScoreCacheFilter_invalidScanResults() throws Exception {
List<ScanResult> invalidScanResults = Lists.newArrayList(
- new ScanResult(),
+ mock(ScanResult.class),
createScanResult("", SCORED_NETWORK.networkKey.wifiKey.bssid),
createScanResult(WifiManager.UNKNOWN_SSID, SCORED_NETWORK.networkKey.wifiKey.bssid),
createScanResult(SSID, null),
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
index d11d987..e1e9b7e 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java
@@ -75,6 +75,7 @@
import android.os.IPowerManager;
import android.os.PowerManager;
import android.os.Process;
+import android.os.RemoteCallback;
import android.os.RemoteException;
import android.testing.DexmakerShareClassLoaderRule;
import android.view.Display;
@@ -85,6 +86,7 @@
import android.view.accessibility.IAccessibilityInteractionConnectionCallback;
import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection;
+import com.android.server.accessibility.test.MessageCapturingHandler;
import com.android.server.wm.WindowManagerInternal;
import org.junit.Before;
@@ -692,6 +694,18 @@
assertThat(result, is(false));
}
+ @Test
+ public void takeScreenshot_returnNull() {
+ // no canTakeScreenshot, should return null.
+ when(mMockSecurityPolicy.canTakeScreenshotLocked(mServiceConnection)).thenReturn(false);
+ assertThat(mServiceConnection.takeScreenshot(Display.DEFAULT_DISPLAY), is(nullValue()));
+
+ // no checkAccessibilityAccess, should return null.
+ when(mMockSecurityPolicy.canTakeScreenshotLocked(mServiceConnection)).thenReturn(true);
+ when(mMockSecurityPolicy.checkAccessibilityAccess(mServiceConnection)).thenReturn(false);
+ assertThat(mServiceConnection.takeScreenshot(Display.DEFAULT_DISPLAY), is(nullValue()));
+ }
+
private void updateServiceInfo(AccessibilityServiceInfo serviceInfo, int eventType,
int feedbackType, int flags, String[] packageNames, int notificationTimeout) {
serviceInfo.eventTypes = eventType;
@@ -803,6 +817,11 @@
}
@Override
+ public boolean switchToInputMethod(String imeId) {
+ return false;
+ }
+
+ @Override
public boolean isAccessibilityButtonAvailable() throws RemoteException {
return false;
}
@@ -826,5 +845,8 @@
@Override
public void onFingerprintGesture(int gesture) {}
+
+ @Override
+ public void takeScreenshotWithCallback(int displayId, RemoteCallback callback) {}
}
}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
new file mode 100644
index 0000000..75239db
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityManagerServiceTest.java
@@ -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 com.android.server.accessibility;
+
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.Manifest;
+import android.app.PendingIntent;
+import android.app.RemoteAction;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Icon;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.server.LocalServices;
+import com.android.server.accessibility.AccessibilityManagerService.AccessibilityDisplayListener;
+import com.android.server.wm.ActivityTaskManagerInternal;
+import com.android.server.wm.WindowManagerInternal;
+
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * APCT tests for {@link AccessibilityManagerService}.
+ */
+public class AccessibilityManagerServiceTest extends AndroidTestCase {
+ private static final String TAG = "A11Y_MANAGER_SERVICE_TEST";
+ private static final int ACTION_ID = 20;
+ private static final String LABEL = "label";
+ private static final String INTENT_ACTION = "TESTACTION";
+ private static final String DESCRIPTION = "description";
+ private static final PendingIntent TEST_PENDING_INTENT = PendingIntent.getBroadcast(
+ InstrumentationRegistry.getTargetContext(), 0, new Intent(INTENT_ACTION), 0);
+ private static final RemoteAction TEST_ACTION = new RemoteAction(
+ Icon.createWithContentUri("content://test"),
+ LABEL,
+ DESCRIPTION,
+ TEST_PENDING_INTENT);
+ private static final AccessibilityAction NEW_ACCESSIBILITY_ACTION =
+ new AccessibilityAction(ACTION_ID, LABEL);
+
+ @Mock private PackageManager mMockPackageManager;
+ @Mock private WindowManagerInternal mMockWindowManagerService;
+ @Mock private AccessibilitySecurityPolicy mMockSecurityPolicy;
+ @Mock private SystemActionPerformer mMockSystemActionPerformer;
+ @Mock private AccessibilityWindowManager mMockA11yWindowManager;
+ @Mock private AccessibilityDisplayListener mMockA11yDisplayListener;
+ @Mock private ActivityTaskManagerInternal mMockActivityTaskManagerInternal;
+
+ private AccessibilityManagerService mA11yms;
+
+ @Override
+ protected void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ LocalServices.removeServiceForTest(WindowManagerInternal.class);
+ LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
+ LocalServices.addService(WindowManagerInternal.class, mMockWindowManagerService);
+ LocalServices.addService(
+ ActivityTaskManagerInternal.class, mMockActivityTaskManagerInternal);
+ mA11yms = new AccessibilityManagerService(
+ InstrumentationRegistry.getContext(),
+ mMockPackageManager,
+ mMockSecurityPolicy,
+ mMockSystemActionPerformer,
+ mMockA11yWindowManager,
+ mMockA11yDisplayListener);
+ }
+
+ @SmallTest
+ public void testRegisterSystemActionWithoutPermission() throws Exception {
+ doThrow(SecurityException.class).when(mMockSecurityPolicy).enforceCallingPermission(
+ Manifest.permission.MANAGE_ACCESSIBILITY,
+ AccessibilityManagerService.FUNCTION_REGISTER_SYSTEM_ACTION);
+
+ try {
+ mA11yms.registerSystemAction(TEST_ACTION, ACTION_ID);
+ fail();
+ } catch (SecurityException expected) {
+ }
+ verify(mMockSystemActionPerformer, never()).registerSystemAction(ACTION_ID, TEST_ACTION);
+ }
+
+ @SmallTest
+ public void testRegisterSystemAction() throws Exception {
+ mA11yms.registerSystemAction(TEST_ACTION, ACTION_ID);
+ verify(mMockSystemActionPerformer).registerSystemAction(ACTION_ID, TEST_ACTION);
+ }
+
+ @SmallTest
+ public void testUnregisterSystemActionWithoutPermission() throws Exception {
+ doThrow(SecurityException.class).when(mMockSecurityPolicy).enforceCallingPermission(
+ Manifest.permission.MANAGE_ACCESSIBILITY,
+ AccessibilityManagerService.FUNCTION_UNREGISTER_SYSTEM_ACTION);
+
+ try {
+ mA11yms.unregisterSystemAction(ACTION_ID);
+ fail();
+ } catch (SecurityException expected) {
+ }
+ verify(mMockSystemActionPerformer, never()).unregisterSystemAction(ACTION_ID);
+ }
+
+ @SmallTest
+ public void testUnregisterSystemAction() throws Exception {
+ mA11yms.unregisterSystemAction(ACTION_ID);
+ verify(mMockSystemActionPerformer).unregisterSystemAction(ACTION_ID);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java
index 04ac7fe..1504097 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilitySecurityPolicyTest.java
@@ -346,6 +346,14 @@
}
@Test
+ public void canTakeScreenshot_hasCapability_returnTrue() {
+ when(mMockA11yServiceConnection.getCapabilities())
+ .thenReturn(AccessibilityServiceInfo.CAPABILITY_CAN_TAKE_SCREENSHOT);
+
+ assertTrue(mA11ySecurityPolicy.canTakeScreenshotLocked(mMockA11yServiceConnection));
+ }
+
+ @Test
public void resolveProfileParent_userIdIsCurrentUser_returnCurrentUser() {
final int currentUserId = 10;
final int userId = currentUserId;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
index 99dd9a1..2ce70b6f 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
@@ -44,6 +44,7 @@
import android.testing.DexmakerShareClassLoaderRule;
import android.view.Display;
+import com.android.server.accessibility.test.MessageCapturingHandler;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.WindowManagerInternal;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
index 96d9c47..ac5169c 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityUserStateTest.java
@@ -113,7 +113,6 @@
mUserState.mAccessibilityButtonTargets.add(COMPONENT_NAME.flattenToString());
mUserState.setTouchExplorationEnabledLocked(true);
mUserState.setDisplayMagnificationEnabledLocked(true);
- mUserState.setNavBarMagnificationEnabledLocked(true);
mUserState.setAutoclickEnabledLocked(true);
mUserState.setUserNonInteractiveUiTimeoutLocked(30);
mUserState.setUserInteractiveUiTimeoutLocked(30);
@@ -132,7 +131,6 @@
assertTrue(mUserState.mAccessibilityButtonTargets.isEmpty());
assertFalse(mUserState.isTouchExplorationEnabledLocked());
assertFalse(mUserState.isDisplayMagnificationEnabledLocked());
- assertFalse(mUserState.isNavBarMagnificationEnabledLocked());
assertFalse(mUserState.isAutoclickEnabledLocked());
assertEquals(0, mUserState.getUserNonInteractiveUiTimeoutLocked());
assertEquals(0, mUserState.getUserInteractiveUiTimeoutLocked());
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
index 67075ed..9db5a08 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityWindowManagerTest.java
@@ -53,6 +53,7 @@
import android.view.accessibility.IAccessibilityInteractionConnection;
import com.android.server.accessibility.AccessibilityWindowManager.RemoteAccessibilityConnection;
+import com.android.server.accessibility.test.MessageCapturingHandler;
import com.android.server.wm.WindowManagerInternal;
import com.android.server.wm.WindowManagerInternal.WindowsForAccessibilityCallback;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java
index 44a514f..96ae102e5 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureControllerTest.java
@@ -31,6 +31,8 @@
import android.accessibilityservice.FingerprintGestureController.FingerprintGestureCallback;
import android.accessibilityservice.IAccessibilityServiceConnection;
+import com.android.server.accessibility.test.MessageCapturingHandler;
+
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureDispatcherTest.java b/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureDispatcherTest.java
index de7bc44..30d00ad 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureDispatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/FingerprintGestureDispatcherTest.java
@@ -31,6 +31,7 @@
import android.view.KeyEvent;
import com.android.server.accessibility.FingerprintGestureDispatcher.FingerprintGestureClient;
+import com.android.server.accessibility.test.MessageCapturingHandler;
import org.junit.After;
import org.junit.Before;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java b/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java
index 23ce483..4123556 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/KeyEventDispatcherTest.java
@@ -44,6 +44,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.server.accessibility.KeyEventDispatcher.KeyEventFilter;
+import com.android.server.accessibility.test.MessageCapturingHandler;
import com.android.server.policy.WindowManagerPolicy;
import org.hamcrest.Description;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java
index 322653b..78e651b 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/KeyboardInterceptorTest.java
@@ -33,6 +33,7 @@
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.accessibility.test.MessageCapturingHandler;
import com.android.server.policy.WindowManagerPolicy;
import org.hamcrest.Description;
@@ -212,4 +213,4 @@
description.appendText("Matches key event");
}
}
-}
\ No newline at end of file
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java
index 773b877..82c6498 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MagnificationControllerTest.java
@@ -47,6 +47,7 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.accessibility.test.MessageCapturingHandler;
import com.android.server.wm.WindowManagerInternal;
import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
index 36e854c..1ac4a8e 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/MotionEventInjectorTest.java
@@ -54,6 +54,7 @@
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.accessibility.test.MessageCapturingHandler;
import com.android.server.accessibility.utils.MotionEventMatcher;
import org.hamcrest.Description;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
index 8da927d..c8baca6 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
@@ -37,6 +37,7 @@
import android.os.IBinder;
import android.view.accessibility.AccessibilityEvent;
+import com.android.server.accessibility.test.MessageCapturingHandler;
import com.android.server.wm.WindowManagerInternal;
import org.junit.After;
diff --git a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
index 0f11566..39a3aae 100644
--- a/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/accounts/AccountManagerServiceTest.java
@@ -212,7 +212,8 @@
String[] list = new String[]{AccountManagerServiceTestFixtures.CALLER_PACKAGE};
when(mMockPackageManager.getPackagesForUid(anyInt())).thenReturn(list);
- Account[] accounts = mAms.getAccounts(null, mContext.getOpPackageName());
+ Account[] accounts = mAms.getAccountsAsUser(null,
+ UserHandle.getCallingUserId(), mContext.getOpPackageName());
Arrays.sort(accounts, new AccountSorter());
assertEquals(6, accounts.length);
assertEquals(a11, accounts[0]);
@@ -222,8 +223,8 @@
assertEquals(a22, accounts[4]);
assertEquals(a32, accounts[5]);
- accounts = mAms.getAccounts(AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1,
- mContext.getOpPackageName());
+ accounts = mAms.getAccountsAsUser(AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1,
+ UserHandle.getCallingUserId(), mContext.getOpPackageName());
Arrays.sort(accounts, new AccountSorter());
assertEquals(3, accounts.length);
assertEquals(a11, accounts[0]);
@@ -232,8 +233,8 @@
mAms.removeAccountInternal(a21);
- accounts = mAms.getAccounts(AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1,
- mContext.getOpPackageName());
+ accounts = mAms.getAccountsAsUser(AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1,
+ UserHandle.getCallingUserId(), mContext.getOpPackageName());
Arrays.sort(accounts, new AccountSorter());
assertEquals(2, accounts.length);
assertEquals(a11, accounts[0]);
@@ -373,7 +374,8 @@
unlockSystemUser();
String[] list = new String[]{AccountManagerServiceTestFixtures.CALLER_PACKAGE};
when(mMockPackageManager.getPackagesForUid(anyInt())).thenReturn(list);
- Account[] accounts = mAms.getAccounts(null, mContext.getOpPackageName());
+ Account[] accounts = mAms.getAccountsAsUser(null, UserHandle.getCallingUserId(),
+ mContext.getOpPackageName());
assertEquals("1 account should be migrated", 1, accounts.length);
assertEquals(PreNTestDatabaseHelper.ACCOUNT_NAME, accounts[0].name);
assertEquals(PreNTestDatabaseHelper.ACCOUNT_PASSWORD, mAms.getPassword(accounts[0]));
@@ -2980,7 +2982,8 @@
Log.d(TAG, logPrefix + " getAccounts started");
long ti = System.currentTimeMillis();
try {
- Account[] accounts = mAms.getAccounts(null, mContext.getOpPackageName());
+ Account[] accounts = mAms.getAccountsAsUser(null,
+ UserHandle.getCallingUserId(), mContext.getOpPackageName());
if (accounts == null || accounts.length != 1
|| !AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1.equals(
accounts[0].type)) {
@@ -3051,7 +3054,8 @@
Log.d(TAG, logPrefix + " getAccounts started");
long ti = System.currentTimeMillis();
try {
- Account[] accounts = mAms.getAccounts(null, mContext.getOpPackageName());
+ Account[] accounts = mAms.getAccountsAsUser(null,
+ UserHandle.getCallingUserId(), mContext.getOpPackageName());
if (accounts == null || accounts.length != 1
|| !AccountManagerServiceTestFixtures.ACCOUNT_TYPE_1.equals(
accounts[0].type)) {
diff --git a/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java
index d4182f3..5a1ad86 100644
--- a/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/adb/AdbDebuggingManagerTest.java
@@ -672,6 +672,30 @@
connectionTime2, mKeyStore.getLastConnectionTime(TEST_KEY_2));
}
+ @Test
+ public void testClearAuthorizationsBeforeAdbEnabled() throws Exception {
+ // The adb key store is not instantiated until adb is enabled; however if the user attempts
+ // to clear the adb authorizations when adb is disabled after a boot a NullPointerException
+ // was thrown as deleteKeyStore is invoked against the key store. This test ensures the
+ // key store can be successfully cleared when adb is disabled.
+ mHandler = mManager.new AdbDebuggingHandler(FgThread.get().getLooper());
+
+ clearKeyStore();
+ }
+
+ @Test
+ public void testClearAuthorizationsDeletesKeyFiles() throws Exception {
+ mAdbKeyFile.createNewFile();
+ mAdbKeyXmlFile.createNewFile();
+
+ clearKeyStore();
+
+ assertFalse("The adb key file should have been deleted after revocation of the grants",
+ mAdbKeyFile.exists());
+ assertFalse("The adb xml key file should have been deleted after revocation of the grants",
+ mAdbKeyXmlFile.exists());
+ }
+
/**
* Runs an adb test with the provided configuration.
*
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index 79cc3db..f122014 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -31,6 +31,7 @@
import static com.google.android.collect.Lists.newArrayList;
import static com.google.android.collect.Sets.newHashSet;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertEquals;
@@ -54,6 +55,7 @@
import static org.mockito.Mockito.when;
import android.annotation.UserIdInt;
+import android.app.ActivityManager;
import android.app.IUserSwitchObserver;
import android.content.Context;
import android.content.IIntentReceiver;
@@ -74,10 +76,10 @@
import android.platform.test.annotations.Presubmit;
import android.util.Log;
-
import androidx.test.filters.SmallTest;
import com.android.server.FgThread;
+import com.android.server.am.UserState.KeyEvictedCallback;
import com.android.server.pm.UserManagerService;
import com.android.server.wm.WindowManagerService;
@@ -107,6 +109,7 @@
private static final int TEST_USER_ID = 100;
private static final int TEST_USER_ID1 = 101;
private static final int TEST_USER_ID2 = 102;
+ private static final int TEST_USER_ID3 = 103;
private static final int NONEXIST_USER_ID = 2;
private static final int TEST_PRE_CREATED_USER_ID = 103;
@@ -120,6 +123,8 @@
private TestInjector mInjector;
private final HashMap<Integer, UserState> mUserStates = new HashMap<>();
+ private final KeyEvictedCallback mKeyEvictedCallback = (userId) -> { /* ignore */ };
+
private static final List<String> START_FOREGROUND_USER_ACTIONS = newArrayList(
Intent.ACTION_USER_STARTED,
Intent.ACTION_USER_SWITCHED,
@@ -153,6 +158,7 @@
doNothing().when(mInjector).activityManagerOnUserStopped(anyInt());
doNothing().when(mInjector).clearBroadcastQueueForUser(anyInt());
doNothing().when(mInjector).stackSupervisorRemoveUser(anyInt());
+ // All UserController params are set to default.
mUserController = new UserController(mInjector);
setUpUser(TEST_USER_ID, NO_USERINFO_FLAGS);
setUpUser(TEST_PRE_CREATED_USER_ID, NO_USERINFO_FLAGS, /* preCreated=*/ true);
@@ -187,7 +193,9 @@
@Test
public void testStartUserUIDisabled() {
- mUserController.mUserSwitchUiEnabled = false;
+ mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false,
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+
mUserController.startUser(TEST_USER_ID, true /* foreground */);
verify(mInjector.getWindowManager(), never()).startFreezingScreen(anyInt(), anyInt());
verify(mInjector.getWindowManager(), never()).stopFreezingScreen();
@@ -245,7 +253,9 @@
@Test
public void testFailedStartUserInForeground() {
- mUserController.mUserSwitchUiEnabled = false;
+ mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false,
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+
mUserController.startUserInForeground(NONEXIST_USER_ID);
verify(mInjector.getWindowManager(), times(1)).setSwitchingUser(anyBoolean());
verify(mInjector.getWindowManager()).setSwitchingUser(false);
@@ -326,7 +336,9 @@
@Test
public void testContinueUserSwitchUIDisabled() throws RemoteException {
- mUserController.mUserSwitchUiEnabled = false;
+ mUserController.setInitialConfig(/* userSwitchUiEnabled= */ false,
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+
// Start user -- this will update state of mUserController
mUserController.startUser(TEST_USER_ID, true);
Message reportMsg = mInjector.mHandler.getMessageForCode(REPORT_USER_SWITCH_MSG);
@@ -389,11 +401,13 @@
* Test stopping of user from max running users limit.
*/
@Test
- public void testUserStoppingForMultipleUsersNormalMode()
+ public void testUserLockingFromUserSwitchingForMultipleUsersNonDelayedLocking()
throws InterruptedException, RemoteException {
+ mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+
setUpUser(TEST_USER_ID1, 0);
setUpUser(TEST_USER_ID2, 0);
- mUserController.mMaxRunningUsers = 3;
int numerOfUserSwitches = 1;
addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM,
numerOfUserSwitches, false);
@@ -430,12 +444,13 @@
* all middle steps which takes too much work to mock.
*/
@Test
- public void testUserStoppingForMultipleUsersDelayedLockingMode()
+ public void testUserLockingFromUserSwitchingForMultipleUsersDelayedLockingMode()
throws InterruptedException, RemoteException {
+ mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true);
+
setUpUser(TEST_USER_ID1, 0);
setUpUser(TEST_USER_ID2, 0);
- mUserController.mMaxRunningUsers = 3;
- mUserController.mDelayUserDataLocking = true;
int numerOfUserSwitches = 1;
addForegroundUserAndContinueUserSwitch(TEST_USER_ID, UserHandle.USER_SYSTEM,
numerOfUserSwitches, false);
@@ -456,7 +471,7 @@
// Skip all other steps and test unlock delaying only
UserState uss = mUserStates.get(TEST_USER_ID);
uss.setState(UserState.STATE_SHUTDOWN); // necessary state change from skipped part
- mUserController.finishUserStopped(uss);
+ mUserController.finishUserStopped(uss, /* allowDelayedLocking= */ true);
// Cannot mock FgThread handler, so confirm that there is no posted message left before
// checking.
waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS);
@@ -473,12 +488,87 @@
mUserController.getRunningUsersLU());
UserState ussUser1 = mUserStates.get(TEST_USER_ID1);
ussUser1.setState(UserState.STATE_SHUTDOWN);
- mUserController.finishUserStopped(ussUser1);
+ mUserController.finishUserStopped(ussUser1, /* allowDelayedLocking= */ true);
waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS);
verify(mInjector.mStorageManagerMock, times(1))
.lockUserKey(TEST_USER_ID);
}
+ /**
+ * Test locking user with mDelayUserDataLocking false.
+ */
+ @Test
+ public void testUserLockingWithStopUserForNonDelayedLockingMode() throws Exception {
+ mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false);
+
+ setUpAndStartUserInBackground(TEST_USER_ID);
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID, /* delayedLocking= */ true,
+ /* keyEvictedCallback= */ null, /* expectLocking= */ true);
+
+ setUpAndStartUserInBackground(TEST_USER_ID1);
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+ /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
+
+ setUpAndStartUserInBackground(TEST_USER_ID2);
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* delayedLocking= */ false,
+ /* keyEvictedCallback= */ null, /* expectLocking= */ true);
+
+ setUpAndStartUserInBackground(TEST_USER_ID3);
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID3, /* delayedLocking= */ false,
+ /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
+ }
+
+ /**
+ * Test conditional delayed locking with mDelayUserDataLocking true.
+ */
+ @Test
+ public void testUserLockingForDelayedLockingMode() throws Exception {
+ mUserController.setInitialConfig(/* userSwitchUiEnabled= */ true,
+ /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ true);
+
+ // delayedLocking set and no KeyEvictedCallback, so it should not lock.
+ setUpAndStartUserInBackground(TEST_USER_ID);
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID, /* delayedLocking= */ true,
+ /* keyEvictedCallback= */ null, /* expectLocking= */ false);
+
+ setUpAndStartUserInBackground(TEST_USER_ID1);
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true,
+ /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
+
+ setUpAndStartUserInBackground(TEST_USER_ID2);
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* delayedLocking= */ false,
+ /* keyEvictedCallback= */ null, /* expectLocking= */ true);
+
+ setUpAndStartUserInBackground(TEST_USER_ID3);
+ assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID3, /* delayedLocking= */ false,
+ /* keyEvictedCallback= */ mKeyEvictedCallback, /* expectLocking= */ true);
+ }
+
+ private void setUpAndStartUserInBackground(int userId) throws Exception {
+ setUpUser(userId, 0);
+ mUserController.startUser(userId, /* foreground= */ false);
+ verify(mInjector.mStorageManagerMock, times(1))
+ .unlockUserKey(TEST_USER_ID, 0, null, null);
+ mUserStates.put(userId, mUserController.getStartedUserState(userId));
+ }
+
+ private void assertUserLockedOrUnlockedAfterStopping(int userId, boolean delayedLocking,
+ KeyEvictedCallback keyEvictedCallback, boolean expectLocking) throws Exception {
+ int r = mUserController.stopUser(userId, /* force= */ true, /* delayedLocking= */
+ delayedLocking, null, keyEvictedCallback);
+ assertThat(r).isEqualTo(ActivityManager.USER_OP_SUCCESS);
+ // fake all interim steps
+ UserState ussUser = mUserStates.get(userId);
+ ussUser.setState(UserState.STATE_SHUTDOWN);
+ // Passing delayedLocking invalidates incorrect internal data passing but currently there is
+ // no easy way to get that information passed through lambda.
+ mUserController.finishUserStopped(ussUser, delayedLocking);
+ waitForHandlerToComplete(FgThread.getHandler(), HANDLER_WAIT_TIME_MS);
+ verify(mInjector.mStorageManagerMock, times(expectLocking ? 1 : 0))
+ .lockUserKey(userId);
+ }
+
private void addForegroundUserAndContinueUserSwitch(int newUserId, int expectedOldUserId,
int expectedNumberOfCalls, boolean expectOldUserStopping)
throws RemoteException {
diff --git a/services/tests/servicestests/src/com/android/server/appsearch/impl/AppSearchImplTest.java b/services/tests/servicestests/src/com/android/server/appsearch/impl/AppSearchImplTest.java
new file mode 100644
index 0000000..4195679
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/appsearch/impl/AppSearchImplTest.java
@@ -0,0 +1,107 @@
+/*
+ * 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 com.android.server.appsearch.impl;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.expectThrows;
+
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.os.UserHandle;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.android.icing.proto.IndexingConfig;
+import com.google.android.icing.proto.PropertyConfigProto;
+import com.google.android.icing.proto.SchemaProto;
+import com.google.android.icing.proto.SchemaTypeConfigProto;
+import com.google.android.icing.proto.TermMatchType;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class AppSearchImplTest {
+ private final Context mContext = InstrumentationRegistry.getContext();
+ private final @UserIdInt int mUserId = UserHandle.getCallingUserId();
+
+ @Test
+ public void testRewriteSchemaTypes() {
+ SchemaProto inSchema = SchemaProto.newBuilder()
+ .addTypes(SchemaTypeConfigProto.newBuilder()
+ .setSchemaType("TestType")
+ .addProperties(PropertyConfigProto.newBuilder()
+ .setPropertyName("subject")
+ .setDataType(PropertyConfigProto.DataType.Code.STRING)
+ .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
+ .setIndexingConfig(
+ IndexingConfig.newBuilder()
+ .setTokenizerType(
+ IndexingConfig.TokenizerType.Code.PLAIN)
+ .setTermMatchType(TermMatchType.Code.PREFIX)
+ .build()
+ ).build()
+ ).addProperties(PropertyConfigProto.newBuilder()
+ .setPropertyName("link")
+ .setDataType(PropertyConfigProto.DataType.Code.DOCUMENT)
+ .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
+ .setSchemaType("RefType")
+ .build()
+ ).build()
+ ).build();
+
+ SchemaProto expectedSchema = SchemaProto.newBuilder()
+ .addTypes(SchemaTypeConfigProto.newBuilder()
+ .setSchemaType("com.android.server.appsearch.impl@42:TestType")
+ .addProperties(PropertyConfigProto.newBuilder()
+ .setPropertyName("subject")
+ .setDataType(PropertyConfigProto.DataType.Code.STRING)
+ .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
+ .setIndexingConfig(
+ IndexingConfig.newBuilder()
+ .setTokenizerType(
+ IndexingConfig.TokenizerType.Code.PLAIN)
+ .setTermMatchType(TermMatchType.Code.PREFIX)
+ .build()
+ ).build()
+ ).addProperties(PropertyConfigProto.newBuilder()
+ .setPropertyName("link")
+ .setDataType(PropertyConfigProto.DataType.Code.DOCUMENT)
+ .setCardinality(PropertyConfigProto.Cardinality.Code.OPTIONAL)
+ .setSchemaType("com.android.server.appsearch.impl@42:RefType")
+ .build()
+ ).build()
+ ).build();
+
+ AppSearchImpl impl = new AppSearchImpl(mContext, mUserId);
+ SchemaProto.Builder actualSchema = inSchema.toBuilder();
+ impl.rewriteSchemaTypes("com.android.server.appsearch.impl@42:", actualSchema);
+
+ assertThat(actualSchema.build()).isEqualTo(expectedSchema);
+ }
+
+ @Test
+ public void testPackageNotFound() {
+ AppSearchImpl impl = new AppSearchImpl(mContext, mUserId);
+ IllegalStateException e = expectThrows(
+ IllegalStateException.class,
+ () -> impl.setSchema(
+ /*callingUid=*/Integer.MAX_VALUE, SchemaProto.getDefaultInstance()));
+ assertThat(e).hasMessageThat().contains("Failed to look up package name");
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
index f3c76b6..8871348 100644
--- a/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/appwidget/AppWidgetServiceImplTest.java
@@ -28,6 +28,7 @@
import android.app.admin.DevicePolicyManagerInternal;
import android.appwidget.AppWidgetManager;
+import android.appwidget.AppWidgetManagerInternal;
import android.appwidget.AppWidgetProviderInfo;
import android.appwidget.PendingHostUpdate;
import android.content.BroadcastReceiver;
@@ -80,6 +81,7 @@
super.setUp();
LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
LocalServices.removeServiceForTest(ShortcutServiceInternal.class);
+ LocalServices.removeServiceForTest(AppWidgetManagerInternal.class);
mTestContext = new TestContext();
mPkgName = mTestContext.getOpPackageName();
diff --git a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
index 2326dfd..3de006c 100644
--- a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
@@ -27,6 +27,7 @@
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
@@ -56,9 +57,9 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -83,6 +84,8 @@
@Mock
private UserBackupManagerService mUserBackupManagerService;
@Mock
+ private UserBackupManagerService mNonSystemUserBackupManagerService;
+ @Mock
private Context mContextMock;
@Mock
private PrintWriter mPrintWriterMock;
@@ -105,7 +108,7 @@
mUserServices = new SparseArray<>();
mUserServices.append(UserHandle.USER_SYSTEM, mUserBackupManagerService);
- mUserServices.append(NON_USER_SYSTEM, mUserBackupManagerService);
+ mUserServices.append(NON_USER_SYSTEM, mNonSystemUserBackupManagerService);
when(mUserManagerMock.getUserInfo(UserHandle.USER_SYSTEM)).thenReturn(mUserInfoMock);
when(mUserManagerMock.getUserInfo(NON_USER_SYSTEM)).thenReturn(mUserInfoMock);
@@ -512,19 +515,53 @@
mService.dump(mFileDescriptorStub, mPrintWriterMock, new String[0]);
verifyNoMoreInteractions(mUserBackupManagerService);
+ verifyNoMoreInteractions(mNonSystemUserBackupManagerService);
+ }
+
+ /**
+ * Test that {@link BackupManagerService#dump()} dumps system user information before non-system
+ * user information.
+ */
+ @Test
+ public void testDump_systemUserFirst() {
+ String[] args = new String[0];
+ mService.dumpWithoutCheckingPermission(mFileDescriptorStub, mPrintWriterMock, args);
+
+ InOrder inOrder =
+ inOrder(mUserBackupManagerService, mNonSystemUserBackupManagerService);
+ inOrder.verify(mUserBackupManagerService)
+ .dump(mFileDescriptorStub, mPrintWriterMock, args);
+ inOrder.verify(mNonSystemUserBackupManagerService)
+ .dump(mFileDescriptorStub, mPrintWriterMock, args);
+ inOrder.verifyNoMoreInteractions();
}
@Test
- @Ignore("b/147012496")
- public void testGetUserForAncestralSerialNumber() {
+ public void testGetUserForAncestralSerialNumber_forSystemUser() {
BackupManagerServiceTestable.sBackupDisabled = false;
BackupManagerService backupManagerService =
new BackupManagerServiceTestable(mContextMock, mUserServices);
+ when(mUserManagerMock.getProfileIds(UserHandle.getCallingUserId(), false))
+ .thenReturn(new int[] {UserHandle.USER_SYSTEM, NON_USER_SYSTEM});
when(mUserBackupManagerService.getAncestralSerialNumber()).thenReturn(11L);
UserHandle user = backupManagerService.getUserForAncestralSerialNumber(11L);
- assertThat(user).isEqualTo(UserHandle.of(1));
+ assertThat(user).isEqualTo(UserHandle.of(UserHandle.USER_SYSTEM));
+ }
+
+ @Test
+ public void testGetUserForAncestralSerialNumber_forNonSystemUser() {
+ BackupManagerServiceTestable.sBackupDisabled = false;
+ BackupManagerService backupManagerService =
+ new BackupManagerServiceTestable(mContextMock, mUserServices);
+ when(mUserManagerMock.getProfileIds(UserHandle.getCallingUserId(), false))
+ .thenReturn(new int[] {UserHandle.USER_SYSTEM, NON_USER_SYSTEM});
+ when(mNonSystemUserBackupManagerService.getAncestralSerialNumber()).thenReturn(11L);
+
+ UserHandle user = backupManagerService.getUserForAncestralSerialNumber(11L);
+
+ assertThat(user).isEqualTo(UserHandle.of(NON_USER_SYSTEM));
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index f96d996..bec265e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -411,7 +411,8 @@
// HAT sent to keystore
verify(mBiometricService.mKeyStore).addAuthToken(any(byte[].class));
// Send onAuthenticated to client
- verify(mReceiver1).onAuthenticationSucceeded();
+ verify(mReceiver1).onAuthenticationSucceeded(
+ BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC);
// Current session becomes null
assertNull(mBiometricService.mCurrentAuthSession);
}
@@ -461,7 +462,8 @@
BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED);
waitForIdle();
verify(mBiometricService.mKeyStore).addAuthToken(any(byte[].class));
- verify(mReceiver1).onAuthenticationSucceeded();
+ verify(mReceiver1).onAuthenticationSucceeded(
+ BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC);
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
index abe39f0..312ff2c 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
@@ -223,4 +223,22 @@
Utils.biometricConstantsToBiometricManager(testCases[i][0]));
}
}
+
+ @Test
+ public void testGetAuthenticationTypeForResult_getsCorrectType() {
+ assertEquals(Utils.getAuthenticationTypeForResult(
+ BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED),
+ BiometricPrompt.AUTHENTICATION_RESULT_TYPE_DEVICE_CREDENTIAL);
+ assertEquals(Utils.getAuthenticationTypeForResult(
+ BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED),
+ BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC);
+ assertEquals(Utils.getAuthenticationTypeForResult(
+ BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED),
+ BiometricPrompt.AUTHENTICATION_RESULT_TYPE_BIOMETRIC);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testGetAuthResultType_throwsForInvalidReason() {
+ Utils.getAuthenticationTypeForResult(BiometricPrompt.DISMISSED_REASON_NEGATIVE);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java b/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java
new file mode 100644
index 0000000..d0767cc
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/compat/ApplicationInfoBuilder.java
@@ -0,0 +1,58 @@
+/*
+ * 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 com.android.server.compat;
+
+import android.content.pm.ApplicationInfo;
+
+class ApplicationInfoBuilder {
+ private boolean mIsDebuggable;
+ private int mTargetSdk;
+ private String mPackageName;
+
+ private ApplicationInfoBuilder() {
+ mTargetSdk = -1;
+ }
+
+ static ApplicationInfoBuilder create() {
+ return new ApplicationInfoBuilder();
+ }
+
+ ApplicationInfoBuilder withTargetSdk(int targetSdk) {
+ mTargetSdk = targetSdk;
+ return this;
+ }
+
+ ApplicationInfoBuilder debuggable() {
+ mIsDebuggable = true;
+ return this;
+ }
+
+ ApplicationInfoBuilder withPackageName(String packageName) {
+ mPackageName = packageName;
+ return this;
+ }
+
+ ApplicationInfo build() {
+ final ApplicationInfo applicationInfo = new ApplicationInfo();
+ if (mIsDebuggable) {
+ applicationInfo.flags |= ApplicationInfo.FLAG_DEBUGGABLE;
+ }
+ applicationInfo.packageName = mPackageName;
+ applicationInfo.targetSdkVersion = mTargetSdk;
+ return applicationInfo;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigBuilder.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigBuilder.java
new file mode 100644
index 0000000..328c71d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigBuilder.java
@@ -0,0 +1,99 @@
+/*
+ * 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 com.android.server.compat;
+
+import android.content.Context;
+
+import com.android.internal.compat.AndroidBuildClassifier;
+
+import java.util.ArrayList;
+
+/**
+ * Helper class for creating a CompatConfig.
+ */
+class CompatConfigBuilder {
+ private ArrayList<CompatChange> mChanges;
+ private AndroidBuildClassifier mBuildClassifier;
+ private Context mContext;
+
+ private CompatConfigBuilder(AndroidBuildClassifier buildClassifier, Context context) {
+ mChanges = new ArrayList<>();
+ mBuildClassifier = buildClassifier;
+ mContext = context;
+ }
+
+ static CompatConfigBuilder create(AndroidBuildClassifier buildClassifier, Context context) {
+ return new CompatConfigBuilder(buildClassifier, context);
+ }
+
+ CompatConfigBuilder addTargetSdkChangeWithId(int sdk, long id) {
+ mChanges.add(new CompatChange(id, "", sdk, false, ""));
+ return this;
+ }
+
+ CompatConfigBuilder addTargetSdkDisabledChangeWithId(int sdk, long id) {
+ mChanges.add(new CompatChange(id, "", sdk, true, ""));
+ return this;
+ }
+
+ CompatConfigBuilder addTargetSdkChangeWithIdAndName(int sdk, long id, String name) {
+ mChanges.add(new CompatChange(id, name, sdk, false, ""));
+ return this;
+ }
+
+ CompatConfigBuilder addTargetSdkChangeWithIdAndDescription(int sdk, long id,
+ String description) {
+ mChanges.add(new CompatChange(id, "", sdk, false, description));
+ return this;
+ }
+
+ CompatConfigBuilder addEnabledChangeWithId(long id) {
+ mChanges.add(new CompatChange(id, "", -1, false, ""));
+ return this;
+ }
+
+ CompatConfigBuilder addEnabledChangeWithIdAndName(long id, String name) {
+ mChanges.add(new CompatChange(id, name, -1, false, ""));
+ return this;
+ }
+ CompatConfigBuilder addEnabledChangeWithIdAndDescription(long id, String description) {
+ mChanges.add(new CompatChange(id, "", -1, false, description));
+ return this;
+ }
+
+ CompatConfigBuilder addDisabledChangeWithId(long id) {
+ mChanges.add(new CompatChange(id, "", -1, true, ""));
+ return this;
+ }
+
+ CompatConfigBuilder addDisabledChangeWithIdAndName(long id, String name) {
+ mChanges.add(new CompatChange(id, name, -1, true, ""));
+ return this;
+ }
+
+ CompatConfigBuilder addDisabledChangeWithIdAndDescription(long id, String description) {
+ mChanges.add(new CompatChange(id, "", -1, true, description));
+ return this;
+ }
+
+ CompatConfig build() {
+ CompatConfig config = new CompatConfig(mBuildClassifier, mContext);
+ for (CompatChange change : mChanges) {
+ config.addChange(change);
+ }
+ return config;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
index cb99c11..407f67e 100644
--- a/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/CompatConfigTest.java
@@ -18,12 +18,25 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.content.Context;
import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.compat.AndroidBuildClassifier;
+
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
import java.io.File;
import java.io.FileOutputStream;
@@ -34,12 +47,12 @@
@RunWith(AndroidJUnit4.class)
public class CompatConfigTest {
- private ApplicationInfo makeAppInfo(String pName, int targetSdkVersion) {
- ApplicationInfo ai = new ApplicationInfo();
- ai.packageName = pName;
- ai.targetSdkVersion = targetSdkVersion;
- return ai;
- }
+ @Mock
+ private Context mContext;
+ @Mock
+ PackageManager mPackageManager;
+ @Mock
+ private AndroidBuildClassifier mBuildClassifier;
private File createTempDir() {
String base = System.getProperty("java.io.tmpdir");
@@ -54,112 +67,206 @@
os.close();
}
- @Test
- public void testUnknownChangeEnabled() {
- CompatConfig pc = new CompatConfig();
- assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 1))).isTrue();
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ // Assume userdebug/eng non-final build
+ when(mBuildClassifier.isDebuggableBuild()).thenReturn(true);
+ when(mBuildClassifier.isFinalBuild()).thenReturn(false);
}
@Test
- public void testDisabledChangeDisabled() {
- CompatConfig pc = new CompatConfig();
- pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, true, ""));
- assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 1))).isFalse();
+ public void testUnknownChangeEnabled() throws Exception {
+ CompatConfig compatConfig = new CompatConfig(mBuildClassifier, mContext);
+ assertThat(compatConfig.isChangeEnabled(1234L, ApplicationInfoBuilder.create().build()))
+ .isTrue();
}
@Test
- public void testTargetSdkChangeDisabled() {
- CompatConfig pc = new CompatConfig();
- pc.addChange(new CompatChange(1234L, "MY_CHANGE", 2, false, null));
- assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isFalse();
+ public void testDisabledChangeDisabled() throws Exception {
+ CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+ .addDisabledChangeWithId(1234L)
+ .build();
+
+ assertThat(compatConfig.isChangeEnabled(1234L, ApplicationInfoBuilder.create().build()))
+ .isFalse();
}
@Test
- public void testTargetSdkChangeEnabled() {
- CompatConfig pc = new CompatConfig();
- pc.addChange(new CompatChange(1234L, "MY_CHANGE", 2, false, ""));
- assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 3))).isTrue();
+ public void testTargetSdkChangeDisabled() throws Exception {
+ CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+ .addTargetSdkChangeWithId(2, 1234L)
+ .build();
+
+ assertThat(compatConfig.isChangeEnabled(1234L,
+ ApplicationInfoBuilder.create().withTargetSdk(2).build()))
+ .isFalse();
}
@Test
- public void testDisabledOverrideTargetSdkChange() {
- CompatConfig pc = new CompatConfig();
- pc.addChange(new CompatChange(1234L, "MY_CHANGE", 2, true, null));
- assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 3))).isFalse();
+ public void testTargetSdkChangeEnabled() throws Exception {
+ CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+ .addTargetSdkChangeWithId(2, 1234L)
+ .build();
+
+ assertThat(compatConfig.isChangeEnabled(1234L,
+ ApplicationInfoBuilder.create().withTargetSdk(3).build())).isTrue();
}
@Test
- public void testGetDisabledChanges() {
- CompatConfig pc = new CompatConfig();
- pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, true, null));
- pc.addChange(new CompatChange(2345L, "OTHER_CHANGE", -1, false, null));
- assertThat(pc.getDisabledChanges(
- makeAppInfo("com.some.package", 2))).asList().containsExactly(1234L);
+ public void testDisabledOverrideTargetSdkChange() throws Exception {
+ CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+ .addTargetSdkDisabledChangeWithId(2, 1234L)
+ .build();
+
+ assertThat(compatConfig.isChangeEnabled(1234L,
+ ApplicationInfoBuilder.create().withTargetSdk(3).build())).isFalse();
}
@Test
- public void testGetDisabledChangesSorted() {
- CompatConfig pc = new CompatConfig();
- pc.addChange(new CompatChange(1234L, "MY_CHANGE", 2, true, null));
- pc.addChange(new CompatChange(123L, "OTHER_CHANGE", 2, true, null));
- pc.addChange(new CompatChange(12L, "THIRD_CHANGE", 2, true, null));
- assertThat(pc.getDisabledChanges(
- makeAppInfo("com.some.package", 2))).asList().containsExactly(12L, 123L, 1234L);
+ public void testGetDisabledChanges() throws Exception {
+ CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+ .addDisabledChangeWithId(1234L)
+ .addEnabledChangeWithId(2345L)
+ .build();
+
+ assertThat(compatConfig.getDisabledChanges(
+ ApplicationInfoBuilder.create().build())).asList().containsExactly(1234L);
}
@Test
- public void testPackageOverrideEnabled() {
- CompatConfig pc = new CompatConfig();
- pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, true, null)); // disabled
- pc.addOverride(1234L, "com.some.package", true);
- assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isTrue();
- assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.other.package", 2))).isFalse();
+ public void testGetDisabledChangesSorted() throws Exception {
+ CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+ .addDisabledChangeWithId(1234L)
+ .addDisabledChangeWithId(123L)
+ .addDisabledChangeWithId(12L)
+ .build();
+
+ assertThat(compatConfig.getDisabledChanges(ApplicationInfoBuilder.create().build()))
+ .asList().containsExactly(12L, 123L, 1234L);
}
@Test
- public void testPackageOverrideDisabled() {
- CompatConfig pc = new CompatConfig();
- pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, false, null));
- pc.addOverride(1234L, "com.some.package", false);
- assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isFalse();
- assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.other.package", 2))).isTrue();
+ public void testPackageOverrideEnabled() throws Exception {
+ CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+ .addDisabledChangeWithId(1234L)
+ .build();
+
+ compatConfig.addOverride(1234L, "com.some.package", true);
+
+ assertThat(compatConfig.isChangeEnabled(1234L, ApplicationInfoBuilder.create()
+ .withPackageName("com.some.package").build())).isTrue();
+ assertThat(compatConfig.isChangeEnabled(1234L, ApplicationInfoBuilder.create()
+ .withPackageName("com.other.package").build())).isFalse();
}
@Test
- public void testPackageOverrideUnknownPackage() {
- CompatConfig pc = new CompatConfig();
- pc.addOverride(1234L, "com.some.package", false);
- assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isFalse();
- assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.other.package", 2))).isTrue();
+ public void testPackageOverrideDisabled() throws Exception {
+ CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+ .addEnabledChangeWithId(1234L)
+ .build();
+
+ compatConfig.addOverride(1234L, "com.some.package", false);
+
+ assertThat(compatConfig.isChangeEnabled(1234L, ApplicationInfoBuilder.create()
+ .withPackageName("com.some.package").build())).isFalse();
+ assertThat(compatConfig.isChangeEnabled(1234L, ApplicationInfoBuilder.create()
+ .withPackageName("com.other.package").build())).isTrue();
}
@Test
- public void testPackageOverrideUnknownChange() {
- CompatConfig pc = new CompatConfig();
- assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 1))).isTrue();
+ public void testPackageOverrideUnknownPackage() throws Exception {
+ CompatConfig compatConfig = new CompatConfig(mBuildClassifier, mContext);
+
+ compatConfig.addOverride(1234L, "com.some.package", false);
+
+ assertThat(compatConfig.isChangeEnabled(1234L, ApplicationInfoBuilder.create()
+ .withPackageName("com.some.package").build())).isFalse();
+ assertThat(compatConfig.isChangeEnabled(1234L, ApplicationInfoBuilder.create()
+ .withPackageName("com.other.package").build())).isTrue();
}
@Test
- public void testRemovePackageOverride() {
- CompatConfig pc = new CompatConfig();
- pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, false, null));
- pc.addOverride(1234L, "com.some.package", false);
- pc.removeOverride(1234L, "com.some.package");
- assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 2))).isTrue();
+ public void testPreventAddOverride() throws Exception {
+ final long changeId = 1234L;
+ CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+ .addDisabledChangeWithId(1234L)
+ .build();
+ ApplicationInfo applicationInfo = ApplicationInfoBuilder.create()
+ .withPackageName("com.some.package")
+ .build();
+ PackageManager packageManager = mock(PackageManager.class);
+ when(mContext.getPackageManager()).thenReturn(packageManager);
+ when(packageManager.getApplicationInfo(eq("com.some.package"), anyInt()))
+ .thenReturn(applicationInfo);
+
+ // Force the validator to prevent overriding the change by using a user build.
+ when(mBuildClassifier.isDebuggableBuild()).thenReturn(false);
+ when(mBuildClassifier.isFinalBuild()).thenReturn(true);
+
+ assertThrows(SecurityException.class,
+ () -> compatConfig.addOverride(1234L, "com.some.package", true)
+ );
+ assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse();
}
@Test
- public void testLookupChangeId() {
- CompatConfig pc = new CompatConfig();
- pc.addChange(new CompatChange(1234L, "MY_CHANGE", -1, false, null));
- pc.addChange(new CompatChange(2345L, "ANOTHER_CHANGE", -1, false, null));
- assertThat(pc.lookupChangeId("MY_CHANGE")).isEqualTo(1234L);
+ public void testPreventRemoveOverride() throws Exception {
+ CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+ .addDisabledChangeWithId(1234L)
+ .build();
+ ApplicationInfo applicationInfo = ApplicationInfoBuilder.create()
+ .withPackageName("com.some.package")
+ .build();
+ when(mPackageManager.getApplicationInfo(eq("com.some.package"), anyInt()))
+ .thenReturn(applicationInfo);
+ // Assume the override was allowed to be added.
+ compatConfig.addOverride(1234L, "com.some.package", true);
+
+ // Validator allows turning on the change.
+ assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isTrue();
+
+ // Reject all override attempts.
+ // Force the validator to prevent overriding the change by using a user build.
+ when(mBuildClassifier.isDebuggableBuild()).thenReturn(false);
+ when(mBuildClassifier.isFinalBuild()).thenReturn(true);
+ // Try to turn off change, but validator prevents it.
+ assertThrows(SecurityException.class,
+ () -> compatConfig.removeOverride(1234L, "com.some.package"));
+ assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isTrue();
}
@Test
- public void testLookupChangeIdNotPresent() {
- CompatConfig pc = new CompatConfig();
- assertThat(pc.lookupChangeId("MY_CHANGE")).isEqualTo(-1L);
+ public void testRemovePackageOverride() throws Exception {
+ CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+ .addEnabledChangeWithId(1234L)
+ .build();
+ ApplicationInfo applicationInfo = ApplicationInfoBuilder.create()
+ .withPackageName("com.some.package")
+ .build();
+
+ assertThat(compatConfig.addOverride(1234L, "com.some.package", false)).isTrue();
+ assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isFalse();
+
+ compatConfig.removeOverride(1234L, "com.some.package");
+ assertThat(compatConfig.isChangeEnabled(1234L, applicationInfo)).isTrue();
+ }
+
+ @Test
+ public void testLookupChangeId() throws Exception {
+ CompatConfig compatConfig = CompatConfigBuilder.create(mBuildClassifier, mContext)
+ .addEnabledChangeWithIdAndName(1234L, "MY_CHANGE")
+ .addEnabledChangeWithIdAndName(2345L, "MY_OTHER_CHANGE")
+ .build();
+
+ assertThat(compatConfig.lookupChangeId("MY_CHANGE")).isEqualTo(1234L);
+ }
+
+ @Test
+ public void testLookupChangeIdNotPresent() throws Exception {
+ CompatConfig compatConfig = new CompatConfig(mBuildClassifier, mContext);
+ assertThat(compatConfig.lookupChangeId("MY_CHANGE")).isEqualTo(-1L);
}
@Test
@@ -172,14 +279,17 @@
File dir = createTempDir();
writeToFile(dir, "platform_compat_config.xml", configXml);
+ CompatConfig compatConfig = new CompatConfig(mBuildClassifier, mContext);
+ compatConfig.initConfigFromLib(dir);
- CompatConfig pc = new CompatConfig();
- pc.initConfigFromLib(dir);
-
- assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 1))).isFalse();
- assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 3))).isTrue();
- assertThat(pc.isChangeEnabled(1235L, makeAppInfo("com.some.package", 5))).isFalse();
- assertThat(pc.isChangeEnabled(1236L, makeAppInfo("com.some.package", 1))).isTrue();
+ assertThat(compatConfig.isChangeEnabled(1234L,
+ ApplicationInfoBuilder.create().withTargetSdk(1).build())).isFalse();
+ assertThat(compatConfig.isChangeEnabled(1234L,
+ ApplicationInfoBuilder.create().withTargetSdk(3).build())).isTrue();
+ assertThat(compatConfig.isChangeEnabled(1235L,
+ ApplicationInfoBuilder.create().withTargetSdk(5).build())).isFalse();
+ assertThat(compatConfig.isChangeEnabled(1236L,
+ ApplicationInfoBuilder.create().withTargetSdk(1).build())).isTrue();
}
@Test
@@ -195,15 +305,16 @@
File dir = createTempDir();
writeToFile(dir, "libcore_platform_compat_config.xml", configXml1);
writeToFile(dir, "frameworks_platform_compat_config.xml", configXml2);
+ CompatConfig compatConfig = new CompatConfig(mBuildClassifier, mContext);
+ compatConfig.initConfigFromLib(dir);
- CompatConfig pc = new CompatConfig();
- pc.initConfigFromLib(dir);
-
- assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 1))).isFalse();
- assertThat(pc.isChangeEnabled(1234L, makeAppInfo("com.some.package", 3))).isTrue();
- assertThat(pc.isChangeEnabled(1235L, makeAppInfo("com.some.package", 5))).isFalse();
- assertThat(pc.isChangeEnabled(1236L, makeAppInfo("com.some.package", 1))).isTrue();
+ assertThat(compatConfig.isChangeEnabled(1234L,
+ ApplicationInfoBuilder.create().withTargetSdk(1).build())).isFalse();
+ assertThat(compatConfig.isChangeEnabled(1234L,
+ ApplicationInfoBuilder.create().withTargetSdk(3).build())).isTrue();
+ assertThat(compatConfig.isChangeEnabled(1235L,
+ ApplicationInfoBuilder.create().withTargetSdk(5).build())).isFalse();
+ assertThat(compatConfig.isChangeEnabled(1236L,
+ ApplicationInfoBuilder.create().withTargetSdk(1).build())).isTrue();
}
}
-
-
diff --git a/services/tests/servicestests/src/com/android/server/compat/CompatibilityChangeConfigBuilder.java b/services/tests/servicestests/src/com/android/server/compat/CompatibilityChangeConfigBuilder.java
new file mode 100644
index 0000000..793296e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/compat/CompatibilityChangeConfigBuilder.java
@@ -0,0 +1,52 @@
+/*
+ * 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 com.android.server.compat;
+
+import android.compat.Compatibility;
+
+import com.android.internal.compat.CompatibilityChangeConfig;
+
+import java.util.HashSet;
+import java.util.Set;
+
+class CompatibilityChangeConfigBuilder {
+ private Set<Long> mEnabled;
+ private Set<Long> mDisabled;
+
+ private CompatibilityChangeConfigBuilder() {
+ mEnabled = new HashSet<>();
+ mDisabled = new HashSet<>();
+ }
+
+ static CompatibilityChangeConfigBuilder create() {
+ return new CompatibilityChangeConfigBuilder();
+ }
+
+ CompatibilityChangeConfigBuilder enable(Long id) {
+ mEnabled.add(id);
+ return this;
+ }
+
+ CompatibilityChangeConfigBuilder disable(Long id) {
+ mDisabled.add(id);
+ return this;
+ }
+
+ CompatibilityChangeConfig build() {
+ return new CompatibilityChangeConfig(new Compatibility.ChangeConfig(mEnabled, mDisabled));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java b/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java
new file mode 100644
index 0000000..ecd07bd
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/compat/OverrideValidatorImplTest.java
@@ -0,0 +1,383 @@
+/*
+ * 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 com.android.server.compat;
+
+import static com.android.internal.compat.OverrideAllowedState.ALLOWED;
+import static com.android.internal.compat.OverrideAllowedState.DISABLED_NON_TARGET_SDK;
+import static com.android.internal.compat.OverrideAllowedState.DISABLED_NOT_DEBUGGABLE;
+import static com.android.internal.compat.OverrideAllowedState.DISABLED_TARGET_SDK_TOO_HIGH;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.compat.AndroidBuildClassifier;
+import com.android.internal.compat.IOverrideValidator;
+import com.android.internal.compat.OverrideAllowedState;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidJUnit4.class)
+public class OverrideValidatorImplTest {
+ private static final String PACKAGE_NAME = "my.package";
+ private static final int TARGET_SDK = 10;
+ private static final int TARGET_SDK_BEFORE = 9;
+ private static final int TARGET_SDK_AFTER = 11;
+
+ @Mock
+ private PackageManager mPackageManager;
+ @Mock
+ Context mContext;
+
+ private AndroidBuildClassifier debuggableBuild() {
+ AndroidBuildClassifier buildClassifier = mock(AndroidBuildClassifier.class);
+ when(buildClassifier.isDebuggableBuild()).thenReturn(true);
+ return buildClassifier;
+ }
+
+ private AndroidBuildClassifier betaBuild() {
+ AndroidBuildClassifier buildClassifier = mock(AndroidBuildClassifier.class);
+ when(buildClassifier.isDebuggableBuild()).thenReturn(false);
+ when(buildClassifier.isFinalBuild()).thenReturn(false);
+ return buildClassifier;
+ }
+
+ private AndroidBuildClassifier finalBuild() {
+ AndroidBuildClassifier buildClassifier = mock(AndroidBuildClassifier.class);
+ when(buildClassifier.isDebuggableBuild()).thenReturn(false);
+ when(buildClassifier.isFinalBuild()).thenReturn(true);
+ return buildClassifier;
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ }
+
+ @Test
+ public void getOverrideAllowedState_debugBuildAnyChangeDebugApp_allowOverride()
+ throws Exception {
+ CompatConfig config = CompatConfigBuilder.create(debuggableBuild(), mContext)
+ .addTargetSdkChangeWithId(TARGET_SDK_BEFORE, 1)
+ .addTargetSdkChangeWithId(TARGET_SDK, 2)
+ .addTargetSdkChangeWithId(TARGET_SDK_AFTER, 3)
+ .addEnabledChangeWithId(4)
+ .addDisabledChangeWithId(5).build();
+ IOverrideValidator overrideValidator = config.getOverrideValidator();
+ when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(ApplicationInfoBuilder.create()
+ .debuggable()
+ .withTargetSdk(TARGET_SDK)
+ .withPackageName(PACKAGE_NAME).build());
+
+ OverrideAllowedState stateTargetSdkLessChange =
+ overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+ OverrideAllowedState stateTargetSdkEqualChange =
+ overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME);
+ OverrideAllowedState stateTargetSdkAfterChange =
+ overrideValidator.getOverrideAllowedState(3, PACKAGE_NAME);
+ OverrideAllowedState stateEnabledChange =
+ overrideValidator.getOverrideAllowedState(4, PACKAGE_NAME);
+ OverrideAllowedState stateDisabledChange =
+ overrideValidator.getOverrideAllowedState(5, PACKAGE_NAME);
+
+ assertThat(stateTargetSdkLessChange)
+ .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1));
+ assertThat(stateTargetSdkEqualChange)
+ .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1));
+ assertThat(stateTargetSdkAfterChange)
+ .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1));
+ assertThat(stateEnabledChange)
+ .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1));
+ assertThat(stateDisabledChange)
+ .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1));
+ }
+
+ @Test
+ public void getOverrideAllowedState_debugBuildAnyChangeReleaseApp_allowOverride()
+ throws Exception {
+ CompatConfig config = CompatConfigBuilder.create(debuggableBuild(), mContext)
+ .addTargetSdkChangeWithId(TARGET_SDK_BEFORE, 1)
+ .addTargetSdkChangeWithId(TARGET_SDK, 2)
+ .addTargetSdkChangeWithId(TARGET_SDK_AFTER, 3)
+ .addEnabledChangeWithId(4)
+ .addDisabledChangeWithId(5).build();
+ IOverrideValidator overrideValidator = config.getOverrideValidator();
+ when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(ApplicationInfoBuilder.create()
+ .withPackageName(PACKAGE_NAME)
+ .withTargetSdk(TARGET_SDK).build());
+
+ OverrideAllowedState stateTargetSdkLessChange =
+ overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+ OverrideAllowedState stateTargetSdkEqualChange =
+ overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME);
+ OverrideAllowedState stateTargetSdkAfterChange =
+ overrideValidator.getOverrideAllowedState(3, PACKAGE_NAME);
+ OverrideAllowedState stateEnabledChange =
+ overrideValidator.getOverrideAllowedState(4, PACKAGE_NAME);
+ OverrideAllowedState stateDisabledChange =
+ overrideValidator.getOverrideAllowedState(5, PACKAGE_NAME);
+
+ assertThat(stateTargetSdkLessChange)
+ .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1));
+ assertThat(stateTargetSdkEqualChange)
+ .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1));
+ assertThat(stateTargetSdkAfterChange)
+ .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1));
+ assertThat(stateEnabledChange)
+ .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1));
+ assertThat(stateDisabledChange)
+ .isEqualTo(new OverrideAllowedState(ALLOWED, -1, -1));
+ }
+
+ @Test
+ public void getOverrideAllowedState_betaBuildTargetSdkChangeDebugApp_allowOverride()
+ throws Exception {
+ CompatConfig config = CompatConfigBuilder.create(betaBuild(), mContext)
+ .addTargetSdkChangeWithId(TARGET_SDK_BEFORE, 1)
+ .addTargetSdkChangeWithId(TARGET_SDK, 2)
+ .addTargetSdkChangeWithId(TARGET_SDK_AFTER, 3).build();
+ IOverrideValidator overrideValidator = config.getOverrideValidator();
+ when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(ApplicationInfoBuilder.create()
+ .debuggable()
+ .withTargetSdk(TARGET_SDK)
+ .withPackageName(PACKAGE_NAME).build());
+
+ OverrideAllowedState stateTargetSdkLessChange =
+ overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+ OverrideAllowedState stateTargetSdkEqualChange =
+ overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME);
+ OverrideAllowedState stateTargetSdkAfterChange =
+ overrideValidator.getOverrideAllowedState(3, PACKAGE_NAME);
+
+ assertThat(stateTargetSdkLessChange)
+ .isEqualTo(new OverrideAllowedState(ALLOWED, TARGET_SDK, TARGET_SDK_BEFORE));
+ assertThat(stateTargetSdkEqualChange)
+ .isEqualTo(new OverrideAllowedState(ALLOWED, TARGET_SDK, TARGET_SDK));
+ assertThat(stateTargetSdkAfterChange)
+ .isEqualTo(new OverrideAllowedState(ALLOWED, TARGET_SDK, TARGET_SDK_AFTER));
+ }
+
+ @Test
+ public void getOverrideAllowedState_betaBuildEnabledChangeDebugApp_rejectOverride()
+ throws Exception {
+ CompatConfig config = CompatConfigBuilder.create(betaBuild(), mContext)
+ .addEnabledChangeWithId(1).build();
+ IOverrideValidator overrideValidator = config.getOverrideValidator();
+ when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(ApplicationInfoBuilder.create()
+ .withPackageName(PACKAGE_NAME)
+ .debuggable()
+ .build());
+
+ OverrideAllowedState allowedState =
+ overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+
+ assertThat(allowedState)
+ .isEqualTo(new OverrideAllowedState(DISABLED_NON_TARGET_SDK, -1, -1));
+ }
+
+ @Test
+ public void getOverrideAllowedState_betaBuildDisabledChangeDebugApp_rejectOverride()
+ throws Exception {
+ CompatConfig config = CompatConfigBuilder.create(betaBuild(), mContext)
+ .addDisabledChangeWithId(1).build();
+ IOverrideValidator overrideValidator = config.getOverrideValidator();
+ when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(ApplicationInfoBuilder.create()
+ .debuggable()
+ .withPackageName(PACKAGE_NAME).build());
+
+ OverrideAllowedState allowedState =
+ overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+
+ assertThat(allowedState)
+ .isEqualTo(new OverrideAllowedState(DISABLED_NON_TARGET_SDK, -1, -1));
+ }
+
+ @Test
+ public void getOverrideAllowedState_betaBuildAnyChangeReleaseApp_rejectOverride()
+ throws Exception {
+ CompatConfig config = CompatConfigBuilder.create(betaBuild(), mContext)
+ .addTargetSdkChangeWithId(TARGET_SDK_BEFORE, 1)
+ .addTargetSdkChangeWithId(TARGET_SDK, 2)
+ .addTargetSdkChangeWithId(TARGET_SDK_AFTER, 3)
+ .addEnabledChangeWithId(4)
+ .addDisabledChangeWithId(5).build();
+ IOverrideValidator overrideValidator = config.getOverrideValidator();
+ when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(ApplicationInfoBuilder.create()
+ .withPackageName(PACKAGE_NAME)
+ .withTargetSdk(TARGET_SDK).build());
+
+ OverrideAllowedState stateTargetSdkLessChange =
+ overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+ OverrideAllowedState stateTargetSdkEqualChange =
+ overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME);
+ OverrideAllowedState stateTargetSdkAfterChange =
+ overrideValidator.getOverrideAllowedState(3, PACKAGE_NAME);
+ OverrideAllowedState stateEnabledChange =
+ overrideValidator.getOverrideAllowedState(4, PACKAGE_NAME);
+ OverrideAllowedState stateDisabledChange =
+ overrideValidator.getOverrideAllowedState(5, PACKAGE_NAME);
+
+ assertThat(stateTargetSdkLessChange)
+ .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1));
+ assertThat(stateTargetSdkEqualChange)
+ .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1));
+ assertThat(stateTargetSdkAfterChange)
+ .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1));
+ assertThat(stateEnabledChange)
+ .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1));
+ assertThat(stateDisabledChange)
+ .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1));
+ }
+
+ @Test
+ public void getOverrideAllowedState_finalBuildTargetSdkChangeDebugAppOptin_allowOverride()
+ throws Exception {
+ CompatConfig config = CompatConfigBuilder.create(finalBuild(), mContext)
+ .addTargetSdkChangeWithId(TARGET_SDK_AFTER, 1).build();
+ IOverrideValidator overrideValidator = config.getOverrideValidator();
+ when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(ApplicationInfoBuilder.create()
+ .debuggable()
+ .withTargetSdk(TARGET_SDK)
+ .withPackageName(PACKAGE_NAME).build());
+
+ OverrideAllowedState allowedState =
+ overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+
+ assertThat(allowedState)
+ .isEqualTo(new OverrideAllowedState(ALLOWED, TARGET_SDK, TARGET_SDK_AFTER));
+ }
+
+ @Test
+ public void getOverrideAllowedState_finalBuildTargetSdkChangeDebugAppOptout_rejectOverride()
+ throws Exception {
+ CompatConfig config = CompatConfigBuilder.create(finalBuild(), mContext)
+ .addTargetSdkChangeWithId(TARGET_SDK_BEFORE, 1)
+ .addTargetSdkChangeWithId(TARGET_SDK, 2).build();
+ IOverrideValidator overrideValidator = config.getOverrideValidator();
+ when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(ApplicationInfoBuilder.create()
+ .withPackageName(PACKAGE_NAME)
+ .withTargetSdk(TARGET_SDK)
+ .debuggable()
+ .build());
+
+ OverrideAllowedState stateTargetSdkLessChange =
+ overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+ OverrideAllowedState stateTargetSdkEqualChange =
+ overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME);
+
+ assertThat(stateTargetSdkLessChange).isEqualTo(
+ new OverrideAllowedState(DISABLED_TARGET_SDK_TOO_HIGH, TARGET_SDK,
+ TARGET_SDK_BEFORE));
+ assertThat(stateTargetSdkEqualChange).isEqualTo(
+ new OverrideAllowedState(DISABLED_TARGET_SDK_TOO_HIGH, TARGET_SDK, TARGET_SDK));
+ }
+
+ @Test
+ public void getOverrideAllowedState_finalBuildEnabledChangeDebugApp_rejectOverride()
+ throws Exception {
+ CompatConfig config = CompatConfigBuilder.create(finalBuild(), mContext)
+ .addEnabledChangeWithId(1).build();
+ IOverrideValidator overrideValidator = config.getOverrideValidator();
+ when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(ApplicationInfoBuilder.create()
+ .withPackageName(PACKAGE_NAME)
+ .debuggable().build());
+
+ OverrideAllowedState allowedState =
+ overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+
+ assertThat(allowedState)
+ .isEqualTo(new OverrideAllowedState(DISABLED_NON_TARGET_SDK, -1, -1));
+ }
+
+ @Test
+ public void getOverrideAllowedState_finalBuildDisabledChangeDebugApp_rejectOverride()
+ throws Exception {
+ CompatConfig config = CompatConfigBuilder.create(finalBuild(), mContext)
+ .addDisabledChangeWithId(1).build();
+ IOverrideValidator overrideValidator = config.getOverrideValidator();
+ when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(ApplicationInfoBuilder.create()
+ .withPackageName(PACKAGE_NAME)
+ .debuggable().build());
+
+ OverrideAllowedState allowedState =
+ overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+
+ assertThat(allowedState)
+ .isEqualTo(new OverrideAllowedState(DISABLED_NON_TARGET_SDK, -1, -1));
+ }
+
+ @Test
+ public void getOverrideAllowedState_finalBuildAnyChangeReleaseApp_rejectOverride()
+ throws Exception {
+ CompatConfig config = CompatConfigBuilder.create(finalBuild(), mContext)
+ .addTargetSdkChangeWithId(TARGET_SDK_BEFORE, 1)
+ .addTargetSdkChangeWithId(TARGET_SDK, 2)
+ .addTargetSdkChangeWithId(TARGET_SDK_AFTER, 3)
+ .addEnabledChangeWithId(4)
+ .addDisabledChangeWithId(5).build();
+ IOverrideValidator overrideValidator = config.getOverrideValidator();
+ when(mPackageManager.getApplicationInfo(eq(PACKAGE_NAME), anyInt()))
+ .thenReturn(ApplicationInfoBuilder.create()
+ .withPackageName(PACKAGE_NAME)
+ .withTargetSdk(TARGET_SDK).build());
+
+ OverrideAllowedState stateTargetSdkLessChange =
+ overrideValidator.getOverrideAllowedState(1, PACKAGE_NAME);
+ OverrideAllowedState stateTargetSdkEqualChange =
+ overrideValidator.getOverrideAllowedState(2, PACKAGE_NAME);
+ OverrideAllowedState stateTargetSdkAfterChange =
+ overrideValidator.getOverrideAllowedState(3, PACKAGE_NAME);
+ OverrideAllowedState stateEnabledChange =
+ overrideValidator.getOverrideAllowedState(4, PACKAGE_NAME);
+ OverrideAllowedState stateDisabledChange =
+ overrideValidator.getOverrideAllowedState(5, PACKAGE_NAME);
+
+ assertThat(stateTargetSdkLessChange)
+ .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1));
+ assertThat(stateTargetSdkEqualChange)
+ .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1));
+ assertThat(stateTargetSdkAfterChange)
+ .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1));
+ assertThat(stateEnabledChange)
+ .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1));
+ assertThat(stateDisabledChange)
+ .isEqualTo(new OverrideAllowedState(DISABLED_NOT_DEBUGGABLE, -1, -1));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
index c406876..ce5d6d9 100644
--- a/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
+++ b/services/tests/servicestests/src/com/android/server/compat/PlatformCompatTest.java
@@ -26,21 +26,20 @@
import static org.mockito.internal.verification.VerificationModeFactory.times;
import static org.testng.Assert.assertThrows;
-import android.compat.Compatibility;
import android.content.Context;
import android.content.pm.PackageManager;
-import com.android.internal.compat.CompatibilityChangeConfig;
+import androidx.test.runner.AndroidJUnit4;
-import com.google.common.collect.ImmutableSet;
+import com.android.internal.compat.AndroidBuildClassifier;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.MockitoAnnotations;
-@RunWith(MockitoJUnitRunner.class)
+@RunWith(AndroidJUnit4.class)
public class PlatformCompatTest {
private static final String PACKAGE_NAME = "my.package";
@@ -50,84 +49,77 @@
private PackageManager mPackageManager;
@Mock
CompatChange.ChangeListener mListener1, mListener2;
-
+ PlatformCompat mPlatformCompat;
+ CompatConfig mCompatConfig;
+ @Mock
+ private AndroidBuildClassifier mBuildClassifier;
@Before
public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
when(mContext.getPackageManager()).thenReturn(mPackageManager);
when(mPackageManager.getPackageUid(eq(PACKAGE_NAME), eq(0))).thenThrow(
new PackageManager.NameNotFoundException());
- CompatConfig.get().clearChanges();
+ mCompatConfig = new CompatConfig(mBuildClassifier, mContext);
+ mPlatformCompat = new PlatformCompat(mContext, mCompatConfig);
+ // Assume userdebug/eng non-final build
+ when(mBuildClassifier.isDebuggableBuild()).thenReturn(true);
+ when(mBuildClassifier.isFinalBuild()).thenReturn(false);
}
@Test
- public void testRegisterListenerToSameIdThrows() {
- PlatformCompat pc = new PlatformCompat(mContext);
-
+ public void testRegisterListenerToSameIdThrows() throws Exception {
// Registering a listener to change 1 is successful.
- pc.registerListener(1, mListener1);
+ mPlatformCompat.registerListener(1, mListener1);
// Registering a listener to change 2 is successful.
- pc.registerListener(2, mListener1);
+ mPlatformCompat.registerListener(2, mListener1);
// Trying to register another listener to change id 1 fails.
- assertThrows(IllegalStateException.class, () -> pc.registerListener(1, mListener1));
+ assertThrows(IllegalStateException.class,
+ () -> mPlatformCompat.registerListener(1, mListener1));
}
@Test
- public void testRegisterListenerReturn() {
- PlatformCompat pc = new PlatformCompat(mContext);
-
- pc.setOverrides(
- new CompatibilityChangeConfig(
- new Compatibility.ChangeConfig(ImmutableSet.of(1L), ImmutableSet.of())),
+ public void testRegisterListenerReturn() throws Exception {
+ mPlatformCompat.setOverrides(
+ CompatibilityChangeConfigBuilder.create().enable(1L).build(),
PACKAGE_NAME);
// Change id 1 is known (added in setOverrides).
- assertThat(pc.registerListener(1, mListener1)).isTrue();
+ assertThat(mPlatformCompat.registerListener(1, mListener1)).isTrue();
// Change 2 is unknown.
- assertThat(pc.registerListener(2, mListener1)).isFalse();
+ assertThat(mPlatformCompat.registerListener(2, mListener1)).isFalse();
}
@Test
- public void testListenerCalledOnSetOverrides() {
- PlatformCompat pc = new PlatformCompat(mContext);
+ public void testListenerCalledOnSetOverrides() throws Exception {
+ mPlatformCompat.registerListener(1, mListener1);
+ mPlatformCompat.registerListener(2, mListener1);
- pc.registerListener(1, mListener1);
- pc.registerListener(2, mListener1);
-
- pc.setOverrides(
- new CompatibilityChangeConfig(
- new Compatibility.ChangeConfig(ImmutableSet.of(1L), ImmutableSet.of(2L))),
+ mPlatformCompat.setOverrides(
+ CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
PACKAGE_NAME);
verify(mListener1, times(2)).onCompatChange(PACKAGE_NAME);
}
@Test
- public void testListenerNotCalledOnWrongPackage() {
- PlatformCompat pc = new PlatformCompat(mContext);
+ public void testListenerNotCalledOnWrongPackage() throws Exception {
+ mPlatformCompat.registerListener(1, mListener1);
+ mPlatformCompat.registerListener(2, mListener1);
- pc.registerListener(1, mListener1);
- pc.registerListener(2, mListener1);
-
- pc.setOverridesForTest(
- new CompatibilityChangeConfig(
- new Compatibility.ChangeConfig(ImmutableSet.of(1L), ImmutableSet.of(2L))),
+ mPlatformCompat.setOverrides(
+ CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
PACKAGE_NAME);
verify(mListener1, never()).onCompatChange("other.package");
}
@Test
- public void testListenerCalledOnSetOverridesTwoListeners() {
- PlatformCompat pc = new PlatformCompat(mContext);
- pc.registerListener(1, mListener1);
+ public void testListenerCalledOnSetOverridesTwoListeners() throws Exception {
+ mPlatformCompat.registerListener(1, mListener1);
- final ImmutableSet<Long> enabled = ImmutableSet.of(1L);
- final ImmutableSet<Long> disabled = ImmutableSet.of(2L);
-
- pc.setOverrides(
- new CompatibilityChangeConfig(
- new Compatibility.ChangeConfig(enabled, disabled)),
+ mPlatformCompat.setOverrides(
+ CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
PACKAGE_NAME);
verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
@@ -136,11 +128,10 @@
reset(mListener1);
reset(mListener2);
- pc.registerListener(2, mListener2);
+ mPlatformCompat.registerListener(2, mListener2);
- pc.setOverrides(
- new CompatibilityChangeConfig(
- new Compatibility.ChangeConfig(enabled, disabled)),
+ mPlatformCompat.setOverrides(
+ CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
PACKAGE_NAME);
verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
@@ -148,31 +139,23 @@
}
@Test
- public void testListenerCalledOnSetOverridesForTest() {
- PlatformCompat pc = new PlatformCompat(mContext);
+ public void testListenerCalledOnSetOverridesForTest() throws Exception {
+ mPlatformCompat.registerListener(1, mListener1);
+ mPlatformCompat.registerListener(2, mListener1);
- pc.registerListener(1, mListener1);
- pc.registerListener(2, mListener1);
-
- pc.setOverridesForTest(
- new CompatibilityChangeConfig(
- new Compatibility.ChangeConfig(ImmutableSet.of(1L), ImmutableSet.of(2L))),
+ mPlatformCompat.setOverrides(
+ CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
PACKAGE_NAME);
verify(mListener1, times(2)).onCompatChange(PACKAGE_NAME);
}
@Test
- public void testListenerCalledOnSetOverridesTwoListenersForTest() {
- PlatformCompat pc = new PlatformCompat(mContext);
- pc.registerListener(1, mListener1);
+ public void testListenerCalledOnSetOverridesTwoListenersForTest() throws Exception {
+ mPlatformCompat.registerListener(1, mListener1);
- final ImmutableSet<Long> enabled = ImmutableSet.of(1L);
- final ImmutableSet<Long> disabled = ImmutableSet.of(2L);
-
- pc.setOverridesForTest(
- new CompatibilityChangeConfig(
- new Compatibility.ChangeConfig(enabled, disabled)),
+ mPlatformCompat.setOverrides(
+ CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
PACKAGE_NAME);
verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
@@ -181,10 +164,10 @@
reset(mListener1);
reset(mListener2);
- pc.registerListener(2, mListener2);
- pc.setOverridesForTest(
- new CompatibilityChangeConfig(
- new Compatibility.ChangeConfig(enabled, disabled)),
+ mPlatformCompat.registerListener(2, mListener2);
+
+ mPlatformCompat.setOverrides(
+ CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
PACKAGE_NAME);
verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
@@ -192,15 +175,12 @@
}
@Test
- public void testListenerCalledOnClearOverrides() {
- PlatformCompat pc = new PlatformCompat(mContext);
+ public void testListenerCalledOnClearOverrides() throws Exception {
+ mPlatformCompat.registerListener(1, mListener1);
+ mPlatformCompat.registerListener(2, mListener2);
- pc.registerListener(1, mListener1);
- pc.registerListener(2, mListener2);
-
- pc.setOverrides(
- new CompatibilityChangeConfig(
- new Compatibility.ChangeConfig(ImmutableSet.of(1L), ImmutableSet.of())),
+ mPlatformCompat.setOverrides(
+ CompatibilityChangeConfigBuilder.create().enable(1L).build(),
PACKAGE_NAME);
verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
verify(mListener2, never()).onCompatChange(PACKAGE_NAME);
@@ -208,21 +188,18 @@
reset(mListener1);
reset(mListener2);
- pc.clearOverrides(PACKAGE_NAME);
+ mPlatformCompat.clearOverrides(PACKAGE_NAME);
verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
verify(mListener2, never()).onCompatChange(PACKAGE_NAME);
}
@Test
- public void testListenerCalledOnClearOverridesMultipleOverrides() {
- PlatformCompat pc = new PlatformCompat(mContext);
+ public void testListenerCalledOnClearOverridesMultipleOverrides() throws Exception {
+ mPlatformCompat.registerListener(1, mListener1);
+ mPlatformCompat.registerListener(2, mListener2);
- pc.registerListener(1, mListener1);
- pc.registerListener(2, mListener2);
-
- pc.setOverrides(
- new CompatibilityChangeConfig(
- new Compatibility.ChangeConfig(ImmutableSet.of(1L), ImmutableSet.of(2L))),
+ mPlatformCompat.setOverrides(
+ CompatibilityChangeConfigBuilder.create().enable(1L).disable(2L).build(),
PACKAGE_NAME);
verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
verify(mListener2, times(1)).onCompatChange(PACKAGE_NAME);
@@ -230,21 +207,18 @@
reset(mListener1);
reset(mListener2);
- pc.clearOverrides(PACKAGE_NAME);
+ mPlatformCompat.clearOverrides(PACKAGE_NAME);
verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
verify(mListener2, times(1)).onCompatChange(PACKAGE_NAME);
}
@Test
- public void testListenerCalledOnClearOverrideExists() {
- PlatformCompat pc = new PlatformCompat(mContext);
+ public void testListenerCalledOnClearOverrideExists() throws Exception {
+ mPlatformCompat.registerListener(1, mListener1);
+ mPlatformCompat.registerListener(2, mListener2);
- pc.registerListener(1, mListener1);
- pc.registerListener(2, mListener2);
-
- pc.setOverrides(
- new CompatibilityChangeConfig(
- new Compatibility.ChangeConfig(ImmutableSet.of(1L), ImmutableSet.of())),
+ mPlatformCompat.setOverrides(
+ CompatibilityChangeConfigBuilder.create().enable(1L).build(),
PACKAGE_NAME);
verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
verify(mListener2, never()).onCompatChange(PACKAGE_NAME);
@@ -252,21 +226,17 @@
reset(mListener1);
reset(mListener2);
- pc.clearOverride(1, PACKAGE_NAME);
+ mPlatformCompat.clearOverride(1, PACKAGE_NAME);
verify(mListener1, times(1)).onCompatChange(PACKAGE_NAME);
verify(mListener2, never()).onCompatChange(PACKAGE_NAME);
}
@Test
- public void testListenerCalledOnClearOverrideDoesntExist() {
- PlatformCompat pc = new PlatformCompat(mContext);
+ public void testListenerCalledOnClearOverrideDoesntExist() throws Exception {
+ mPlatformCompat.registerListener(1, mListener1);
- pc.registerListener(1, mListener1);
-
- pc.clearOverride(1, PACKAGE_NAME);
+ mPlatformCompat.clearOverride(1, PACKAGE_NAME);
// Listener not called when a non existing override is removed.
verify(mListener1, never()).onCompatChange(PACKAGE_NAME);
}
-
-
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
index 5f1f308..46b8371 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceMigrationTest.java
@@ -15,6 +15,10 @@
*/
package com.android.server.devicepolicy;
+import static android.os.UserHandle.USER_SYSTEM;
+
+import static com.android.server.devicepolicy.DpmTestUtils.writeInputStreamToFile;
+
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
@@ -22,12 +26,14 @@
import static org.mockito.Mockito.when;
import android.app.admin.DevicePolicyManagerInternal;
+import android.content.ComponentName;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
+import com.android.frameworks.servicestests.R;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.devicepolicy.DevicePolicyManagerServiceTestable.OwnersTestable;
@@ -37,9 +43,13 @@
import java.util.Map;
import java.util.Set;
+// TODO (b/143516163): Fix old test cases and put into presubmit.
public class DevicePolicyManagerServiceMigrationTest extends DpmTestBase {
private static final String USER_TYPE_EMPTY = "";
+ private static final int COPE_ADMIN1_APP_ID = 123;
+ private static final int COPE_ANOTHER_ADMIN_APP_ID = 125;
+ private static final int COPE_PROFILE_USER_ID = 11;
private DpmMockContext mContext;
@@ -85,7 +95,7 @@
// Set up UserManager
when(getServices().userManagerInternal.getBaseUserRestrictions(
- eq(UserHandle.USER_SYSTEM))).thenReturn(DpmTestUtils.newRestrictions(
+ eq(USER_SYSTEM))).thenReturn(DpmTestUtils.newRestrictions(
UserManager.DISALLOW_ADD_USER,
UserManager.DISALLOW_RECORD_AUDIO));
@@ -137,7 +147,7 @@
}
assertTrue(dpms.mOwners.hasDeviceOwner());
- assertFalse(dpms.mOwners.hasProfileOwner(UserHandle.USER_SYSTEM));
+ assertFalse(dpms.mOwners.hasProfileOwner(USER_SYSTEM));
assertTrue(dpms.mOwners.hasProfileOwner(10));
assertTrue(dpms.mOwners.hasProfileOwner(11));
assertFalse(dpms.mOwners.hasProfileOwner(12));
@@ -145,7 +155,7 @@
// Now all information should be migrated.
assertFalse(dpms.mOwners.getDeviceOwnerUserRestrictionsNeedsMigration());
assertFalse(dpms.mOwners.getProfileOwnerUserRestrictionsNeedsMigration(
- UserHandle.USER_SYSTEM));
+ USER_SYSTEM));
assertFalse(dpms.mOwners.getProfileOwnerUserRestrictionsNeedsMigration(10));
assertFalse(dpms.mOwners.getProfileOwnerUserRestrictionsNeedsMigration(11));
assertFalse(dpms.mOwners.getProfileOwnerUserRestrictionsNeedsMigration(12));
@@ -155,7 +165,7 @@
DpmTestUtils.newRestrictions(
UserManager.DISALLOW_RECORD_AUDIO
),
- newBaseRestrictions.get(UserHandle.USER_SYSTEM));
+ newBaseRestrictions.get(USER_SYSTEM));
DpmTestUtils.assertRestrictions(
DpmTestUtils.newRestrictions(
@@ -214,7 +224,7 @@
// Set up UserManager
when(getServices().userManagerInternal.getBaseUserRestrictions(
- eq(UserHandle.USER_SYSTEM))).thenReturn(DpmTestUtils.newRestrictions(
+ eq(USER_SYSTEM))).thenReturn(DpmTestUtils.newRestrictions(
UserManager.DISALLOW_ADD_USER,
UserManager.DISALLOW_RECORD_AUDIO,
UserManager.DISALLOW_SMS,
@@ -249,19 +259,19 @@
mContext.binder.restoreCallingIdentity(ident);
}
assertFalse(dpms.mOwners.hasDeviceOwner());
- assertTrue(dpms.mOwners.hasProfileOwner(UserHandle.USER_SYSTEM));
+ assertTrue(dpms.mOwners.hasProfileOwner(USER_SYSTEM));
// Now all information should be migrated.
assertFalse(dpms.mOwners.getDeviceOwnerUserRestrictionsNeedsMigration());
assertFalse(dpms.mOwners.getProfileOwnerUserRestrictionsNeedsMigration(
- UserHandle.USER_SYSTEM));
+ USER_SYSTEM));
// Check the new base restrictions.
DpmTestUtils.assertRestrictions(
DpmTestUtils.newRestrictions(
UserManager.DISALLOW_RECORD_AUDIO
),
- newBaseRestrictions.get(UserHandle.USER_SYSTEM));
+ newBaseRestrictions.get(USER_SYSTEM));
// Check the new owner restrictions.
DpmTestUtils.assertRestrictions(
@@ -270,7 +280,7 @@
UserManager.DISALLOW_SMS,
UserManager.DISALLOW_OUTGOING_CALLS
),
- dpms.getProfileOwnerAdminLocked(UserHandle.USER_SYSTEM).ensureUserRestrictions());
+ dpms.getProfileOwnerAdminLocked(USER_SYSTEM).ensureUserRestrictions());
}
// Test setting default restrictions for managed profile.
@@ -332,4 +342,92 @@
assertEquals(alreadySet.size(), 1);
assertTrue(alreadySet.contains(UserManager.DISALLOW_BLUETOOTH_SHARING));
}
+
+ public void testCompMigrationUnAffiliated_skipped() throws Exception {
+ prepareAdmin1AsDo();
+ prepareAdminAnotherPackageAsPo(COPE_PROFILE_USER_ID);
+
+ final DevicePolicyManagerServiceTestable dpms;
+ dpms = bootDpmsUp();
+
+ // DO should still be DO since no migration should happen.
+ assertTrue(dpms.mOwners.hasDeviceOwner());
+ }
+
+ public void testCompMigrationAffiliated() throws Exception {
+ prepareAdmin1AsDo();
+ prepareAdmin1AsPo(COPE_PROFILE_USER_ID);
+
+ // Secure lock screen is needed for password policy APIs to work.
+ when(getServices().lockPatternUtils.hasSecureLockScreen()).thenReturn(true);
+
+ final DevicePolicyManagerServiceTestable dpms;
+ dpms = bootDpmsUp();
+
+ // DO should cease to be DO.
+ assertFalse(dpms.mOwners.hasDeviceOwner());
+
+ final DpmMockContext poContext = new DpmMockContext(getServices(), mRealTestContext);
+ poContext.binder.callingUid = UserHandle.getUid(COPE_PROFILE_USER_ID, COPE_ADMIN1_APP_ID);
+
+ runAsCaller(poContext, dpms, dpm -> {
+ // Check that DO policy is now set on parent instance.
+ assertEquals(33, dpm.getParentProfileInstance(admin1).getPasswordHistoryLength(admin1));
+ // And NOT set on profile instance.
+ assertEquals(0, dpm.getPasswordHistoryLength(admin1));
+
+ // TODO(b/143516163): verify more policies.
+ });
+ }
+
+ private DevicePolicyManagerServiceTestable bootDpmsUp() {
+ DevicePolicyManagerServiceTestable dpms;
+ final long ident = mContext.binder.clearCallingIdentity();
+ try {
+ LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
+
+ dpms = new DevicePolicyManagerServiceTestable(getServices(), mContext);
+
+ dpms.systemReady(SystemService.PHASE_LOCK_SETTINGS_READY);
+ dpms.systemReady(SystemService.PHASE_ACTIVITY_MANAGER_READY);
+ dpms.systemReady(SystemService.PHASE_BOOT_COMPLETED);
+ } finally {
+ mContext.binder.restoreCallingIdentity(ident);
+ }
+ return dpms;
+ }
+
+ private void prepareAdmin1AsDo() throws Exception {
+ setUpPackageManagerForAdmin(admin1, UserHandle.getUid(USER_SYSTEM, COPE_ADMIN1_APP_ID));
+ final int xmlResource = R.raw.comp_policies_primary;
+ writeInputStreamToFile(getRawStream(xmlResource),
+ (new File(getServices().systemUserDataDir, "device_policies.xml"))
+ .getAbsoluteFile());
+ writeInputStreamToFile(getRawStream(R.raw.comp_device_owner),
+ (new File(getServices().dataDir, "device_owner_2.xml"))
+ .getAbsoluteFile());
+ }
+
+ private void prepareAdmin1AsPo(int profileUserId) throws Exception {
+ preparePo(profileUserId, admin1, R.raw.comp_profile_owner_same_package,
+ R.raw.comp_policies_profile_same_package, COPE_ADMIN1_APP_ID);
+ }
+
+ private void prepareAdminAnotherPackageAsPo(int profileUserId) throws Exception {
+ preparePo(profileUserId, adminAnotherPackage, R.raw.comp_profile_owner_another_package,
+ R.raw.comp_policies_profile_another_package, COPE_ANOTHER_ADMIN_APP_ID);
+ }
+
+ private void preparePo(int profileUserId, ComponentName admin, int profileOwnerXmlResId,
+ int policyXmlResId, int adminAppId) throws Exception {
+ final File profileDir = getServices().addUser(profileUserId, 0,
+ UserManager.USER_TYPE_PROFILE_MANAGED, USER_SYSTEM /* profile group */);
+ setUpPackageManagerForFakeAdmin(
+ admin, UserHandle.getUid(profileUserId, adminAppId), admin1);
+ writeInputStreamToFile(getRawStream(policyXmlResId),
+ (new File(profileDir, "device_policies.xml")).getAbsoluteFile());
+ writeInputStreamToFile(getRawStream(profileOwnerXmlResId),
+ (new File(profileDir, "profile_owner.xml")).getAbsoluteFile());
+ }
+
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index ac555fd..3a8258b 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -51,6 +51,7 @@
import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockSettingsInternal;
+import com.android.server.PersistentDataBlockManagerInternal;
import com.android.server.net.NetworkPolicyManagerInternal;
import java.io.File;
@@ -223,6 +224,11 @@
}
@Override
+ PersistentDataBlockManagerInternal getPersistentDataBlockManagerInternal() {
+ return services.persistentDataBlockManagerInternal;
+ }
+
+ @Override
Looper getMyLooper() {
return Looper.getMainLooper();
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 133d35d..bfadeea 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -31,7 +31,10 @@
import static com.android.internal.widget.LockPatternUtils.EscrowTokenStateChangeCallback;
import static com.android.server.testutils.TestUtils.assertExpectException;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyObject;
@@ -55,13 +58,13 @@
import static org.testng.Assert.assertThrows;
import android.Manifest.permission;
-import android.annotation.RawRes;
import android.app.Activity;
import android.app.AppOpsManager;
import android.app.Notification;
import android.app.admin.DeviceAdminReceiver;
import android.app.admin.DevicePolicyManager;
import android.app.admin.DevicePolicyManagerInternal;
+import android.app.admin.FactoryResetProtectionPolicy;
import android.app.admin.PasswordMetrics;
import android.app.timedetector.ManualTimeSuggestion;
import android.app.timezonedetector.ManualTimeZoneSuggestion;
@@ -104,10 +107,10 @@
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.mockito.Mockito;
+import org.mockito.internal.util.collections.Sets;
import org.mockito.stubbing.Answer;
import java.io.File;
-import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -271,6 +274,29 @@
}).when(getServices().userManager).getApplicationRestrictions(
anyString(), any(UserHandle.class));
+ // Emulate UserManager.setUserRestriction/getUserRestrictions
+ final Map<UserHandle, Bundle> userRestrictions = new HashMap<>();
+
+ doAnswer((Answer<Void>) invocation -> {
+ String key = (String) invocation.getArguments()[0];
+ boolean value = (Boolean) invocation.getArguments()[1];
+ UserHandle user = (UserHandle) invocation.getArguments()[2];
+ Bundle userBundle = userRestrictions.getOrDefault(user, new Bundle());
+ userBundle.putBoolean(key, value);
+
+ userRestrictions.put(user, userBundle);
+ return null;
+ }).when(getServices().userManager).setUserRestriction(
+ anyString(), anyBoolean(), any(UserHandle.class));
+
+ doAnswer((Answer<Boolean>) invocation -> {
+ String key = (String) invocation.getArguments()[0];
+ UserHandle user = (UserHandle) invocation.getArguments()[1];
+ Bundle userBundle = userRestrictions.getOrDefault(user, new Bundle());
+ return userBundle.getBoolean(key);
+ }).when(getServices().userManager).hasUserRestriction(
+ anyString(), any(UserHandle.class));
+
// Add the first secondary user.
getServices().addUser(DpmMockContext.CALLER_USER_HANDLE, 0,
UserManager.USER_TYPE_FULL_SECONDARY);
@@ -818,10 +844,8 @@
final int MANAGED_PROFILE_ADMIN_UID =
UserHandle.getUid(MANAGED_PROFILE_USER_ID, DpmMockContext.SYSTEM_UID);
- // Setup device owner.
mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
mContext.packageName = admin1.getPackageName();
- setupDeviceOwner();
// Add a managed profile belonging to the system user.
addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
@@ -829,18 +853,13 @@
// Change the parent user's password.
dpm.reportPasswordChanged(UserHandle.USER_SYSTEM);
- // Both the device owner and the managed profile owner should receive this broadcast.
+ // The managed profile owner should receive this broadcast.
final Intent intent = new Intent(DeviceAdminReceiver.ACTION_PASSWORD_CHANGED);
intent.setComponent(admin1);
intent.putExtra(Intent.EXTRA_USER, UserHandle.of(UserHandle.USER_SYSTEM));
verify(mContext.spiedContext, times(1)).sendBroadcastAsUser(
MockUtils.checkIntent(intent),
- MockUtils.checkUserHandle(UserHandle.USER_SYSTEM),
- eq(null),
- any(Bundle.class));
- verify(mContext.spiedContext, times(1)).sendBroadcastAsUser(
- MockUtils.checkIntent(intent),
MockUtils.checkUserHandle(MANAGED_PROFILE_USER_ID),
eq(null),
any(Bundle.class));
@@ -860,12 +879,11 @@
final int MANAGED_PROFILE_ADMIN_UID =
UserHandle.getUid(MANAGED_PROFILE_USER_ID, DpmMockContext.SYSTEM_UID);
- // Setup device owner.
+ // Configure system as having separate profile challenge.
mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
mContext.packageName = admin1.getPackageName();
doReturn(true).when(getServices().lockPatternUtils)
.isSeparateProfileChallengeEnabled(MANAGED_PROFILE_USER_ID);
- setupDeviceOwner();
// Add a managed profile belonging to the system user.
addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
@@ -950,6 +968,10 @@
verify(getServices().iactivityManager, times(1)).updateDeviceOwner(
eq(admin1.getPackageName()));
+ verify(getServices().userManager, times(1)).setUserRestriction(
+ eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
+ eq(true), eq(UserHandle.SYSTEM));
+
verify(mContext.spiedContext, times(1)).sendBroadcastAsUser(
MockUtils.checkIntentAction(DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED),
MockUtils.checkUserHandle(UserHandle.USER_SYSTEM));
@@ -1944,6 +1966,29 @@
// TODO Make sure restrictions are written to the file.
}
+ private static final Set<String> PROFILE_OWNER_ORGANIZATION_OWNED_GLOBAL_RESTRICTIONS =
+ Sets.newSet(
+ UserManager.DISALLOW_CONFIG_DATE_TIME,
+ UserManager.DISALLOW_ADD_USER,
+ UserManager.DISALLOW_BLUETOOTH,
+ UserManager.DISALLOW_BLUETOOTH_SHARING,
+ UserManager.DISALLOW_CONFIG_BLUETOOTH,
+ UserManager.DISALLOW_CONFIG_CELL_BROADCASTS,
+ UserManager.DISALLOW_CONFIG_LOCATION,
+ UserManager.DISALLOW_CONFIG_MOBILE_NETWORKS,
+ UserManager.DISALLOW_CONFIG_PRIVATE_DNS,
+ UserManager.DISALLOW_CONFIG_TETHERING,
+ UserManager.DISALLOW_CONFIG_WIFI,
+ UserManager.DISALLOW_CONTENT_CAPTURE,
+ UserManager.DISALLOW_CONTENT_SUGGESTIONS,
+ UserManager.DISALLOW_DATA_ROAMING,
+ UserManager.DISALLOW_DEBUGGING_FEATURES,
+ UserManager.DISALLOW_SAFE_BOOT,
+ UserManager.DISALLOW_SHARE_LOCATION,
+ UserManager.DISALLOW_SMS,
+ UserManager.DISALLOW_USB_FILE_TRANSFER
+ );
+
public void testSetUserRestriction_asPoOfOrgOwnedDevice() throws Exception {
final int MANAGED_PROFILE_USER_ID = DpmMockContext.CALLER_USER_HANDLE;
final int MANAGED_PROFILE_ADMIN_UID =
@@ -1956,15 +2001,9 @@
when(getServices().userManager.getProfileParent(MANAGED_PROFILE_USER_ID))
.thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
- parentDpm.addUserRestriction(admin1, UserManager.DISALLOW_CONFIG_DATE_TIME);
- verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
- eq(MANAGED_PROFILE_USER_ID),
- MockUtils.checkUserRestrictions(UserManager.DISALLOW_CONFIG_DATE_TIME),
- eq(UserManagerInternal.OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE));
- reset(getServices().userManagerInternal);
-
- parentDpm.clearUserRestriction(admin1, UserManager.DISALLOW_CONFIG_DATE_TIME);
- reset(getServices().userManagerInternal);
+ for (String restriction : PROFILE_OWNER_ORGANIZATION_OWNED_GLOBAL_RESTRICTIONS) {
+ addAndRemoveUserRestrictionOnParentDpm(restriction);
+ }
parentDpm.setCameraDisabled(admin1, true);
verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
@@ -1981,6 +2020,20 @@
reset(getServices().userManagerInternal);
}
+ private void addAndRemoveUserRestrictionOnParentDpm(String restriction) {
+ parentDpm.addUserRestriction(admin1, restriction);
+ verify(getServices().userManagerInternal).setDevicePolicyUserRestrictions(
+ eq(DpmMockContext.CALLER_USER_HANDLE),
+ MockUtils.checkUserRestrictions(restriction),
+ eq(UserManagerInternal.OWNER_TYPE_PROFILE_OWNER_OF_ORGANIZATION_OWNED_DEVICE));
+ parentDpm.clearUserRestriction(admin1, restriction);
+ DpmTestUtils.assertRestrictions(
+ DpmTestUtils.newRestrictions(),
+ parentDpm.getUserRestrictions(admin1)
+ );
+ reset(getServices().userManagerInternal);
+ }
+
public void testNoDefaultEnabledUserRestrictions() throws Exception {
mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
mContext.callerPermissions.add(permission.MANAGE_USERS);
@@ -2001,12 +2054,11 @@
assertNoDeviceOwnerRestrictions();
- // Initialize DPMS again and check that the user restriction wasn't enabled again.
reset(getServices().userManagerInternal);
- initializeDpms();
- assertTrue(dpm.isDeviceOwnerApp(admin1.getPackageName()));
- assertNotNull(dpms.getDeviceOwnerAdminLocked());
+ // Ensure the DISALLOW_REMOVE_MANAGED_PROFILES restriction doesn't show up as a
+ // restriction to the device owner.
+ dpm.addUserRestriction(admin1, UserManager.DISALLOW_REMOVE_MANAGED_PROFILE);
assertNoDeviceOwnerRestrictions();
}
@@ -2021,6 +2073,116 @@
);
}
+ public void testSetFactoryResetProtectionPolicyWithDO() throws Exception {
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ setupDeviceOwner();
+
+ when(getServices().persistentDataBlockManagerInternal.getAllowedUid()).thenReturn(
+ DpmMockContext.CALLER_UID);
+
+ FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
+ .setFactoryResetProtectionAccounts(new ArrayList<>())
+ .setFactoryResetProtectionDisabled(true)
+ .build();
+ dpm.setFactoryResetProtectionPolicy(admin1, policy);
+
+ FactoryResetProtectionPolicy result = dpm.getFactoryResetProtectionPolicy(admin1);
+ assertThat(result).isEqualTo(policy);
+ assertPoliciesAreEqual(policy, result);
+
+ verify(mContext.spiedContext).sendBroadcastAsUser(
+ MockUtils.checkIntentAction(
+ DevicePolicyManager.ACTION_RESET_PROTECTION_POLICY_CHANGED),
+ MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE));
+ }
+
+ public void testSetFactoryResetProtectionPolicyFailWithPO() throws Exception {
+ setupProfileOwner();
+
+ FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
+ .setFactoryResetProtectionDisabled(true)
+ .build();
+
+ assertExpectException(SecurityException.class, null,
+ () -> dpm.setFactoryResetProtectionPolicy(admin1, policy));
+ }
+
+ public void testSetFactoryResetProtectionPolicyWithPOOfOrganizationOwnedDevice()
+ throws Exception {
+ setupProfileOwner();
+ configureProfileOwnerOfOrgOwnedDevice(admin1, DpmMockContext.CALLER_USER_HANDLE);
+
+ when(getServices().persistentDataBlockManagerInternal.getAllowedUid()).thenReturn(
+ DpmMockContext.CALLER_UID);
+
+ List<String> accounts = new ArrayList<>();
+ accounts.add("Account 1");
+ accounts.add("Account 2");
+
+ FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
+ .setFactoryResetProtectionAccounts(accounts)
+ .build();
+
+ dpm.setFactoryResetProtectionPolicy(admin1, policy);
+
+ FactoryResetProtectionPolicy result = dpm.getFactoryResetProtectionPolicy(admin1);
+ assertThat(result).isEqualTo(policy);
+ assertPoliciesAreEqual(policy, result);
+
+ verify(mContext.spiedContext, times(2)).sendBroadcastAsUser(
+ MockUtils.checkIntentAction(
+ DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED),
+ MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE));
+ verify(mContext.spiedContext).sendBroadcastAsUser(
+ MockUtils.checkIntentAction(
+ DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED),
+ MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE));
+ verify(mContext.spiedContext).sendBroadcastAsUser(
+ MockUtils.checkIntentAction(
+ DevicePolicyManager.ACTION_RESET_PROTECTION_POLICY_CHANGED),
+ MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE));
+ }
+
+ public void testGetFactoryResetProtectionPolicyWithFrpManagementAgent()
+ throws Exception {
+ mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+ setupDeviceOwner();
+ when(getServices().persistentDataBlockManagerInternal.getAllowedUid()).thenReturn(
+ DpmMockContext.CALLER_UID);
+
+ FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
+ .setFactoryResetProtectionAccounts(new ArrayList<>())
+ .setFactoryResetProtectionDisabled(true)
+ .build();
+
+ dpm.setFactoryResetProtectionPolicy(admin1, policy);
+
+ mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
+ mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+ dpm.setActiveAdmin(admin1, /*replace=*/ false);
+ FactoryResetProtectionPolicy result = dpm.getFactoryResetProtectionPolicy(null);
+ assertThat(result).isEqualTo(policy);
+ assertPoliciesAreEqual(policy, result);
+
+ verify(mContext.spiedContext).sendBroadcastAsUser(
+ MockUtils.checkIntentAction(
+ DevicePolicyManager.ACTION_RESET_PROTECTION_POLICY_CHANGED),
+ MockUtils.checkUserHandle(DpmMockContext.CALLER_USER_HANDLE));
+ }
+
+ private void assertPoliciesAreEqual(FactoryResetProtectionPolicy expectedPolicy,
+ FactoryResetProtectionPolicy actualPolicy) {
+ assertThat(actualPolicy.isFactoryResetProtectionDisabled()).isEqualTo(
+ expectedPolicy.isFactoryResetProtectionDisabled());
+ assertAccountsAreEqual(expectedPolicy.getFactoryResetProtectionAccounts(),
+ actualPolicy.getFactoryResetProtectionAccounts());
+ }
+
+ private void assertAccountsAreEqual(List<String> expectedAccounts,
+ List<String> actualAccounts) {
+ assertThat(actualAccounts).containsExactlyElementsIn(expectedAccounts);
+ }
+
public void testGetMacAddress() throws Exception {
mContext.callerPermissions.add(permission.MANAGE_DEVICE_ADMINS);
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
@@ -2844,6 +3006,7 @@
mContext.packageName = admin1.getPackageName();
setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, false);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, false);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
false);
@@ -2855,6 +3018,8 @@
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED);
+ assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DevicePolicyManager.CODE_DEVICE_ADMIN_NOT_SUPPORTED);
assertCheckProvisioningPreCondition(
@@ -2881,6 +3046,7 @@
mContext.packageName = admin1.getPackageName();
setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, true);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
false);
@@ -2889,6 +3055,7 @@
// Test again when split user is on
when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(true);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, true);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
true);
@@ -2900,6 +3067,8 @@
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
DevicePolicyManager.CODE_OK);
+ assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ DevicePolicyManager.CODE_OK);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DevicePolicyManager.CODE_MANAGED_USERS_NOT_SUPPORTED);
assertCheckProvisioningPreCondition(
@@ -2912,6 +3081,8 @@
when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(true);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
DevicePolicyManager.CODE_OK);
+ assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ DevicePolicyManager.CODE_OK);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DevicePolicyManager.CODE_MANAGED_USERS_NOT_SUPPORTED);
assertCheckProvisioningPreCondition(
@@ -2937,6 +3108,7 @@
mContext.packageName = admin1.getPackageName();
setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, true);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
false /* because of non-split user */);
@@ -2950,6 +3122,8 @@
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
DevicePolicyManager.CODE_OK);
+ assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ DevicePolicyManager.CODE_OK);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DevicePolicyManager.CODE_OK);
assertCheckProvisioningPreCondition(
@@ -2980,7 +3154,6 @@
setup_nonSplitUser_withDo_primaryUser();
final int MANAGED_PROFILE_USER_ID = 18;
final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 1308);
- addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM,
false /* we can't remove a managed profile */)).thenReturn(false);
when(getServices().userManager.canAddMoreManagedProfiles(UserHandle.USER_SYSTEM,
@@ -2994,6 +3167,8 @@
setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
false/* because of completed device setup */);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ false/* because of completed device setup */);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
false/* because of non-split user */);
@@ -3007,6 +3182,8 @@
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
DevicePolicyManager.CODE_USER_SETUP_COMPLETED);
+ assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ DevicePolicyManager.CODE_USER_SETUP_COMPLETED);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DevicePolicyManager.CODE_OK);
assertCheckProvisioningPreCondition(
@@ -3023,43 +3200,21 @@
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
DevicePolicyManager.CODE_HAS_DEVICE_OWNER);
+ assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ DevicePolicyManager.CODE_HAS_DEVICE_OWNER);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, false);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, false);
- // COMP mode is allowed.
+ // COMP mode NOT is allowed.
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
- DevicePolicyManager.CODE_OK);
- assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
+ DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
- // And other DPCs can also provision a managed profile (DO + BYOD case).
+ // And other DPCs can NOT provision a managed profile.
assertCheckProvisioningPreCondition(
DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DpmMockContext.ANOTHER_PACKAGE_NAME,
- DevicePolicyManager.CODE_OK);
- assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true,
- DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID);
- }
-
- public void testProvisioning_nonSplitUser_withDo_primaryUser_restrictedByDo() throws Exception {
- setup_nonSplitUser_withDo_primaryUser();
- mContext.packageName = admin1.getPackageName();
- mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
- // The DO should be allowed to initiate provisioning if it set the restriction itself, but
- // other packages should be forbidden.
- when(getServices().userManager.hasUserRestriction(
- eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
- eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
- .thenReturn(true);
- when(getServices().userManager.getUserRestrictionSource(
- eq(UserManager.DISALLOW_ADD_MANAGED_PROFILE),
- eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
- .thenReturn(UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
- assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
- DevicePolicyManager.CODE_OK);
- assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
- assertCheckProvisioningPreCondition(
- DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
- DpmMockContext.ANOTHER_PACKAGE_NAME,
- DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED);
+ DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false,
DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID);
}
@@ -3080,31 +3235,46 @@
eq(UserHandle.getUserHandleForUid(mContext.binder.callingUid))))
.thenReturn(UserManager.RESTRICTION_SOURCE_SYSTEM);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
- DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED);
+ DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
assertCheckProvisioningPreCondition(
DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DpmMockContext.ANOTHER_PACKAGE_NAME,
- DevicePolicyManager.CODE_ADD_MANAGED_PROFILE_DISALLOWED);
+ DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false,
DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID);
}
- public void testCheckProvisioningPreCondition_nonSplitUser_comp() throws Exception {
+ public void testCheckCannotSetProfileOwnerWithDeviceOwner() throws Exception {
+ setup_nonSplitUser_withDo_primaryUser();
+ final int managedProfileUserId = 18;
+ final int managedProfileAdminUid = UserHandle.getUid(managedProfileUserId, 1308);
+
+ final int userId = UserHandle.getUserId(managedProfileAdminUid);
+ getServices().addUser(userId, 0, UserManager.USER_TYPE_PROFILE_MANAGED,
+ UserHandle.USER_SYSTEM);
+ mContext.callerPermissions.addAll(OWNER_SETUP_PERMISSIONS);
+ setUpPackageManagerForFakeAdmin(admin1, managedProfileAdminUid, admin1);
+ dpm.setActiveAdmin(admin1, false, userId);
+ assertFalse(dpm.setProfileOwner(admin1, null, userId));
+ mContext.callerPermissions.removeAll(OWNER_SETUP_PERMISSIONS);
+ }
+
+ public void testCheckProvisioningPreCondition_nonSplitUser_attemptingComp() throws Exception {
setup_nonSplitUser_withDo_primaryUser_ManagedProfile();
mContext.packageName = admin1.getPackageName();
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
// We can delete the managed profile to create a new one, so provisioning is allowed.
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
- DevicePolicyManager.CODE_OK);
- assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
+ DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
assertCheckProvisioningPreCondition(
DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DpmMockContext.ANOTHER_PACKAGE_NAME,
- DevicePolicyManager.CODE_OK);
- assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true,
+ DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false,
DpmMockContext.ANOTHER_PACKAGE_NAME, DpmMockContext.ANOTHER_UID);
}
@@ -3132,8 +3302,8 @@
// But the device owner can still do it because it has set the restriction itself.
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
- DevicePolicyManager.CODE_OK);
- assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
+ DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
}
private void setup_splitUser_firstBoot_systemUser() throws Exception {
@@ -3152,6 +3322,7 @@
mContext.packageName = admin1.getPackageName();
setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, true);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
false /* because canAddMoreManagedProfiles returns false */);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
@@ -3166,6 +3337,8 @@
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
DevicePolicyManager.CODE_OK);
+ assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ DevicePolicyManager.CODE_OK);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DevicePolicyManager.CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER);
assertCheckProvisioningPreCondition(
@@ -3192,6 +3365,8 @@
setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
true/* it's undefined behavior. Can be changed into false in the future */);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ true/* it's undefined behavior. Can be changed into false in the future */);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
false /* because canAddMoreManagedProfiles returns false */);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
@@ -3206,6 +3381,8 @@
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
DevicePolicyManager.CODE_OK);
+ assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ DevicePolicyManager.CODE_OK);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DevicePolicyManager.CODE_SPLIT_SYSTEM_USER_DEVICE_SYSTEM_USER);
assertCheckProvisioningPreCondition(
@@ -3231,6 +3408,7 @@
mContext.packageName = admin1.getPackageName();
setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE, true);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE, true);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
true);
@@ -3243,6 +3421,8 @@
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
DevicePolicyManager.CODE_OK);
+ assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ DevicePolicyManager.CODE_OK);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DevicePolicyManager.CODE_OK);
assertCheckProvisioningPreCondition(
@@ -3270,6 +3450,8 @@
setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
true/* it's undefined behavior. Can be changed into false in the future */);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ true/* it's undefined behavior. Can be changed into false in the future */);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_SHAREABLE_DEVICE,
true/* it's undefined behavior. Can be changed into false in the future */);
@@ -3283,6 +3465,8 @@
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE,
DevicePolicyManager.CODE_OK);
+ assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_FINANCED_DEVICE,
+ DevicePolicyManager.CODE_OK);
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
DevicePolicyManager.CODE_OK);
assertCheckProvisioningPreCondition(
@@ -3328,6 +3512,8 @@
when(getServices().ipackageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS, 0))
.thenReturn(true);
when(getServices().userManagerForMock.isSplitSystemUser()).thenReturn(true);
+ when(getServices().userManager.getProfileParent(DpmMockContext.CALLER_USER_HANDLE))
+ .thenReturn(new UserInfo(UserHandle.USER_SYSTEM, "user system", 0));
when(getServices().userManager.canAddMoreManagedProfiles(DpmMockContext.CALLER_USER_HANDLE,
true)).thenReturn(true);
setUserSetupCompleteForUser(false, DpmMockContext.CALLER_USER_HANDLE);
@@ -3340,7 +3526,7 @@
setup_provisionManagedProfileWithDeviceOwner_primaryUser();
setUpPackageManagerForAdmin(admin1, mContext.binder.callingUid);
mContext.packageName = admin1.getPackageName();
- assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, true);
+ assertProvisioningAllowed(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE, false);
}
public void testCheckProvisioningPreCondition_provisionManagedProfileWithDeviceOwner_primaryUser()
@@ -3348,9 +3534,9 @@
setup_provisionManagedProfileWithDeviceOwner_primaryUser();
mContext.callerPermissions.add(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS);
- // COMP mode is allowed.
+ // COMP mode is NOT allowed.
assertCheckProvisioningPreCondition(DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE,
- DevicePolicyManager.CODE_OK);
+ DevicePolicyManager.CODE_CANNOT_ADD_MANAGED_PROFILE);
}
private void setup_provisionManagedProfileCantRemoveUser_primaryUser() throws Exception {
@@ -3635,6 +3821,25 @@
verify(getServices().settings).settingsGlobalPutInt(Settings.Global.AUTO_TIME_ZONE, 0);
}
+ public void testIsOrganizationOwnedDevice() throws Exception {
+ setupProfileOwner();
+ // Set up the user manager to return correct user info
+ UserInfo managedProfileUserInfo = new UserInfo(DpmMockContext.CALLER_USER_HANDLE,
+ "managed profile",
+ UserInfo.FLAG_MANAGED_PROFILE);
+ when(getServices().userManager.getUsers())
+ .thenReturn(Arrays.asList(managedProfileUserInfo));
+
+ // Any caller should be able to call this method.
+ assertFalse(dpm.isOrganizationOwnedDeviceWithManagedProfile());
+ configureProfileOwnerOfOrgOwnedDevice(admin1, DpmMockContext.CALLER_USER_HANDLE);
+ assertTrue(dpm.isOrganizationOwnedDeviceWithManagedProfile());
+
+ // A random caller from another user should also be able to get the right result.
+ mContext.binder.callingUid = DpmMockContext.ANOTHER_UID;
+ assertTrue(dpm.isOrganizationOwnedDeviceWithManagedProfile());
+ }
+
public void testSetTime() throws Exception {
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
setupDeviceOwner();
@@ -3844,11 +4049,6 @@
List<UserHandle> targetUsers = dpm.getBindDeviceAdminTargetUsers(admin1);
MoreAsserts.assertEmpty(targetUsers);
- // Setup a managed profile managed by the same admin.
- final int MANAGED_PROFILE_USER_ID = 15;
- final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 20456);
- addManagedProfile(admin1, MANAGED_PROFILE_ADMIN_UID, admin1);
-
// Add a secondary user, it should never talk with.
final int ANOTHER_USER_ID = 36;
getServices().addUser(ANOTHER_USER_ID, 0, UserManager.USER_TYPE_FULL_SECONDARY);
@@ -3858,30 +4058,11 @@
targetUsers = dpm.getBindDeviceAdminTargetUsers(admin1);
MoreAsserts.assertEmpty(targetUsers);
- mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
- targetUsers = dpm.getBindDeviceAdminTargetUsers(admin1);
- MoreAsserts.assertEmpty(targetUsers);
-
// Setting affiliation ids
final Set<String> userAffiliationIds = Collections.singleton("some.affiliation-id");
mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
dpm.setAffiliationIds(admin1, userAffiliationIds);
- mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
- dpm.setAffiliationIds(admin1, userAffiliationIds);
-
- // Calling from device owner admin, the result list should just contain the managed
- // profile user id.
- mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
- targetUsers = dpm.getBindDeviceAdminTargetUsers(admin1);
- MoreAsserts.assertContentsInAnyOrder(targetUsers, UserHandle.of(MANAGED_PROFILE_USER_ID));
-
- // Calling from managed profile admin, the result list should just contain the system
- // user id.
- mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
- targetUsers = dpm.getBindDeviceAdminTargetUsers(admin1);
- MoreAsserts.assertContentsInAnyOrder(targetUsers, UserHandle.SYSTEM);
-
// Changing affiliation ids in one
dpm.setAffiliationIds(admin1, Collections.singleton("some-different-affiliation-id"));
@@ -3895,38 +4076,6 @@
MoreAsserts.assertEmpty(targetUsers);
}
- public void testGetBindDeviceAdminTargetUsers_differentPackage() throws Exception {
- // Setup a device owner.
- mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
- setupDeviceOwner();
-
- // Set up a managed profile managed by different package.
- final int MANAGED_PROFILE_USER_ID = 15;
- final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 20456);
- final ComponentName adminDifferentPackage =
- new ComponentName("another.package", "whatever.class");
- addManagedProfile(adminDifferentPackage, MANAGED_PROFILE_ADMIN_UID, admin2);
-
- // Setting affiliation ids
- final Set<String> userAffiliationIds = Collections.singleton("some-affiliation-id");
- dpm.setAffiliationIds(admin1, userAffiliationIds);
-
- mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
- dpm.setAffiliationIds(adminDifferentPackage, userAffiliationIds);
-
- // Calling from device owner admin, we should get zero bind device admin target users as
- // their packages are different.
- mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
- List<UserHandle> targetUsers = dpm.getBindDeviceAdminTargetUsers(admin1);
- MoreAsserts.assertEmpty(targetUsers);
-
- // Calling from managed profile admin, we should still get zero target users for the same
- // reason.
- mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
- targetUsers = dpm.getBindDeviceAdminTargetUsers(adminDifferentPackage);
- MoreAsserts.assertEmpty(targetUsers);
- }
-
private void verifyLockTaskState(int userId) throws Exception {
verifyLockTaskState(userId, new String[0],
DevicePolicyManager.LOCK_TASK_FEATURE_GLOBAL_ACTIONS);
@@ -3963,79 +4112,6 @@
() -> dpm.setLockTaskFeatures(who, flags));
}
- public void testLockTaskPolicyAllowedForAffiliatedUsers() throws Exception {
- // Setup a device owner.
- mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
- setupDeviceOwner();
- // Lock task policy is updated when loading user data.
- verifyLockTaskState(UserHandle.USER_SYSTEM);
-
- // Set up a managed profile managed by different package (package name shouldn't matter)
- final int MANAGED_PROFILE_USER_ID = 15;
- final int MANAGED_PROFILE_ADMIN_UID = UserHandle.getUid(MANAGED_PROFILE_USER_ID, 20456);
- final ComponentName adminDifferentPackage =
- new ComponentName("another.package", "whatever.class");
- addManagedProfile(adminDifferentPackage, MANAGED_PROFILE_ADMIN_UID, admin2);
- verifyLockTaskState(MANAGED_PROFILE_USER_ID);
-
- // Setup a PO on the secondary user
- mContext.binder.callingUid = DpmMockContext.CALLER_UID;
- setAsProfileOwner(admin3);
- verifyLockTaskState(DpmMockContext.CALLER_USER_HANDLE);
-
- // The DO can still set lock task packages
- final String[] doPackages = {"doPackage1", "doPackage2"};
- final int flags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
- | DevicePolicyManager.LOCK_TASK_FEATURE_HOME
- | DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
- verifyCanSetLockTask(DpmMockContext.CALLER_SYSTEM_USER_UID, UserHandle.USER_SYSTEM, admin1, doPackages, flags);
-
- final String[] secondaryPoPackages = {"secondaryPoPackage1", "secondaryPoPackage2"};
- final int secondaryPoFlags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
- | DevicePolicyManager.LOCK_TASK_FEATURE_HOME
- | DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
- verifyCanNotSetLockTask(DpmMockContext.CALLER_UID, admin3, secondaryPoPackages, secondaryPoFlags);
-
- // Managed profile is unaffiliated - shouldn't be able to setLockTaskPackages.
- mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
- final String[] poPackages = {"poPackage1", "poPackage2"};
- final int poFlags = DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS
- | DevicePolicyManager.LOCK_TASK_FEATURE_HOME
- | DevicePolicyManager.LOCK_TASK_FEATURE_OVERVIEW;
- verifyCanNotSetLockTask(MANAGED_PROFILE_ADMIN_UID, adminDifferentPackage, poPackages, poFlags);
-
- // Setting same affiliation ids
- final Set<String> userAffiliationIds = Collections.singleton("some-affiliation-id");
- mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
- dpm.setAffiliationIds(admin1, userAffiliationIds);
-
- mContext.binder.callingUid = MANAGED_PROFILE_ADMIN_UID;
- dpm.setAffiliationIds(adminDifferentPackage, userAffiliationIds);
-
- // Now the managed profile can set lock task packages.
- dpm.setLockTaskPackages(adminDifferentPackage, poPackages);
- MoreAsserts.assertEquals(poPackages, dpm.getLockTaskPackages(adminDifferentPackage));
- assertTrue(dpm.isLockTaskPermitted("poPackage1"));
- assertFalse(dpm.isLockTaskPermitted("doPackage2"));
- // And it can set lock task features.
- dpm.setLockTaskFeatures(adminDifferentPackage, poFlags);
- verifyLockTaskState(MANAGED_PROFILE_USER_ID, poPackages, poFlags);
-
- // Unaffiliate the profile, lock task mode no longer available on the profile.
- dpm.setAffiliationIds(adminDifferentPackage, Collections.emptySet());
- assertFalse(dpm.isLockTaskPermitted("poPackage1"));
- // Lock task packages cleared when loading user data and when the user becomes unaffiliated.
- verify(getServices().iactivityManager, times(2)).updateLockTaskPackages(
- MANAGED_PROFILE_USER_ID, new String[0]);
- verify(getServices().iactivityTaskManager, times(2)).updateLockTaskFeatures(
- MANAGED_PROFILE_USER_ID, DevicePolicyManager.LOCK_TASK_FEATURE_NONE);
-
- // Verify that lock task packages were not cleared for the DO
- mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
- assertTrue(dpm.isLockTaskPermitted("doPackage1"));
-
- }
-
public void testLockTaskPolicyForProfileOwner() throws Exception {
// Setup a PO
mContext.binder.callingUid = DpmMockContext.CALLER_UID;
@@ -5524,6 +5600,36 @@
assertTrue(dpm.isPackageAllowedToAccessCalendar(testPackage));
}
+ public void testSetProtectedPackages_asDO() throws Exception {
+ final List<String> testPackages = new ArrayList<>();
+ testPackages.add("package_1");
+ testPackages.add("package_2");
+
+ mContext.callerPermissions.add(android.Manifest.permission.MANAGE_DEVICE_ADMINS);
+ setDeviceOwner();
+
+ dpm.setProtectedPackages(admin1, testPackages);
+
+ verify(getServices().packageManagerInternal).setDeviceOwnerProtectedPackages(testPackages);
+
+ assertEquals(testPackages, dpm.getProtectedPackages(admin1));
+ }
+
+ public void testSetProtectedPackages_failingAsPO() throws Exception {
+ final List<String> testPackages = new ArrayList<>();
+ testPackages.add("package_1");
+ testPackages.add("package_2");
+
+ mContext.callerPermissions.add(android.Manifest.permission.MANAGE_DEVICE_ADMINS);
+ setAsProfileOwner(admin1);
+
+ assertExpectException(SecurityException.class, /* messageRegex= */ null,
+ () -> dpm.setProtectedPackages(admin1, testPackages));
+
+ assertExpectException(SecurityException.class, /* messageRegex= */ null,
+ () -> dpm.getProtectedPackages(admin1));
+ }
+
private void configureProfileOwnerOfOrgOwnedDevice(ComponentName who, int userId) {
when(getServices().userManager.getProfileParent(eq(UserHandle.of(userId))))
.thenReturn(UserHandle.SYSTEM);
@@ -5570,6 +5676,57 @@
assertEquals(packages, dpm.getCrossProfilePackages(admin1));
}
+ public void testGetAllCrossProfilePackages_notSet_returnsEmpty() throws Exception {
+ addManagedProfile(admin1, mServiceContext.binder.callingUid, admin1);
+
+ setCrossProfileAppsList();
+
+ assertTrue(dpm.getAllCrossProfilePackages().isEmpty());
+ }
+
+ public void testGetAllCrossProfilePackages_notSet_dpmsReinitialized_returnsEmpty()
+ throws Exception {
+ addManagedProfile(admin1, mServiceContext.binder.callingUid, admin1);
+
+ setCrossProfileAppsList();
+ initializeDpms();
+
+ assertTrue(dpm.getAllCrossProfilePackages().isEmpty());
+ }
+
+ public void testGetAllCrossProfilePackages_whenSet_returnsCombinedSet() throws Exception {
+ addManagedProfile(admin1, mServiceContext.binder.callingUid, admin1);
+ final Set<String> packages = Sets.newSet("TEST_PACKAGE", "TEST_COMMON_PACKAGE");
+
+ dpm.setCrossProfilePackages(admin1, packages);
+ setCrossProfileAppsList("TEST_DEFAULT_PACKAGE", "TEST_COMMON_PACKAGE");
+
+ assertEquals(Sets.newSet(
+ "TEST_PACKAGE", "TEST_DEFAULT_PACKAGE", "TEST_COMMON_PACKAGE"),
+ dpm.getAllCrossProfilePackages());
+
+ }
+
+ public void testGetAllCrossProfilePackages_whenSet_dpmsReinitialized_returnsCombinedSet()
+ throws Exception {
+ addManagedProfile(admin1, mServiceContext.binder.callingUid, admin1);
+ final Set<String> packages = Sets.newSet("TEST_PACKAGE", "TEST_COMMON_PACKAGE");
+
+ dpm.setCrossProfilePackages(admin1, packages);
+ setCrossProfileAppsList("TEST_DEFAULT_PACKAGE", "TEST_COMMON_PACKAGE");
+ initializeDpms();
+
+ assertEquals(Sets.newSet(
+ "TEST_PACKAGE", "TEST_DEFAULT_PACKAGE", "TEST_COMMON_PACKAGE"),
+ dpm.getAllCrossProfilePackages());
+ }
+
+ private void setCrossProfileAppsList(String... packages) {
+ when(mContext.getResources()
+ .getStringArray(eq(R.array.cross_profile_apps)))
+ .thenReturn(packages);
+ }
+
// admin1 is the outgoing DPC, adminAnotherPakcage is the incoming one.
private void assertDeviceOwnershipRevertedWithFakeTransferMetadata() throws Exception {
writeFakeTransferMetadataFile(UserHandle.USER_SYSTEM,
@@ -5651,10 +5808,6 @@
return new File(parentDir, "device_policies.xml");
}
- private InputStream getRawStream(@RawRes int id) {
- return mRealTestContext.getResources().openRawResource(id);
- }
-
private void setUserSetupCompleteForUser(boolean isUserSetupComplete, int userhandle) {
when(getServices().settings.settingsSecureGetIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0,
userhandle)).thenReturn(isUserSetupComplete ? 1 : 0);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
index a34c2ff..9a1a5fb 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DpmTestBase.java
@@ -24,6 +24,7 @@
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
+import android.annotation.RawRes;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
@@ -36,6 +37,7 @@
import android.os.UserHandle;
import android.test.AndroidTestCase;
+import java.io.InputStream;
import java.util.List;
public abstract class DpmTestBase extends AndroidTestCase {
@@ -256,4 +258,8 @@
invocation -> invocation.getArguments()[1]
);
}
+
+ protected InputStream getRawStream(@RawRes int id) {
+ return mRealTestContext.getResources().openRawResource(id);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java
new file mode 100644
index 0000000..bc853c6
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/FactoryResetProtectionPolicyTest.java
@@ -0,0 +1,150 @@
+/*
+ * 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 com.android.server.devicepolicy;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.app.admin.FactoryResetProtectionPolicy;
+import android.os.Parcel;
+import android.util.Xml;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.FastXmlSerializer;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit tests for {@link android.app.admin.FactoryResetProtectionPolicy}.
+ *
+ * atest com.android.server.devicepolicy.FactoryResetProtectionPolicyTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class FactoryResetProtectionPolicyTest {
+
+ private static final String TAG_FACTORY_RESET_PROTECTION_POLICY =
+ "factory_reset_protection_policy";
+
+ @Test
+ public void testNonDefaultFactoryResetProtectionPolicyObject() throws Exception {
+ List<String> accounts = new ArrayList<>();
+ accounts.add("Account 1");
+ accounts.add("Account 2");
+
+ FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
+ .setFactoryResetProtectionAccounts(accounts)
+ .setFactoryResetProtectionDisabled(true)
+ .build();
+
+ testParcelAndUnparcel(policy);
+ testSerializationAndDeserialization(policy);
+ }
+
+ @Test
+ public void testInvalidXmlFactoryResetProtectionPolicyObject() throws Exception {
+ List<String> accounts = new ArrayList<>();
+ accounts.add("Account 1");
+ accounts.add("Account 2");
+
+ FactoryResetProtectionPolicy policy = new FactoryResetProtectionPolicy.Builder()
+ .setFactoryResetProtectionAccounts(accounts)
+ .setFactoryResetProtectionDisabled(true)
+ .build();
+
+ testParcelAndUnparcel(policy);
+ testInvalidXmlSerializationAndDeserialization(policy);
+ }
+
+ private void testParcelAndUnparcel(FactoryResetProtectionPolicy policy) {
+ Parcel parcel = Parcel.obtain();
+ policy.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ FactoryResetProtectionPolicy actualPolicy =
+ FactoryResetProtectionPolicy.CREATOR.createFromParcel(parcel);
+ assertPoliciesAreEqual(policy, actualPolicy);
+ parcel.recycle();
+ }
+
+ private void testSerializationAndDeserialization(FactoryResetProtectionPolicy policy)
+ throws Exception {
+ ByteArrayOutputStream outStream = serialize(policy);
+ ByteArrayInputStream inStream = new ByteArrayInputStream(outStream.toByteArray());
+ XmlPullParser parser = Xml.newPullParser();
+ parser.setInput(new InputStreamReader(inStream));
+ assertEquals(XmlPullParser.START_TAG, parser.next());
+
+ assertPoliciesAreEqual(policy, policy.readFromXml(parser));
+ }
+
+ private void testInvalidXmlSerializationAndDeserialization(FactoryResetProtectionPolicy policy)
+ throws Exception {
+ ByteArrayOutputStream outStream = serialize(policy);
+ ByteArrayInputStream inStream = new ByteArrayInputStream(outStream.toByteArray());
+ XmlPullParser parser = mock(XmlPullParser.class);
+ when(parser.next()).thenThrow(XmlPullParserException.class);
+ parser.setInput(new InputStreamReader(inStream));
+
+ // If deserialization fails, then null is returned.
+ assertNull(policy.readFromXml(parser));
+ }
+
+ private ByteArrayOutputStream serialize(FactoryResetProtectionPolicy policy)
+ throws IOException {
+ ByteArrayOutputStream outStream = new ByteArrayOutputStream();
+ final XmlSerializer outXml = new FastXmlSerializer();
+ outXml.setOutput(outStream, StandardCharsets.UTF_8.name());
+ outXml.startDocument(null, true);
+ outXml.startTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY);
+ policy.writeToXml(outXml);
+ outXml.endTag(null, TAG_FACTORY_RESET_PROTECTION_POLICY);
+ outXml.endDocument();
+ outXml.flush();
+ return outStream;
+ }
+
+ private void assertPoliciesAreEqual(FactoryResetProtectionPolicy expectedPolicy,
+ FactoryResetProtectionPolicy actualPolicy) {
+ assertEquals(expectedPolicy.isFactoryResetProtectionDisabled(),
+ actualPolicy.isFactoryResetProtectionDisabled());
+ assertAccountsAreEqual(expectedPolicy.getFactoryResetProtectionAccounts(),
+ actualPolicy.getFactoryResetProtectionAccounts());
+ }
+
+ private void assertAccountsAreEqual(List<String> expectedAccounts,
+ List<String> actualAccounts) {
+ assertEquals(expectedAccounts.size(), actualAccounts.size());
+ for (int i = 0; i < expectedAccounts.size(); i++) {
+ assertEquals(expectedAccounts.get(i), actualAccounts.get(i));
+ }
+ }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 919a3f6..068daf5 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -15,6 +15,7 @@
*/
package com.android.server.devicepolicy;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
@@ -68,6 +69,7 @@
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockSettingsInternal;
+import com.android.server.PersistentDataBlockManagerInternal;
import com.android.server.net.NetworkPolicyManagerInternal;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -117,6 +119,7 @@
public final TimeDetector timeDetector;
public final TimeZoneDetector timeZoneDetector;
public final KeyChain.KeyChainConnection keyChainConnection;
+ public final PersistentDataBlockManagerInternal persistentDataBlockManagerInternal;
/** Note this is a partial mock, not a real mock. */
public final PackageManager packageManager;
public final BuildMock buildMock = new BuildMock();
@@ -160,6 +163,7 @@
timeDetector = mock(TimeDetector.class);
timeZoneDetector = mock(TimeZoneDetector.class);
keyChainConnection = mock(KeyChain.KeyChainConnection.class, RETURNS_DEEP_STUBS);
+ persistentDataBlockManagerInternal = mock(PersistentDataBlockManagerInternal.class);
// Package manager is huge, so we use a partial mock instead.
packageManager = spy(realContext.getPackageManager());
@@ -233,6 +237,13 @@
return ui == null ? null : getUserInfo(ui.profileGroupId);
}
);
+ when(userManager.getProfileParent(any(UserHandle.class))).thenAnswer(
+ invocation -> {
+ final UserHandle userHandle = (UserHandle) invocation.getArguments()[0];
+ final UserInfo ui = getUserInfo(userHandle.getIdentifier());
+ return ui == null ? UserHandle.USER_NULL : UserHandle.of(ui.profileGroupId);
+ }
+ );
when(userManager.getProfiles(anyInt())).thenAnswer(
invocation -> {
final int userId12 = (int) invocation.getArguments()[0];
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index ebca240..25d0778 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -78,7 +78,7 @@
int displayId = 0;
// With no votes present, DisplayModeDirector should allow any refresh rate.
- assertEquals(new DisplayModeDirector.DesiredDisplayModeSpecs(/*defaultModeId=*/60,
+ assertEquals(new DisplayModeDirector.DesiredDisplayModeSpecs(/*baseModeId=*/60,
new DisplayModeDirector.RefreshRateRange(0f, Float.POSITIVE_INFINITY)),
createDisplayModeDirectorWithDisplayFpsRange(60, 90).getDesiredDisplayModeSpecs(
displayId));
@@ -105,7 +105,7 @@
director.injectVotesByDisplay(votesByDisplay);
assertEquals(
new DisplayModeDirector.DesiredDisplayModeSpecs(
- /*defaultModeId=*/minFps + i,
+ /*baseModeId=*/minFps + i,
new DisplayModeDirector.RefreshRateRange(minFps + i, maxFps - i)),
director.getDesiredDisplayModeSpecs(displayId));
}
@@ -126,7 +126,7 @@
votes.put(DisplayModeDirector.Vote.MIN_PRIORITY,
DisplayModeDirector.Vote.forRefreshRates(70, 80));
director.injectVotesByDisplay(votesByDisplay);
- assertEquals(new DisplayModeDirector.DesiredDisplayModeSpecs(/*defaultModeId=*/70,
+ assertEquals(new DisplayModeDirector.DesiredDisplayModeSpecs(/*baseModeId=*/70,
new DisplayModeDirector.RefreshRateRange(70, 80)),
director.getDesiredDisplayModeSpecs(displayId));
}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java b/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java
index 63189e7..a1810b9 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/IntegrityFileManagerTest.java
@@ -16,14 +16,9 @@
package com.android.server.integrity;
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.hasItems;
-import static org.junit.Assert.assertThat;
-import static org.testng.Assert.assertEquals;
-import static org.testng.Assert.assertFalse;
-import static org.testng.Assert.assertNotNull;
-import static org.testng.Assert.assertNull;
-import static org.testng.Assert.assertTrue;
+import static com.android.server.integrity.model.IndexingFileConstants.INDEXING_BLOCK_SIZE;
+
+import static com.google.common.truth.Truth.assertThat;
import android.content.integrity.AppInstallMetadata;
import android.content.integrity.AtomicFormula;
@@ -33,26 +28,26 @@
import android.content.integrity.Rule;
import android.util.Slog;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.server.integrity.parser.RuleXmlParser;
-import com.android.server.integrity.serializer.RuleXmlSerializer;
+import com.android.server.integrity.parser.RuleBinaryParser;
+import com.android.server.integrity.serializer.RuleBinarySerializer;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/** Unit test for {@link IntegrityFileManager} */
-@RunWith(AndroidJUnit4.class)
+@RunWith(JUnit4.class)
public class IntegrityFileManagerTest {
private static final String TAG = "IntegrityFileManagerTest";
@@ -72,7 +67,7 @@
// Use Xml Parser/Serializer to help with debugging since we can just print the file.
mIntegrityFileManager =
new IntegrityFileManager(
- new RuleXmlParser(), new RuleXmlSerializer(), mTmpDir);
+ new RuleBinaryParser(), new RuleBinarySerializer(), mTmpDir);
Files.walk(mTmpDir.toPath())
.forEach(
path -> {
@@ -97,12 +92,19 @@
@Test
public void testGetMetadata() throws Exception {
- assertNull(mIntegrityFileManager.readMetadata());
+ assertThat(mIntegrityFileManager.readMetadata()).isNull();
mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, Collections.EMPTY_LIST);
- assertNotNull(mIntegrityFileManager.readMetadata());
- assertEquals(VERSION, mIntegrityFileManager.readMetadata().getVersion());
- assertEquals(RULE_PROVIDER, mIntegrityFileManager.readMetadata().getRuleProvider());
+ assertThat(mIntegrityFileManager.readMetadata()).isNotNull();
+ assertThat(mIntegrityFileManager.readMetadata().getVersion()).isEqualTo(VERSION);
+ assertThat(mIntegrityFileManager.readMetadata().getRuleProvider()).isEqualTo(RULE_PROVIDER);
+ }
+
+ @Test
+ public void testIsInitialized() throws Exception {
+ assertThat(mIntegrityFileManager.initialized()).isFalse();
+ mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, Collections.EMPTY_LIST);
+ assertThat(mIntegrityFileManager.initialized()).isTrue();
}
@Test
@@ -110,20 +112,8 @@
String packageName = "package";
String packageCert = "cert";
int version = 123;
- Rule packageNameRule =
- new Rule(
- new StringAtomicFormula(
- AtomicFormula.PACKAGE_NAME,
- packageName,
- /* isHashedValue= */ false),
- Rule.DENY);
- Rule packageCertRule =
- new Rule(
- new StringAtomicFormula(
- AtomicFormula.APP_CERTIFICATE,
- packageCert,
- /* isHashedValue= */ false),
- Rule.DENY);
+ Rule packageNameRule = getPackageNameIndexedRule(packageName);
+ Rule packageCertRule = getAppCertificateIndexedRule(packageCert);
Rule versionCodeRule =
new Rule(
new IntAtomicFormula(AtomicFormula.VERSION_CODE, AtomicFormula.LE, version),
@@ -142,34 +132,107 @@
AtomicFormula.LE,
version))),
Rule.DENY);
- // We will test the specifics of indexing in other classes. Here, we just require that all
- // rules that are related to the given AppInstallMetadata are returned and do not assert
- // anything on other rules.
+
List<Rule> rules =
Arrays.asList(packageNameRule, packageCertRule, versionCodeRule, randomRule);
mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, rules);
- AppInstallMetadata appInstallMetadata = new AppInstallMetadata.Builder()
- .setPackageName(packageName)
- .setAppCertificate(packageCert)
- .setVersionCode(version)
- .setInstallerName("abc")
- .setInstallerCertificate("abc")
- .setIsPreInstalled(true)
- .build();
+ AppInstallMetadata appInstallMetadata =
+ new AppInstallMetadata.Builder()
+ .setPackageName(packageName)
+ .setAppCertificate(packageCert)
+ .setVersionCode(version)
+ .setInstallerName("abc")
+ .setInstallerCertificate("abc")
+ .setIsPreInstalled(true)
+ .build();
List<Rule> rulesFetched = mIntegrityFileManager.readRules(appInstallMetadata);
- assertThat(rulesFetched, hasItems(
- equalTo(packageNameRule),
- equalTo(packageCertRule),
- equalTo(versionCodeRule)
- ));
+ assertThat(rulesFetched)
+ .containsExactly(packageNameRule, packageCertRule, versionCodeRule, randomRule);
}
@Test
- public void testIsInitialized() throws Exception {
- assertFalse(mIntegrityFileManager.initialized());
+ public void testGetRules_indexedForManyRules() throws Exception {
+ String packageName = "package";
+ String installerName = "installer";
+ String appCertificate = "cert";
+
+ // Create a rule set with 2500 package name indexed, 2500 app certificate indexed and
+ // 500 unindexed rules.
+ List<Rule> rules = new ArrayList<>();
+ int unindexedRuleCount = 70;
+
+ for (int i = 0; i < 2500; i++) {
+ rules.add(getPackageNameIndexedRule(String.format("%s%04d", packageName, i)));
+ rules.add(getAppCertificateIndexedRule(String.format("%s%04d", appCertificate, i)));
+ }
+
+ for (int i = 0; i < unindexedRuleCount; i++) {
+ rules.add(getInstallerCertificateRule(String.format("%s%04d", installerName, i)));
+ }
+
+ // Write the rules and get them indexed.
+ mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, rules);
+
+ // Read the rules for a specific rule.
+ String installedPackageName = String.format("%s%04d", packageName, 264);
+ String installedAppCertificate = String.format("%s%04d", appCertificate, 1264);
+ AppInstallMetadata appInstallMetadata =
+ new AppInstallMetadata.Builder()
+ .setPackageName(installedPackageName)
+ .setAppCertificate(installedAppCertificate)
+ .setVersionCode(250)
+ .setInstallerName("abc")
+ .setInstallerCertificate("abc")
+ .setIsPreInstalled(true)
+ .build();
+ List<Rule> rulesFetched = mIntegrityFileManager.readRules(appInstallMetadata);
+
+ // Verify that we do not load all the rules and we have the necessary rules to evaluate.
+ assertThat(rulesFetched.size())
+ .isEqualTo(INDEXING_BLOCK_SIZE * 2 + unindexedRuleCount);
+ assertThat(rulesFetched)
+ .containsAllOf(
+ getPackageNameIndexedRule(installedPackageName),
+ getAppCertificateIndexedRule(installedAppCertificate));
+ }
+
+ private Rule getPackageNameIndexedRule(String packageName) {
+ return new Rule(
+ new StringAtomicFormula(
+ AtomicFormula.PACKAGE_NAME, packageName, /* isHashedValue= */ false),
+ Rule.DENY);
+ }
+
+ private Rule getAppCertificateIndexedRule(String appCertificate) {
+ return new Rule(
+ new StringAtomicFormula(
+ AtomicFormula.APP_CERTIFICATE, appCertificate, /* isHashedValue= */ false),
+ Rule.DENY);
+ }
+
+ private Rule getInstallerCertificateRule(String installerCert) {
+ return new Rule(
+ new StringAtomicFormula(
+ AtomicFormula.INSTALLER_NAME, installerCert, /* isHashedValue= */ false),
+ Rule.DENY);
+ }
+
+ @Test
+ public void testStagingDirectoryCleared() throws Exception {
+ // We must push rules two times to ensure that staging directory is empty because we cleared
+ // it, rather than because original rules directory is empty.
mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, Collections.EMPTY_LIST);
- assertTrue(mIntegrityFileManager.initialized());
+ mIntegrityFileManager.writeRules(VERSION, RULE_PROVIDER, Collections.EMPTY_LIST);
+
+ assertStagingDirectoryCleared();
+ }
+
+ private void assertStagingDirectoryCleared() {
+ File stagingDir = new File(mTmpDir, "integrity_staging");
+ assertThat(stagingDir.exists()).isTrue();
+ assertThat(stagingDir.isDirectory()).isTrue();
+ assertThat(stagingDir.listFiles()).isEmpty();
}
}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java
index 7a070ee..eda2b70 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/engine/RuleEvaluatorTest.java
@@ -188,7 +188,7 @@
}
@Test
- public void testEvaluateRules_ruleNotInDNF_ignoreAndAllow() {
+ public void testEvaluateRules_orRules() {
CompoundFormula compoundFormula =
new CompoundFormula(
CompoundFormula.OR,
@@ -206,11 +206,11 @@
IntegrityCheckResult result =
RuleEvaluator.evaluateRules(Collections.singletonList(rule), APP_INSTALL_METADATA);
- assertEquals(ALLOW, result.getEffect());
+ assertEquals(DENY, result.getEffect());
}
@Test
- public void testEvaluateRules_compoundFormulaWithNot_allow() {
+ public void testEvaluateRules_compoundFormulaWithNot_deny() {
CompoundFormula openSubFormula =
new CompoundFormula(
CompoundFormula.AND,
@@ -230,7 +230,7 @@
IntegrityCheckResult result =
RuleEvaluator.evaluateRules(Collections.singletonList(rule), APP_INSTALL_METADATA);
- assertEquals(ALLOW, result.getEffect());
+ assertEquals(DENY, result.getEffect());
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/ByteTrackedOutputStreamTest.java b/services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java
similarity index 87%
rename from services/tests/servicestests/src/com/android/server/integrity/serializer/ByteTrackedOutputStreamTest.java
rename to services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java
index 5ecb8b5c..57274bf 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/ByteTrackedOutputStreamTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/model/ByteTrackedOutputStreamTest.java
@@ -14,12 +14,10 @@
* limitations under the License.
*/
-package com.android.server.integrity.serializer;
+package com.android.server.integrity.model;
import static com.google.common.truth.Truth.assertThat;
-import com.android.server.integrity.model.BitOutputStream;
-
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@@ -55,17 +53,17 @@
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ByteTrackedOutputStream byteTrackedOutputStream = new ByteTrackedOutputStream(outputStream);
- BitOutputStream bitOutputStream = new BitOutputStream();
+ BitOutputStream bitOutputStream = new BitOutputStream(byteTrackedOutputStream);
bitOutputStream.setNext(/* numOfBits= */5, /* value= */1);
- byteTrackedOutputStream.write(bitOutputStream.toByteArray());
+ bitOutputStream.flush();
// Even though we wrote 5 bits, this will complete to 1 byte.
assertThat(byteTrackedOutputStream.getWrittenBytesCount()).isEqualTo(1);
// Add a bit less than 2 bytes (10 bits).
- bitOutputStream.clear();
bitOutputStream.setNext(/* numOfBits= */10, /* value= */1);
- byteTrackedOutputStream.write(bitOutputStream.toByteArray());
+ bitOutputStream.flush();
+ assertThat(byteTrackedOutputStream.getWrittenBytesCount()).isEqualTo(3);
assertThat(outputStream.toByteArray().length).isEqualTo(3);
}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/BinaryFileOperationsTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/BinaryFileOperationsTest.java
new file mode 100644
index 0000000..cfa1de3
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/integrity/parser/BinaryFileOperationsTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.integrity.parser;
+
+import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS;
+import static com.android.server.integrity.parser.BinaryFileOperations.getBooleanValue;
+import static com.android.server.integrity.parser.BinaryFileOperations.getIntValue;
+import static com.android.server.integrity.parser.BinaryFileOperations.getStringValue;
+import static com.android.server.integrity.utils.TestUtils.getBits;
+import static com.android.server.integrity.utils.TestUtils.getBytes;
+import static com.android.server.integrity.utils.TestUtils.getValueBits;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.server.integrity.IntegrityUtils;
+import com.android.server.integrity.model.BitInputStream;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+@RunWith(JUnit4.class)
+public class BinaryFileOperationsTest {
+
+ private static final String IS_NOT_HASHED = "0";
+ private static final String IS_HASHED = "1";
+ private static final String PACKAGE_NAME = "com.test.app";
+ private static final String APP_CERTIFICATE = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+
+ @Test
+ public void testGetStringValue() throws IOException {
+ byte[] stringBytes =
+ getBytes(
+ IS_NOT_HASHED
+ + getBits(PACKAGE_NAME.length(), VALUE_SIZE_BITS)
+ + getValueBits(PACKAGE_NAME));
+ BitInputStream inputStream = new BitInputStream(new ByteArrayInputStream(stringBytes));
+
+ String resultString = getStringValue(inputStream);
+
+ assertThat(resultString).isEqualTo(PACKAGE_NAME);
+ }
+
+ @Test
+ public void testGetHashedStringValue() throws IOException {
+ byte[] ruleBytes =
+ getBytes(
+ IS_HASHED
+ + getBits(APP_CERTIFICATE.length(), VALUE_SIZE_BITS)
+ + getValueBits(APP_CERTIFICATE));
+ BitInputStream inputStream = new BitInputStream(new ByteArrayInputStream(ruleBytes));
+
+ String resultString = getStringValue(inputStream);
+
+ assertThat(resultString)
+ .isEqualTo(IntegrityUtils.getHexDigest(
+ APP_CERTIFICATE.getBytes(StandardCharsets.UTF_8)));
+ }
+
+ @Test
+ public void testGetStringValue_withSizeAndHashingInfo() throws IOException {
+ byte[] ruleBytes = getBytes(getValueBits(PACKAGE_NAME));
+ BitInputStream inputStream = new BitInputStream(new ByteArrayInputStream(ruleBytes));
+
+ String resultString = getStringValue(inputStream,
+ PACKAGE_NAME.length(), /* isHashedValue= */false);
+
+ assertThat(resultString).isEqualTo(PACKAGE_NAME);
+ }
+
+ @Test
+ public void testGetIntValue() throws IOException {
+ int randomValue = 15;
+ byte[] ruleBytes = getBytes(getBits(randomValue, /* numOfBits= */ 32));
+ BitInputStream inputStream = new BitInputStream(new ByteArrayInputStream(ruleBytes));
+
+ assertThat(getIntValue(inputStream)).isEqualTo(randomValue);
+ }
+
+ @Test
+ public void testGetBooleanValue_true() throws IOException {
+ String booleanValue = "1";
+ byte[] ruleBytes = getBytes(booleanValue);
+ BitInputStream inputStream = new BitInputStream(new ByteArrayInputStream(ruleBytes));
+
+ assertThat(getBooleanValue(inputStream)).isEqualTo(true);
+ }
+
+ @Test
+ public void testGetBooleanValue_false() throws IOException {
+ String booleanValue = "0";
+ byte[] ruleBytes = getBytes(booleanValue);
+ BitInputStream inputStream = new BitInputStream(new ByteArrayInputStream(ruleBytes));
+
+ assertThat(getBooleanValue(inputStream)).isEqualTo(false);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java
index 9cc0ed8..e0b2e22 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleBinaryParserTest.java
@@ -44,8 +44,6 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
@@ -97,8 +95,10 @@
private static final byte[] DEFAULT_FORMAT_VERSION_BYTES =
getBytes(getBits(DEFAULT_FORMAT_VERSION, FORMAT_VERSION_BITS));
+ private static final List<RuleIndexRange> NO_INDEXING = Collections.emptyList();
+
@Test
- public void testBinaryStream_validCompoundFormula() throws Exception {
+ public void testBinaryStream_validCompoundFormula_noIndexing() throws Exception {
String packageName = "com.test.app";
String ruleBits =
START_BIT
@@ -119,7 +119,6 @@
rule.put(DEFAULT_FORMAT_VERSION_BYTES);
rule.put(ruleBytes);
RuleParser binaryParser = new RuleBinaryParser();
- InputStream inputStream = new ByteArrayInputStream(rule.array());
Rule expectedRule =
new Rule(
new CompoundFormula(
@@ -131,13 +130,14 @@
/* isHashedValue= */ false))),
Rule.DENY);
- List<Rule> rules = binaryParser.parse(inputStream);
+ List<Rule> rules =
+ binaryParser.parse(RandomAccessObject.ofBytes(rule.array()), NO_INDEXING);
assertThat(rules).isEqualTo(Collections.singletonList(expectedRule));
}
@Test
- public void testBinaryString_validCompoundFormula_notConnector() throws Exception {
+ public void testBinaryString_validCompoundFormula_notConnector_noIndexing() throws Exception {
String packageName = "com.test.app";
String ruleBits =
START_BIT
@@ -175,7 +175,7 @@
}
@Test
- public void testBinaryString_validCompoundFormula_andConnector() throws Exception {
+ public void testBinaryString_validCompoundFormula_andConnector_noIndexing() throws Exception {
String packageName = "com.test.app";
String appCertificate = "test_cert";
String ruleBits =
@@ -223,7 +223,7 @@
}
@Test
- public void testBinaryString_validCompoundFormula_orConnector() throws Exception {
+ public void testBinaryString_validCompoundFormula_orConnector_noIndexing() throws Exception {
String packageName = "com.test.app";
String appCertificate = "test_cert";
String ruleBits =
@@ -272,7 +272,7 @@
}
@Test
- public void testBinaryString_validAtomicFormula_stringValue() throws Exception {
+ public void testBinaryString_validAtomicFormula_stringValue_noIndexing() throws Exception {
String packageName = "com.test.app";
String ruleBits =
START_BIT
@@ -304,7 +304,7 @@
}
@Test
- public void testBinaryString_validAtomicFormula_hashedValue() throws Exception {
+ public void testBinaryString_validAtomicFormula_hashedValue_noIndexing() throws Exception {
String appCertificate = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
String ruleBits =
START_BIT
@@ -321,6 +321,7 @@
ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length + ruleBytes.length);
rule.put(DEFAULT_FORMAT_VERSION_BYTES);
rule.put(ruleBytes);
+
RuleParser binaryParser = new RuleBinaryParser();
Rule expectedRule =
new Rule(
@@ -337,7 +338,7 @@
}
@Test
- public void testBinaryString_validAtomicFormula_integerValue() throws Exception {
+ public void testBinaryString_validAtomicFormula_integerValue_noIndexing() throws Exception {
int versionCode = 1;
String ruleBits =
START_BIT
@@ -365,7 +366,7 @@
}
@Test
- public void testBinaryString_validAtomicFormula_booleanValue() throws Exception {
+ public void testBinaryString_validAtomicFormula_booleanValue_noIndexing() throws Exception {
String isPreInstalled = "1";
String ruleBits =
START_BIT
@@ -392,7 +393,7 @@
}
@Test
- public void testBinaryString_invalidAtomicFormula() throws Exception {
+ public void testBinaryString_invalidAtomicFormula_noIndexing() {
int versionCode = 1;
String ruleBits =
START_BIT
@@ -410,12 +411,12 @@
assertExpectException(
RuleParseException.class,
- /* expectedExceptionMessageRegex */ "A rule must end with a '1' bit.",
+ /* expectedExceptionMessageRegex= */ "A rule must end with a '1' bit.",
() -> binaryParser.parse(rule.array()));
}
@Test
- public void testBinaryString_withNoRuleList() throws RuleParseException {
+ public void testBinaryString_withNoRuleList_noIndexing() throws RuleParseException {
ByteBuffer rule = ByteBuffer.allocate(DEFAULT_FORMAT_VERSION_BYTES.length);
rule.put(DEFAULT_FORMAT_VERSION_BYTES);
RuleParser binaryParser = new RuleBinaryParser();
@@ -426,7 +427,7 @@
}
@Test
- public void testBinaryString_withEmptyRule() throws RuleParseException {
+ public void testBinaryString_withEmptyRule_noIndexing() {
String ruleBits = START_BIT;
byte[] ruleBytes = getBytes(ruleBits);
ByteBuffer rule =
@@ -437,12 +438,12 @@
assertExpectException(
RuleParseException.class,
- /* expectedExceptionMessageRegex */ "Invalid byte index",
+ /* expectedExceptionMessageRegex */ "",
() -> binaryParser.parse(rule.array()));
}
@Test
- public void testBinaryString_invalidCompoundFormula_invalidNumberOfFormulas() throws Exception {
+ public void testBinaryString_invalidCompoundFormula_invalidNumberOfFormulas_noIndexing() {
String packageName = "com.test.app";
String appCertificate = "test_cert";
String ruleBits =
@@ -478,7 +479,7 @@
}
@Test
- public void testBinaryString_invalidRule_invalidOperator() throws Exception {
+ public void testBinaryString_invalidRule_invalidOperator_noIndexing() {
int versionCode = 1;
String ruleBits =
START_BIT
@@ -506,7 +507,7 @@
}
@Test
- public void testBinaryString_invalidRule_invalidEffect() throws Exception {
+ public void testBinaryString_invalidRule_invalidEffect_noIndexing() {
String packageName = "com.test.app";
String ruleBits =
START_BIT
@@ -536,7 +537,7 @@
}
@Test
- public void testBinaryString_invalidRule_invalidConnector() throws Exception {
+ public void testBinaryString_invalidRule_invalidConnector_noIndexing() {
String packageName = "com.test.app";
String ruleBits =
START_BIT
@@ -566,7 +567,7 @@
}
@Test
- public void testBinaryString_invalidRule_invalidKey() throws Exception {
+ public void testBinaryString_invalidRule_invalidKey_noIndexing() {
String packageName = "com.test.app";
String ruleBits =
START_BIT
@@ -596,7 +597,7 @@
}
@Test
- public void testBinaryString_invalidRule_invalidSeparator() throws Exception {
+ public void testBinaryString_invalidRule_invalidSeparator_noIndexing() {
String packageName = "com.test.app";
String ruleBits =
START_BIT
@@ -626,7 +627,7 @@
}
@Test
- public void testBinaryString_invalidRule_invalidEndMarker() throws Exception {
+ public void testBinaryString_invalidRule_invalidEndMarker_noIndexing() {
String packageName = "com.test.app";
String ruleBits =
START_BIT
diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java
new file mode 100644
index 0000000..742952e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleIndexingControllerTest.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.integrity.parser;
+
+import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS;
+import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY;
+import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY;
+import static com.android.server.integrity.utils.TestUtils.getBits;
+import static com.android.server.integrity.utils.TestUtils.getBytes;
+import static com.android.server.integrity.utils.TestUtils.getValueBits;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.integrity.AppInstallMetadata;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public class RuleIndexingControllerTest {
+
+ @Test
+ public void verifyIndexRangeSearchIsCorrect() throws IOException {
+ InputStream inputStream = obtainDefaultIndexingMapForTest();
+
+ RuleIndexingController indexingController = new RuleIndexingController(inputStream);
+
+ AppInstallMetadata appInstallMetadata =
+ new AppInstallMetadata.Builder()
+ .setPackageName("ddd")
+ .setAppCertificate("777")
+ .build();
+
+ List<RuleIndexRange> resultingIndexes =
+ indexingController.identifyRulesToEvaluate(appInstallMetadata);
+
+ assertThat(resultingIndexes)
+ .containsExactly(
+ new RuleIndexRange(200, 300),
+ new RuleIndexRange(700, 800),
+ new RuleIndexRange(900, 945));
+ }
+
+ @Test
+ public void verifyIndexRangeSearchIsCorrect_keysInFirstAndLastBlock() throws IOException {
+ InputStream inputStream = obtainDefaultIndexingMapForTest();
+
+ RuleIndexingController indexingController = new RuleIndexingController(inputStream);
+
+ AppInstallMetadata appInstallMetadata =
+ new AppInstallMetadata.Builder()
+ .setPackageName("bbb")
+ .setAppCertificate("999")
+ .build();
+
+ List<RuleIndexRange> resultingIndexes =
+ indexingController.identifyRulesToEvaluate(appInstallMetadata);
+
+ assertThat(resultingIndexes)
+ .containsExactly(
+ new RuleIndexRange(100, 200),
+ new RuleIndexRange(800, 900),
+ new RuleIndexRange(900, 945));
+ }
+
+ @Test
+ public void verifyIndexRangeSearchIsCorrect_keysMatchWithValues() throws IOException {
+ InputStream inputStream = obtainDefaultIndexingMapForTest();
+
+ RuleIndexingController indexingController = new RuleIndexingController(inputStream);
+
+ AppInstallMetadata appInstallMetadata =
+ new AppInstallMetadata.Builder()
+ .setPackageName("ccc")
+ .setAppCertificate("444")
+ .build();
+
+ List<RuleIndexRange> resultingIndexes =
+ indexingController.identifyRulesToEvaluate(appInstallMetadata);
+
+ assertThat(resultingIndexes)
+ .containsExactly(
+ new RuleIndexRange(200, 300),
+ new RuleIndexRange(700, 800),
+ new RuleIndexRange(900, 945));
+ }
+
+ @Test
+ public void verifyIndexRangeSearchIsCorrect_noIndexesAvailable() throws IOException {
+ byte[] stringBytes =
+ getBytes(
+ getKeyValueString(START_INDEXING_KEY, 100)
+ + getKeyValueString(END_INDEXING_KEY, 500)
+ + getKeyValueString(START_INDEXING_KEY, 500)
+ + getKeyValueString(END_INDEXING_KEY, 900)
+ + getKeyValueString(START_INDEXING_KEY, 900)
+ + getKeyValueString(END_INDEXING_KEY, 945));
+ ByteBuffer rule = ByteBuffer.allocate(stringBytes.length);
+ rule.put(stringBytes);
+ InputStream inputStream = new ByteArrayInputStream(rule.array());
+
+ RuleIndexingController indexingController = new RuleIndexingController(inputStream);
+
+ AppInstallMetadata appInstallMetadata =
+ new AppInstallMetadata.Builder()
+ .setPackageName("ccc")
+ .setAppCertificate("444")
+ .build();
+
+ List<RuleIndexRange> resultingIndexes =
+ indexingController.identifyRulesToEvaluate(appInstallMetadata);
+
+ assertThat(resultingIndexes)
+ .containsExactly(
+ new RuleIndexRange(100, 500),
+ new RuleIndexRange(500, 900),
+ new RuleIndexRange(900, 945));
+ }
+
+ private static InputStream obtainDefaultIndexingMapForTest() {
+ byte[] stringBytes =
+ getBytes(
+ getKeyValueString(START_INDEXING_KEY, 100)
+ + getKeyValueString("ccc", 200)
+ + getKeyValueString("eee", 300)
+ + getKeyValueString("hhh", 400)
+ + getKeyValueString(END_INDEXING_KEY, 500)
+ + getKeyValueString(START_INDEXING_KEY, 500)
+ + getKeyValueString("111", 600)
+ + getKeyValueString("444", 700)
+ + getKeyValueString("888", 800)
+ + getKeyValueString(END_INDEXING_KEY, 900)
+ + getKeyValueString(START_INDEXING_KEY, 900)
+ + getKeyValueString(END_INDEXING_KEY, 945));
+ ByteBuffer rule = ByteBuffer.allocate(stringBytes.length);
+ rule.put(stringBytes);
+ return new ByteArrayInputStream(rule.array());
+ }
+
+ private static String getKeyValueString(String key, int value) {
+ String isNotHashed = "0";
+ return isNotHashed
+ + getBits(key.length(), VALUE_SIZE_BITS)
+ + getValueBits(key)
+ + getBits(value, /* numOfBits= */ 32);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java
index a14197b..0f0dee9 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/parser/RuleXmlParserTest.java
@@ -28,8 +28,6 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
@@ -61,7 +59,6 @@
+ "</R>"
+ "</RL>";
RuleParser xmlParser = new RuleXmlParser();
- InputStream inputStream = new ByteArrayInputStream(ruleXmlCompoundFormula.getBytes());
Rule expectedRule =
new Rule(
new CompoundFormula(
@@ -73,7 +70,7 @@
/* isHashedValue= */ false))),
Rule.DENY);
- List<Rule> rules = xmlParser.parse(inputStream);
+ List<Rule> rules = xmlParser.parse(ruleXmlCompoundFormula.getBytes());
assertThat(rules).isEqualTo(Collections.singletonList(expectedRule));
}
@@ -617,13 +614,12 @@
/* tag= */ "AF", atomicFormulaAttrs, /* closed= */ true)
+ "</OF>"
+ "</R>";
- InputStream inputStream = new ByteArrayInputStream(ruleXmlWithNoRuleList.getBytes());
RuleParser xmlParser = new RuleXmlParser();
assertExpectException(
RuleParseException.class,
/* expectedExceptionMessageRegex */ "Rules must start with RuleList <RL> tag",
- () -> xmlParser.parse(inputStream));
+ () -> xmlParser.parse(ruleXmlWithNoRuleList.getBytes()));
}
private String generateTagWithAttribute(
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
index 981db6a..8ee5f5f 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleBinarySerializerTest.java
@@ -27,6 +27,9 @@
import static com.android.server.integrity.model.ComponentBitSize.OPERATOR_BITS;
import static com.android.server.integrity.model.ComponentBitSize.SEPARATOR_BITS;
import static com.android.server.integrity.model.ComponentBitSize.VALUE_SIZE_BITS;
+import static com.android.server.integrity.model.IndexingFileConstants.END_INDEXING_KEY;
+import static com.android.server.integrity.model.IndexingFileConstants.INDEXING_BLOCK_SIZE;
+import static com.android.server.integrity.model.IndexingFileConstants.START_INDEXING_KEY;
import static com.android.server.integrity.utils.TestUtils.getBits;
import static com.android.server.integrity.utils.TestUtils.getBytes;
import static com.android.server.integrity.utils.TestUtils.getValueBits;
@@ -94,6 +97,15 @@
private static final byte[] DEFAULT_FORMAT_VERSION_BYTES =
getBytes(getBits(DEFAULT_FORMAT_VERSION, FORMAT_VERSION_BITS));
+ private static final String SERIALIZED_START_INDEXING_KEY =
+ IS_NOT_HASHED
+ + getBits(START_INDEXING_KEY.length(), VALUE_SIZE_BITS)
+ + getValueBits(START_INDEXING_KEY);
+ private static final String SERIALIZED_END_INDEXING_KEY =
+ IS_NOT_HASHED
+ + getBits(END_INDEXING_KEY.length(), VALUE_SIZE_BITS)
+ + getValueBits(END_INDEXING_KEY);
+
@Test
public void testBinaryString_serializeNullRules() {
RuleSerializer binarySerializer = new RuleBinarySerializer();
@@ -107,15 +119,35 @@
@Test
public void testBinaryString_emptyRules() throws Exception {
- ByteArrayOutputStream expectedArrayOutputStream = new ByteArrayOutputStream();
- expectedArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
-
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
+ ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
RuleSerializer binarySerializer = new RuleBinarySerializer();
- binarySerializer.serialize(
- Collections.emptyList(), /* formatVersion= */ Optional.empty(), outputStream);
- assertThat(outputStream.toByteArray()).isEqualTo(expectedArrayOutputStream.toByteArray());
+ binarySerializer.serialize(
+ Collections.emptyList(),
+ /* formatVersion= */ Optional.empty(),
+ ruleOutputStream,
+ indexingOutputStream);
+
+ ByteArrayOutputStream expectedRuleOutputStream = new ByteArrayOutputStream();
+ expectedRuleOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
+ assertThat(ruleOutputStream.toByteArray())
+ .isEqualTo(expectedRuleOutputStream.toByteArray());
+
+ ByteArrayOutputStream expectedIndexingOutputStream = new ByteArrayOutputStream();
+ String serializedIndexingBytes =
+ SERIALIZED_START_INDEXING_KEY
+ + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32)
+ + SERIALIZED_END_INDEXING_KEY
+ + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32);
+ byte[] expectedIndexingBytes =
+ getBytes(
+ serializedIndexingBytes
+ + serializedIndexingBytes
+ + serializedIndexingBytes);
+ expectedIndexingOutputStream.write(expectedIndexingBytes);
+ assertThat(indexingOutputStream.toByteArray())
+ .isEqualTo(expectedIndexingOutputStream.toByteArray());
}
@Test
@@ -131,8 +163,16 @@
packageName,
/* isHashedValue= */ false))),
Rule.DENY);
+
+ ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
+ ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
RuleSerializer binarySerializer = new RuleBinarySerializer();
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ binarySerializer.serialize(
+ Collections.singletonList(rule),
+ /* formatVersion= */ Optional.empty(),
+ ruleOutputStream,
+ indexingOutputStream);
+
String expectedBits =
START_BIT
+ COMPOUND_FORMULA_START_BITS
@@ -146,18 +186,33 @@
+ COMPOUND_FORMULA_END_BITS
+ DENY
+ END_BIT;
- ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
- byteArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
- byteArrayOutputStream.write(getBytes(expectedBits));
- byte[] expectedRules = byteArrayOutputStream.toByteArray();
+ ByteArrayOutputStream expectedRuleOutputStream = new ByteArrayOutputStream();
+ expectedRuleOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
+ expectedRuleOutputStream.write(getBytes(expectedBits));
+ assertThat(ruleOutputStream.toByteArray())
+ .isEqualTo(expectedRuleOutputStream.toByteArray());
- binarySerializer.serialize(
- Collections.singletonList(rule),
- /* formatVersion= */ Optional.empty(),
- outputStream);
+ ByteArrayOutputStream expectedIndexingOutputStream = new ByteArrayOutputStream();
+ String expectedIndexingBitsForIndexed =
+ SERIALIZED_START_INDEXING_KEY
+ + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32)
+ + SERIALIZED_END_INDEXING_KEY
+ + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32);
+ String expectedIndexingBitsForUnindexed =
+ SERIALIZED_START_INDEXING_KEY
+ + getBits(DEFAULT_FORMAT_VERSION_BYTES.length, /* numOfBits= */ 32)
+ + SERIALIZED_END_INDEXING_KEY
+ + getBits(
+ DEFAULT_FORMAT_VERSION_BYTES.length + getBytes(expectedBits).length,
+ /* numOfBits= */ 32);
+ expectedIndexingOutputStream.write(
+ getBytes(
+ expectedIndexingBitsForIndexed
+ + expectedIndexingBitsForIndexed
+ + expectedIndexingBitsForUnindexed));
- byte[] actualRules = outputStream.toByteArray();
- assertThat(actualRules).isEqualTo(expectedRules);
+ assertThat(indexingOutputStream.toByteArray())
+ .isEqualTo(expectedIndexingOutputStream.toByteArray());
}
@Test
@@ -453,91 +508,114 @@
}
@Test
- public void testBinaryString_serializeComplexCompoundFormula_indexingOrderValid()
- throws Exception {
- String packageNameA = "aaa";
- String packageNameB = "bbb";
- String packageNameC = "ccc";
- String appCert1 = "cert1";
- String appCert2 = "cert2";
- String appCert3 = "cert3";
- Rule installerRule =
- new Rule(
- new CompoundFormula(
- CompoundFormula.AND,
- Arrays.asList(
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.INSTALLER_NAME,
- SAMPLE_INSTALLER_NAME,
- /* isHashedValue= */ false),
- new AtomicFormula.StringAtomicFormula(
- AtomicFormula.INSTALLER_CERTIFICATE,
- SAMPLE_INSTALLER_CERT,
- /* isHashedValue= */ false))),
- Rule.DENY);
+ public void testBinaryString_verifyManyRulesAreIndexedCorrectly() throws Exception {
+ int ruleCount = 225;
+ String packagePrefix = "package.name.";
+ String appCertificatePrefix = "app.cert.";
+ String installerNamePrefix = "installer.";
- RuleSerializer binarySerializer = new RuleBinarySerializer();
+ // Create the rule set with 225 package name based rules, 225 app certificate indexed rules,
+ // and 225 non-indexed rules..
List<Rule> ruleList = new ArrayList();
- ruleList.add(getRuleWithAppCertificateAndSampleInstallerName(appCert3));
- ruleList.add(getRuleWithAppCertificateAndSampleInstallerName(appCert2));
- ruleList.add(getRuleWithAppCertificateAndSampleInstallerName(appCert1));
- ruleList.add(getRuleWithPackageNameAndSampleInstallerName(packageNameB));
- ruleList.add(getRuleWithPackageNameAndSampleInstallerName(packageNameC));
- ruleList.add(getRuleWithPackageNameAndSampleInstallerName(packageNameA));
- ruleList.add(installerRule);
- byte[] actualRules =
- binarySerializer.serialize(ruleList, /* formatVersion= */ Optional.empty());
+ for (int count = 0; count < ruleCount; count++) {
+ ruleList.add(
+ getRuleWithPackageNameAndSampleInstallerName(
+ String.format("%s%04d", packagePrefix, count)));
+ }
+ for (int count = 0; count < ruleCount; count++) {
+ ruleList.add(
+ getRuleWithAppCertificateAndSampleInstallerName(
+ String.format("%s%04d", appCertificatePrefix, count)));
+ }
+ for (int count = 0; count < ruleCount; count++) {
+ ruleList.add(
+ getNonIndexedRuleWithInstallerName(
+ String.format("%s%04d", installerNamePrefix, count)));
+ }
- // Note that ordering is important here and the test verifies that the rules are written
- // in this sorted order.
- ByteArrayOutputStream expectedArrayOutputStream = new ByteArrayOutputStream();
- expectedArrayOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
- expectedArrayOutputStream.write(
- getBytes(
- getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
- packageNameA)));
- expectedArrayOutputStream.write(
- getBytes(
- getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
- packageNameB)));
- expectedArrayOutputStream.write(
- getBytes(
- getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
- packageNameC)));
- expectedArrayOutputStream.write(
- getBytes(
- getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
- appCert1)));
- expectedArrayOutputStream.write(
- getBytes(
- getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
- appCert2)));
- expectedArrayOutputStream.write(
- getBytes(
- getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
- appCert3)));
- String expectedBitsForInstallerRule =
- START_BIT
- + COMPOUND_FORMULA_START_BITS
- + AND
- + ATOMIC_FORMULA_START_BITS
- + INSTALLER_NAME
- + EQ
- + IS_NOT_HASHED
- + getBits(SAMPLE_INSTALLER_NAME.length(), VALUE_SIZE_BITS)
- + getValueBits(SAMPLE_INSTALLER_NAME)
- + ATOMIC_FORMULA_START_BITS
- + INSTALLER_CERTIFICATE
- + EQ
- + IS_NOT_HASHED
- + getBits(SAMPLE_INSTALLER_CERT.length(), VALUE_SIZE_BITS)
- + getValueBits(SAMPLE_INSTALLER_CERT)
- + COMPOUND_FORMULA_END_BITS
- + DENY
- + END_BIT;
- expectedArrayOutputStream.write(getBytes(expectedBitsForInstallerRule));
+ // Serialize the rules.
+ ByteArrayOutputStream ruleOutputStream = new ByteArrayOutputStream();
+ ByteArrayOutputStream indexingOutputStream = new ByteArrayOutputStream();
+ RuleSerializer binarySerializer = new RuleBinarySerializer();
+ binarySerializer.serialize(
+ ruleList,
+ /* formatVersion= */ Optional.empty(),
+ ruleOutputStream,
+ indexingOutputStream);
- assertThat(actualRules).isEqualTo(expectedArrayOutputStream.toByteArray());
+ // Verify the rules file and index files.
+ ByteArrayOutputStream expectedOrderedRuleOutputStream = new ByteArrayOutputStream();
+ ByteArrayOutputStream expectedIndexingOutputStream = new ByteArrayOutputStream();
+
+ expectedOrderedRuleOutputStream.write(DEFAULT_FORMAT_VERSION_BYTES);
+ int totalBytesWritten = DEFAULT_FORMAT_VERSION_BYTES.length;
+
+ String expectedIndexingBytesForPackageNameIndexed =
+ SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
+ for (int count = 0; count < ruleCount; count++) {
+ String packageName = String.format("%s%04d", packagePrefix, count);
+ if (count > 0 && count % INDEXING_BLOCK_SIZE == 0) {
+ expectedIndexingBytesForPackageNameIndexed +=
+ IS_NOT_HASHED
+ + getBits(packageName.length(), VALUE_SIZE_BITS)
+ + getValueBits(packageName)
+ + getBits(totalBytesWritten, /* numOfBits= */ 32);
+ }
+
+ byte[] bytesForPackage =
+ getBytes(
+ getSerializedCompoundRuleWithPackageNameAndSampleInstallerName(
+ packageName));
+ expectedOrderedRuleOutputStream.write(bytesForPackage);
+ totalBytesWritten += bytesForPackage.length;
+ }
+ expectedIndexingBytesForPackageNameIndexed +=
+ SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
+
+ String expectedIndexingBytesForAppCertificateIndexed =
+ SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
+ for (int count = 0; count < ruleCount; count++) {
+ String appCertificate = String.format("%s%04d", appCertificatePrefix, count);
+ if (count > 0 && count % INDEXING_BLOCK_SIZE == 0) {
+ expectedIndexingBytesForAppCertificateIndexed +=
+ IS_NOT_HASHED
+ + getBits(appCertificate.length(), VALUE_SIZE_BITS)
+ + getValueBits(appCertificate)
+ + getBits(totalBytesWritten, /* numOfBits= */ 32);
+ }
+
+ byte[] bytesForPackage =
+ getBytes(
+ getSerializedCompoundRuleWithCertificateNameAndSampleInstallerName(
+ appCertificate));
+ expectedOrderedRuleOutputStream.write(bytesForPackage);
+ totalBytesWritten += bytesForPackage.length;
+ }
+ expectedIndexingBytesForAppCertificateIndexed +=
+ SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
+
+ String expectedIndexingBytesForUnindexed =
+ SERIALIZED_START_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
+ for (int count = 0; count < ruleCount; count++) {
+ byte[] bytesForPackage =
+ getBytes(
+ getSerializedCompoundRuleWithInstallerNameAndInstallerCert(
+ String.format("%s%04d", installerNamePrefix, count)));
+ expectedOrderedRuleOutputStream.write(bytesForPackage);
+ totalBytesWritten += bytesForPackage.length;
+ }
+ expectedIndexingBytesForUnindexed +=
+ SERIALIZED_END_INDEXING_KEY + getBits(totalBytesWritten, /* numOfBits= */ 32);
+ expectedIndexingOutputStream.write(
+ getBytes(
+ expectedIndexingBytesForPackageNameIndexed
+ + expectedIndexingBytesForAppCertificateIndexed
+ + expectedIndexingBytesForUnindexed));
+
+ assertThat(ruleOutputStream.toByteArray())
+ .isEqualTo(expectedOrderedRuleOutputStream.toByteArray());
+ assertThat(indexingOutputStream.toByteArray())
+ .isEqualTo(expectedIndexingOutputStream.toByteArray());
}
private Rule getRuleWithPackageNameAndSampleInstallerName(String packageName) {
@@ -616,6 +694,44 @@
+ END_BIT;
}
+ private Rule getNonIndexedRuleWithInstallerName(String installerName) {
+ return new Rule(
+ new CompoundFormula(
+ CompoundFormula.AND,
+ Arrays.asList(
+ new AtomicFormula.StringAtomicFormula(
+ AtomicFormula.INSTALLER_NAME,
+ installerName,
+ /* isHashedValue= */ false),
+ new AtomicFormula.StringAtomicFormula(
+ AtomicFormula.INSTALLER_CERTIFICATE,
+ SAMPLE_INSTALLER_CERT,
+ /* isHashedValue= */ false))),
+ Rule.DENY);
+ }
+
+ private String getSerializedCompoundRuleWithInstallerNameAndInstallerCert(
+ String installerName) {
+ return START_BIT
+ + COMPOUND_FORMULA_START_BITS
+ + AND
+ + ATOMIC_FORMULA_START_BITS
+ + INSTALLER_NAME
+ + EQ
+ + IS_NOT_HASHED
+ + getBits(installerName.length(), VALUE_SIZE_BITS)
+ + getValueBits(installerName)
+ + ATOMIC_FORMULA_START_BITS
+ + INSTALLER_CERTIFICATE
+ + EQ
+ + IS_NOT_HASHED
+ + getBits(SAMPLE_INSTALLER_CERT.length(), VALUE_SIZE_BITS)
+ + getValueBits(SAMPLE_INSTALLER_CERT)
+ + COMPOUND_FORMULA_END_BITS
+ + DENY
+ + END_BIT;
+ }
+
private static Formula getInvalidFormula() {
return new Formula() {
@Override
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
index 55fada4..1674422 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleIndexingDetailsIdentifierTest.java
@@ -38,10 +38,8 @@
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
-import java.util.TreeMap;
/** Unit tests for {@link RuleIndexingDetailsIdentifier}. */
@RunWith(JUnit4.class)
@@ -140,7 +138,7 @@
List<Rule> ruleList = new ArrayList();
ruleList.add(RULE_WITH_PACKAGE_NAME);
- Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
+ Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
// Verify the resulting map content.
assertThat(result.keySet())
@@ -157,7 +155,7 @@
List<Rule> ruleList = new ArrayList();
ruleList.add(RULE_WITH_APP_CERTIFICATE);
- Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
+ Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
assertThat(result.keySet())
.containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
@@ -174,7 +172,7 @@
List<Rule> ruleList = new ArrayList();
ruleList.add(RULE_WITH_INSTALLER_RESTRICTIONS);
- Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
+ Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
assertThat(result.keySet())
.containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
@@ -189,7 +187,7 @@
List<Rule> ruleList = new ArrayList();
ruleList.add(RULE_WITH_NONSTRING_RESTRICTIONS);
- Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
+ Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
assertThat(result.keySet())
.containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
@@ -215,7 +213,7 @@
List<Rule> ruleList = new ArrayList();
ruleList.add(negatedRule);
- Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
+ Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
assertThat(result.keySet())
.containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
@@ -225,7 +223,7 @@
}
@Test
- public void getIndexType_allRulesTogetherInValidOrder() {
+ public void getIndexType_allRulesTogetherSplitCorrectly() {
Rule packageNameRuleA = getRuleWithPackageName("aaa");
Rule packageNameRuleB = getRuleWithPackageName("bbb");
Rule packageNameRuleC = getRuleWithPackageName("ccc");
@@ -243,38 +241,20 @@
ruleList.add(RULE_WITH_INSTALLER_RESTRICTIONS);
ruleList.add(RULE_WITH_NONSTRING_RESTRICTIONS);
- Map<Integer, TreeMap<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
+ Map<Integer, Map<String, List<Rule>>> result = splitRulesIntoIndexBuckets(ruleList);
assertThat(result.keySet())
.containsExactly(NOT_INDEXED, PACKAGE_NAME_INDEXED, APP_CERTIFICATE_INDEXED);
// We check asserts this way to ensure ordering based on package name.
assertThat(result.get(PACKAGE_NAME_INDEXED).keySet()).containsExactly("aaa", "bbb", "ccc");
- Iterator<String> keySetIterator = result.get(PACKAGE_NAME_INDEXED).keySet().iterator();
- assertThat(keySetIterator.next()).isEqualTo("aaa");
- assertThat(keySetIterator.next()).isEqualTo("bbb");
- assertThat(keySetIterator.next()).isEqualTo("ccc");
- assertThat(result.get(PACKAGE_NAME_INDEXED).get("aaa")).containsExactly(packageNameRuleA);
- assertThat(result.get(PACKAGE_NAME_INDEXED).get("bbb")).containsExactly(packageNameRuleB);
- assertThat(result.get(PACKAGE_NAME_INDEXED).get("ccc")).containsExactly(packageNameRuleC);
// We check asserts this way to ensure ordering based on app certificate.
assertThat(result.get(APP_CERTIFICATE_INDEXED).keySet()).containsExactly("cert1", "cert2",
"cert3");
- keySetIterator = result.get(APP_CERTIFICATE_INDEXED).keySet().iterator();
- assertThat(keySetIterator.next()).isEqualTo("cert1");
- assertThat(keySetIterator.next()).isEqualTo("cert2");
- assertThat(keySetIterator.next()).isEqualTo("cert3");
- assertThat(result.get(APP_CERTIFICATE_INDEXED).get("cert1")).containsExactly(
- certificateRule1);
- assertThat(result.get(APP_CERTIFICATE_INDEXED).get("cert2")).containsExactly(
- certificateRule2);
- assertThat(result.get(APP_CERTIFICATE_INDEXED).get("cert3")).containsExactly(
- certificateRule3);
assertThat(result.get(NOT_INDEXED).get("N/A"))
- .containsExactly(
- RULE_WITH_INSTALLER_RESTRICTIONS,
+ .containsExactly(RULE_WITH_INSTALLER_RESTRICTIONS,
RULE_WITH_NONSTRING_RESTRICTIONS);
}
diff --git a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleXmlSerializerTest.java b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleXmlSerializerTest.java
index 0bb2d44..ff7722c 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleXmlSerializerTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/serializer/RuleXmlSerializerTest.java
@@ -145,7 +145,8 @@
xmlSerializer.serialize(
Collections.singletonList(rule),
/* formatVersion= */ Optional.empty(),
- outputStream);
+ outputStream,
+ new ByteArrayOutputStream());
byte[] actualRules = outputStream.toString().getBytes(StandardCharsets.UTF_8);
assertEquals(expectedRules, new String(actualRules, StandardCharsets.UTF_8));
diff --git a/services/tests/servicestests/src/com/android/server/location/CustomCountryDetectorTestClass.java b/services/tests/servicestests/src/com/android/server/location/CustomCountryDetectorTestClass.java
new file mode 100644
index 0000000..e159012
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/location/CustomCountryDetectorTestClass.java
@@ -0,0 +1,36 @@
+/*
+ * 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 com.android.server.location;
+
+import android.content.Context;
+import android.location.Country;
+
+public class CustomCountryDetectorTestClass extends CountryDetectorBase {
+ public CustomCountryDetectorTestClass(Context ctx) {
+ super(ctx);
+ }
+
+ @Override
+ public Country detectCountry() {
+ return null;
+ }
+
+ @Override
+ public void stop() {
+
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java
new file mode 100644
index 0000000..54c552b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowDataTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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 com.android.server.locksettings;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import javax.crypto.spec.SecretKeySpec;
+
+/**
+ * atest FrameworksServicesTests:RebootEscrowDataTest
+ */
+@RunWith(AndroidJUnit4.class)
+public class RebootEscrowDataTest {
+ private static byte[] getTestSp() {
+ byte[] testSp = new byte[10];
+ for (int i = 0; i < testSp.length; i++) {
+ testSp[i] = (byte) i;
+ }
+ return testSp;
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void fromEntries_failsOnNull() throws Exception {
+ RebootEscrowData.fromSyntheticPassword((byte) 2, null);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void fromEncryptedData_failsOnNullData() throws Exception {
+ byte[] testSp = getTestSp();
+ RebootEscrowData expected = RebootEscrowData.fromSyntheticPassword((byte) 2, testSp);
+ SecretKeySpec key = RebootEscrowData.fromKeyBytes(expected.getKey());
+ RebootEscrowData.fromEncryptedData(key, null);
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void fromEncryptedData_failsOnNullKey() throws Exception {
+ byte[] testSp = getTestSp();
+ RebootEscrowData expected = RebootEscrowData.fromSyntheticPassword((byte) 2, testSp);
+ RebootEscrowData.fromEncryptedData(null, expected.getBlob());
+ }
+
+ @Test
+ public void fromEntries_loopback_success() throws Exception {
+ byte[] testSp = getTestSp();
+ RebootEscrowData expected = RebootEscrowData.fromSyntheticPassword((byte) 2, testSp);
+
+ SecretKeySpec key = RebootEscrowData.fromKeyBytes(expected.getKey());
+ RebootEscrowData actual = RebootEscrowData.fromEncryptedData(key, expected.getBlob());
+
+ assertThat(actual.getSpVersion(), is(expected.getSpVersion()));
+ assertThat(actual.getIv(), is(expected.getIv()));
+ assertThat(actual.getKey(), is(expected.getKey()));
+ assertThat(actual.getBlob(), is(expected.getBlob()));
+ assertThat(actual.getSyntheticPassword(), is(expected.getSyntheticPassword()));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
new file mode 100644
index 0000000..78a5a0b
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.locksettings;
+
+import static android.content.pm.UserInfo.FLAG_FULL;
+import static android.content.pm.UserInfo.FLAG_PRIMARY;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.UserInfo;
+import android.hardware.rebootescrow.IRebootEscrow;
+import android.os.RemoteException;
+import android.os.UserManager;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.widget.RebootEscrowListener;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.util.ArrayList;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class RebootEscrowManagerTests {
+ protected static final int PRIMARY_USER_ID = 0;
+ protected static final int NONSECURE_USER_ID = 10;
+ private static final byte FAKE_SP_VERSION = 1;
+ private static final byte[] FAKE_AUTH_TOKEN = new byte[] {
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
+ };
+
+ private Context mContext;
+ private UserManager mUserManager;
+ private RebootEscrowManager.Callbacks mCallbacks;
+ private IRebootEscrow mRebootEscrow;
+
+ LockSettingsStorageTestable mStorage;
+
+ private RebootEscrowManager mService;
+
+ static class MockInjector extends RebootEscrowManager.Injector {
+ private final IRebootEscrow mRebootEscrow;
+ private final UserManager mUserManager;
+
+ MockInjector(Context context, UserManager userManager, IRebootEscrow rebootEscrow) {
+ super(context);
+ mRebootEscrow = rebootEscrow;
+ mUserManager = userManager;
+ }
+
+ @Override
+ public UserManager getUserManager() {
+ return mUserManager;
+ }
+
+ @Override
+ public IRebootEscrow getRebootEscrow() {
+ return mRebootEscrow;
+ }
+ }
+
+ @Before
+ public void setUp_baseServices() throws Exception {
+ mContext = mock(Context.class);
+ mUserManager = mock(UserManager.class);
+ mCallbacks = mock(RebootEscrowManager.Callbacks.class);
+ mRebootEscrow = mock(IRebootEscrow.class);
+
+ mStorage = new LockSettingsStorageTestable(mContext,
+ new File(InstrumentationRegistry.getContext().getFilesDir(), "locksettings"));
+
+ ArrayList<UserInfo> users = new ArrayList<>();
+ users.add(new UserInfo(PRIMARY_USER_ID, "primary", FLAG_PRIMARY));
+ users.add(new UserInfo(NONSECURE_USER_ID, "non-secure", FLAG_FULL));
+ when(mUserManager.getUsers()).thenReturn(users);
+ when(mCallbacks.isUserSecure(PRIMARY_USER_ID)).thenReturn(true);
+ when(mCallbacks.isUserSecure(NONSECURE_USER_ID)).thenReturn(false);
+ mService = new RebootEscrowManager(new MockInjector(mContext, mUserManager, mRebootEscrow),
+ mCallbacks, mStorage);
+ }
+
+ @Test
+ public void prepareRebootEscrow_Success() throws Exception {
+ RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
+ mService.setRebootEscrowListener(mockListener);
+ mService.prepareRebootEscrow();
+
+ clearInvocations(mRebootEscrow);
+ mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+ verify(mockListener).onPreparedForReboot(eq(true));
+ verify(mRebootEscrow, never()).storeKey(any());
+ }
+
+ @Test
+ public void prepareRebootEscrow_ClearCredentials_Success() throws Exception {
+ RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
+ mService.setRebootEscrowListener(mockListener);
+ mService.prepareRebootEscrow();
+ mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+ verify(mockListener).onPreparedForReboot(eq(true));
+
+ clearInvocations(mRebootEscrow);
+ mService.clearRebootEscrow();
+ verify(mockListener).onPreparedForReboot(eq(false));
+ verify(mRebootEscrow).storeKey(eq(new byte[32]));
+ }
+
+ @Test
+ public void armService_Success() throws Exception {
+ RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
+ mService.setRebootEscrowListener(mockListener);
+ mService.prepareRebootEscrow();
+
+ clearInvocations(mRebootEscrow);
+ mService.callToRebootEscrowIfNeeded(PRIMARY_USER_ID, FAKE_SP_VERSION, FAKE_AUTH_TOKEN);
+ verify(mockListener).onPreparedForReboot(eq(true));
+ verify(mRebootEscrow, never()).storeKey(any());
+
+ assertTrue(mService.armRebootEscrowIfNeeded());
+ verify(mRebootEscrow).storeKey(any());
+ }
+
+ @Test
+ public void armService_NoInitialization_Failure() throws Exception {
+ assertFalse(mService.armRebootEscrowIfNeeded());
+ verifyNoMoreInteractions(mRebootEscrow);
+ }
+
+ @Test
+ public void armService_RebootEscrowServiceException_Failure() throws Exception {
+ doThrow(RemoteException.class).when(mRebootEscrow).storeKey(any());
+ assertFalse(mService.armRebootEscrowIfNeeded());
+ verifyNoMoreInteractions(mRebootEscrow);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
index 821d97a..670bd81 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
@@ -67,13 +67,25 @@
public class PlatformKeyManagerTest {
private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
- private static final int USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS = 15;
+ private static final int MIN_GENERATION_ID = 1000000;
+ private static final int PRIMARY_USER_ID_FIXTURE = 0;
private static final int USER_ID_FIXTURE = 42;
private static final long USER_SID = 4200L;
private static final String KEY_ALGORITHM = "AES";
private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
private static final String TESTING_KEYSTORE_KEY_ALIAS = "testing-key-store-key-alias";
+ private static final String ENCRYPTION_KEY_ALIAS_1 =
+ "com.android.server.locksettings.recoverablekeystore/platform/42/1000000/encrypt";
+ private static final String DECRYPTION_KEY_ALIAS_1 =
+ "com.android.server.locksettings.recoverablekeystore/platform/42/1000000/decrypt";
+ private static final String DECRYPTION_KEY_FOR_ALIAS_PRIMARY_USER_1 =
+ "com.android.server.locksettings.recoverablekeystore/platform/0/1000000/decrypt";
+ private static final String ENCRYPTION_KEY_ALIAS_2 =
+ "com.android.server.locksettings.recoverablekeystore/platform/42/1000001/encrypt";
+ private static final String DECRYPTION_KEY_ALIAS_2 =
+ "com.android.server.locksettings.recoverablekeystore/platform/42/1000001/decrypt";
+
@Mock private Context mContext;
@Mock private KeyStoreProxy mKeyStoreProxy;
@Mock private KeyguardManager mKeyguardManager;
@@ -114,7 +126,7 @@
mPlatformKeyManager.init(USER_ID_FIXTURE);
verify(mKeyStoreProxy).setEntry(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
+ eq(ENCRYPTION_KEY_ALIAS_1),
any(),
any());
}
@@ -156,7 +168,7 @@
mPlatformKeyManager.init(USER_ID_FIXTURE);
verify(mKeyStoreProxy).setEntry(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+ eq(DECRYPTION_KEY_ALIAS_1),
any(),
any());
}
@@ -187,19 +199,33 @@
}
@Test
- public void init_createsDecryptKeyWithAuthenticationRequired() throws Exception {
- mPlatformKeyManager.init(USER_ID_FIXTURE);
+ public void init_primaryUser_createsDecryptKeyWithUnlockedDeviceRequired() throws Exception {
+ mPlatformKeyManager.init(PRIMARY_USER_ID_FIXTURE);
- assertTrue(getDecryptKeyProtection().isUserAuthenticationRequired());
+ assertTrue(getDecryptKeyProtectionForPrimaryUser().isUnlockedDeviceRequired());
}
@Test
- public void init_createsDecryptKeyWithAuthenticationValidFor15Seconds() throws Exception {
+ public void init_primaryUser_createsDecryptKeyWithoutAuthenticationRequired() throws Exception {
+ mPlatformKeyManager.init(PRIMARY_USER_ID_FIXTURE);
+
+ assertFalse(getDecryptKeyProtectionForPrimaryUser().isUserAuthenticationRequired());
+ }
+
+ @Test
+ public void init_secondaryUser_createsDecryptKeyWithoutUnlockedDeviceRequired()
+ throws Exception {
mPlatformKeyManager.init(USER_ID_FIXTURE);
- assertEquals(
- USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS,
- getDecryptKeyProtection().getUserAuthenticationValidityDurationSeconds());
+ assertFalse(getDecryptKeyProtection().isUnlockedDeviceRequired());
+ }
+
+ @Test
+ public void init_secondaryUserUser_createsDecryptKeyWithAuthenticationRequired()
+ throws Exception {
+ mPlatformKeyManager.init(USER_ID_FIXTURE);
+
+ assertTrue(getDecryptKeyProtection().isUserAuthenticationRequired());
}
@Test
@@ -219,7 +245,7 @@
mPlatformKeyManager.init(USER_ID_FIXTURE);
verify(mKeyStoreProxy, never()).setEntry(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+ eq(DECRYPTION_KEY_ALIAS_1),
any(),
any());
}
@@ -231,7 +257,7 @@
expectThrows(RemoteException.class, () -> mPlatformKeyManager.init(USER_ID_FIXTURE));
verify(mKeyStoreProxy, never()).setEntry(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+ eq(DECRYPTION_KEY_ALIAS_1),
any(),
any());
}
@@ -251,15 +277,15 @@
public void init_savesGenerationIdToDatabase() throws Exception {
mPlatformKeyManager.init(USER_ID_FIXTURE);
- assertEquals(1,
+ assertEquals(MIN_GENERATION_ID,
mRecoverableKeyStoreDb.getPlatformKeyGenerationId(USER_ID_FIXTURE));
}
@Test
- public void init_setsGenerationIdTo1() throws Exception {
+ public void init_setsGenerationId() throws Exception {
mPlatformKeyManager.init(USER_ID_FIXTURE);
- assertEquals(1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
+ assertEquals(MIN_GENERATION_ID, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
}
@Test
@@ -268,22 +294,20 @@
mPlatformKeyManager.init(USER_ID_FIXTURE);
- assertEquals(2, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
+ assertEquals(MIN_GENERATION_ID + 1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
}
@Test
public void init_doesNotIncrementGenerationIdIfKeyAvailable() throws Exception {
mPlatformKeyManager.init(USER_ID_FIXTURE);
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/decrypt")).thenReturn(true);
+ .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true);
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/encrypt")).thenReturn(true);
+ .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
mPlatformKeyManager.init(USER_ID_FIXTURE);
- assertEquals(1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
+ assertEquals(MIN_GENERATION_ID, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
}
@Test
@@ -294,226 +318,194 @@
@Test
public void getDecryptKey_getsDecryptKeyWithCorrectAlias() throws Exception {
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/decrypt")).thenReturn(true);
+ .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true);
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/encrypt")).thenReturn(true);
+ .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
when(mKeyStoreProxy.getKey(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+ eq(DECRYPTION_KEY_ALIAS_1),
any())).thenReturn(generateAndroidKeyStoreKey());
mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE);
verify(mKeyStoreProxy).getKey(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+ eq(DECRYPTION_KEY_ALIAS_1),
any());
}
@Test
public void getDecryptKey_generatesNewKeyIfOldDecryptKeyWasRemoved() throws Exception {
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/encrypt")).thenReturn(true);
+ .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/decrypt")).thenReturn(false); // was removed
+ .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/2/encrypt")).thenReturn(true); // new version is available
+ .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true); // new version
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/2/decrypt")).thenReturn(true);
+ .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true);
when(mKeyStoreProxy.getKey(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+ eq(DECRYPTION_KEY_ALIAS_2),
any())).thenReturn(generateAndroidKeyStoreKey());
mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE);
verify(mKeyStoreProxy).containsAlias(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"));
+ eq(DECRYPTION_KEY_ALIAS_1));
// Attempt to get regenerated key.
verify(mKeyStoreProxy).getKey(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+ eq(DECRYPTION_KEY_ALIAS_2),
any());
}
@Test
public void getDecryptKey_generatesNewKeyIfOldEncryptKeyWasRemoved() throws Exception {
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/encrypt")).thenReturn(false); // was removed
+ .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/decrypt")).thenReturn(true);
+ .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true);
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/2/encrypt")).thenReturn(true);
+ .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true);
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/2/decrypt")).thenReturn(true);
+ .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true);
mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE);
verify(mKeyStoreProxy).containsAlias(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"));
+ eq(ENCRYPTION_KEY_ALIAS_1));
// Attempt to get regenerated key.
verify(mKeyStoreProxy).getKey(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+ eq(DECRYPTION_KEY_ALIAS_2),
any());
}
@Test
public void getEncryptKey_generatesNewKeyIfOldOneIsInvalid() throws Exception {
doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
+ eq(ENCRYPTION_KEY_ALIAS_1),
any());
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/encrypt")).thenReturn(true);
+ .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/decrypt")).thenReturn(true);
+ .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true);
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/2/encrypt")).thenReturn(true);
+ .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true);
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/2/decrypt")).thenReturn(true);
+ .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true);
mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
verify(mKeyStoreProxy).getKey(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
+ eq(ENCRYPTION_KEY_ALIAS_1),
any());
// Attempt to get regenerated key.
verify(mKeyStoreProxy).getKey(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"),
+ eq(ENCRYPTION_KEY_ALIAS_2),
any());
}
@Test
public void getDecryptKey_generatesNewKeyIfOldOneIsInvalid() throws Exception {
doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+ eq(DECRYPTION_KEY_ALIAS_1),
any());
when(mKeyStoreProxy.getKey(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+ eq(DECRYPTION_KEY_ALIAS_2),
any())).thenReturn(generateAndroidKeyStoreKey());
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/encrypt")).thenReturn(true);
+ .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/decrypt")).thenReturn(true);
+ .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true);
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/2/encrypt")).thenReturn(true);
+ .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true);
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/2/decrypt")).thenReturn(true);
+ .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true);
mPlatformKeyManager.getDecryptKey(USER_ID_FIXTURE);
verify(mKeyStoreProxy).containsAlias(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"));
+ eq(DECRYPTION_KEY_ALIAS_1));
// Attempt to get regenerated key.
verify(mKeyStoreProxy).getKey(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+ eq(DECRYPTION_KEY_ALIAS_2),
any());
}
@Test
public void getEncryptKey_generatesNewKeyIfDecryptKeyIsUnrecoverable() throws Exception {
doThrow(new UnrecoverableKeyException()).when(mKeyStoreProxy).getKey(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+ eq(DECRYPTION_KEY_ALIAS_1),
any());
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/encrypt")).thenReturn(true);
+ .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/decrypt")).thenReturn(false); // was removed
+ .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/2/encrypt")).thenReturn(true); // new version is available
+ .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true); // new version
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/2/decrypt")).thenReturn(true);
+ .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true);
mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
// Attempt to get regenerated key.
verify(mKeyStoreProxy).getKey(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"),
+ eq(ENCRYPTION_KEY_ALIAS_2),
any());
}
@Test
public void getEncryptKey_generatesNewKeyIfOldDecryptKeyWasRemoved() throws Exception {
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/encrypt")).thenReturn(true);
+ .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/decrypt")).thenReturn(false); // was removed
+ .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/2/encrypt")).thenReturn(true); // new version is available
+ .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true); // new version
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/2/decrypt")).thenReturn(true);
+ .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true);
mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
verify(mKeyStoreProxy).containsAlias(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"));
+ eq(ENCRYPTION_KEY_ALIAS_1));
// Attempt to get regenerated key.
verify(mKeyStoreProxy).getKey(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"),
+ eq(ENCRYPTION_KEY_ALIAS_2),
any());
}
@Test
public void getEncryptKey_generatesNewKeyIfOldEncryptKeyWasRemoved() throws Exception {
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/encrypt")).thenReturn(false); // was removed
+ .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(false); // was removed
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/decrypt")).thenReturn(true);
+ .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true);
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/2/encrypt")).thenReturn(true);
+ .containsAlias(ENCRYPTION_KEY_ALIAS_2)).thenReturn(true);
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/2/decrypt")).thenReturn(true);
+ .containsAlias(DECRYPTION_KEY_ALIAS_2)).thenReturn(true);
mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
verify(mKeyStoreProxy).containsAlias(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"));
+ eq(ENCRYPTION_KEY_ALIAS_1));
// Attempt to get regenerated key.
verify(mKeyStoreProxy).getKey(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"),
+ eq(ENCRYPTION_KEY_ALIAS_2),
any());
}
@Test
public void getEncryptKey_getsEncryptKeyWithCorrectAlias() throws Exception {
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/encrypt")).thenReturn(true);
+ .containsAlias(ENCRYPTION_KEY_ALIAS_1)).thenReturn(true);
when(mKeyStoreProxy
- .containsAlias("com.android.server.locksettings.recoverablekeystore/"
- + "platform/42/1/decrypt")).thenReturn(true);
+ .containsAlias(DECRYPTION_KEY_ALIAS_1)).thenReturn(true);
mPlatformKeyManager.getEncryptKey(USER_ID_FIXTURE);
verify(mKeyStoreProxy).getKey(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
+ eq(ENCRYPTION_KEY_ALIAS_1),
any());
}
@@ -523,7 +515,7 @@
mPlatformKeyManager.regenerate(USER_ID_FIXTURE);
- assertEquals(2, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
+ assertEquals(MIN_GENERATION_ID + 1, mPlatformKeyManager.getGenerationId(USER_ID_FIXTURE));
}
@Test
@@ -533,17 +525,17 @@
mPlatformKeyManager.regenerate(USER_ID_FIXTURE);
verify(mKeyStoreProxy).deleteEntry(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"));
+ eq(ENCRYPTION_KEY_ALIAS_1));
verify(mKeyStoreProxy).deleteEntry(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"));
+ eq(DECRYPTION_KEY_ALIAS_1));
mPlatformKeyManager.regenerate(USER_ID_FIXTURE);
// Removes second generation keys.
verify(mKeyStoreProxy).deleteEntry(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"));
+ eq(ENCRYPTION_KEY_ALIAS_2));
verify(mKeyStoreProxy).deleteEntry(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"));
+ eq(DECRYPTION_KEY_ALIAS_2));
}
@Test
@@ -553,7 +545,7 @@
mPlatformKeyManager.regenerate(USER_ID_FIXTURE);
verify(mKeyStoreProxy).setEntry(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/encrypt"),
+ eq(ENCRYPTION_KEY_ALIAS_2),
any(),
any());
}
@@ -565,14 +557,14 @@
mPlatformKeyManager.regenerate(USER_ID_FIXTURE);
verify(mKeyStoreProxy).setEntry(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/2/decrypt"),
+ eq(DECRYPTION_KEY_ALIAS_2),
any(),
any());
}
private KeyProtection getEncryptKeyProtection() throws Exception {
verify(mKeyStoreProxy).setEntry(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/encrypt"),
+ eq(ENCRYPTION_KEY_ALIAS_1),
any(),
mProtectionParameterCaptor.capture());
return (KeyProtection) mProtectionParameterCaptor.getValue();
@@ -580,7 +572,15 @@
private KeyProtection getDecryptKeyProtection() throws Exception {
verify(mKeyStoreProxy).setEntry(
- eq("com.android.server.locksettings.recoverablekeystore/platform/42/1/decrypt"),
+ eq(DECRYPTION_KEY_ALIAS_1),
+ any(),
+ mProtectionParameterCaptor.capture());
+ return (KeyProtection) mProtectionParameterCaptor.getValue();
+ }
+
+ private KeyProtection getDecryptKeyProtectionForPrimaryUser() throws Exception {
+ verify(mKeyStoreProxy).setEntry(
+ eq(DECRYPTION_KEY_FOR_ALIAS_PRIMARY_USER_1),
any(),
mProtectionParameterCaptor.capture());
return (KeyProtection) mProtectionParameterCaptor.getValue();
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
index 6890017..ac74470 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
@@ -29,6 +29,7 @@
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeast;
@@ -84,7 +85,7 @@
import java.util.ArrayList;
import java.util.Map;
import java.util.Random;
-import java.util.concurrent.ExecutorService;
+import java.util.concurrent.ScheduledExecutorService;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
@@ -157,7 +158,7 @@
@Mock private PlatformKeyManager mPlatformKeyManager;
@Mock private ApplicationKeyStorage mApplicationKeyStorage;
@Mock private CleanupManager mCleanupManager;
- @Mock private ExecutorService mExecutorService;
+ @Mock private ScheduledExecutorService mExecutorService;
@Spy private TestOnlyInsecureCertificateHelper mTestOnlyInsecureCertificateHelper;
private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
@@ -1253,7 +1254,7 @@
mRecoverableKeyStoreManager.lockScreenSecretAvailable(
LockPatternUtils.CREDENTIAL_TYPE_PATTERN, "password".getBytes(), 11);
- verify(mExecutorService).execute(any());
+ verify(mExecutorService).schedule(any(Runnable.class), anyLong(), any());
}
@Test
@@ -1263,7 +1264,7 @@
"password".getBytes(),
11);
- verify(mExecutorService).execute(any());
+ verify(mExecutorService).schedule(any(Runnable.class), anyLong(), any());
}
private static byte[] encryptedApplicationKey(
diff --git a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
index 7529bc5..355cada 100644
--- a/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/net/NetworkPolicyManagerServiceTest.java
@@ -37,6 +37,7 @@
import static android.net.NetworkStats.IFACE_ALL;
import static android.net.NetworkStats.SET_ALL;
import static android.net.NetworkStats.TAG_ALL;
+import static android.net.NetworkStats.TAG_NONE;
import static android.net.NetworkTemplate.buildTemplateMobileAll;
import static android.net.NetworkTemplate.buildTemplateWifi;
import static android.net.TrafficStats.MB_IN_BYTES;
@@ -111,7 +112,7 @@
import android.net.NetworkStats;
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
-import android.net.StringNetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
import android.os.Binder;
import android.os.Handler;
import android.os.INetworkManagementService;
@@ -1138,11 +1139,11 @@
new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(360), 0L, 0L, 0L, 0));
reset(mTelephonyManager, mNetworkManager, mNotifManager);
- expectMobileDefaults();
+ TelephonyManager tmSub = expectMobileDefaults();
mService.updateNetworks();
- verify(mTelephonyManager, atLeastOnce()).setPolicyDataEnabled(true, TEST_SUB_ID);
+ verify(tmSub, atLeastOnce()).setPolicyDataEnabled(true);
verify(mNetworkManager, atLeastOnce()).setInterfaceQuota(TEST_IFACE,
DataUnit.MEGABYTES.toBytes(1800 - 360));
verify(mNotifManager, never()).notifyAsUser(any(), anyInt(), any(), any());
@@ -1155,11 +1156,11 @@
new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1799), 0L, 0L, 0L, 0));
reset(mTelephonyManager, mNetworkManager, mNotifManager);
- expectMobileDefaults();
+ TelephonyManager tmSub = expectMobileDefaults();
mService.updateNetworks();
- verify(mTelephonyManager, atLeastOnce()).setPolicyDataEnabled(true, TEST_SUB_ID);
+ verify(tmSub, atLeastOnce()).setPolicyDataEnabled(true);
verify(mNetworkManager, atLeastOnce()).setInterfaceQuota(TEST_IFACE,
DataUnit.MEGABYTES.toBytes(1800 - 1799));
verify(mNotifManager, atLeastOnce()).notifyAsUser(any(), eq(TYPE_WARNING),
@@ -1173,12 +1174,12 @@
new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1799), 0L, 0L, 0L, 0));
reset(mTelephonyManager, mNetworkManager, mNotifManager);
- expectMobileDefaults();
+ TelephonyManager tmSub = expectMobileDefaults();
expectDefaultCarrierConfig();
mService.updateNetworks();
- verify(mTelephonyManager, atLeastOnce()).setPolicyDataEnabled(true, TEST_SUB_ID);
+ verify(tmSub, atLeastOnce()).setPolicyDataEnabled(true);
verify(mNetworkManager, atLeastOnce()).setInterfaceQuota(TEST_IFACE,
DataUnit.MEGABYTES.toBytes(1800 - 1799));
// Since this isn't from the identified carrier, there should be no notifications
@@ -1192,11 +1193,11 @@
new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1810), 0L, 0L, 0L, 0));
reset(mTelephonyManager, mNetworkManager, mNotifManager);
- expectMobileDefaults();
+ TelephonyManager tmSub = expectMobileDefaults();
mService.updateNetworks();
- verify(mTelephonyManager, atLeastOnce()).setPolicyDataEnabled(false, TEST_SUB_ID);
+ verify(tmSub, atLeastOnce()).setPolicyDataEnabled(false);
verify(mNetworkManager, atLeastOnce()).setInterfaceQuota(TEST_IFACE, 1);
verify(mNotifManager, atLeastOnce()).notifyAsUser(any(), eq(TYPE_LIMIT),
isA(Notification.class), eq(UserHandle.ALL));
@@ -1205,12 +1206,12 @@
// Snooze limit
{
reset(mTelephonyManager, mNetworkManager, mNotifManager);
- expectMobileDefaults();
+ TelephonyManager tmSub = expectMobileDefaults();
mService.snoozeLimit(NetworkTemplate.buildTemplateMobileAll(TEST_IMSI));
mService.updateNetworks();
- verify(mTelephonyManager, atLeastOnce()).setPolicyDataEnabled(true, TEST_SUB_ID);
+ verify(tmSub, atLeastOnce()).setPolicyDataEnabled(true);
verify(mNetworkManager, atLeastOnce()).setInterfaceQuota(TEST_IFACE,
Long.MAX_VALUE);
verify(mNotifManager, atLeastOnce()).notifyAsUser(any(), eq(TYPE_LIMIT_SNOOZED),
@@ -1273,11 +1274,11 @@
history.recordData(start, end,
new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1440), 0L, 0L, 0L, 0));
stats.clear();
- stats.addValues(IFACE_ALL, UID_A, SET_ALL, TAG_ALL,
+ stats.addEntry(IFACE_ALL, UID_A, SET_ALL, TAG_ALL,
DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0);
- stats.addValues(IFACE_ALL, UID_B, SET_ALL, TAG_ALL,
+ stats.addEntry(IFACE_ALL, UID_B, SET_ALL, TAG_ALL,
DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0);
- stats.addValues(IFACE_ALL, UID_C, SET_ALL, TAG_ALL,
+ stats.addEntry(IFACE_ALL, UID_C, SET_ALL, TAG_ALL,
DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0);
reset(mNotifManager);
@@ -1301,9 +1302,9 @@
history.recordData(start, end,
new NetworkStats.Entry(DataUnit.MEGABYTES.toBytes(1440), 0L, 0L, 0L, 0));
stats.clear();
- stats.addValues(IFACE_ALL, UID_A, SET_ALL, TAG_ALL,
+ stats.addEntry(IFACE_ALL, UID_A, SET_ALL, TAG_ALL,
DataUnit.MEGABYTES.toBytes(960), 0, 0, 0, 0);
- stats.addValues(IFACE_ALL, UID_B, SET_ALL, TAG_ALL,
+ stats.addEntry(IFACE_ALL, UID_B, SET_ALL, TAG_ALL,
DataUnit.MEGABYTES.toBytes(480), 0, 0, 0, 0);
reset(mNotifManager);
@@ -1758,6 +1759,59 @@
}
/**
+ * Test that when StatsProvider triggers limit reached, new limit will be calculated and
+ * re-armed.
+ */
+ @Test
+ public void testStatsProviderLimitReached() throws Exception {
+ final int CYCLE_DAY = 15;
+
+ final NetworkStats stats = new NetworkStats(0L, 1);
+ stats.addEntry(TEST_IFACE, UID_A, SET_ALL, TAG_NONE,
+ 2999, 1, 2000, 1, 0);
+ when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong()))
+ .thenReturn(stats.getTotalBytes());
+ when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong()))
+ .thenReturn(stats);
+
+ // Get active mobile network in place
+ expectMobileDefaults();
+ mService.updateNetworks();
+ verify(mStatsService).setStatsProviderLimit(TEST_IFACE, Long.MAX_VALUE);
+
+ // Set limit to 10KB.
+ setNetworkPolicies(new NetworkPolicy(
+ sTemplateMobileAll, CYCLE_DAY, TIMEZONE_UTC, WARNING_DISABLED, 10000L,
+ true));
+ postMsgAndWaitForCompletion();
+
+ // Verifies that remaining quota is set to providers.
+ verify(mStatsService).setStatsProviderLimit(TEST_IFACE, 10000L - 4999L);
+
+ reset(mStatsService);
+
+ // Increase the usage.
+ stats.addEntry(TEST_IFACE, UID_A, SET_ALL, TAG_NONE,
+ 1000, 1, 999, 1, 0);
+ when(mStatsService.getNetworkTotalBytes(any(), anyLong(), anyLong()))
+ .thenReturn(stats.getTotalBytes());
+ when(mStatsService.getNetworkUidBytes(any(), anyLong(), anyLong()))
+ .thenReturn(stats);
+
+ // Simulates that limit reached fires earlier by provider, but actually the quota is not
+ // yet reached.
+ final NetworkPolicyManagerInternal npmi = LocalServices
+ .getService(NetworkPolicyManagerInternal.class);
+ npmi.onStatsProviderLimitReached("TEST");
+
+ // Verifies that the limit reached leads to a force update and new limit should be set.
+ postMsgAndWaitForCompletion();
+ verify(mStatsService).forceUpdate();
+ postMsgAndWaitForCompletion();
+ verify(mStatsService).setStatsProviderLimit(TEST_IFACE, 10000L - 4999L - 1999L);
+ }
+
+ /**
* Exhaustively test isUidNetworkingBlocked to output the expected results based on external
* conditions.
*/
@@ -1859,7 +1913,8 @@
if (!roaming) {
nc.addCapability(NET_CAPABILITY_NOT_ROAMING);
}
- nc.setNetworkSpecifier(new StringNetworkSpecifier(String.valueOf(subId)));
+ nc.setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
+ .setSubscriptionId(subId).build());
return nc;
}
@@ -1928,10 +1983,11 @@
.thenReturn(CarrierConfigManager.getDefaultConfig());
}
- private void expectMobileDefaults() throws Exception {
- setupTelephonySubscriptionManagers(TEST_SUB_ID, TEST_IMSI);
- doNothing().when(mTelephonyManager).setPolicyDataEnabled(anyBoolean(), anyInt());
+ private TelephonyManager expectMobileDefaults() throws Exception {
+ TelephonyManager tmSub = setupTelephonySubscriptionManagers(TEST_SUB_ID, TEST_IMSI);
+ doNothing().when(tmSub).setPolicyDataEnabled(anyBoolean());
expectNetworkState(false /* roaming */);
+ return tmSub;
}
private void verifyAdvisePersistThreshold() throws Exception {
@@ -2090,8 +2146,10 @@
/**
* Creates a mock {@link TelephonyManager} and {@link SubscriptionManager}.
+ *
*/
- private void setupTelephonySubscriptionManagers(int subscriptionId, String subscriberId) {
+ private TelephonyManager setupTelephonySubscriptionManagers(int subscriptionId,
+ String subscriberId) {
when(mSubscriptionManager.getActiveSubscriptionInfoList()).thenReturn(
createSubscriptionInfoList(subscriptionId));
@@ -2101,6 +2159,7 @@
when(subTelephonyManager.getSubscriberId()).thenReturn(subscriberId);
when(mTelephonyManager.createForSubscriptionId(subscriptionId))
.thenReturn(subTelephonyManager);
+ return subTelephonyManager;
}
/**
diff --git a/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java b/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
new file mode 100644
index 0000000..d3166b9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/people/PeopleServiceTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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 com.android.server.people;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.prediction.AppPredictionContext;
+import android.app.prediction.AppPredictionSessionId;
+import android.app.prediction.AppTarget;
+import android.app.prediction.IPredictionCallback;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.os.Binder;
+import android.os.RemoteException;
+
+import com.android.server.LocalServices;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+@RunWith(JUnit4.class)
+public final class PeopleServiceTest {
+
+ private static final String APP_PREDICTION_SHARE_UI_SURFACE = "share";
+ private static final int APP_PREDICTION_TARGET_COUNT = 4;
+ private static final String TEST_PACKAGE_NAME = "com.example";
+
+ private PeopleServiceInternal mServiceInternal;
+ private PeopleService.LocalService mLocalService;
+ private AppPredictionSessionId mSessionId;
+ private AppPredictionContext mPredictionContext;
+
+ @Mock private Context mContext;
+ @Mock private IPredictionCallback mCallback;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ when(mContext.getPackageName()).thenReturn(TEST_PACKAGE_NAME);
+ when(mCallback.asBinder()).thenReturn(new Binder());
+
+ PeopleService service = new PeopleService(mContext);
+ service.onStart();
+
+ mServiceInternal = LocalServices.getService(PeopleServiceInternal.class);
+ mLocalService = (PeopleService.LocalService) mServiceInternal;
+
+ mSessionId = new AppPredictionSessionId("abc");
+ mPredictionContext = new AppPredictionContext.Builder(mContext)
+ .setUiSurface(APP_PREDICTION_SHARE_UI_SURFACE)
+ .setPredictedTargetCount(APP_PREDICTION_TARGET_COUNT)
+ .build();
+ }
+
+ @After
+ public void tearDown() {
+ LocalServices.removeServiceForTest(PeopleServiceInternal.class);
+ }
+
+ @Test
+ public void testRegisterCallbacks() throws RemoteException {
+ mServiceInternal.onCreatePredictionSession(mPredictionContext, mSessionId);
+
+ SessionInfo sessionInfo = mLocalService.getSessionInfo(mSessionId);
+
+ mServiceInternal.registerPredictionUpdates(mSessionId, mCallback);
+
+ Consumer<List<AppTarget>> updatePredictionMethod =
+ sessionInfo.getPredictor().getUpdatePredictionsMethod();
+ updatePredictionMethod.accept(new ArrayList<>());
+ updatePredictionMethod.accept(new ArrayList<>());
+
+ verify(mCallback, times(2)).onResult(any(ParceledListSlice.class));
+
+ mServiceInternal.unregisterPredictionUpdates(mSessionId, mCallback);
+
+ updatePredictionMethod.accept(new ArrayList<>());
+
+ // After the un-registration, the callback should no longer be called.
+ verify(mCallback, times(2)).onResult(any(ParceledListSlice.class));
+
+ mServiceInternal.onDestroyPredictionSession(mSessionId);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
index 82bbdcb..cb9d816 100644
--- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterTest.java
@@ -28,10 +28,12 @@
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.parsing.AndroidPackage;
+import android.content.pm.parsing.ComponentParseUtils;
import android.content.pm.parsing.ComponentParseUtils.ParsedActivity;
import android.content.pm.parsing.ComponentParseUtils.ParsedActivityIntentInfo;
import android.content.pm.parsing.PackageImpl;
import android.content.pm.parsing.ParsingPackage;
+import android.net.Uri;
import android.os.Build;
import android.os.Process;
import android.util.ArrayMap;
@@ -49,6 +51,7 @@
import org.mockito.MockitoAnnotations;
import java.util.Collections;
+import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -116,6 +119,15 @@
.addActivity(activity);
}
+ private static ParsingPackage pkgWithProvider(String packageName, String authority) {
+ ComponentParseUtils.ParsedProvider provider = new ComponentParseUtils.ParsedProvider();
+ provider.setPackageName(packageName);
+ provider.setExported(true);
+ provider.setAuthority(authority);
+ return pkg(packageName)
+ .addProvider(provider);
+ }
+
@Before
public void setup() throws Exception {
mExisting = new ArrayMap<>();
@@ -149,6 +161,55 @@
}
@Test
+ public void testQueriesProvider_FilterMatches() {
+ final AppsFilter appsFilter =
+ new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+ appsFilter.onSystemReady();
+
+ PackageSetting target = simulateAddPackage(appsFilter,
+ pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_UID);
+ PackageSetting calling = simulateAddPackage(appsFilter,
+ pkg("com.some.other.package",
+ new Intent().setData(Uri.parse("content://com.some.authority"))),
+ DUMMY_CALLING_UID);
+
+ assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
+ }
+
+ @Test
+ public void testQueriesDifferentProvider_Filters() {
+ final AppsFilter appsFilter =
+ new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+ appsFilter.onSystemReady();
+
+ PackageSetting target = simulateAddPackage(appsFilter,
+ pkgWithProvider("com.some.package", "com.some.authority"), DUMMY_TARGET_UID);
+ PackageSetting calling = simulateAddPackage(appsFilter,
+ pkg("com.some.other.package",
+ new Intent().setData(Uri.parse("content://com.some.other.authority"))),
+ DUMMY_CALLING_UID);
+
+ assertTrue(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
+ }
+
+ @Test
+ public void testQueriesProviderWithSemiColon_FilterMatches() {
+ final AppsFilter appsFilter =
+ new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
+ appsFilter.onSystemReady();
+
+ PackageSetting target = simulateAddPackage(appsFilter,
+ pkgWithProvider("com.some.package", "com.some.authority;com.some.other.authority"),
+ DUMMY_TARGET_UID);
+ PackageSetting calling = simulateAddPackage(appsFilter,
+ pkg("com.some.other.package",
+ new Intent().setData(Uri.parse("content://com.some.authority"))),
+ DUMMY_CALLING_UID);
+
+ assertFalse(appsFilter.shouldFilterApplication(DUMMY_CALLING_UID, calling, target, 0));
+ }
+
+ @Test
public void testQueriesAction_NoMatchingAction_Filters() {
final AppsFilter appsFilter =
new AppsFilter(mFeatureConfigMock, new String[]{}, false, null);
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
index c478ec4..15327b6 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageInstallerSessionTest.java
@@ -156,8 +156,7 @@
if (isMultiPackage) {
params.isMultiPackage = true;
}
- InstallSource installSource = InstallSource.create("testInstaller", null, "testInstaller",
- false);
+ InstallSource installSource = InstallSource.create("testInstaller", null, "testInstaller");
return new PackageInstallerSession(
/* callback */ null,
/* context */null,
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
index 1e760cc..ac27a08 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
@@ -139,18 +139,18 @@
assertEquals("A Name", mUserManagerService.getUserInfo(TEST_ID).name);
}
- /** Test UMS.getUserTypeForUser(). */
+ /** Test UMS.isUserOfType(). */
@Test
- public void testGetUserTypeForUser() throws Exception {
- final String typeSys = mUserManagerService.getUserTypeForUser(UserHandle.USER_SYSTEM);
- assertTrue("System user was of invalid type " + typeSys,
- typeSys.equals(USER_TYPE_SYSTEM_HEADLESS) || typeSys.equals(USER_TYPE_FULL_SYSTEM));
+ public void testIsUserOfType() throws Exception {
+ assertTrue("System user was of invalid type",
+ mUserManagerService.isUserOfType(UserHandle.USER_SYSTEM, USER_TYPE_SYSTEM_HEADLESS)
+ || mUserManagerService.isUserOfType(UserHandle.USER_SYSTEM, USER_TYPE_FULL_SYSTEM));
final int testId = 100;
final String typeName = "A type";
UserInfo userInfo = createUser(testId, 0, typeName);
mUserManagerService.putUserInfo(userInfo);
- assertEquals(typeName, mUserManagerService.getUserTypeForUser(testId));
+ assertTrue(mUserManagerService.isUserOfType(testId, typeName));
}
/** Tests upgradeIfNecessaryLP (but without locking) for upgrading from version 8 to 9+. */
@@ -169,22 +169,22 @@
mUserManagerService.upgradeIfNecessaryLP(null, versionToTest - 1);
- assertEquals(USER_TYPE_PROFILE_MANAGED, mUserManagerService.getUserTypeForUser(100));
+ assertTrue(mUserManagerService.isUserOfType(100, USER_TYPE_PROFILE_MANAGED));
assertTrue((mUserManagerService.getUserInfo(100).flags & FLAG_PROFILE) != 0);
- assertEquals(USER_TYPE_FULL_GUEST, mUserManagerService.getUserTypeForUser(101));
+ assertTrue(mUserManagerService.isUserOfType(101, USER_TYPE_FULL_GUEST));
- assertEquals(USER_TYPE_FULL_RESTRICTED, mUserManagerService.getUserTypeForUser(102));
+ assertTrue(mUserManagerService.isUserOfType(102, USER_TYPE_FULL_RESTRICTED));
assertTrue((mUserManagerService.getUserInfo(102).flags & FLAG_PROFILE) == 0);
- assertEquals(USER_TYPE_FULL_SECONDARY, mUserManagerService.getUserTypeForUser(103));
+ assertTrue(mUserManagerService.isUserOfType(103, USER_TYPE_FULL_SECONDARY));
assertTrue((mUserManagerService.getUserInfo(103).flags & FLAG_PROFILE) == 0);
- assertEquals(USER_TYPE_SYSTEM_HEADLESS, mUserManagerService.getUserTypeForUser(104));
+ assertTrue(mUserManagerService.isUserOfType(104, USER_TYPE_SYSTEM_HEADLESS));
- assertEquals(USER_TYPE_FULL_SYSTEM, mUserManagerService.getUserTypeForUser(105));
+ assertTrue(mUserManagerService.isUserOfType(105, USER_TYPE_FULL_SYSTEM));
- assertEquals(USER_TYPE_FULL_DEMO, mUserManagerService.getUserTypeForUser(106));
+ assertTrue(mUserManagerService.isUserOfType(106, USER_TYPE_FULL_DEMO));
}
/** Creates a UserInfo with the given flags and userType. */
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index 06b3dc1..77376f0 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -21,6 +21,7 @@
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
+import static org.testng.Assert.assertThrows;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
@@ -140,20 +141,15 @@
assertThat(userInfo).isNotNull();
List<UserInfo> list = mUserManager.getUsers();
- boolean found = false;
for (UserInfo user : list) {
if (user.id == userInfo.id && user.name.equals("Guest 1")
&& user.isGuest()
&& !user.isAdmin()
&& !user.isPrimary()) {
- found = true;
- Bundle restrictions = mUserManager.getUserRestrictions(user.getUserHandle());
- assertWithMessage("Guest user should have DISALLOW_CONFIG_WIFI=true by default")
- .that(restrictions.getBoolean(UserManager.DISALLOW_CONFIG_WIFI))
- .isTrue();
+ return;
}
}
- assertThat(found).isTrue();
+ fail("Didn't find a guest: " + list);
}
@MediumTest
@@ -206,14 +202,7 @@
@MediumTest
@Test
public void testRemoveUserByHandle_ThrowsException() {
- synchronized (mUserRemoveLock) {
- try {
- mUserManager.removeUser(null);
- fail("Expected IllegalArgumentException on passing in a null UserHandle.");
- } catch (IllegalArgumentException expected) {
- // Do nothing - exception is expected.
- }
- }
+ assertThrows(IllegalArgumentException.class, () -> mUserManager.removeUser(null));
}
/** Tests creating a FULL user via specifying userType. */
@@ -343,7 +332,6 @@
UserManager.USER_TYPE_PROFILE_MANAGED, primaryUserId);
assertThat(userInfo).isNotNull();
final int userId = userInfo.id;
- final UserHandle userHandle = new UserHandle(userId);
assertThat(mUserManager.hasBadge(userId)).isEqualTo(userTypeDetails.hasBadge());
assertThat(mUserManager.getUserIconBadgeResId(userId))
@@ -353,13 +341,13 @@
assertThat(mUserManager.getUserBadgeNoBackgroundResId(userId))
.isEqualTo(userTypeDetails.getBadgeNoBackground());
assertThat(mUserManager.isProfile(userId)).isEqualTo(userTypeDetails.isProfile());
- assertThat(mUserManager.getUserTypeForUser(userHandle))
- .isEqualTo(userTypeDetails.getName());
+ assertThat(mUserManager.isUserOfType(asHandle(userId), userTypeDetails.getName()))
+ .isTrue();
final int badgeIndex = userInfo.profileBadge;
assertThat(mUserManager.getUserBadgeColor(userId)).isEqualTo(
Resources.getSystem().getColor(userTypeDetails.getBadgeColor(badgeIndex), null));
- assertThat(mUserManager.getBadgedLabelForUser("Test", userHandle)).isEqualTo(
+ assertThat(mUserManager.getBadgedLabelForUser("Test", asHandle(userId))).isEqualTo(
Resources.getSystem().getString(userTypeDetails.getBadgeLabel(badgeIndex), "Test"));
}
@@ -438,9 +426,8 @@
@MediumTest
@Test
public void testCreateUser_disallowAddUser() throws Exception {
- final int creatorId = isAutomotive() ? ActivityManager.getCurrentUser()
- : mUserManager.getPrimaryUser().id;
- final UserHandle creatorHandle = new UserHandle(creatorId);
+ final int creatorId = ActivityManager.getCurrentUser();
+ final UserHandle creatorHandle = asHandle(creatorId);
mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, true, creatorHandle);
try {
UserInfo createadInfo = createUser("SecondaryUser", /*flags=*/ 0);
@@ -457,7 +444,7 @@
public void testCreateProfileForUser_disallowAddManagedProfile() throws Exception {
assumeManagedUsersSupported();
final int primaryUserId = mUserManager.getPrimaryUser().id;
- final UserHandle primaryUserHandle = new UserHandle(primaryUserId);
+ final UserHandle primaryUserHandle = asHandle(primaryUserId);
mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, true,
primaryUserHandle);
try {
@@ -476,7 +463,7 @@
public void testCreateProfileForUserEvenWhenDisallowed() throws Exception {
assumeManagedUsersSupported();
final int primaryUserId = mUserManager.getPrimaryUser().id;
- final UserHandle primaryUserHandle = new UserHandle(primaryUserId);
+ final UserHandle primaryUserHandle = asHandle(primaryUserId);
mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_MANAGED_PROFILE, true,
primaryUserHandle);
try {
@@ -495,7 +482,7 @@
public void testCreateProfileForUser_disallowAddUser() throws Exception {
assumeManagedUsersSupported();
final int primaryUserId = mUserManager.getPrimaryUser().id;
- final UserHandle primaryUserHandle = new UserHandle(primaryUserId);
+ final UserHandle primaryUserHandle = asHandle(primaryUserId);
mUserManager.setUserRestriction(UserManager.DISALLOW_ADD_USER, true, primaryUserHandle);
try {
UserInfo userInfo = createProfileForUser("Managed",
@@ -540,8 +527,7 @@
@MediumTest
@Test
- public void testGetUserCreationTime() throws Exception {
- // TODO: should add a regular user instead of a profile, so it can be tested everywhere
+ public void testGetManagedProfileCreationTime() throws Exception {
assumeManagedUsersSupported();
final int primaryUserId = mUserManager.getPrimaryUser().id;
final long startTime = System.currentTimeMillis();
@@ -556,38 +542,40 @@
assertWithMessage("creationTime must be 0 if the time is not > EPOCH_PLUS_30_years")
.that(profile.creationTime).isEqualTo(0);
}
- assertThat(mUserManager.getUserCreationTime(
- new UserHandle(profile.id))).isEqualTo(profile.creationTime);
+ assertThat(mUserManager.getUserCreationTime(asHandle(profile.id)))
+ .isEqualTo(profile.creationTime);
long ownerCreationTime = mUserManager.getUserInfo(primaryUserId).creationTime;
- assertThat(mUserManager.getUserCreationTime(
- new UserHandle(primaryUserId))).isEqualTo(ownerCreationTime);
+ assertThat(mUserManager.getUserCreationTime(asHandle(primaryUserId)))
+ .isEqualTo(ownerCreationTime);
+ }
+
+ @MediumTest
+ @Test
+ public void testGetUserCreationTime() throws Exception {
+ long startTime = System.currentTimeMillis();
+ UserInfo user = createUser("User", /* flags= */ 0);
+ long endTime = System.currentTimeMillis();
+ assertThat(user).isNotNull();
+ assertWithMessage("creationTime must be set when the user is created")
+ .that(user.creationTime).isIn(Range.closed(startTime, endTime));
}
@SmallTest
@Test
public void testGetUserCreationTime_nonExistentUser() throws Exception {
- try {
- int noSuchUserId = 100500;
- mUserManager.getUserCreationTime(new UserHandle(noSuchUserId));
- fail("SecurityException should be thrown for nonexistent user");
- } catch (Exception e) {
- assertWithMessage("SecurityException should be thrown for nonexistent user").that(e)
- .isInstanceOf(SecurityException.class);
- }
+ int noSuchUserId = 100500;
+ assertThrows(SecurityException.class,
+ () -> mUserManager.getUserCreationTime(asHandle(noSuchUserId)));
}
@SmallTest
@Test
public void testGetUserCreationTime_otherUser() throws Exception {
UserInfo user = createUser("User 1", 0);
- try {
- mUserManager.getUserCreationTime(new UserHandle(user.id));
- fail("SecurityException should be thrown for other user");
- } catch (Exception e) {
- assertWithMessage("SecurityException should be thrown for other user").that(e)
- .isInstanceOf(SecurityException.class);
- }
+ assertThat(user).isNotNull();
+ assertThrows(SecurityException.class,
+ () -> mUserManager.getUserCreationTime(asHandle(user.id)));
}
private boolean findUser(int id) {
@@ -658,11 +646,11 @@
UserInfo testUser = createUser("User 1", 0);
mUserManager.setUserRestriction(
- UserManager.DISALLOW_INSTALL_APPS, true, new UserHandle(testUser.id));
+ UserManager.DISALLOW_INSTALL_APPS, true, asHandle(testUser.id));
mUserManager.setUserRestriction(
- UserManager.DISALLOW_CONFIG_WIFI, false, new UserHandle(testUser.id));
+ UserManager.DISALLOW_CONFIG_WIFI, false, asHandle(testUser.id));
- Bundle stored = mUserManager.getUserRestrictions(new UserHandle(testUser.id));
+ Bundle stored = mUserManager.getUserRestrictions(asHandle(testUser.id));
// Note this will fail if DO already sets those restrictions.
assertThat(stored.getBoolean(UserManager.DISALLOW_CONFIG_WIFI)).isFalse();
assertThat(stored.getBoolean(UserManager.DISALLOW_UNINSTALL_APPS)).isFalse();
@@ -738,15 +726,8 @@
@Test
public void testSwitchUserByHandle_ThrowsException() {
- synchronized (mUserSwitchLock) {
- try {
- ActivityManager am = mContext.getSystemService(ActivityManager.class);
- am.switchUser(null);
- fail("Expected IllegalArgumentException on passing in a null UserHandle.");
- } catch (IllegalArgumentException expected) {
- // Do nothing - exception is expected.
- }
- }
+ ActivityManager am = mContext.getSystemService(ActivityManager.class);
+ assertThrows(IllegalArgumentException.class, () -> am.switchUser(null));
}
@MediumTest
@@ -903,4 +884,8 @@
private boolean isAutomotive() {
return mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
}
+
+ private static UserHandle asHandle(int userId) {
+ return new UserHandle(userId);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
index f08044c..f5e5e2a 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
@@ -160,6 +160,31 @@
}
@Test
+ public void testNotifyPrimaryAndSecondary() {
+ List<String> dexFiles = mFooUser0.getBaseAndSplitDexPaths();
+ List<String> secondaries = mFooUser0.getSecondaryDexPaths();
+ int baseAndSplitCount = dexFiles.size();
+ dexFiles.addAll(secondaries);
+
+ notifyDexLoad(mFooUser0, dexFiles, mUser0);
+
+ PackageUseInfo pui = getPackageUseInfo(mFooUser0);
+ assertIsUsedByOtherApps(mFooUser0, pui, false);
+ assertEquals(secondaries.size(), pui.getDexUseInfoMap().size());
+
+ String[] allExpectedContexts = DexoptUtils.processContextForDexLoad(
+ Arrays.asList(mFooUser0.mClassLoader),
+ Arrays.asList(String.join(File.pathSeparator, dexFiles)));
+ String[] secondaryExpectedContexts = Arrays.copyOfRange(allExpectedContexts,
+ baseAndSplitCount, dexFiles.size());
+
+ assertSecondaryUse(mFooUser0, pui, secondaries, /*isUsedByOtherApps*/false, mUser0,
+ secondaryExpectedContexts);
+
+ assertHasDclInfo(mFooUser0, mFooUser0, secondaries);
+ }
+
+ @Test
public void testNotifySecondaryForeign() {
// Foo loads bar secondary files.
List<String> barSecondaries = mBarUser0.getSecondaryDexPaths();
diff --git a/services/tests/servicestests/src/com/android/server/power/NotifierTest.java b/services/tests/servicestests/src/com/android/server/power/NotifierTest.java
new file mode 100644
index 0000000..7666ab9
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/power/NotifierTest.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.power;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.SensorManager;
+import android.hardware.display.AmbientDisplayConfiguration;
+import android.os.BatteryStats;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.ServiceManager;
+import android.os.Vibrator;
+import android.os.test.TestLooper;
+import android.provider.Settings;
+import android.testing.TestableContext;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.os.BatteryStatsImpl;
+import com.android.server.LocalServices;
+import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.power.batterysaver.BatterySaverPolicy;
+import com.android.server.power.batterysaver.BatterySavingStats;
+import com.android.server.statusbar.StatusBarManagerInternal;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for {@link com.android.server.power.Notifier}
+ */
+public class NotifierTest {
+ private static final String SYSTEM_PROPERTY_QUIESCENT = "ro.boot.quiescent";
+ private static final int USER_ID = 0;
+
+ @Mock private BatterySaverPolicy mBatterySaverPolicyMock;
+ @Mock private PowerManagerService.NativeWrapper mNativeWrapperMock;
+ @Mock private Notifier mNotifierMock;
+ @Mock private WirelessChargerDetector mWirelessChargerDetectorMock;
+ @Mock private AmbientDisplayConfiguration mAmbientDisplayConfigurationMock;
+ @Mock private SystemPropertiesWrapper mSystemPropertiesMock;
+ @Mock private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock;
+ @Mock private BatteryStatsImpl mBatteryStats;
+ @Mock private Vibrator mVibrator;
+ @Mock private StatusBarManagerInternal mStatusBarManagerInternal;
+
+ private PowerManagerService mService;
+ private Context mContextSpy;
+ private Resources mResourcesSpy;
+ private TestLooper mTestLooper = new TestLooper();
+ private Notifier mNotifier;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ LocalServices.removeServiceForTest(StatusBarManagerInternal.class);
+ LocalServices.addService(StatusBarManagerInternal.class, mStatusBarManagerInternal);
+
+ mContextSpy = spy(new TestableContext(InstrumentationRegistry.getContext()));
+ mResourcesSpy = spy(mContextSpy.getResources());
+ when(mContextSpy.getResources()).thenReturn(mResourcesSpy);
+ when(mSystemPropertiesMock.get(eq(SYSTEM_PROPERTY_QUIESCENT), anyString())).thenReturn("");
+ when(mContextSpy.getSystemService(Vibrator.class)).thenReturn(mVibrator);
+
+ mService = new PowerManagerService(mContextSpy, mInjector);
+ }
+
+ @Test
+ public void testVibrateEnabled_wiredCharging() {
+ createNotifier();
+
+ // GIVEN the charging vibration is enabled
+ enableChargingVibration(true);
+
+ // WHEN wired charging starts
+ mNotifier.onWiredChargingStarted(USER_ID);
+ mTestLooper.dispatchAll();
+
+ // THEN the device vibrates once
+ verify(mVibrator, times(1)).vibrate(any(), any());
+ }
+
+ @Test
+ public void testVibrateDisabled_wiredCharging() {
+ createNotifier();
+
+ // GIVEN the charging vibration is disabled
+ enableChargingVibration(false);
+
+ // WHEN wired charging starts
+ mNotifier.onWiredChargingStarted(USER_ID);
+ mTestLooper.dispatchAll();
+
+ // THEN the device doesn't vibrate
+ verify(mVibrator, never()).vibrate(any(), any());
+ }
+
+ @Test
+ public void testVibrateEnabled_wirelessCharging() {
+ createNotifier();
+
+ // GIVEN the charging vibration is enabled
+ enableChargingVibration(true);
+
+ // WHEN wireless charging starts
+ mNotifier.onWirelessChargingStarted(5, USER_ID);
+ mTestLooper.dispatchAll();
+
+ // THEN the device vibrates once
+ verify(mVibrator, times(1)).vibrate(any(), any());
+ }
+
+ @Test
+ public void testVibrateDisabled_wirelessCharging() {
+ createNotifier();
+
+ // GIVEN the charging vibration is disabeld
+ enableChargingVibration(false);
+
+ // WHEN wireless charging starts
+ mNotifier.onWirelessChargingStarted(5, USER_ID);
+ mTestLooper.dispatchAll();
+
+ // THEN the device doesn't vibrate
+ verify(mVibrator, never()).vibrate(any(), any());
+ }
+
+ @Test
+ public void testVibrateEnabled_dndOn() {
+ createNotifier();
+
+ // GIVEN the charging vibration is enabled but dnd is on
+ enableChargingVibration(true);
+ enableChargingFeedback(
+ /* chargingFeedbackEnabled */ true,
+ /* dndOn */ true);
+
+ // WHEN wired charging starts
+ mNotifier.onWiredChargingStarted(USER_ID);
+ mTestLooper.dispatchAll();
+
+ // THEN the device doesn't vibrate
+ verify(mVibrator, never()).vibrate(any(), any());
+ }
+
+ @Test
+ public void testWirelessAnimationEnabled() {
+ // GIVEN the wireless charging animation is enabled
+ when(mResourcesSpy.getBoolean(
+ com.android.internal.R.bool.config_showBuiltinWirelessChargingAnim))
+ .thenReturn(true);
+ createNotifier();
+
+ // WHEN wireless charging starts
+ mNotifier.onWirelessChargingStarted(5, USER_ID);
+ mTestLooper.dispatchAll();
+
+ // THEN the charging animation is triggered
+ verify(mStatusBarManagerInternal, times(1)).showChargingAnimation(5);
+ }
+
+ @Test
+ public void testWirelessAnimationDisabled() {
+ // GIVEN the wireless charging animation is disabled
+ when(mResourcesSpy.getBoolean(
+ com.android.internal.R.bool.config_showBuiltinWirelessChargingAnim))
+ .thenReturn(false);
+ createNotifier();
+
+ // WHEN wireless charging starts
+ mNotifier.onWirelessChargingStarted(5, USER_ID);
+ mTestLooper.dispatchAll();
+
+ // THEN the charging animation never gets called
+ verify(mStatusBarManagerInternal, never()).showChargingAnimation(anyInt());
+ }
+
+ private final PowerManagerService.Injector mInjector = new PowerManagerService.Injector() {
+ @Override
+ Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
+ SuspendBlocker suspendBlocker, WindowManagerPolicy policy) {
+ return mNotifierMock;
+ }
+
+ @Override
+ SuspendBlocker createSuspendBlocker(PowerManagerService service, String name) {
+ return super.createSuspendBlocker(service, name);
+ }
+
+ @Override
+ BatterySaverPolicy createBatterySaverPolicy(
+ Object lock, Context context, BatterySavingStats batterySavingStats) {
+ return mBatterySaverPolicyMock;
+ }
+
+ @Override
+ PowerManagerService.NativeWrapper createNativeWrapper() {
+ return mNativeWrapperMock;
+ }
+
+ @Override
+ WirelessChargerDetector createWirelessChargerDetector(
+ SensorManager sensorManager, SuspendBlocker suspendBlocker, Handler handler) {
+ return mWirelessChargerDetectorMock;
+ }
+
+ @Override
+ AmbientDisplayConfiguration createAmbientDisplayConfiguration(Context context) {
+ return mAmbientDisplayConfigurationMock;
+ }
+
+ @Override
+ InattentiveSleepWarningController createInattentiveSleepWarningController() {
+ return mInattentiveSleepWarningControllerMock;
+ }
+
+ @Override
+ public SystemPropertiesWrapper createSystemPropertiesWrapper() {
+ return mSystemPropertiesMock;
+ }
+ };
+
+ private void enableChargingFeedback(boolean chargingFeedbackEnabled, boolean dndOn) {
+ // enable/disable charging feedback
+ Settings.Secure.putIntForUser(
+ mContextSpy.getContentResolver(),
+ Settings.Secure.CHARGING_SOUNDS_ENABLED,
+ chargingFeedbackEnabled ? 1 : 0,
+ USER_ID);
+
+ // toggle on/off dnd
+ Settings.Global.putInt(
+ mContextSpy.getContentResolver(),
+ Settings.Global.ZEN_MODE,
+ dndOn ? Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS
+ : Settings.Global.ZEN_MODE_OFF);
+ }
+
+ private void enableChargingVibration(boolean enable) {
+ enableChargingFeedback(true, false);
+
+ Settings.Secure.putIntForUser(
+ mContextSpy.getContentResolver(),
+ Settings.Secure.CHARGING_VIBRATION_ENABLED,
+ enable ? 1 : 0,
+ USER_ID);
+ }
+
+ private void createNotifier() {
+ mNotifier = new Notifier(
+ mTestLooper.getLooper(),
+ mContextSpy,
+ IBatteryStats.Stub.asInterface(ServiceManager.getService(
+ BatteryStats.SERVICE_NAME)),
+ mInjector.createSuspendBlocker(mService, "testBlocker"),
+ null);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java
index 1f312bf..d5cdbeb 100644
--- a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTest.java
@@ -20,16 +20,19 @@
import static org.junit.Assert.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.content.Context;
+import android.content.IntentSender;
import android.os.Handler;
import android.os.IPowerManager;
import android.os.IRecoverySystemProgressListener;
@@ -40,6 +43,8 @@
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.widget.LockSettingsInternal;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -58,6 +63,7 @@
private Context mContext;
private IPowerManager mIPowerManager;
private FileWriter mUncryptUpdateFileWriter;
+ private LockSettingsInternal mLockSettingsInternal;
@Before
public void setup() {
@@ -65,6 +71,9 @@
mSystemProperties = new RecoverySystemServiceTestable.FakeSystemProperties();
mUncryptSocket = mock(RecoverySystemService.UncryptSocket.class);
mUncryptUpdateFileWriter = mock(FileWriter.class);
+ mLockSettingsInternal = mock(LockSettingsInternal.class);
+
+ when(mLockSettingsInternal.armRebootEscrow()).thenReturn(true);
Looper looper = InstrumentationRegistry.getContext().getMainLooper();
mIPowerManager = mock(IPowerManager.class);
@@ -72,7 +81,7 @@
new Handler(looper));
mRecoverySystemService = new RecoverySystemServiceTestable(mContext, mSystemProperties,
- powerManager, mUncryptUpdateFileWriter, mUncryptSocket);
+ powerManager, mUncryptUpdateFileWriter, mUncryptSocket, mLockSettingsInternal);
}
@Test
@@ -194,4 +203,100 @@
verify(mUncryptSocket).sendAck();
verify(mUncryptSocket).close();
}
+
+ @Test(expected = SecurityException.class)
+ public void requestLskf_protected() {
+ doThrow(SecurityException.class).when(mContext).enforceCallingOrSelfPermission(
+ eq(android.Manifest.permission.RECOVERY), any());
+ mRecoverySystemService.requestLskf("test", null);
+ }
+
+
+ @Test
+ public void requestLskf_nullToken_failure() {
+ assertThat(mRecoverySystemService.requestLskf(null, null), is(false));
+ }
+
+ @Test
+ public void requestLskf_success() throws Exception {
+ IntentSender intentSender = mock(IntentSender.class);
+ assertThat(mRecoverySystemService.requestLskf("test", intentSender), is(true));
+ mRecoverySystemService.onPreparedForReboot(true);
+ verify(intentSender).sendIntent(any(), anyInt(), any(), any(), any());
+ }
+
+ @Test
+ public void requestLskf_subsequentRequestClearsPrepared() throws Exception {
+ IntentSender intentSender = mock(IntentSender.class);
+ assertThat(mRecoverySystemService.requestLskf("test", intentSender), is(true));
+ mRecoverySystemService.onPreparedForReboot(true);
+ verify(intentSender).sendIntent(any(), anyInt(), any(), any(), any());
+
+ assertThat(mRecoverySystemService.requestLskf("test2", null), is(true));
+ assertThat(mRecoverySystemService.rebootWithLskf("test", null), is(false));
+ assertThat(mRecoverySystemService.rebootWithLskf("test2", "foobar"), is(false));
+
+ mRecoverySystemService.onPreparedForReboot(true);
+ assertThat(mRecoverySystemService.rebootWithLskf("test2", "foobar"), is(true));
+ verify(intentSender).sendIntent(any(), anyInt(), any(), any(), any());
+ verify(mIPowerManager).reboot(anyBoolean(), eq("foobar"), anyBoolean());
+ }
+
+
+ @Test
+ public void requestLskf_requestedButNotPrepared() throws Exception {
+ IntentSender intentSender = mock(IntentSender.class);
+ assertThat(mRecoverySystemService.requestLskf("test", intentSender), is(true));
+ verify(intentSender, never()).sendIntent(any(), anyInt(), any(), any(), any());
+ }
+
+ @Test(expected = SecurityException.class)
+ public void clearLskf_protected() {
+ doThrow(SecurityException.class).when(mContext).enforceCallingOrSelfPermission(
+ eq(android.Manifest.permission.RECOVERY), any());
+ mRecoverySystemService.clearLskf();
+ }
+
+ @Test
+ public void clearLskf_requestedThenCleared() throws Exception {
+ IntentSender intentSender = mock(IntentSender.class);
+ assertThat(mRecoverySystemService.requestLskf("test", intentSender), is(true));
+ mRecoverySystemService.onPreparedForReboot(true);
+ verify(intentSender).sendIntent(any(), anyInt(), any(), any(), any());
+
+ assertThat(mRecoverySystemService.clearLskf(), is(true));
+ verify(mLockSettingsInternal).clearRebootEscrow();
+ }
+
+ @Test
+ public void startup_setRebootEscrowListener() throws Exception {
+ mRecoverySystemService.onSystemServicesReady();
+ verify(mLockSettingsInternal).setRebootEscrowListener(any());
+ }
+
+ @Test(expected = SecurityException.class)
+ public void rebootWithLskf_protected() {
+ doThrow(SecurityException.class).when(mContext).enforceCallingOrSelfPermission(
+ eq(android.Manifest.permission.RECOVERY), any());
+ mRecoverySystemService.rebootWithLskf("test1", null);
+ }
+
+ @Test
+ public void rebootWithLskf_Success() throws Exception {
+ assertThat(mRecoverySystemService.requestLskf("test", null), is(true));
+ mRecoverySystemService.onPreparedForReboot(true);
+ assertThat(mRecoverySystemService.rebootWithLskf("test", "ab-update"), is(true));
+ verify(mIPowerManager).reboot(anyBoolean(), eq("ab-update"), anyBoolean());
+ }
+
+ @Test
+ public void rebootWithLskf_withoutPrepare_Failure() throws Exception {
+ assertThat(mRecoverySystemService.rebootWithLskf("test1", null), is(false));
+ }
+
+ @Test
+ public void rebootWithLskf_withNullUpdateToken_Failure() throws Exception {
+ assertThat(mRecoverySystemService.rebootWithLskf(null, null), is(false));
+ verifyNoMoreInteractions(mIPowerManager);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTestable.java b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTestable.java
index a986b71..131e4f3 100644
--- a/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/recoverysystem/RecoverySystemServiceTestable.java
@@ -19,6 +19,8 @@
import android.content.Context;
import android.os.PowerManager;
+import com.android.internal.widget.LockSettingsInternal;
+
import java.io.FileWriter;
public class RecoverySystemServiceTestable extends RecoverySystemService {
@@ -27,15 +29,17 @@
private final PowerManager mPowerManager;
private final FileWriter mUncryptPackageFileWriter;
private final UncryptSocket mUncryptSocket;
+ private final LockSettingsInternal mLockSettingsInternal;
MockInjector(Context context, FakeSystemProperties systemProperties,
PowerManager powerManager, FileWriter uncryptPackageFileWriter,
- UncryptSocket uncryptSocket) {
+ UncryptSocket uncryptSocket, LockSettingsInternal lockSettingsInternal) {
super(context);
mSystemProperties = systemProperties;
mPowerManager = powerManager;
mUncryptPackageFileWriter = uncryptPackageFileWriter;
mUncryptSocket = uncryptSocket;
+ mLockSettingsInternal = lockSettingsInternal;
}
@Override
@@ -76,13 +80,18 @@
@Override
public void threadSleep(long millis) {
}
+
+ @Override
+ public LockSettingsInternal getLockSettingsService() {
+ return mLockSettingsInternal;
+ }
}
RecoverySystemServiceTestable(Context context, FakeSystemProperties systemProperties,
PowerManager powerManager, FileWriter uncryptPackageFileWriter,
- UncryptSocket uncryptSocket) {
+ UncryptSocket uncryptSocket, LockSettingsInternal lockSettingsInternal) {
super(new MockInjector(context, systemProperties, powerManager, uncryptPackageFileWriter,
- uncryptSocket));
+ uncryptSocket, lockSettingsInternal));
}
public static class FakeSystemProperties {
diff --git a/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java b/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java
index a83d940..f871203 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/rollback/AppDataRollbackHelperTest.java
@@ -98,7 +98,7 @@
final int[] installedUsers) {
return new PackageRollbackInfo(
new VersionedPackage(packageName, 2), new VersionedPackage(packageName, 1),
- new IntArray(), new ArrayList<>(), false, IntArray.wrap(installedUsers),
+ new IntArray(), new ArrayList<>(), false, false, IntArray.wrap(installedUsers),
new SparseLongArray());
}
diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java
index 757a884..d0d2edc 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackStoreTest.java
@@ -87,13 +87,15 @@
+ "[{'versionRolledBackFrom':{'packageName':'blah','longVersionCode':55},"
+ "'versionRolledBackTo':{'packageName':'blah1','longVersionCode':50},'pendingBackups':"
+ "[59,1245,124544],'pendingRestores':[{'userId':498,'appId':32322,'seInfo':'wombles'},"
- + "{'userId':-895,'appId':1,'seInfo':'pingu'}],'isApex':false,'installedUsers':"
+ + "{'userId':-895,'appId':1,'seInfo':'pingu'}],'isApex':false,'isApkInApex':false,"
+ + "'installedUsers':"
+ "[498468432,1111,98464],'ceSnapshotInodes':[{'userId':1,'ceSnapshotInode':-6},"
+ "{'userId':2222,'ceSnapshotInode':81641654445},{'userId':546546,"
+ "'ceSnapshotInode':345689375}]},{'versionRolledBackFrom':{'packageName':'chips',"
+ "'longVersionCode':28},'versionRolledBackTo':{'packageName':'com.chips.test',"
+ "'longVersionCode':48},'pendingBackups':[5],'pendingRestores':[{'userId':18,"
- + "'appId':-12,'seInfo':''}],'isApex':false,'installedUsers':[55,79],"
+ + "'appId':-12,'seInfo':''}],'isApex':false,'isApkInApex':false,"
+ + "'installedUsers':[55,79],"
+ "'ceSnapshotInodes':[]}],'isStaged':false,'causePackages':[{'packageName':'hello',"
+ "'longVersionCode':23},{'packageName':'something','longVersionCode':999}],"
+ "'committedSessionId':45654465},'timestamp':'2019-10-01T12:29:08.855Z',"
@@ -155,7 +157,7 @@
PackageRollbackInfo pkgInfo1 =
new PackageRollbackInfo(new VersionedPackage("com.made.up", 18),
new VersionedPackage("com.something.else", 5), new IntArray(),
- new ArrayList<>(), false, new IntArray(), new SparseLongArray());
+ new ArrayList<>(), false, false, new IntArray(), new SparseLongArray());
pkgInfo1.getPendingBackups().add(8);
pkgInfo1.getPendingBackups().add(888);
pkgInfo1.getPendingBackups().add(88885);
@@ -175,7 +177,7 @@
PackageRollbackInfo pkgInfo2 = new PackageRollbackInfo(
new VersionedPackage("another.package", 2),
new VersionedPackage("com.test.ing", 48888), new IntArray(), new ArrayList<>(),
- false, new IntArray(), new SparseLongArray());
+ false, false, new IntArray(), new SparseLongArray());
pkgInfo2.getPendingBackups().add(57);
pkgInfo2.getPendingRestores().add(
@@ -205,7 +207,7 @@
PackageRollbackInfo pkgInfo1 = new PackageRollbackInfo(new VersionedPackage("blah", 55),
new VersionedPackage("blah1", 50), new IntArray(), new ArrayList<>(),
- false, new IntArray(), new SparseLongArray());
+ false, false, new IntArray(), new SparseLongArray());
pkgInfo1.getPendingBackups().add(59);
pkgInfo1.getPendingBackups().add(1245);
pkgInfo1.getPendingBackups().add(124544);
@@ -224,7 +226,7 @@
PackageRollbackInfo pkgInfo2 = new PackageRollbackInfo(new VersionedPackage("chips", 28),
new VersionedPackage("com.chips.test", 48), new IntArray(), new ArrayList<>(),
- false, new IntArray(), new SparseLongArray());
+ false, false, new IntArray(), new SparseLongArray());
pkgInfo2.getPendingBackups().add(5);
pkgInfo2.getPendingRestores().add(
diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
index e368d63..164c883 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
+++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
@@ -295,7 +295,8 @@
String packageName, long fromVersion, long toVersion, boolean isApex) {
return new PackageRollbackInfo(new VersionedPackage(packageName, fromVersion),
new VersionedPackage(packageName, toVersion),
- new IntArray(), new ArrayList<>(), isApex, new IntArray(), new SparseLongArray());
+ new IntArray(), new ArrayList<>(), isApex, false, new IntArray(),
+ new SparseLongArray());
}
private static class PackageRollbackInfoForPackage implements
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
index 82f32f8..c56034a 100644
--- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
@@ -37,6 +37,7 @@
import android.hardware.soundtrigger.V2_3.OptionalModelParameterRange;
import android.media.audio.common.AudioChannelMask;
import android.media.audio.common.AudioFormat;
+import android.media.soundtrigger_middleware.AudioCapabilities;
import android.media.soundtrigger_middleware.ConfidenceLevel;
import android.media.soundtrigger_middleware.ISoundTriggerCallback;
import android.media.soundtrigger_middleware.ISoundTriggerModule;
@@ -59,8 +60,7 @@
import android.os.IHwBinder;
import android.os.IHwInterface;
import android.os.RemoteException;
-
-import androidx.test.runner.AndroidJUnit4;
+import android.util.Pair;
import org.junit.Before;
import org.junit.Test;
@@ -144,7 +144,11 @@
properties.maxSoundModels = 456;
properties.maxKeyPhrases = 567;
properties.maxUsers = 678;
- properties.recognitionModes = 789;
+ properties.recognitionModes =
+ android.hardware.soundtrigger.V2_0.RecognitionMode.VOICE_TRIGGER
+ | android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION
+ | android.hardware.soundtrigger.V2_0.RecognitionMode.USER_AUTHENTICATION
+ | android.hardware.soundtrigger.V2_0.RecognitionMode.GENERIC_TRIGGER;
properties.captureTransition = true;
properties.maxBufferMs = 321;
properties.concurrentCapture = supportConcurrentCapture;
@@ -153,7 +157,19 @@
return properties;
}
- private static void validateDefaultProperties(SoundTriggerModuleProperties properties,
+ private static android.hardware.soundtrigger.V2_3.Properties createDefaultProperties_2_3(
+ boolean supportConcurrentCapture) {
+ android.hardware.soundtrigger.V2_3.Properties properties =
+ new android.hardware.soundtrigger.V2_3.Properties();
+ properties.base = createDefaultProperties(supportConcurrentCapture);
+ properties.supportedModelArch = "supportedModelArch";
+ properties.audioCapabilities =
+ android.hardware.soundtrigger.V2_3.AudioCapabilities.ECHO_CANCELLATION
+ | android.hardware.soundtrigger.V2_3.AudioCapabilities.NOISE_SUPPRESSION;
+ return properties;
+ }
+
+ private void validateDefaultProperties(SoundTriggerModuleProperties properties,
boolean supportConcurrentCapture) {
assertEquals("implementor", properties.implementor);
assertEquals("description", properties.description);
@@ -162,14 +178,32 @@
assertEquals(456, properties.maxSoundModels);
assertEquals(567, properties.maxKeyPhrases);
assertEquals(678, properties.maxUsers);
- assertEquals(789, properties.recognitionModes);
+ assertEquals(RecognitionMode.GENERIC_TRIGGER
+ | RecognitionMode.USER_AUTHENTICATION
+ | RecognitionMode.USER_IDENTIFICATION
+ | RecognitionMode.VOICE_TRIGGER, properties.recognitionModes);
assertTrue(properties.captureTransition);
assertEquals(321, properties.maxBufferMs);
assertEquals(supportConcurrentCapture, properties.concurrentCapture);
assertTrue(properties.triggerInEvent);
assertEquals(432, properties.powerConsumptionMw);
+
+ if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+ assertEquals("supportedModelArch", properties.supportedModelArch);
+ assertEquals(AudioCapabilities.ECHO_CANCELLATION | AudioCapabilities.NOISE_SUPPRESSION,
+ properties.audioCapabilities);
+ } else {
+ assertEquals("", properties.supportedModelArch);
+ assertEquals(0, properties.audioCapabilities);
+ }
}
+ private void verifyNotGetProperties() throws RemoteException {
+ if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+ verify((android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver,
+ never()).getProperties(any());
+ }
+ }
private static android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.RecognitionEvent createRecognitionEvent_2_0(
int hwHandle,
@@ -285,15 +319,38 @@
properties);
return null;
}).when(mHalDriver).getProperties(any());
- mService = new SoundTriggerMiddlewareImpl(mHalDriver, mAudioSessionProvider);
+
+ if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+ doAnswer(invocation -> {
+ android.hardware.soundtrigger.V2_3.Properties properties =
+ createDefaultProperties_2_3(
+ supportConcurrentCapture);
+ ((android.hardware.soundtrigger.V2_3.ISoundTriggerHw.getProperties_2_3Callback)
+ invocation.getArgument(
+ 0)).onValues(0,
+ properties);
+ return null;
+ }).when(driver).getProperties_2_3(any());
+ }
+
+ mService = new SoundTriggerMiddlewareImpl(() -> {
+ return mHalDriver;
+ }, mAudioSessionProvider);
}
- private int loadGenericModel_2_0(ISoundTriggerModule module, int hwHandle)
- throws RemoteException {
+ private Pair<Integer, SoundTriggerHwCallback> loadGenericModel_2_0(ISoundTriggerModule module,
+ int hwHandle) throws RemoteException {
SoundModel model = createGenericSoundModel();
ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel> modelCaptor =
ArgumentCaptor.forClass(
android.hardware.soundtrigger.V2_0.ISoundTriggerHw.SoundModel.class);
+ ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback> callbackCaptor =
+ ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.class);
+ ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
+
doAnswer(invocation -> {
android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback =
invocation.getArgument(1);
@@ -312,7 +369,8 @@
modelEvent.model = hwHandle;
callback.soundModelCallback(modelEvent, callbackCookie);
return null;
- }).when(mHalDriver).loadSoundModel(modelCaptor.capture(), any(), anyInt(), any());
+ }).when(mHalDriver).loadSoundModel(modelCaptor.capture(), callbackCaptor.capture(),
+ cookieCaptor.capture(), any());
when(mAudioSessionProvider.acquireSession()).thenReturn(
new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
@@ -329,17 +387,23 @@
assertEquals(model.vendorUuid, ConversionUtil.hidl2aidlUuid(hidlModel.vendorUuid));
assertArrayEquals(new Byte[]{91, 92, 93, 94, 95}, hidlModel.data.toArray());
- return handle;
+ return new Pair<>(handle,
+ new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue()));
}
- private int loadGenericModel_2_1(ISoundTriggerModule module, int hwHandle)
- throws RemoteException {
+ private Pair<Integer, SoundTriggerHwCallback> loadGenericModel_2_1(ISoundTriggerModule module,
+ int hwHandle) throws RemoteException {
android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver =
(android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver;
SoundModel model = createGenericSoundModel();
ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel> modelCaptor =
ArgumentCaptor.forClass(
android.hardware.soundtrigger.V2_1.ISoundTriggerHw.SoundModel.class);
+ ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback> callbackCaptor =
+ ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.class);
+ ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
+
doAnswer(invocation -> {
android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback =
invocation.getArgument(1);
@@ -358,7 +422,8 @@
modelEvent.header.model = hwHandle;
callback.soundModelCallback_2_1(modelEvent, callbackCookie);
return null;
- }).when(driver).loadSoundModel_2_1(modelCaptor.capture(), any(), anyInt(), any());
+ }).when(driver).loadSoundModel_2_1(modelCaptor.capture(), callbackCaptor.capture(),
+ cookieCaptor.capture(), any());
when(mAudioSessionProvider.acquireSession()).thenReturn(
new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
@@ -376,10 +441,12 @@
assertArrayEquals(new byte[]{91, 92, 93, 94, 95},
HidlMemoryUtil.hidlMemoryToByteArray(hidlModel.data));
- return handle;
+ return new Pair<>(handle,
+ new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue()));
}
- private int loadGenericModel(ISoundTriggerModule module, int hwHandle) throws RemoteException {
+ private Pair<Integer, SoundTriggerHwCallback> loadGenericModel(ISoundTriggerModule module,
+ int hwHandle) throws RemoteException {
if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
return loadGenericModel_2_1(module, hwHandle);
} else {
@@ -387,12 +454,17 @@
}
}
- private int loadPhraseModel_2_0(ISoundTriggerModule module, int hwHandle)
- throws RemoteException {
+ private Pair<Integer, SoundTriggerHwCallback> loadPhraseModel_2_0(ISoundTriggerModule module,
+ int hwHandle) throws RemoteException {
PhraseSoundModel model = createPhraseSoundModel();
ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel>
modelCaptor = ArgumentCaptor.forClass(
android.hardware.soundtrigger.V2_0.ISoundTriggerHw.PhraseSoundModel.class);
+ ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback> callbackCaptor =
+ ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.class);
+ ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
+
doAnswer(invocation -> {
android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback callback =
invocation.getArgument(
@@ -414,7 +486,8 @@
modelEvent.model = hwHandle;
callback.soundModelCallback(modelEvent, callbackCookie);
return null;
- }).when(mHalDriver).loadPhraseSoundModel(modelCaptor.capture(), any(), anyInt(), any());
+ }).when(mHalDriver).loadPhraseSoundModel(modelCaptor.capture(), callbackCaptor.capture(),
+ cookieCaptor.capture(), any());
when(mAudioSessionProvider.acquireSession()).thenReturn(
new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
@@ -446,11 +519,12 @@
| android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION,
hidlPhrase.recognitionModes);
- return handle;
+ return new Pair<>(handle,
+ new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue()));
}
- private int loadPhraseModel_2_1(ISoundTriggerModule module, int hwHandle)
- throws RemoteException {
+ private Pair<Integer, SoundTriggerHwCallback> loadPhraseModel_2_1(ISoundTriggerModule module,
+ int hwHandle) throws RemoteException {
android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver =
(android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver;
@@ -458,6 +532,11 @@
ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel>
modelCaptor = ArgumentCaptor.forClass(
android.hardware.soundtrigger.V2_1.ISoundTriggerHw.PhraseSoundModel.class);
+ ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback> callbackCaptor =
+ ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.class);
+ ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
+
doAnswer(invocation -> {
android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback callback =
invocation.getArgument(
@@ -479,7 +558,8 @@
modelEvent.header.model = hwHandle;
callback.soundModelCallback_2_1(modelEvent, callbackCookie);
return null;
- }).when(driver).loadPhraseSoundModel_2_1(modelCaptor.capture(), any(), anyInt(), any());
+ }).when(driver).loadPhraseSoundModel_2_1(modelCaptor.capture(), callbackCaptor.capture(),
+ cookieCaptor.capture(), any());
when(mAudioSessionProvider.acquireSession()).thenReturn(
new SoundTriggerMiddlewareImpl.AudioSessionProvider.AudioSession(101, 102, 103));
@@ -512,10 +592,12 @@
| android.hardware.soundtrigger.V2_0.RecognitionMode.USER_IDENTIFICATION,
hidlPhrase.recognitionModes);
- return handle;
+ return new Pair<>(handle,
+ new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue()));
}
- private int loadPhraseModel(ISoundTriggerModule module, int hwHandle) throws RemoteException {
+ private Pair<Integer, SoundTriggerHwCallback> loadPhraseModel(
+ ISoundTriggerModule module, int hwHandle) throws RemoteException {
if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
return loadPhraseModel_2_1(module, hwHandle);
} else {
@@ -530,18 +612,14 @@
verify(mAudioSessionProvider).releaseSession(101);
}
- private SoundTriggerHwCallback startRecognition_2_0(ISoundTriggerModule module, int handle,
+ private void startRecognition_2_0(ISoundTriggerModule module, int handle,
int hwHandle) throws RemoteException {
ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig>
configCaptor = ArgumentCaptor.forClass(
android.hardware.soundtrigger.V2_0.ISoundTriggerHw.RecognitionConfig.class);
- ArgumentCaptor<android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback> callbackCaptor =
- ArgumentCaptor.forClass(
- android.hardware.soundtrigger.V2_0.ISoundTriggerHwCallback.class);
- ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
- when(mHalDriver.startRecognition(eq(hwHandle), configCaptor.capture(),
- callbackCaptor.capture(), cookieCaptor.capture())).thenReturn(0);
+ when(mHalDriver.startRecognition(eq(hwHandle), configCaptor.capture(), any(), anyInt()))
+ .thenReturn(0);
RecognitionConfig config = createRecognitionConfig();
@@ -564,11 +642,9 @@
assertEquals(234, halLevel.userId);
assertEquals(34, halLevel.levelPercent);
assertArrayEquals(new Byte[]{5, 4, 3, 2, 1}, halConfig.data.toArray());
-
- return new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue());
}
- private SoundTriggerHwCallback startRecognition_2_1(ISoundTriggerModule module, int handle,
+ private void startRecognition_2_1(ISoundTriggerModule module, int handle,
int hwHandle) throws RemoteException {
android.hardware.soundtrigger.V2_1.ISoundTriggerHw driver =
(android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver;
@@ -576,13 +652,9 @@
ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig>
configCaptor = ArgumentCaptor.forClass(
android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig.class);
- ArgumentCaptor<android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback> callbackCaptor =
- ArgumentCaptor.forClass(
- android.hardware.soundtrigger.V2_1.ISoundTriggerHwCallback.class);
- ArgumentCaptor<Integer> cookieCaptor = ArgumentCaptor.forClass(Integer.class);
- when(driver.startRecognition_2_1(eq(hwHandle), configCaptor.capture(),
- callbackCaptor.capture(), cookieCaptor.capture())).thenReturn(0);
+ when(driver.startRecognition_2_1(eq(hwHandle), configCaptor.capture(), any(), anyInt()))
+ .thenReturn(0);
RecognitionConfig config = createRecognitionConfig();
@@ -606,16 +678,56 @@
assertEquals(34, halLevel.levelPercent);
assertArrayEquals(new byte[]{5, 4, 3, 2, 1},
HidlMemoryUtil.hidlMemoryToByteArray(halConfig.data));
-
- return new SoundTriggerHwCallback(callbackCaptor.getValue(), cookieCaptor.getValue());
}
- private SoundTriggerHwCallback startRecognition(ISoundTriggerModule module, int handle,
+ private void startRecognition_2_3(ISoundTriggerModule module, int handle,
int hwHandle) throws RemoteException {
- if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
- return startRecognition_2_1(module, handle, hwHandle);
+ android.hardware.soundtrigger.V2_3.ISoundTriggerHw driver =
+ (android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver;
+
+ ArgumentCaptor<android.hardware.soundtrigger.V2_3.RecognitionConfig>
+ configCaptor = ArgumentCaptor.forClass(
+ android.hardware.soundtrigger.V2_3.RecognitionConfig.class);
+
+ when(driver.startRecognition_2_3(eq(hwHandle), configCaptor.capture())).thenReturn(0);
+
+ RecognitionConfig config = createRecognitionConfig();
+
+ module.startRecognition(handle, config);
+ verify(driver).startRecognition_2_3(eq(hwHandle), any());
+
+ android.hardware.soundtrigger.V2_3.RecognitionConfig halConfigExtended =
+ configCaptor.getValue();
+ android.hardware.soundtrigger.V2_1.ISoundTriggerHw.RecognitionConfig halConfig_2_1 =
+ halConfigExtended.base;
+
+ assertTrue(halConfig_2_1.header.captureRequested);
+ assertEquals(102, halConfig_2_1.header.captureHandle);
+ assertEquals(103, halConfig_2_1.header.captureDevice);
+ assertEquals(1, halConfig_2_1.header.phrases.size());
+ android.hardware.soundtrigger.V2_0.PhraseRecognitionExtra halPhraseExtra =
+ halConfig_2_1.header.phrases.get(0);
+ assertEquals(123, halPhraseExtra.id);
+ assertEquals(4, halPhraseExtra.confidenceLevel);
+ assertEquals(5, halPhraseExtra.recognitionModes);
+ assertEquals(1, halPhraseExtra.levels.size());
+ android.hardware.soundtrigger.V2_0.ConfidenceLevel halLevel = halPhraseExtra.levels.get(0);
+ assertEquals(234, halLevel.userId);
+ assertEquals(34, halLevel.levelPercent);
+ assertArrayEquals(new byte[]{5, 4, 3, 2, 1},
+ HidlMemoryUtil.hidlMemoryToByteArray(halConfig_2_1.data));
+ assertEquals(AudioCapabilities.ECHO_CANCELLATION
+ | AudioCapabilities.NOISE_SUPPRESSION, halConfigExtended.audioCapabilities);
+ }
+
+ private void startRecognition(ISoundTriggerModule module, int handle,
+ int hwHandle) throws RemoteException {
+ if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+ startRecognition_2_3(module, handle, hwHandle);
+ } else if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
+ startRecognition_2_1(module, handle, hwHandle);
} else {
- return startRecognition_2_0(module, handle, hwHandle);
+ startRecognition_2_0(module, handle, hwHandle);
}
}
@@ -630,6 +742,8 @@
config.phraseRecognitionExtras[0].levels[0].userId = 234;
config.phraseRecognitionExtras[0].levels[0].levelPercent = 34;
config.data = new byte[]{5, 4, 3, 2, 1};
+ config.audioCapabilities = AudioCapabilities.ECHO_CANCELLATION
+ | AudioCapabilities.NOISE_SUPPRESSION;
return config;
}
@@ -645,6 +759,9 @@
if (mHalDriver instanceof android.hardware.soundtrigger.V2_1.ISoundTriggerHw) {
verify((android.hardware.soundtrigger.V2_1.ISoundTriggerHw) mHalDriver,
never()).startRecognition_2_1(anyInt(), any(), any(), anyInt());
+ } else if (mHalDriver instanceof android.hardware.soundtrigger.V2_3.ISoundTriggerHw) {
+ verify((android.hardware.soundtrigger.V2_3.ISoundTriggerHw) mHalDriver,
+ never()).startRecognition_2_3(anyInt(), any());
}
}
@@ -683,12 +800,12 @@
@Override
public boolean linkToDeath(DeathRecipient recipient, long cookie) {
- throw new UnsupportedOperationException();
+ return true;
}
@Override
public boolean unlinkToDeath(DeathRecipient recipient) {
- throw new UnsupportedOperationException();
+ return true;
}
};
@@ -711,6 +828,7 @@
SoundTriggerModuleProperties properties = allDescriptors[0].properties;
validateDefaultProperties(properties, true);
+ verifyNotGetProperties();
}
@Test
@@ -755,7 +873,7 @@
ISoundTriggerModule module = mService.attach(0, callback);
final int hwHandle = 7;
- int handle = loadGenericModel(module, hwHandle);
+ int handle = loadGenericModel(module, hwHandle).first;
unloadModel(module, handle, hwHandle);
module.detach();
}
@@ -767,7 +885,7 @@
ISoundTriggerModule module = mService.attach(0, callback);
final int hwHandle = 73;
- int handle = loadPhraseModel(module, hwHandle);
+ int handle = loadPhraseModel(module, hwHandle).first;
unloadModel(module, handle, hwHandle);
module.detach();
}
@@ -780,7 +898,7 @@
// Load the model.
final int hwHandle = 7;
- int handle = loadGenericModel(module, hwHandle);
+ int handle = loadGenericModel(module, hwHandle).first;
// Initiate a recognition.
startRecognition(module, handle, hwHandle);
@@ -801,7 +919,7 @@
// Load the model.
final int hwHandle = 67;
- int handle = loadPhraseModel(module, hwHandle);
+ int handle = loadPhraseModel(module, hwHandle).first;
// Initiate a recognition.
startRecognition(module, handle, hwHandle);
@@ -822,10 +940,12 @@
// Load the model.
final int hwHandle = 7;
- int handle = loadGenericModel(module, hwHandle);
+ Pair<Integer, SoundTriggerHwCallback> modelHandles = loadGenericModel(module, hwHandle);
+ int handle = modelHandles.first;
+ SoundTriggerHwCallback hwCallback = modelHandles.second;
// Initiate a recognition.
- SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+ startRecognition(module, handle, hwHandle);
// Signal a capture from the driver.
hwCallback.sendRecognitionEvent(hwHandle,
@@ -851,10 +971,12 @@
// Load the model.
final int hwHandle = 7;
- int handle = loadPhraseModel(module, hwHandle);
+ Pair<Integer, SoundTriggerHwCallback> modelHandles = loadPhraseModel(module, hwHandle);
+ int handle = modelHandles.first;
+ SoundTriggerHwCallback hwCallback = modelHandles.second;
// Initiate a recognition.
- SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+ startRecognition(module, handle, hwHandle);
// Signal a capture from the driver.
hwCallback.sendPhraseRecognitionEvent(hwHandle,
@@ -887,10 +1009,12 @@
// Load the model.
final int hwHandle = 17;
- int handle = loadGenericModel(module, hwHandle);
+ Pair<Integer, SoundTriggerHwCallback> modelHandles = loadGenericModel(module, hwHandle);
+ int handle = modelHandles.first;
+ SoundTriggerHwCallback hwCallback = modelHandles.second;
// Initiate a recognition.
- SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+ startRecognition(module, handle, hwHandle);
// Force a trigger.
module.forceRecognitionEvent(handle);
@@ -930,10 +1054,12 @@
// Load the model.
final int hwHandle = 17;
- int handle = loadPhraseModel(module, hwHandle);
+ Pair<Integer, SoundTriggerHwCallback> modelHandles = loadPhraseModel(module, hwHandle);
+ int handle = modelHandles.first;
+ SoundTriggerHwCallback hwCallback = modelHandles.second;
// Initiate a recognition.
- SoundTriggerHwCallback hwCallback = startRecognition(module, handle, hwHandle);
+ startRecognition(module, handle, hwHandle);
// Force a trigger.
module.forceRecognitionEvent(handle);
@@ -970,7 +1096,7 @@
// Load the model.
final int hwHandle = 11;
- int handle = loadGenericModel(module, hwHandle);
+ int handle = loadGenericModel(module, hwHandle).first;
// Initiate a recognition.
startRecognition(module, handle, hwHandle);
@@ -1018,7 +1144,7 @@
// Load the model.
final int hwHandle = 11;
- int handle = loadPhraseModel(module, hwHandle);
+ int handle = loadPhraseModel(module, hwHandle).first;
// Initiate a recognition.
startRecognition(module, handle, hwHandle);
@@ -1066,7 +1192,7 @@
// Load the model.
final int hwHandle = 13;
- int handle = loadGenericModel(module, hwHandle);
+ int handle = loadGenericModel(module, hwHandle).first;
// Initiate a recognition.
startRecognition(module, handle, hwHandle);
@@ -1102,7 +1228,7 @@
// Load the model.
final int hwHandle = 13;
- int handle = loadPhraseModel(module, hwHandle);
+ int handle = loadPhraseModel(module, hwHandle).first;
// Initiate a recognition.
startRecognition(module, handle, hwHandle);
@@ -1139,7 +1265,7 @@
ISoundTriggerCallback callback = createCallbackMock();
ISoundTriggerModule module = mService.attach(0, callback);
final int hwHandle = 12;
- int modelHandle = loadGenericModel(module, hwHandle);
+ int modelHandle = loadGenericModel(module, hwHandle).first;
doAnswer((Answer<Void>) invocation -> {
android.hardware.soundtrigger.V2_3.ISoundTriggerHw.queryParameterCallback
@@ -1175,7 +1301,7 @@
ISoundTriggerCallback callback = createCallbackMock();
ISoundTriggerModule module = mService.attach(0, callback);
final int hwHandle = 13;
- int modelHandle = loadGenericModel(module, hwHandle);
+ int modelHandle = loadGenericModel(module, hwHandle).first;
ModelParameterRange range = module.queryModelParameterSupport(modelHandle,
ModelParameter.THRESHOLD_FACTOR);
@@ -1196,7 +1322,7 @@
ISoundTriggerCallback callback = createCallbackMock();
ISoundTriggerModule module = mService.attach(0, callback);
final int hwHandle = 13;
- int modelHandle = loadGenericModel(module, hwHandle);
+ int modelHandle = loadGenericModel(module, hwHandle).first;
doAnswer(invocation -> {
android.hardware.soundtrigger.V2_3.ISoundTriggerHw.queryParameterCallback
@@ -1229,7 +1355,7 @@
ISoundTriggerCallback callback = createCallbackMock();
ISoundTriggerModule module = mService.attach(0, callback);
final int hwHandle = 14;
- int modelHandle = loadGenericModel(module, hwHandle);
+ int modelHandle = loadGenericModel(module, hwHandle).first;
doAnswer(invocation -> {
android.hardware.soundtrigger.V2_3.ISoundTriggerHw.getParameterCallback
@@ -1261,7 +1387,7 @@
ISoundTriggerCallback callback = createCallbackMock();
ISoundTriggerModule module = mService.attach(0, callback);
final int hwHandle = 17;
- int modelHandle = loadGenericModel(module, hwHandle);
+ int modelHandle = loadGenericModel(module, hwHandle).first;
when(driver.setParameter(hwHandle,
android.hardware.soundtrigger.V2_3.ModelParameter.THRESHOLD_FACTOR,
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
index 72a7f50..ae53692 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -18,15 +18,18 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.timedetector.ManualTimeSuggestion;
+import android.app.timedetector.NetworkTimeSuggestion;
import android.app.timedetector.PhoneTimeSuggestion;
import android.content.Context;
import android.content.pm.PackageManager;
@@ -34,7 +37,7 @@
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
-import android.util.TimestampedValue;
+import android.os.TimestampedValue;
import androidx.test.runner.AndroidJUnit4;
@@ -77,6 +80,22 @@
mHandlerThread.join();
}
+ @Test(expected = SecurityException.class)
+ public void testSuggestPhoneTime_withoutPermission() {
+ doThrow(new SecurityException("Mock"))
+ .when(mMockContext).enforceCallingPermission(anyString(), any());
+ PhoneTimeSuggestion phoneTimeSuggestion = createPhoneTimeSuggestion();
+
+ try {
+ mTimeDetectorService.suggestPhoneTime(phoneTimeSuggestion);
+ fail();
+ } finally {
+ verify(mMockContext).enforceCallingPermission(
+ eq(android.Manifest.permission.SUGGEST_PHONE_TIME_AND_ZONE),
+ anyString());
+ }
+ }
+
@Test
public void testSuggestPhoneTime() throws Exception {
doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
@@ -86,13 +105,29 @@
mTestHandler.assertTotalMessagesEnqueued(1);
verify(mMockContext).enforceCallingPermission(
- eq(android.Manifest.permission.SET_TIME),
+ eq(android.Manifest.permission.SUGGEST_PHONE_TIME_AND_ZONE),
anyString());
mTestHandler.waitForEmptyQueue();
mStubbedTimeDetectorStrategy.verifySuggestPhoneTimeCalled(phoneTimeSuggestion);
}
+ @Test(expected = SecurityException.class)
+ public void testSuggestManualTime_withoutPermission() {
+ doThrow(new SecurityException("Mock"))
+ .when(mMockContext).enforceCallingOrSelfPermission(anyString(), any());
+ ManualTimeSuggestion manualTimeSuggestion = createManualTimeSuggestion();
+
+ try {
+ mTimeDetectorService.suggestManualTime(manualTimeSuggestion);
+ fail();
+ } finally {
+ verify(mMockContext).enforceCallingOrSelfPermission(
+ eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE),
+ anyString());
+ }
+ }
+
@Test
public void testSuggestManualTime() throws Exception {
doNothing().when(mMockContext).enforceCallingOrSelfPermission(anyString(), any());
@@ -102,13 +137,43 @@
mTestHandler.assertTotalMessagesEnqueued(1);
verify(mMockContext).enforceCallingOrSelfPermission(
- eq(android.Manifest.permission.SET_TIME),
+ eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE),
anyString());
mTestHandler.waitForEmptyQueue();
mStubbedTimeDetectorStrategy.verifySuggestManualTimeCalled(manualTimeSuggestion);
}
+ @Test(expected = SecurityException.class)
+ public void testSuggestNetworkTime_withoutPermission() {
+ doThrow(new SecurityException("Mock"))
+ .when(mMockContext).enforceCallingOrSelfPermission(anyString(), any());
+ NetworkTimeSuggestion NetworkTimeSuggestion = createNetworkTimeSuggestion();
+
+ try {
+ mTimeDetectorService.suggestNetworkTime(NetworkTimeSuggestion);
+ fail();
+ } finally {
+ verify(mMockContext).enforceCallingOrSelfPermission(
+ eq(android.Manifest.permission.SET_TIME), anyString());
+ }
+ }
+
+ @Test
+ public void testSuggestNetworkTime() throws Exception {
+ doNothing().when(mMockContext).enforceCallingOrSelfPermission(anyString(), any());
+
+ NetworkTimeSuggestion NetworkTimeSuggestion = createNetworkTimeSuggestion();
+ mTimeDetectorService.suggestNetworkTime(NetworkTimeSuggestion);
+ mTestHandler.assertTotalMessagesEnqueued(1);
+
+ verify(mMockContext).enforceCallingOrSelfPermission(
+ eq(android.Manifest.permission.SET_TIME), anyString());
+
+ mTestHandler.waitForEmptyQueue();
+ mStubbedTimeDetectorStrategy.verifySuggestNetworkTimeCalled(NetworkTimeSuggestion);
+ }
+
@Test
public void testDump() {
when(mMockContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP))
@@ -146,11 +211,17 @@
return new ManualTimeSuggestion(timeValue);
}
+ private static NetworkTimeSuggestion createNetworkTimeSuggestion() {
+ TimestampedValue<Long> timeValue = new TimestampedValue<>(100L, 1_000_000L);
+ return new NetworkTimeSuggestion(timeValue);
+ }
+
private static class StubbedTimeDetectorStrategy implements TimeDetectorStrategy {
// Call tracking.
private PhoneTimeSuggestion mLastPhoneSuggestion;
private ManualTimeSuggestion mLastManualSuggestion;
+ private NetworkTimeSuggestion mLastNetworkSuggestion;
private boolean mLastAutoTimeDetectionToggleCalled;
private boolean mDumpCalled;
@@ -171,6 +242,12 @@
}
@Override
+ public void suggestNetworkTime(NetworkTimeSuggestion timeSuggestion) {
+ resetCallTracking();
+ mLastNetworkSuggestion = timeSuggestion;
+ }
+
+ @Override
public void handleAutoTimeDetectionChanged() {
resetCallTracking();
mLastAutoTimeDetectionToggleCalled = true;
@@ -185,6 +262,7 @@
void resetCallTracking() {
mLastPhoneSuggestion = null;
mLastManualSuggestion = null;
+ mLastNetworkSuggestion = null;
mLastAutoTimeDetectionToggleCalled = false;
mDumpCalled = false;
}
@@ -197,6 +275,10 @@
assertEquals(expectedSuggestion, mLastManualSuggestion);
}
+ public void verifySuggestNetworkTimeCalled(NetworkTimeSuggestion expectedSuggestion) {
+ assertEquals(expectedSuggestion, mLastNetworkSuggestion);
+ }
+
void verifyHandleAutoTimeDetectionToggleCalled() {
assertTrue(mLastAutoTimeDetectionToggleCalled);
}
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
index 1aa3d8f..d940a6a 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
@@ -18,18 +18,17 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import android.app.timedetector.ManualTimeSuggestion;
+import android.app.timedetector.NetworkTimeSuggestion;
import android.app.timedetector.PhoneTimeSuggestion;
-import android.content.Intent;
import android.icu.util.Calendar;
import android.icu.util.GregorianCalendar;
import android.icu.util.TimeZone;
-import android.util.TimestampedValue;
+import android.os.TimestampedValue;
import androidx.test.runner.AndroidJUnit4;
@@ -45,14 +44,16 @@
private static final TimestampedValue<Long> ARBITRARY_CLOCK_INITIALIZATION_INFO =
new TimestampedValue<>(
123456789L /* realtimeClockMillis */,
- createUtcTime(1977, 1, 1, 12, 0, 0));
+ createUtcTime(2008, 5, 23, 12, 0, 0));
+ /**
+ * An arbitrary time, very different from the {@link #ARBITRARY_CLOCK_INITIALIZATION_INFO}
+ * time. Can be used as the basis for time suggestions.
+ */
private static final long ARBITRARY_TEST_TIME_MILLIS = createUtcTime(2018, 1, 1, 12, 0, 0);
private static final int ARBITRARY_PHONE_ID = 123456;
- private static final long ONE_DAY_MILLIS = Duration.ofDays(1).toMillis();
-
private Script mScript;
@Before
@@ -67,15 +68,15 @@
int phoneId = ARBITRARY_PHONE_ID;
long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
+
PhoneTimeSuggestion timeSuggestion =
mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis);
- int clockIncrement = 1000;
- long expectedSystemClockMillis = testTimeMillis + clockIncrement;
+ mScript.simulateTimePassing()
+ .simulatePhoneTimeSuggestion(timeSuggestion);
- mScript.simulateTimePassing(clockIncrement)
- .simulatePhoneTimeSuggestion(timeSuggestion)
- .verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis, true /* expectNetworkBroadcast */)
+ long expectedSystemClockMillis =
+ mScript.calculateTimeInMillisForNow(timeSuggestion.getUtcTime());
+ mScript.verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis)
.assertLatestPhoneSuggestion(phoneId, timeSuggestion);
}
@@ -94,30 +95,27 @@
@Test
public void testSuggestPhoneTime_systemClockThreshold() {
- int systemClockUpdateThresholdMillis = 1000;
+ final int systemClockUpdateThresholdMillis = 1000;
+ final int clockIncrementMillis = 100;
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeThresholds(systemClockUpdateThresholdMillis)
.pokeAutoTimeDetectionEnabled(true);
- final int clockIncrement = 100;
int phoneId = ARBITRARY_PHONE_ID;
// Send the first time signal. It should be used.
{
- long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
PhoneTimeSuggestion timeSuggestion1 =
- mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis);
- TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime();
+ mScript.generatePhoneTimeSuggestion(phoneId, ARBITRARY_TEST_TIME_MILLIS);
// Increment the the device clocks to simulate the passage of time.
- mScript.simulateTimePassing(clockIncrement);
+ mScript.simulateTimePassing(clockIncrementMillis);
long expectedSystemClockMillis1 =
- TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis());
+ mScript.calculateTimeInMillisForNow(timeSuggestion1.getUtcTime());
mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
- .verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis1, true /* expectNetworkBroadcast */)
+ .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1)
.assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
}
@@ -127,7 +125,7 @@
int underThresholdMillis = systemClockUpdateThresholdMillis - 1;
PhoneTimeSuggestion timeSuggestion2 = mScript.generatePhoneTimeSuggestion(
phoneId, mScript.peekSystemClockMillis() + underThresholdMillis);
- mScript.simulateTimePassing(clockIncrement)
+ mScript.simulateTimePassing(clockIncrementMillis)
.simulatePhoneTimeSuggestion(timeSuggestion2)
.verifySystemClockWasNotSetAndResetCallTracking()
.assertLatestPhoneSuggestion(phoneId, timeSuggestion2);
@@ -138,15 +136,13 @@
PhoneTimeSuggestion timeSuggestion3 = mScript.generatePhoneTimeSuggestion(
phoneId,
mScript.peekSystemClockMillis() + systemClockUpdateThresholdMillis);
- mScript.simulateTimePassing(clockIncrement);
+ mScript.simulateTimePassing(clockIncrementMillis);
long expectedSystemClockMillis3 =
- TimeDetectorStrategy.getTimeAt(timeSuggestion3.getUtcTime(),
- mScript.peekElapsedRealtimeMillis());
+ mScript.calculateTimeInMillisForNow(timeSuggestion3.getUtcTime());
mScript.simulatePhoneTimeSuggestion(timeSuggestion3)
- .verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis3, true /* expectNetworkBroadcast */)
+ .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis3)
.assertLatestPhoneSuggestion(phoneId, timeSuggestion3);
}
}
@@ -162,50 +158,48 @@
int phone1Id = ARBITRARY_PHONE_ID;
int phone2Id = ARBITRARY_PHONE_ID + 1;
long phone1TimeMillis = ARBITRARY_TEST_TIME_MILLIS;
- long phone2TimeMillis = phone1TimeMillis + 60000;
-
- final int clockIncrement = 999;
+ long phone2TimeMillis = ARBITRARY_TEST_TIME_MILLIS + Duration.ofDays(1).toMillis();
// Make a suggestion with phone2Id.
{
PhoneTimeSuggestion phone2TimeSuggestion =
mScript.generatePhoneTimeSuggestion(phone2Id, phone2TimeMillis);
- mScript.simulateTimePassing(clockIncrement);
+ mScript.simulateTimePassing();
- long expectedSystemClockMillis = phone2TimeMillis + clockIncrement;
+ long expectedSystemClockMillis =
+ mScript.calculateTimeInMillisForNow(phone2TimeSuggestion.getUtcTime());
mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion)
- .verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis, true /* expectNetworkBroadcast */)
+ .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis)
.assertLatestPhoneSuggestion(phone1Id, null)
.assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion);
}
- mScript.simulateTimePassing(clockIncrement);
+ mScript.simulateTimePassing();
// Now make a different suggestion with phone1Id.
{
PhoneTimeSuggestion phone1TimeSuggestion =
mScript.generatePhoneTimeSuggestion(phone1Id, phone1TimeMillis);
- mScript.simulateTimePassing(clockIncrement);
+ mScript.simulateTimePassing();
- long expectedSystemClockMillis = phone1TimeMillis + clockIncrement;
+ long expectedSystemClockMillis =
+ mScript.calculateTimeInMillisForNow(phone1TimeSuggestion.getUtcTime());
mScript.simulatePhoneTimeSuggestion(phone1TimeSuggestion)
- .verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis, true /* expectNetworkBroadcast */)
+ .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis)
.assertLatestPhoneSuggestion(phone1Id, phone1TimeSuggestion);
}
- mScript.simulateTimePassing(clockIncrement);
+ mScript.simulateTimePassing();
// Make another suggestion with phone2Id. It should be stored but not used because the
// phone1Id suggestion will still "win".
{
PhoneTimeSuggestion phone2TimeSuggestion =
mScript.generatePhoneTimeSuggestion(phone2Id, phone2TimeMillis);
- mScript.simulateTimePassing(clockIncrement);
+ mScript.simulateTimePassing();
mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion)
.verifySystemClockWasNotSetAndResetCallTracking()
@@ -220,13 +214,13 @@
{
PhoneTimeSuggestion phone2TimeSuggestion =
mScript.generatePhoneTimeSuggestion(phone2Id, phone2TimeMillis);
- mScript.simulateTimePassing(clockIncrement);
+ mScript.simulateTimePassing();
- long expectedSystemClockMillis = phone2TimeMillis + clockIncrement;
+ long expectedSystemClockMillis =
+ mScript.calculateTimeInMillisForNow(phone2TimeSuggestion.getUtcTime());
mScript.simulatePhoneTimeSuggestion(phone2TimeSuggestion)
- .verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis, true /* expectNetworkBroadcast */)
+ .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis)
.assertLatestPhoneSuggestion(phone2Id, phone2TimeSuggestion);
}
}
@@ -239,7 +233,7 @@
int phoneId = ARBITRARY_PHONE_ID;
PhoneTimeSuggestion timeSuggestion =
mScript.generatePhoneTimeSuggestion(phoneId, ARBITRARY_TEST_TIME_MILLIS);
- mScript.simulateTimePassing(1000)
+ mScript.simulateTimePassing()
.simulatePhoneTimeSuggestion(timeSuggestion)
.verifySystemClockWasNotSetAndResetCallTracking()
.assertLatestPhoneSuggestion(phoneId, timeSuggestion);
@@ -260,12 +254,10 @@
TimestampedValue<Long> utcTime1 = timeSuggestion1.getUtcTime();
// Initialize the strategy / device with a time set from a phone suggestion.
- mScript.simulateTimePassing(100);
- long expectedSystemClockMillis1 =
- TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis());
+ mScript.simulateTimePassing();
+ long expectedSystemClockMillis1 = mScript.calculateTimeInMillisForNow(utcTime1);
mScript.simulatePhoneTimeSuggestion(timeSuggestion1)
- .verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis1, true /* expectNetworkBroadcast */)
+ .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1)
.assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
// The UTC time increment should be larger than the system clock update threshold so we
@@ -299,13 +291,11 @@
long validReferenceTimeMillis = utcTime1.getReferenceTimeMillis() + 100;
TimestampedValue<Long> utcTime4 = new TimestampedValue<>(
validReferenceTimeMillis, validUtcTimeMillis);
- long expectedSystemClockMillis4 =
- TimeDetectorStrategy.getTimeAt(utcTime4, mScript.peekElapsedRealtimeMillis());
+ long expectedSystemClockMillis4 = mScript.calculateTimeInMillisForNow(utcTime4);
PhoneTimeSuggestion timeSuggestion4 =
createPhoneTimeSuggestion(phoneId, utcTime4);
mScript.simulatePhoneTimeSuggestion(timeSuggestion4)
- .verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis4, true /* expectNetworkBroadcast */)
+ .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis4)
.assertLatestPhoneSuggestion(phoneId, timeSuggestion4);
}
@@ -335,13 +325,11 @@
// Simulate more time passing.
mScript.simulateTimePassing(clockIncrementMillis);
- long expectedSystemClockMillis1 =
- TimeDetectorStrategy.getTimeAt(utcTime1, mScript.peekElapsedRealtimeMillis());
+ long expectedSystemClockMillis1 = mScript.calculateTimeInMillisForNow(utcTime1);
// Turn on auto time detection.
mScript.simulateAutoTimeDetectionToggle()
- .verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis1, true /* expectNetworkBroadcast */)
+ .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis1)
.assertLatestPhoneSuggestion(phoneId, timeSuggestion1);
// Turn off auto time detection.
@@ -357,8 +345,8 @@
// Simulate more time passing.
mScript.simulateTimePassing(clockIncrementMillis);
- long expectedSystemClockMillis2 = TimeDetectorStrategy.getTimeAt(
- timeSuggestion2.getUtcTime(), mScript.peekElapsedRealtimeMillis());
+ long expectedSystemClockMillis2 =
+ mScript.calculateTimeInMillisForNow(timeSuggestion2.getUtcTime());
// The new time, though valid, should not be set in the system clock because auto time is
// disabled.
@@ -368,8 +356,7 @@
// Turn on auto time detection.
mScript.simulateAutoTimeDetectionToggle()
- .verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis2, true /* expectNetworkBroadcast */)
+ .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis2)
.assertLatestPhoneSuggestion(phoneId, timeSuggestion2);
}
@@ -382,19 +369,21 @@
long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
PhoneTimeSuggestion phoneSuggestion =
mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis);
- int clockIncrementMillis = 1000;
- mScript.simulateTimePassing(clockIncrementMillis)
- .simulatePhoneTimeSuggestion(phoneSuggestion)
+ mScript.simulateTimePassing();
+
+ long expectedSystemClockMillis =
+ mScript.calculateTimeInMillisForNow(phoneSuggestion.getUtcTime());
+ mScript.simulatePhoneTimeSuggestion(phoneSuggestion)
.verifySystemClockWasSetAndResetCallTracking(
- testTimeMillis + clockIncrementMillis, true /* expectedNetworkBroadcast */)
+ expectedSystemClockMillis /* expectedNetworkBroadcast */)
.assertLatestPhoneSuggestion(phoneId, phoneSuggestion);
// Look inside and check what the strategy considers the current best phone suggestion.
assertEquals(phoneSuggestion, mScript.peekBestPhoneSuggestion());
// Simulate time passing, long enough that phoneSuggestion is now too old.
- mScript.simulateTimePassing(TimeDetectorStrategyImpl.PHONE_MAX_AGE_MILLIS);
+ mScript.simulateTimePassing(TimeDetectorStrategyImpl.MAX_UTC_TIME_AGE_MILLIS);
// Look inside and check what the strategy considers the current best phone suggestion. It
// should still be the, it's just no longer used.
@@ -407,15 +396,15 @@
mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
.pokeAutoTimeDetectionEnabled(false);
- long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
- ManualTimeSuggestion timeSuggestion = mScript.generateManualTimeSuggestion(testTimeMillis);
- final int clockIncrement = 1000;
- long expectedSystemClockMillis = testTimeMillis + clockIncrement;
+ ManualTimeSuggestion timeSuggestion =
+ mScript.generateManualTimeSuggestion(ARBITRARY_TEST_TIME_MILLIS);
- mScript.simulateTimePassing(clockIncrement)
- .simulateManualTimeSuggestion(timeSuggestion)
- .verifySystemClockWasSetAndResetCallTracking(
- expectedSystemClockMillis, false /* expectNetworkBroadcast */);
+ mScript.simulateTimePassing();
+
+ long expectedSystemClockMillis =
+ mScript.calculateTimeInMillisForNow(timeSuggestion.getUtcTime());
+ mScript.simulateManualTimeSuggestion(timeSuggestion)
+ .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis);
}
@Test
@@ -430,21 +419,18 @@
long testTimeMillis = ARBITRARY_TEST_TIME_MILLIS;
PhoneTimeSuggestion phoneTimeSuggestion =
mScript.generatePhoneTimeSuggestion(phoneId, testTimeMillis);
- long expectedAutoClockMillis = phoneTimeSuggestion.getUtcTime().getValue();
- final int clockIncrement = 1000;
// Simulate the passage of time.
- mScript.simulateTimePassing(clockIncrement);
- expectedAutoClockMillis += clockIncrement;
+ mScript.simulateTimePassing();
+ long expectedAutoClockMillis =
+ mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime());
mScript.simulatePhoneTimeSuggestion(phoneTimeSuggestion)
- .verifySystemClockWasSetAndResetCallTracking(
- expectedAutoClockMillis, true /* expectNetworkBroadcast */)
+ .verifySystemClockWasSetAndResetCallTracking(expectedAutoClockMillis)
.assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
// Simulate the passage of time.
- mScript.simulateTimePassing(clockIncrement);
- expectedAutoClockMillis += clockIncrement;
+ mScript.simulateTimePassing();
// Switch to manual.
mScript.simulateAutoTimeDetectionToggle()
@@ -452,28 +438,29 @@
.assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
// Simulate the passage of time.
- mScript.simulateTimePassing(clockIncrement);
- expectedAutoClockMillis += clockIncrement;
+ mScript.simulateTimePassing();
// Simulate a manual suggestion 1 day different from the auto suggestion.
- long manualTimeMillis = testTimeMillis + ONE_DAY_MILLIS;
- long expectedManualClockMillis = manualTimeMillis;
+ long manualTimeMillis = testTimeMillis + Duration.ofDays(1).toMillis();
ManualTimeSuggestion manualTimeSuggestion =
mScript.generateManualTimeSuggestion(manualTimeMillis);
+ mScript.simulateTimePassing();
+
+ long expectedManualClockMillis =
+ mScript.calculateTimeInMillisForNow(manualTimeSuggestion.getUtcTime());
mScript.simulateManualTimeSuggestion(manualTimeSuggestion)
- .verifySystemClockWasSetAndResetCallTracking(
- expectedManualClockMillis, false /* expectNetworkBroadcast */)
+ .verifySystemClockWasSetAndResetCallTracking(expectedManualClockMillis)
.assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
// Simulate the passage of time.
- mScript.simulateTimePassing(clockIncrement);
- expectedAutoClockMillis += clockIncrement;
+ mScript.simulateTimePassing();
// Switch back to auto.
mScript.simulateAutoTimeDetectionToggle();
- mScript.verifySystemClockWasSetAndResetCallTracking(
- expectedAutoClockMillis, true /* expectNetworkBroadcast */)
+ expectedAutoClockMillis =
+ mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime());
+ mScript.verifySystemClockWasSetAndResetCallTracking(expectedAutoClockMillis)
.assertLatestPhoneSuggestion(phoneId, phoneTimeSuggestion);
// Switch back to manual - nothing should happen to the clock.
@@ -492,13 +479,139 @@
ManualTimeSuggestion timeSuggestion =
mScript.generateManualTimeSuggestion(ARBITRARY_TEST_TIME_MILLIS);
- final int clockIncrement = 1000;
- mScript.simulateTimePassing(clockIncrement)
+ mScript.simulateTimePassing()
.simulateManualTimeSuggestion(timeSuggestion)
.verifySystemClockWasNotSetAndResetCallTracking();
}
+ @Test
+ public void testSuggestNetworkTime_autoTimeEnabled() {
+ mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+ .pokeAutoTimeDetectionEnabled(true);
+
+ NetworkTimeSuggestion timeSuggestion =
+ mScript.generateNetworkTimeSuggestion(ARBITRARY_TEST_TIME_MILLIS);
+
+ mScript.simulateTimePassing();
+
+ long expectedSystemClockMillis =
+ mScript.calculateTimeInMillisForNow(timeSuggestion.getUtcTime());
+ mScript.simulateNetworkTimeSuggestion(timeSuggestion)
+ .verifySystemClockWasSetAndResetCallTracking(expectedSystemClockMillis);
+ }
+
+ @Test
+ public void testSuggestNetworkTime_autoTimeDisabled() {
+ mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+ .pokeAutoTimeDetectionEnabled(false);
+
+ NetworkTimeSuggestion timeSuggestion =
+ mScript.generateNetworkTimeSuggestion(ARBITRARY_TEST_TIME_MILLIS);
+
+ mScript.simulateTimePassing()
+ .simulateNetworkTimeSuggestion(timeSuggestion)
+ .verifySystemClockWasNotSetAndResetCallTracking();
+ }
+
+ @Test
+ public void testSuggestNetworkTime_phoneSuggestionsBeatNetworkSuggestions() {
+ mScript.pokeFakeClocks(ARBITRARY_CLOCK_INITIALIZATION_INFO)
+ .pokeAutoTimeDetectionEnabled(true);
+
+ // Three obviously different times that could not be mistaken for each other.
+ long networkTimeMillis1 = ARBITRARY_TEST_TIME_MILLIS;
+ long networkTimeMillis2 = ARBITRARY_TEST_TIME_MILLIS + Duration.ofDays(30).toMillis();
+ long phoneTimeMillis = ARBITRARY_TEST_TIME_MILLIS + Duration.ofDays(60).toMillis();
+ // A small increment used to simulate the passage of time, but not enough to interfere with
+ // macro-level time changes associated with suggestion age.
+ final long smallTimeIncrementMillis = 101;
+
+ // A network suggestion is made. It should be used because there is no phone suggestion.
+ NetworkTimeSuggestion networkTimeSuggestion1 =
+ mScript.generateNetworkTimeSuggestion(networkTimeMillis1);
+ mScript.simulateTimePassing(smallTimeIncrementMillis)
+ .simulateNetworkTimeSuggestion(networkTimeSuggestion1)
+ .verifySystemClockWasSetAndResetCallTracking(
+ mScript.calculateTimeInMillisForNow(networkTimeSuggestion1.getUtcTime()));
+
+ // Check internal state.
+ mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, null)
+ .assertLatestNetworkSuggestion(networkTimeSuggestion1);
+ assertEquals(networkTimeSuggestion1, mScript.peekLatestValidNetworkSuggestion());
+ assertNull(mScript.peekBestPhoneSuggestion());
+
+ // Simulate a little time passing.
+ mScript.simulateTimePassing(smallTimeIncrementMillis)
+ .verifySystemClockWasNotSetAndResetCallTracking();
+
+ // Now a phone suggestion is made. Phone suggestions are prioritized over network
+ // suggestions so it should "win".
+ PhoneTimeSuggestion phoneTimeSuggestion =
+ mScript.generatePhoneTimeSuggestion(ARBITRARY_PHONE_ID, phoneTimeMillis);
+ mScript.simulateTimePassing(smallTimeIncrementMillis)
+ .simulatePhoneTimeSuggestion(phoneTimeSuggestion)
+ .verifySystemClockWasSetAndResetCallTracking(
+ mScript.calculateTimeInMillisForNow(phoneTimeSuggestion.getUtcTime()));
+
+ // Check internal state.
+ mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, phoneTimeSuggestion)
+ .assertLatestNetworkSuggestion(networkTimeSuggestion1);
+ assertEquals(networkTimeSuggestion1, mScript.peekLatestValidNetworkSuggestion());
+ assertEquals(phoneTimeSuggestion, mScript.peekBestPhoneSuggestion());
+
+ // Simulate some significant time passing: half the time allowed before a time signal
+ // becomes "too old to use".
+ mScript.simulateTimePassing(TimeDetectorStrategyImpl.MAX_UTC_TIME_AGE_MILLIS / 2)
+ .verifySystemClockWasNotSetAndResetCallTracking();
+
+ // Now another network suggestion is made. Phone suggestions are prioritized over network
+ // suggestions so the latest phone suggestion should still "win".
+ NetworkTimeSuggestion networkTimeSuggestion2 =
+ mScript.generateNetworkTimeSuggestion(networkTimeMillis2);
+ mScript.simulateTimePassing(smallTimeIncrementMillis)
+ .simulateNetworkTimeSuggestion(networkTimeSuggestion2)
+ .verifySystemClockWasNotSetAndResetCallTracking();
+
+ // Check internal state.
+ mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, phoneTimeSuggestion)
+ .assertLatestNetworkSuggestion(networkTimeSuggestion2);
+ assertEquals(networkTimeSuggestion2, mScript.peekLatestValidNetworkSuggestion());
+ assertEquals(phoneTimeSuggestion, mScript.peekBestPhoneSuggestion());
+
+ // Simulate some significant time passing: half the time allowed before a time signal
+ // becomes "too old to use". This should mean that phoneTimeSuggestion is now too old to be
+ // used but networkTimeSuggestion2 is not.
+ mScript.simulateTimePassing(TimeDetectorStrategyImpl.MAX_UTC_TIME_AGE_MILLIS / 2);
+
+ // NOTE: The TimeDetectorStrategyImpl doesn't set an alarm for the point when the last
+ // suggestion it used becomes too old: it requires a new suggestion or an auto-time toggle
+ // to re-run the detection logic. This may change in future but until then we rely on a
+ // steady stream of suggestions to re-evaluate.
+ mScript.verifySystemClockWasNotSetAndResetCallTracking();
+
+ // Check internal state.
+ mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, phoneTimeSuggestion)
+ .assertLatestNetworkSuggestion(networkTimeSuggestion2);
+ assertEquals(networkTimeSuggestion2, mScript.peekLatestValidNetworkSuggestion());
+ assertNull(mScript.peekBestPhoneSuggestion());
+
+ // Toggle auto-time off and on to force the detection logic to run.
+ mScript.simulateAutoTimeDetectionToggle()
+ .simulateTimePassing(smallTimeIncrementMillis)
+ .simulateAutoTimeDetectionToggle();
+
+ // Verify the latest network time now wins.
+ mScript.verifySystemClockWasSetAndResetCallTracking(
+ mScript.calculateTimeInMillisForNow(networkTimeSuggestion2.getUtcTime()));
+
+ // Check internal state.
+ mScript.assertLatestPhoneSuggestion(ARBITRARY_PHONE_ID, phoneTimeSuggestion)
+ .assertLatestNetworkSuggestion(networkTimeSuggestion2);
+ assertEquals(networkTimeSuggestion2, mScript.peekLatestValidNetworkSuggestion());
+ assertNull(mScript.peekBestPhoneSuggestion());
+ }
+
/**
* A fake implementation of TimeDetectorStrategy.Callback. Besides tracking changes and behaving
* like the real thing should, it also asserts preconditions.
@@ -512,7 +625,6 @@
// Tracking operations.
private boolean mSystemClockWasSet;
- private Intent mBroadcastSent;
@Override
public int systemClockUpdateThresholdMillis() {
@@ -539,7 +651,6 @@
@Override
public long systemClockMillis() {
- assertWakeLockAcquired();
return mSystemClockMillis;
}
@@ -556,12 +667,6 @@
mWakeLockAcquired = false;
}
- @Override
- public void sendStickyBroadcast(Intent intent) {
- assertNotNull(intent);
- mBroadcastSent = intent;
- }
-
// Methods below are for managing the fake's behavior.
void pokeSystemClockUpdateThreshold(int thresholdMillis) {
@@ -606,17 +711,8 @@
assertEquals(expectedSystemClockMillis, mSystemClockMillis);
}
- void verifyIntentWasBroadcast() {
- assertTrue(mBroadcastSent != null);
- }
-
- void verifyIntentWasNotBroadcast() {
- assertNull(mBroadcastSent);
- }
-
void resetCallTracking() {
mSystemClockWasSet = false;
- mBroadcastSent = null;
}
private void assertWakeLockAcquired() {
@@ -674,6 +770,11 @@
return this;
}
+ Script simulateNetworkTimeSuggestion(NetworkTimeSuggestion timeSuggestion) {
+ mTimeDetectorStrategy.suggestNetworkTime(timeSuggestion);
+ return this;
+ }
+
Script simulateAutoTimeDetectionToggle() {
mFakeCallback.simulateAutoTimeZoneDetectionToggle();
mTimeDetectorStrategy.handleAutoTimeDetectionChanged();
@@ -685,19 +786,21 @@
return this;
}
+ /**
+ * Simulates time passing by an arbitrary (but relatively small) amount.
+ */
+ Script simulateTimePassing() {
+ return simulateTimePassing(999);
+ }
+
Script verifySystemClockWasNotSetAndResetCallTracking() {
mFakeCallback.verifySystemClockNotSet();
- mFakeCallback.verifyIntentWasNotBroadcast();
mFakeCallback.resetCallTracking();
return this;
}
- Script verifySystemClockWasSetAndResetCallTracking(
- long expectedSystemClockMillis, boolean expectNetworkBroadcast) {
+ Script verifySystemClockWasSetAndResetCallTracking(long expectedSystemClockMillis) {
mFakeCallback.verifySystemClockWasSet(expectedSystemClockMillis);
- if (expectNetworkBroadcast) {
- mFakeCallback.verifyIntentWasBroadcast();
- }
mFakeCallback.resetCallTracking();
return this;
}
@@ -711,14 +814,30 @@
}
/**
+ * White box test info: Asserts the latest network suggestion is as expected.
+ */
+ Script assertLatestNetworkSuggestion(NetworkTimeSuggestion expected) {
+ assertEquals(expected, mTimeDetectorStrategy.getLatestNetworkSuggestion());
+ return this;
+ }
+
+ /**
* White box test info: Returns the phone suggestion that would be used, if any, given the
- * current elapsed real time clock.
+ * current elapsed real time clock and regardless of origin prioritization.
*/
PhoneTimeSuggestion peekBestPhoneSuggestion() {
return mTimeDetectorStrategy.findBestPhoneSuggestionForTests();
}
/**
+ * White box test info: Returns the network suggestion that would be used, if any, given the
+ * current elapsed real time clock and regardless of origin prioritization.
+ */
+ NetworkTimeSuggestion peekLatestValidNetworkSuggestion() {
+ return mTimeDetectorStrategy.findLatestValidNetworkSuggestionForTests();
+ }
+
+ /**
* Generates a ManualTimeSuggestion using the current elapsed realtime clock for the
* reference time.
*/
@@ -739,6 +858,24 @@
}
return createPhoneTimeSuggestion(phoneId, time);
}
+
+ /**
+ * Generates a NetworkTimeSuggestion using the current elapsed realtime clock for the
+ * reference time.
+ */
+ NetworkTimeSuggestion generateNetworkTimeSuggestion(long timeMillis) {
+ TimestampedValue<Long> utcTime =
+ new TimestampedValue<>(mFakeCallback.peekElapsedRealtimeMillis(), timeMillis);
+ return new NetworkTimeSuggestion(utcTime);
+ }
+
+ /**
+ * Calculates what the supplied time would be when adjusted for the movement of the fake
+ * elapsed realtime clock.
+ */
+ long calculateTimeInMillisForNow(TimestampedValue<Long> utcTime) {
+ return TimeDetectorStrategy.getTimeAt(utcTime, peekElapsedRealtimeMillis());
+ }
}
private static PhoneTimeSuggestion createPhoneTimeSuggestion(int phoneId,
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyTest.java
index 239d413..f1e9191 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyTest.java
@@ -18,7 +18,7 @@
import static org.junit.Assert.assertEquals;
-import android.util.TimestampedValue;
+import android.os.TimestampedValue;
import androidx.test.runner.AndroidJUnit4;
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index 12ba219..6aca58f 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -23,7 +23,8 @@
import static android.app.usage.UsageEvents.Event.SYSTEM_INTERACTION;
import static android.app.usage.UsageEvents.Event.USER_INTERACTION;
import static android.app.usage.UsageStatsManager.REASON_MAIN_DEFAULT;
-import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_SYSTEM;
+import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED_BY_USER;
import static android.app.usage.UsageStatsManager.REASON_MAIN_PREDICTED;
import static android.app.usage.UsageStatsManager.REASON_MAIN_TIMEOUT;
import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
@@ -365,8 +366,9 @@
public void testSetAppStandbyBucket() throws Exception {
// For a known package, standby bucket should be set properly
reportEvent(mController, USER_INTERACTION, 0, PACKAGE_1);
+ mInjector.mElapsedRealtime = HOUR_MS;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
- REASON_MAIN_TIMEOUT, HOUR_MS);
+ REASON_MAIN_TIMEOUT);
assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
// For an unknown package, standby bucket should not be set, hence NEVER is returned
@@ -374,7 +376,7 @@
mController.clearAppIdleForPackage(PACKAGE_UNKNOWN, USER_ID);
isPackageInstalled = false; // Mock package is not installed
mController.setAppStandbyBucket(PACKAGE_UNKNOWN, USER_ID, STANDBY_BUCKET_ACTIVE,
- REASON_MAIN_TIMEOUT, HOUR_MS);
+ REASON_MAIN_TIMEOUT);
isPackageInstalled = true; // Reset mocked variable for other tests
assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_UNKNOWN));
}
@@ -468,12 +470,13 @@
@Test
public void testPredictionTimedout() throws Exception {
// Set it to timeout or usage, so that prediction can override it
+ mInjector.mElapsedRealtime = HOUR_MS;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
- REASON_MAIN_TIMEOUT, HOUR_MS);
+ REASON_MAIN_TIMEOUT);
assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
- REASON_MAIN_PREDICTED, HOUR_MS);
+ REASON_MAIN_PREDICTED);
assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
// Fast forward 12 hours
@@ -497,29 +500,37 @@
@Test
public void testOverrides() throws Exception {
// Can force to NEVER
+ mInjector.mElapsedRealtime = HOUR_MS;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER,
- REASON_MAIN_FORCED, 1 * HOUR_MS);
+ REASON_MAIN_FORCED_BY_USER);
assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_1));
- // Prediction can't override FORCED reason
- mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
- REASON_MAIN_FORCED, 1 * HOUR_MS);
+ // Prediction can't override FORCED reasons
+ mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
+ REASON_MAIN_FORCED_BY_SYSTEM);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
- REASON_MAIN_PREDICTED, 1 * HOUR_MS);
+ REASON_MAIN_PREDICTED);
+ assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController, PACKAGE_1));
+
+ mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
+ REASON_MAIN_FORCED_BY_USER);
+ mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
+ REASON_MAIN_PREDICTED);
assertEquals(STANDBY_BUCKET_FREQUENT, getStandbyBucket(mController, PACKAGE_1));
// Prediction can't override NEVER
+ mInjector.mElapsedRealtime = 2 * HOUR_MS;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER,
- REASON_MAIN_DEFAULT, 2 * HOUR_MS);
+ REASON_MAIN_DEFAULT);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
- REASON_MAIN_PREDICTED, 2 * HOUR_MS);
+ REASON_MAIN_PREDICTED);
assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController, PACKAGE_1));
// Prediction can't set to NEVER
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
- REASON_MAIN_USAGE, 2 * HOUR_MS);
+ REASON_MAIN_USAGE);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER,
- REASON_MAIN_PREDICTED, 2 * HOUR_MS);
+ REASON_MAIN_PREDICTED);
assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController, PACKAGE_1));
}
@@ -530,7 +541,7 @@
mInjector.mElapsedRealtime = 2000;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
- REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime);
+ REASON_MAIN_PREDICTED);
assertBucket(STANDBY_BUCKET_ACTIVE);
// bucketing works after timeout
@@ -554,15 +565,17 @@
assertBucket(STANDBY_BUCKET_ACTIVE);
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
- REASON_MAIN_PREDICTED, 1000);
+ REASON_MAIN_PREDICTED);
assertBucket(STANDBY_BUCKET_ACTIVE);
+ mInjector.mElapsedRealtime = 2000 + mController.mStrongUsageTimeoutMillis;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
- REASON_MAIN_PREDICTED, 2000 + mController.mStrongUsageTimeoutMillis);
+ REASON_MAIN_PREDICTED);
assertBucket(STANDBY_BUCKET_WORKING_SET);
+ mInjector.mElapsedRealtime = 2000 + mController.mNotificationSeenTimeoutMillis;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
- REASON_MAIN_PREDICTED, 2000 + mController.mNotificationSeenTimeoutMillis);
+ REASON_MAIN_PREDICTED);
assertBucket(STANDBY_BUCKET_FREQUENT);
}
@@ -582,18 +595,18 @@
// Still in ACTIVE after first USER_INTERACTION times out
mInjector.mElapsedRealtime = mController.mStrongUsageTimeoutMillis + 1000;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
- REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime);
+ REASON_MAIN_PREDICTED);
assertBucket(STANDBY_BUCKET_ACTIVE);
// Both timed out, so NOTIFICATION_SEEN timeout should be effective
mInjector.mElapsedRealtime = mController.mStrongUsageTimeoutMillis * 2 + 2000;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
- REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime);
+ REASON_MAIN_PREDICTED);
assertBucket(STANDBY_BUCKET_WORKING_SET);
mInjector.mElapsedRealtime = mController.mNotificationSeenTimeoutMillis + 2000;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
- REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime);
+ REASON_MAIN_PREDICTED);
assertBucket(STANDBY_BUCKET_RARE);
}
@@ -625,7 +638,7 @@
mInjector.mElapsedRealtime = 1 * RARE_THRESHOLD + 100;
// Make sure app is in NEVER bucket
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER,
- REASON_MAIN_FORCED, mInjector.mElapsedRealtime);
+ REASON_MAIN_FORCED_BY_USER);
mController.checkIdleStates(USER_ID);
assertBucket(STANDBY_BUCKET_NEVER);
@@ -670,7 +683,7 @@
// Predict to ACTIVE
mInjector.mElapsedRealtime += 1000;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
- REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime);
+ REASON_MAIN_PREDICTED);
assertBucket(STANDBY_BUCKET_ACTIVE);
// CheckIdleStates should not change the prediction
@@ -687,7 +700,7 @@
// Predict to FREQUENT
mInjector.mElapsedRealtime = RARE_THRESHOLD;
mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
- REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime);
+ REASON_MAIN_PREDICTED);
assertBucket(STANDBY_BUCKET_FREQUENT);
// Add a short timeout event
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
index eb45960..f5af3ec 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelExtractorTest.java
@@ -20,7 +20,6 @@
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static org.mockito.Matchers.any;
@@ -65,11 +64,11 @@
NotificationChannel updatedChannel =
new NotificationChannel("a", "", IMPORTANCE_HIGH);
- when(mConfig.getNotificationChannel(any(), anyInt(), eq("a"), eq(false)))
+ when(mConfig.getConversationNotificationChannel(
+ any(), anyInt(), eq("a"), eq(null), eq(true), eq(false)))
.thenReturn(updatedChannel);
assertNull(extractor.process(r));
assertEquals(updatedChannel, r.getChannel());
}
-
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 95617b1..2ac4642 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -112,6 +112,7 @@
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
+import android.os.Parcel;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
@@ -133,6 +134,7 @@
import android.testing.TestablePermissions;
import android.testing.TestableResources;
import android.text.Html;
+import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.AtomicFile;
@@ -469,16 +471,17 @@
mTestableLooper.processAllMessages();
}
- private void setUpPrefsForBubbles(boolean globalEnabled, boolean pkgEnabled,
- boolean channelEnabled) {
- mService.setPreferencesHelper(mPreferencesHelper);
- when(mPreferencesHelper.bubblesEnabled()).thenReturn(globalEnabled);
- when(mPreferencesHelper.areBubblesAllowed(anyString(), anyInt())).thenReturn(pkgEnabled);
- when(mPreferencesHelper.getNotificationChannel(
- anyString(), anyInt(), anyString(), anyBoolean())).thenReturn(
- mTestNotificationChannel);
- when(mPreferencesHelper.getImportance(anyString(), anyInt())).thenReturn(
- mTestNotificationChannel.getImportance());
+ private void setUpPrefsForBubbles(String pkg, int uid, boolean globalEnabled,
+ boolean pkgEnabled, boolean channelEnabled) {
+ Settings.Global.putInt(mContext.getContentResolver(),
+ Settings.Global.NOTIFICATION_BUBBLES, globalEnabled ? 1 : 0);
+ mService.mPreferencesHelper.updateBubblesEnabled();
+ assertEquals(globalEnabled, mService.mPreferencesHelper.bubblesEnabled());
+ try {
+ mBinderService.setBubblesAllowed(pkg, uid, pkgEnabled);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
mTestNotificationChannel.setAllowBubbles(channelEnabled);
}
@@ -492,23 +495,13 @@
return sbn;
}
-
private NotificationRecord generateNotificationRecord(NotificationChannel channel, int id,
String groupKey, boolean isSummary) {
- return generateNotificationRecord(channel, id, groupKey, isSummary, false /* isBubble */);
- }
-
- private NotificationRecord generateNotificationRecord(NotificationChannel channel, int id,
- String groupKey, boolean isSummary, boolean isBubble) {
Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
.setContentTitle("foo")
.setSmallIcon(android.R.drawable.sym_def_app_icon)
.setGroup(groupKey)
.setGroupSummary(isSummary);
- if (isBubble) {
- nb.setBubbleMetadata(getBasicBubbleMetadataBuilder().build());
- }
-
StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, id,
"tag" + System.currentTimeMillis(), mUid, 0,
nb.build(), new UserHandle(mUid), null, 0);
@@ -521,11 +514,6 @@
private NotificationRecord generateNotificationRecord(NotificationChannel channel,
Notification.TvExtender extender) {
- return generateNotificationRecord(channel, extender, false /* isBubble */);
- }
-
- private NotificationRecord generateNotificationRecord(NotificationChannel channel,
- Notification.TvExtender extender, boolean isBubble) {
if (channel == null) {
channel = mTestNotificationChannel;
}
@@ -535,9 +523,6 @@
if (extender != null) {
nb.extend(extender);
}
- if (isBubble) {
- nb.setBubbleMetadata(getBasicBubbleMetadataBuilder().build());
- }
StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 8, "tag", mUid, 0,
nb.build(), new UserHandle(mUid), null, 0);
return new NotificationRecord(mContext, sbn, channel);
@@ -555,6 +540,26 @@
return new NotificationRecord(mContext, sbn, channel);
}
+ private NotificationRecord generateMessageBubbleNotifRecord(NotificationChannel channel,
+ String tag) {
+ return generateMessageBubbleNotifRecord(true, channel, 1, tag, null, false);
+ }
+
+ private NotificationRecord generateMessageBubbleNotifRecord(boolean addMetadata,
+ NotificationChannel channel, int id, String tag, String groupKey, boolean isSummary) {
+ if (channel == null) {
+ channel = mTestNotificationChannel;
+ }
+ if (tag == null) {
+ tag = "tag";
+ }
+ Notification.Builder nb = getMessageStyleNotifBuilder(addMetadata, groupKey, isSummary);
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, id,
+ tag, mUid, 0,
+ nb.build(), new UserHandle(mUid), null, 0);
+ return new NotificationRecord(mContext, sbn, channel);
+ }
+
private Map<String, Answer> getSignalExtractorSideEffects() {
Map<String, Answer> answers = new ArrayMap<>();
@@ -610,23 +615,57 @@
false);
}
- private Notification.BubbleMetadata.Builder getBasicBubbleMetadataBuilder() {
+ private Notification.BubbleMetadata.Builder getBubbleMetadataBuilder() {
PendingIntent pi = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
return new Notification.BubbleMetadata.Builder()
.setIntent(pi)
.setIcon(Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon));
}
+ private Notification.Builder getMessageStyleNotifBuilder(boolean addBubbleMetadata,
+ String groupKey, boolean isSummary) {
+ // Give it a person
+ Person person = new Person.Builder()
+ .setName("bubblebot")
+ .build();
+ // It needs remote input to be bubble-able
+ RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
+ PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
+ Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
+ Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
+ inputIntent).addRemoteInput(remoteInput)
+ .build();
+ // Make it messaging style
+ Notification.Builder nb = new Notification.Builder(mContext,
+ mTestNotificationChannel.getId())
+ .setContentTitle("foo")
+ .setStyle(new Notification.MessagingStyle(person)
+ .setConversationTitle("Bubble Chat")
+ .addMessage("Hello?",
+ SystemClock.currentThreadTimeMillis() - 300000, person)
+ .addMessage("Is it me you're looking for?",
+ SystemClock.currentThreadTimeMillis(), person)
+ )
+ .setActions(replyAction)
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setGroupSummary(isSummary);
+ if (groupKey != null) {
+ nb.setGroup(groupKey);
+ }
+ if (addBubbleMetadata) {
+ nb.setBubbleMetadata(getBubbleMetadataBuilder().build());
+ }
+ return nb;
+ }
+
private NotificationRecord addGroupWithBubblesAndValidateAdded(boolean summaryAutoCancel)
throws RemoteException {
- // Notification that has bubble metadata
- NotificationRecord nrBubble = generateNotificationRecord(mTestNotificationChannel, 1,
- "BUBBLE_GROUP", false /* isSummary */, true /* isBubble */);
+ String groupKey = "BUBBLE_GROUP";
- // Make the package foreground so that we're allowed to be a bubble
- when(mActivityManager.getPackageImportance(nrBubble.sbn.getPackageName())).thenReturn(
- IMPORTANCE_FOREGROUND);
+ // Notification that has bubble metadata
+ NotificationRecord nrBubble = generateMessageBubbleNotifRecord(true /* addMetadata */,
+ mTestNotificationChannel, 1 /* id */, "tag", groupKey, false /* isSummary */);
mBinderService.enqueueNotificationWithTag(PKG, PKG, nrBubble.sbn.getTag(),
nrBubble.sbn.getId(), nrBubble.sbn.getNotification(), nrBubble.sbn.getUserId());
@@ -637,9 +676,10 @@
assertEquals(1, notifsAfter.length);
assertTrue((notifsAfter[0].getNotification().flags & FLAG_BUBBLE) != 0);
- // Plain notification without bubble metadata
- NotificationRecord nrPlain = generateNotificationRecord(mTestNotificationChannel, 2,
- "BUBBLE_GROUP", false /* isSummary */, false /* isBubble */);
+ // Notification without bubble metadata
+ NotificationRecord nrPlain = generateMessageBubbleNotifRecord(false /* addMetadata */,
+ mTestNotificationChannel, 2 /* id */, "tag", groupKey, false /* isSummary */);
+
mBinderService.enqueueNotificationWithTag(PKG, PKG, nrPlain.sbn.getTag(),
nrPlain.sbn.getId(), nrPlain.sbn.getNotification(), nrPlain.sbn.getUserId());
waitForIdle();
@@ -648,8 +688,9 @@
assertEquals(2, notifsAfter.length);
// Summary notification for both of those
- NotificationRecord nrSummary = generateNotificationRecord(mTestNotificationChannel, 3,
- "BUBBLE_GROUP", true /* isSummary */, false /* isBubble */);
+ NotificationRecord nrSummary = generateMessageBubbleNotifRecord(false /* addMetadata */,
+ mTestNotificationChannel, 3 /* id */, "tag", groupKey, true /* isSummary */);
+
if (summaryAutoCancel) {
nrSummary.getNotification().flags |= FLAG_AUTO_CANCEL;
}
@@ -1723,8 +1764,8 @@
Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_onTv", 0,
generateNotificationRecord(null, tv).getNotification(), 0);
- verify(mPreferencesHelper, times(1)).getNotificationChannel(
- anyString(), anyInt(), eq("foo"), anyBoolean());
+ verify(mPreferencesHelper, times(1)).getConversationNotificationChannel(
+ anyString(), anyInt(), eq("foo"), eq(null), anyBoolean(), anyBoolean());
}
@Test
@@ -1738,8 +1779,9 @@
Notification.TvExtender tv = new Notification.TvExtender().setChannelId("foo");
mBinderService.enqueueNotificationWithTag(PKG, PKG, "testTvExtenderChannelOverride_notOnTv",
0, generateNotificationRecord(null, tv).getNotification(), 0);
- verify(mPreferencesHelper, times(1)).getNotificationChannel(
- anyString(), anyInt(), eq(mTestNotificationChannel.getId()), anyBoolean());
+ verify(mPreferencesHelper, times(1)).getConversationNotificationChannel(
+ anyString(), anyInt(), eq(mTestNotificationChannel.getId()), eq(null),
+ anyBoolean(), anyBoolean());
}
@Test
@@ -3172,9 +3214,11 @@
final NotificationVisibility nv = NotificationVisibility.obtain(r.getKey(), 1, 2, true);
mService.mNotificationDelegate.onNotificationVisibilityChanged(
new NotificationVisibility[] {nv}, new NotificationVisibility[]{});
+ verify(mAssistants).notifyAssistantVisibilityChangedLocked(eq(r.sbn), eq(true));
assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasSeen());
mService.mNotificationDelegate.onNotificationVisibilityChanged(
new NotificationVisibility[] {}, new NotificationVisibility[]{nv});
+ verify(mAssistants).notifyAssistantVisibilityChangedLocked(eq(r.sbn), eq(false));
assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasSeen());
}
@@ -4425,6 +4469,16 @@
}
@Test
+ public void testOnPanelRevealedAndHidden() {
+ int items = 5;
+ mService.mNotificationDelegate.onPanelRevealed(false, items);
+ verify(mAssistants, times(1)).onPanelRevealed(eq(items));
+
+ mService.mNotificationDelegate.onPanelHidden();
+ verify(mAssistants, times(1)).onPanelHidden();
+ }
+
+ @Test
public void testOnNotificationSmartReplySent() {
final int replyIndex = 2;
final String reply = "Hello";
@@ -4706,15 +4760,10 @@
@Test
public void testFlagBubble() throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
- // Notif with bubble metadata but not our other misc requirements
- NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
- null /* tvExtender */, true /* isBubble */);
-
- // Say we're foreground
- when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
- IMPORTANCE_FOREGROUND);
+ NotificationRecord nr =
+ generateMessageBubbleNotifRecord(mTestNotificationChannel, "testFlagBubble");
mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
@@ -4730,15 +4779,10 @@
@Test
public void testFlagBubble_noFlag_appNotAllowed() throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, false /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, false /* app */, true /* channel */);
- // Notif with bubble metadata but not our other misc requirements
- NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
- null /* tvExtender */, true /* isBubble */);
-
- // Say we're foreground
- when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
- IMPORTANCE_FOREGROUND);
+ NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
+ "testFlagBubble_noFlag_appNotAllowed");
mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
@@ -4752,174 +4796,40 @@
}
@Test
- public void testFlagBubbleNotifs_flag_appForeground() throws RemoteException {
+ public void testFlagBubbleNotifs_noFlag_whenAppForeground() throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
// Notif with bubble metadata but not our other misc requirements
- NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
- null /* tvExtender */, true /* isBubble */);
+ Notification.Builder nb = new Notification.Builder(mContext,
+ mTestNotificationChannel.getId())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setBubbleMetadata(getBubbleMetadataBuilder().build());
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, "tag", mUid, 0,
+ nb.build(), new UserHandle(mUid), null, 0);
+ NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
// Say we're foreground
when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
IMPORTANCE_FOREGROUND);
-
- mBinderService.enqueueNotificationWithTag(PKG, PKG,
- nr.sbn.getTag(), nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
- waitForIdle();
-
- // yes allowed, yes foreground, yes bubble
- assertTrue(mService.getNotificationRecord(
- nr.sbn.getKey()).getNotification().isBubbleNotification());
- }
-
- @Test
- public void testFlagBubbleNotifs_noFlag_appNotForeground() throws RemoteException {
- // Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
-
- // Notif with bubble metadata but not our other misc requirements
- NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
- null /* tvExtender */, true /* isBubble */);
-
- // Make sure we're NOT foreground
- when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
- IMPORTANCE_VISIBLE);
-
mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
waitForIdle();
- // yes allowed but NOT foreground, no bubble
+ // if notif isn't configured properly it doesn't get to bubble just because app is
+ // foreground.
assertFalse(mService.getNotificationRecord(
nr.sbn.getKey()).getNotification().isBubbleNotification());
}
@Test
- public void testFlagBubbleNotifs_flag_previousForegroundFlag() throws RemoteException {
- // Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
-
- // Notif with bubble metadata but not our other misc requirements
- NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel,
- null /* tvExtender */, true /* isBubble */);
-
- // Send notif when we're foreground
- when(mActivityManager.getPackageImportance(nr1.sbn.getPackageName())).thenReturn(
- IMPORTANCE_FOREGROUND);
- mBinderService.enqueueNotificationWithTag(PKG, PKG, nr1.sbn.getTag(),
- nr1.sbn.getId(), nr1.sbn.getNotification(), nr1.sbn.getUserId());
- waitForIdle();
-
- // yes allowed, yes foreground, yes bubble
- assertTrue(mService.getNotificationRecord(
- nr1.sbn.getKey()).getNotification().isBubbleNotification());
-
- // Send a new update when we're not foreground
- NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel,
- null /* tvExtender */, true /* isBubble */);
-
- when(mActivityManager.getPackageImportance(nr2.sbn.getPackageName())).thenReturn(
- IMPORTANCE_VISIBLE);
- mBinderService.enqueueNotificationWithTag(PKG, PKG, nr2.sbn.getTag(),
- nr2.sbn.getId(), nr2.sbn.getNotification(), nr2.sbn.getUserId());
- waitForIdle();
-
- // yes allowed, previously foreground / flagged, yes bubble
- assertTrue(mService.getNotificationRecord(
- nr2.sbn.getKey()).getNotification().isBubbleNotification());
-
- StatusBarNotification[] notifs2 = mBinderService.getActiveNotifications(PKG);
- assertEquals(1, notifs2.length);
- assertEquals(1, mService.getNotificationRecordCount());
- }
-
- @Test
- public void testFlagBubbleNotifs_noFlag_previousForegroundFlag_afterRemoval()
- throws RemoteException {
- // Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
-
- // Notif with bubble metadata but not our other misc requirements
- NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel,
- null /* tvExtender */, true /* isBubble */);
-
- // Send notif when we're foreground
- when(mActivityManager.getPackageImportance(nr1.sbn.getPackageName())).thenReturn(
- IMPORTANCE_FOREGROUND);
- mBinderService.enqueueNotificationWithTag(PKG, PKG, nr1.sbn.getTag(),
- nr1.sbn.getId(), nr1.sbn.getNotification(), nr1.sbn.getUserId());
- waitForIdle();
-
- // yes allowed, yes foreground, yes bubble
- assertTrue(mService.getNotificationRecord(
- nr1.sbn.getKey()).getNotification().isBubbleNotification());
-
- // Remove the bubble
- mBinderService.cancelNotificationWithTag(PKG, PKG, nr1.sbn.getTag(), nr1.sbn.getId(),
- nr1.sbn.getUserId());
- waitForIdle();
-
- StatusBarNotification[] notifs = mBinderService.getActiveNotifications(PKG);
- assertEquals(0, notifs.length);
- assertEquals(0, mService.getNotificationRecordCount());
-
- // Send a new update when we're not foreground
- NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel,
- null /* tvExtender */, true /* isBubble */);
-
- when(mActivityManager.getPackageImportance(nr2.sbn.getPackageName())).thenReturn(
- IMPORTANCE_VISIBLE);
- mBinderService.enqueueNotificationWithTag(PKG, PKG, nr2.sbn.getTag(),
- nr2.sbn.getId(), nr2.sbn.getNotification(), nr2.sbn.getUserId());
- waitForIdle();
-
- // yes allowed, but was removed & no foreground, so no bubble
- assertFalse(mService.getNotificationRecord(
- nr2.sbn.getKey()).getNotification().isBubbleNotification());
-
- StatusBarNotification[] notifs2 = mBinderService.getActiveNotifications(PKG);
- assertEquals(1, notifs2.length);
- assertEquals(1, mService.getNotificationRecordCount());
- }
-
- @Test
public void testFlagBubbleNotifs_flag_messaging() throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
- // Give it bubble metadata
- Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build();
- // Give it a person
- Person person = new Person.Builder()
- .setName("bubblebot")
- .build();
- // It needs remote input to be bubble-able
- RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
- PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
- Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
- Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
- inputIntent).addRemoteInput(remoteInput)
- .build();
- // Make it messaging style
- Notification.Builder nb = new Notification.Builder(mContext,
- mTestNotificationChannel.getId())
- .setContentTitle("foo")
- .setBubbleMetadata(data)
- .setStyle(new Notification.MessagingStyle(person)
- .setConversationTitle("Bubble Chat")
- .addMessage("Hello?",
- SystemClock.currentThreadTimeMillis() - 300000, person)
- .addMessage("Is it me you're looking for?",
- SystemClock.currentThreadTimeMillis(), person)
- )
- .setActions(replyAction)
- .setSmallIcon(android.R.drawable.sym_def_app_icon);
-
- StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
- "testFlagBubbleNotifs_flag_messaging", mUid, 0,
- nb.build(), new UserHandle(mUid), null, 0);
- NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+ NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
+ "testFlagBubbleNotifs_flag_messaging");
mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
@@ -4927,16 +4837,16 @@
// yes allowed, yes messaging, yes bubble
assertTrue(mService.getNotificationRecord(
- sbn.getKey()).getNotification().isBubbleNotification());
+ nr.sbn.getKey()).getNotification().isBubbleNotification());
}
@Test
public void testFlagBubbleNotifs_flag_phonecall() throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
// Give it bubble metadata
- Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build();
+ Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
// Give it a person
Person person = new Person.Builder()
.setName("bubblebot")
@@ -4969,10 +4879,10 @@
@Test
public void testFlagBubbleNotifs_noFlag_phonecall_noForegroundService() throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
// Give it bubble metadata
- Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build();
+ Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
// Give it a person
Person person = new Person.Builder()
.setName("bubblebot")
@@ -5002,10 +4912,10 @@
@Test
public void testFlagBubbleNotifs_noFlag_phonecall_noPerson() throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
// Give it bubble metadata
- Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build();
+ Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
// Make it a phone call
Notification.Builder nb = new Notification.Builder(mContext,
mTestNotificationChannel.getId())
@@ -5033,10 +4943,10 @@
@Test
public void testFlagBubbleNotifs_noFlag_phonecall_noCategory() throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
// Give it bubble metadata
- Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build();
+ Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
// Give it a person
Person person = new Person.Builder()
.setName("bubblebot")
@@ -5068,32 +4978,10 @@
@Test
public void testFlagBubbleNotifs_noFlag_messaging_appNotAllowed() throws RemoteException {
// Bubbles are NOT allowed!
- setUpPrefsForBubbles(false /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, false /* app */, true /* channel */);
- // Give it bubble metadata
- Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build();
- // Give it a person
- Person person = new Person.Builder()
- .setName("bubblebot")
- .build();
- // Make it messaging style
- Notification.Builder nb = new Notification.Builder(mContext,
- mTestNotificationChannel.getId())
- .setContentTitle("foo")
- .setBubbleMetadata(data)
- .setStyle(new Notification.MessagingStyle(person)
- .setConversationTitle("Bubble Chat")
- .addMessage("Hello?",
- SystemClock.currentThreadTimeMillis() - 300000, person)
- .addMessage("Is it me you're looking for?",
- SystemClock.currentThreadTimeMillis(), person)
- )
- .setSmallIcon(android.R.drawable.sym_def_app_icon);
-
- StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
- "testFlagBubbleNotifs_noFlag_messaging_appNotAllowed", mUid, 0,
- nb.build(), new UserHandle(mUid), null, 0);
- NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+ NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
+ "testFlagBubbleNotifs_noFlag_messaging_appNotAllowed");
// Post the notification
mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
@@ -5102,16 +4990,22 @@
// not allowed, no bubble
assertFalse(mService.getNotificationRecord(
- sbn.getKey()).getNotification().isBubbleNotification());
+ nr.sbn.getKey()).getNotification().isBubbleNotification());
}
@Test
public void testFlagBubbleNotifs_noFlag_notBubble() throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
- // Notif WITHOUT bubble metadata
- NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
+ // Messaging notif WITHOUT bubble metadata
+ Notification.Builder nb = getMessageStyleNotifBuilder(false /* addBubbleMetadata */,
+ null /* groupKey */, false /* isSummary */);
+
+ StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
+ "testFlagBubbleNotifs_noFlag_notBubble", mUid, 0,
+ nb.build(), new UserHandle(mUid), null, 0);
+ NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
// Post the notification
mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
@@ -5126,50 +5020,28 @@
@Test
public void testFlagBubbleNotifs_noFlag_messaging_channelNotAllowed() throws RemoteException {
// Bubbles are allowed except on this channel
- setUpPrefsForBubbles(true /* global */, true /* app */, false /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, false /* channel */);
- // Give it bubble metadata
- Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build();
- // Give it a person
- Person person = new Person.Builder()
- .setName("bubblebot")
- .build();
- // Make it messaging style
- Notification.Builder nb = new Notification.Builder(mContext,
- mTestNotificationChannel.getId())
- .setContentTitle("foo")
- .setBubbleMetadata(data)
- .setStyle(new Notification.MessagingStyle(person)
- .setConversationTitle("Bubble Chat")
- .addMessage("Hello?",
- SystemClock.currentThreadTimeMillis() - 300000, person)
- .addMessage("Is it me you're looking for?",
- SystemClock.currentThreadTimeMillis(), person)
- )
- .setSmallIcon(android.R.drawable.sym_def_app_icon);
-
- StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1,
- "testFlagBubbleNotifs_noFlag_messaging_channelNotAllowed", mUid, 0,
- nb.build(), new UserHandle(mUid), null, 0);
- NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+ NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
+ "testFlagBubbleNotifs_noFlag_messaging_channelNotAllowed");
// Post the notification
- mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
waitForIdle();
// channel not allowed, no bubble
assertFalse(mService.getNotificationRecord(
- sbn.getKey()).getNotification().isBubbleNotification());
+ nr.sbn.getKey()).getNotification().isBubbleNotification());
}
@Test
public void testFlagBubbleNotifs_noFlag_phonecall_notAllowed() throws RemoteException {
// Bubbles are not allowed!
- setUpPrefsForBubbles(false /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, false /* global */, true /* app */, true /* channel */);
// Give it bubble metadata
- Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build();
+ Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
// Give it a person
Person person = new Person.Builder()
.setName("bubblebot")
@@ -5188,10 +5060,9 @@
nb.build(), new UserHandle(mUid), null, 0);
// Make sure it has foreground service
sbn.getNotification().flags |= FLAG_FOREGROUND_SERVICE;
- NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
mBinderService.enqueueNotificationWithTag(PKG, PKG, sbn.getTag(),
- nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+ sbn.getId(), sbn.getNotification(), sbn.getUserId());
waitForIdle();
// yes phone call, yes person, yes foreground service, but not allowed, no bubble
@@ -5202,10 +5073,10 @@
@Test
public void testFlagBubbleNotifs_noFlag_phonecall_channelNotAllowed() throws RemoteException {
// Bubbles are allowed, but not on channel.
- setUpPrefsForBubbles(true /* global */, true /* app */, false /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, false /* channel */);
// Give it bubble metadata
- Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder().build();
+ Notification.BubbleMetadata data = getBubbleMetadataBuilder().build();
// Give it a person
Person person = new Person.Builder()
.setName("bubblebot")
@@ -5410,15 +5281,11 @@
@Test
public void testNotificationBubbleChanged_false() throws Exception {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
- // Notif with bubble metadata but not our other misc requirements
- NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
- null /* tvExtender */, true /* isBubble */);
-
- // Say we're foreground
- when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
- IMPORTANCE_FOREGROUND);
+ // Notif with bubble metadata
+ NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
+ "testNotificationBubbleChanged_false");
mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
@@ -5445,11 +5312,11 @@
@Test
public void testNotificationBubbleChanged_true() throws Exception {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
- // Plain notification that has bubble metadata
+ // Notif that is not a bubble
NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
- null /* tvExtender */, true /* isBubble */);
+ 1, null, false);
mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
waitForIdle();
@@ -5459,9 +5326,12 @@
assertEquals(1, notifsBefore.length);
assertEquals((notifsBefore[0].getNotification().flags & FLAG_BUBBLE), 0);
- // Make the package foreground so that we're allowed to be a bubble
- when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
- IMPORTANCE_FOREGROUND);
+ // Update the notification to be message style / meet bubble requirements
+ NotificationRecord nr2 = generateMessageBubbleNotifRecord(mTestNotificationChannel,
+ nr.sbn.getTag());
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, nr2.sbn.getTag(),
+ nr2.sbn.getId(), nr2.sbn.getNotification(), nr2.sbn.getUserId());
+ waitForIdle();
// Reset as this is called when the notif is first sent
reset(mListeners);
@@ -5479,11 +5349,10 @@
@Test
public void testNotificationBubbleChanged_true_notAllowed() throws Exception {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
// Notif that is not a bubble
- NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
- null /* tvExtender */, true /* isBubble */);
+ NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel);
mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
waitForIdle();
@@ -5679,28 +5548,19 @@
@Test
public void testNotificationBubbles_disabled_lowRamDevice() throws Exception {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
-
- // Plain notification that has bubble metadata
- NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
- null /* tvExtender */, true /* isBubble */);
- mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
- nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
- waitForIdle();
-
- // Would be a normal notification because wouldn't have met requirements to bubble
- StatusBarNotification[] notifsBefore = mBinderService.getActiveNotifications(PKG);
- assertEquals(1, notifsBefore.length);
- assertEquals((notifsBefore[0].getNotification().flags & FLAG_BUBBLE), 0);
-
- // Make the package foreground so that we're allowed to be a bubble
- when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
- IMPORTANCE_FOREGROUND);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
// And we are low ram
when(mActivityManager.isLowRamDevice()).thenReturn(true);
- // We wouldn't be a bubble because the notification didn't meet requirements (low ram)
+ // Notification that would typically bubble
+ NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
+ "testNotificationBubbles_disabled_lowRamDevice");
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
+ nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
+ waitForIdle();
+
+ // But we wouldn't be a bubble because the device is low ram & all bubbles are disabled.
StatusBarNotification[] notifsAfter = mBinderService.getActiveNotifications(PKG);
assertEquals(1, notifsAfter.length);
assertEquals((notifsAfter[0].getNotification().flags & FLAG_BUBBLE), 0);
@@ -5772,52 +5632,25 @@
public void testNotificationBubbles_flagAutoExpandForeground_fails_notForeground()
throws Exception {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
- // Give it bubble metadata
- Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder()
- .setSuppressNotification(true)
- .setAutoExpandBubble(true).build();
- // Give it a person
- Person person = new Person.Builder()
- .setName("bubblebot")
- .build();
- // It needs remote input to be bubble-able
- RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
- PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
- Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
- Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
- inputIntent).addRemoteInput(remoteInput)
- .build();
- // Make it messaging style
- Notification.Builder nb = new Notification.Builder(mContext,
- mTestNotificationChannel.getId())
- .setContentTitle("foo")
- .setBubbleMetadata(data)
- .setStyle(new Notification.MessagingStyle(person)
- .setConversationTitle("Bubble Chat")
- .addMessage("Hello?",
- SystemClock.currentThreadTimeMillis() - 300000, person)
- .addMessage("Is it me you're looking for?",
- SystemClock.currentThreadTimeMillis(), person)
- )
- .setActions(replyAction)
- .setSmallIcon(android.R.drawable.sym_def_app_icon);
-
- StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
- nb.build(), new UserHandle(mUid), null, 0);
- NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+ NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
+ "testNotificationBubbles_flagAutoExpandForeground_fails_notForeground");
+ // Modify metadata flags
+ nr.sbn.getNotification().getBubbleMetadata().setFlags(
+ Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE
+ | Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
// Ensure we're not foreground
when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
IMPORTANCE_VISIBLE);
- mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
waitForIdle();
// yes allowed, yes messaging, yes bubble
- Notification notif = mService.getNotificationRecord(sbn.getKey()).getNotification();
+ Notification notif = mService.getNotificationRecord(nr.sbn.getKey()).getNotification();
assertTrue(notif.isBubbleNotification());
// Our flags should have failed since we're not foreground
@@ -5829,55 +5662,28 @@
public void testNotificationBubbles_flagAutoExpandForeground_succeeds_foreground()
throws RemoteException {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
- // Give it bubble metadata
- Notification.BubbleMetadata data = getBasicBubbleMetadataBuilder()
- .setSuppressNotification(true)
- .setAutoExpandBubble(true).build();
- // Give it a person
- Person person = new Person.Builder()
- .setName("bubblebot")
- .build();
- // It needs remote input to be bubble-able
- RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
- PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
- Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
- Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
- inputIntent).addRemoteInput(remoteInput)
- .build();
- // Make it messaging style
- Notification.Builder nb = new Notification.Builder(mContext,
- mTestNotificationChannel.getId())
- .setContentTitle("foo")
- .setBubbleMetadata(data)
- .setStyle(new Notification.MessagingStyle(person)
- .setConversationTitle("Bubble Chat")
- .addMessage("Hello?",
- SystemClock.currentThreadTimeMillis() - 300000, person)
- .addMessage("Is it me you're looking for?",
- SystemClock.currentThreadTimeMillis(), person)
- )
- .setActions(replyAction)
- .setSmallIcon(android.R.drawable.sym_def_app_icon);
-
- StatusBarNotification sbn = new StatusBarNotification(PKG, PKG, 1, null, mUid, 0,
- nb.build(), new UserHandle(mUid), null, 0);
- NotificationRecord nr = new NotificationRecord(mContext, sbn, mTestNotificationChannel);
+ NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel,
+ "testNotificationBubbles_flagAutoExpandForeground_succeeds_foreground");
+ // Modify metadata flags
+ nr.sbn.getNotification().getBubbleMetadata().setFlags(
+ Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE
+ | Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
// Ensure we are in the foreground
when(mActivityManager.getPackageImportance(nr.sbn.getPackageName())).thenReturn(
IMPORTANCE_FOREGROUND);
- mBinderService.enqueueNotificationWithTag(PKG, PKG, null,
+ mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
waitForIdle();
// yes allowed, yes messaging, yes bubble
- Notification notif = mService.getNotificationRecord(sbn.getKey()).getNotification();
+ Notification notif = mService.getNotificationRecord(nr.sbn.getKey()).getNotification();
assertTrue(notif.isBubbleNotification());
- // Our flags should have failed since we are foreground
+ // Our flags should have passed since we are foreground
assertTrue(notif.getBubbleMetadata().getAutoExpandBubble());
assertTrue(notif.getBubbleMetadata().isNotificationSuppressed());
}
@@ -5886,7 +5692,7 @@
public void testNotificationBubbles_bubbleChildrenStay_whenGroupSummaryDismissed()
throws Exception {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
NotificationRecord nrSummary = addGroupWithBubblesAndValidateAdded(
true /* summaryAutoCancel */);
@@ -5909,7 +5715,7 @@
public void testNotificationBubbles_bubbleChildrenStay_whenGroupSummaryClicked()
throws Exception {
// Bubbles are allowed!
- setUpPrefsForBubbles(true /* global */, true /* app */, true /* channel */);
+ setUpPrefsForBubbles(PKG, mUid, true /* global */, true /* app */, true /* channel */);
NotificationRecord nrSummary = addGroupWithBubblesAndValidateAdded(
true /* summaryAutoCancel */);
@@ -6004,11 +5810,86 @@
@Test
public void testNotificationHistory_addNoisyNotification() throws Exception {
NotificationRecord nr = generateNotificationRecord(mTestNotificationChannel,
- null /* tvExtender */, false);
+ null /* tvExtender */);
mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.sbn.getTag(),
nr.sbn.getId(), nr.sbn.getNotification(), nr.sbn.getUserId());
waitForIdle();
verify(mHistoryManager, times(1)).addNotification(any());
}
+
+ @Test
+ public void createConversationNotificationChannel() throws Exception {
+ NotificationChannel original = new NotificationChannel("a", "a", IMPORTANCE_HIGH);
+ original.setAllowBubbles(!original.canBubble());
+ original.setShowBadge(!original.canShowBadge());
+
+ Parcel parcel = Parcel.obtain();
+ original.writeToParcel(parcel, 0);
+ parcel.setDataPosition(0);
+ NotificationChannel orig = NotificationChannel.CREATOR.createFromParcel(parcel);
+ assertEquals(original, orig);
+ assertFalse(TextUtils.isEmpty(orig.getName()));
+
+ mBinderService.createNotificationChannels(PKG, new ParceledListSlice(Arrays.asList(
+ orig)));
+
+ mBinderService.createConversationNotificationChannelForPackage(PKG, mUid, orig, "friend");
+
+ NotificationChannel friendChannel = mBinderService.getConversationNotificationChannel(
+ PKG, 0, PKG, original.getId(), false, "friend");
+
+ assertEquals(original.getName(), friendChannel.getName());
+ assertEquals(original.getId(), friendChannel.getParentChannelId());
+ assertEquals("friend", friendChannel.getConversationId());
+ assertEquals(null, original.getConversationId());
+ assertEquals(original.canShowBadge(), friendChannel.canShowBadge());
+ assertEquals(original.canBubble(), friendChannel.canBubble());
+ assertFalse(original.getId().equals(friendChannel.getId()));
+ assertNotNull(friendChannel.getId());
+ }
+
+ @Test
+ public void deleteConversationNotificationChannels() throws Exception {
+ NotificationChannel messagesParent =
+ new NotificationChannel("messages", "messages", IMPORTANCE_HIGH);
+ Parcel msgParcel = Parcel.obtain();
+ messagesParent.writeToParcel(msgParcel, 0);
+ msgParcel.setDataPosition(0);
+
+ NotificationChannel callsParent =
+ new NotificationChannel("calls", "calls", IMPORTANCE_HIGH);
+ Parcel callParcel = Parcel.obtain();
+ callsParent.writeToParcel(callParcel, 0);
+ callParcel.setDataPosition(0);
+
+ mBinderService.createNotificationChannels(PKG, new ParceledListSlice(Arrays.asList(
+ messagesParent, callsParent)));
+
+ String conversationId = "friend";
+
+ mBinderService.createConversationNotificationChannelForPackage(
+ PKG, mUid, NotificationChannel.CREATOR.createFromParcel(msgParcel), conversationId);
+ mBinderService.createConversationNotificationChannelForPackage(
+ PKG, mUid, NotificationChannel.CREATOR.createFromParcel(callParcel),
+ conversationId);
+
+ NotificationChannel messagesChild = mBinderService.getConversationNotificationChannel(
+ PKG, 0, PKG, messagesParent.getId(), false, conversationId);
+ NotificationChannel callsChild = mBinderService.getConversationNotificationChannel(
+ PKG, 0, PKG, callsParent.getId(), false, conversationId);
+
+ assertEquals(messagesParent.getId(), messagesChild.getParentChannelId());
+ assertEquals(conversationId, messagesChild.getConversationId());
+
+ assertEquals(callsParent.getId(), callsChild.getParentChannelId());
+ assertEquals(conversationId, callsChild.getConversationId());
+
+ mBinderService.deleteConversationNotificationChannels(PKG, mUid, conversationId);
+
+ assertNull(mBinderService.getConversationNotificationChannel(
+ PKG, 0, PKG, messagesParent.getId(), false, conversationId));
+ assertNull(mBinderService.getConversationNotificationChannel(
+ PKG, 0, PKG, callsParent.getId(), false, conversationId));
+ }
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
index 8961796..6f16574 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java
@@ -15,6 +15,7 @@
*/
package com.android.server.notification;
+import static android.app.NotificationChannel.CONVERSATION_CHANNEL_ID_FORMAT;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_LOW;
@@ -224,6 +225,27 @@
assertEquals(expected.getGroup(), actual.getGroup());
assertEquals(expected.getAudioAttributes(), actual.getAudioAttributes());
assertEquals(expected.getLightColor(), actual.getLightColor());
+ assertEquals(expected.getParentChannelId(), actual.getParentChannelId());
+ assertEquals(expected.getConversationId(), actual.getConversationId());
+ assertEquals(expected.isDemoted(), actual.isDemoted());
+ }
+
+ private void compareChannelsParentChild(NotificationChannel parent,
+ NotificationChannel actual, String conversationId) {
+ assertEquals(parent.getName(), actual.getName());
+ assertEquals(parent.getDescription(), actual.getDescription());
+ assertEquals(parent.shouldVibrate(), actual.shouldVibrate());
+ assertEquals(parent.shouldShowLights(), actual.shouldShowLights());
+ assertEquals(parent.getImportance(), actual.getImportance());
+ assertEquals(parent.getLockscreenVisibility(), actual.getLockscreenVisibility());
+ assertEquals(parent.getSound(), actual.getSound());
+ assertEquals(parent.canBypassDnd(), actual.canBypassDnd());
+ assertTrue(Arrays.equals(parent.getVibrationPattern(), actual.getVibrationPattern()));
+ assertEquals(parent.getGroup(), actual.getGroup());
+ assertEquals(parent.getAudioAttributes(), actual.getAudioAttributes());
+ assertEquals(parent.getLightColor(), actual.getLightColor());
+ assertEquals(parent.getId(), actual.getParentChannelId());
+ assertEquals(conversationId, actual.getConversationId());
}
private void compareGroups(NotificationChannelGroup expected, NotificationChannelGroup actual) {
@@ -333,6 +355,8 @@
channel2.setGroup(ncg.getId());
channel2.setVibrationPattern(new long[]{100, 67, 145, 156});
channel2.setLightColor(Color.BLUE);
+ channel2.setConversationId("id1", "conversation");
+ channel2.setDemoted(true);
mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg, true);
mHelper.createNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, ncg2, true);
@@ -2786,4 +2810,52 @@
assertEquals(user10Importance, mHelper.getNotificationChannel(
pkg, uidList10[0], channelId, false).getImportance());
}
+
+ @Test
+ public void testGetConversationNotificationChannel() {
+ String conversationId = "friend";
+
+ NotificationChannel parent =
+ new NotificationChannel("parent", "messages", IMPORTANCE_DEFAULT);
+ mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false);
+
+ NotificationChannel friend = new NotificationChannel(String.format(
+ CONVERSATION_CHANNEL_ID_FORMAT, parent.getId(), conversationId),
+ "messages", IMPORTANCE_DEFAULT);
+ friend.setConversationId(parent.getId(), conversationId);
+ mHelper.createNotificationChannel(PKG_O, UID_O, friend, true, false);
+
+ compareChannelsParentChild(parent, mHelper.getConversationNotificationChannel(
+ PKG_O, UID_O, parent.getId(), conversationId, false, false), conversationId);
+ }
+
+ @Test
+ public void testGetNotificationChannel_conversationProvidedByNotCustomizedYet() {
+ String conversationId = "friend";
+
+ NotificationChannel parent =
+ new NotificationChannel("parent", "messages", IMPORTANCE_DEFAULT);
+ mHelper.createNotificationChannel(PKG_O, UID_O, parent, true, false);
+
+ compareChannels(parent, mHelper.getConversationNotificationChannel(
+ PKG_O, UID_O, parent.getId(), conversationId, true, false));
+ }
+
+ @Test
+ public void testConversationNotificationChannelsRequireParents() {
+ String parentId = "does not exist";
+ String conversationId = "friend";
+
+ NotificationChannel friend = new NotificationChannel(String.format(
+ CONVERSATION_CHANNEL_ID_FORMAT, parentId, conversationId),
+ "messages", IMPORTANCE_DEFAULT);
+ friend.setConversationId(parentId, conversationId);
+
+ try {
+ mHelper.createNotificationChannel(PKG_O, UID_O, friend, true, false);
+ fail("allowed creation of conversation channel without a parent");
+ } catch (IllegalArgumentException e) {
+ // good
+ }
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 73b2f6b..a3e94599 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -521,11 +521,12 @@
}
@Test
- public void testShouldPauseWhenMakeClientVisible() {
+ public void testShouldStartWhenMakeClientActive() {
ActivityRecord topActivity = new ActivityBuilder(mService).setTask(mTask).build();
topActivity.setOccludesParent(false);
mActivity.setState(ActivityStack.ActivityState.STOPPED, "Testing");
- mActivity.makeClientVisible();
+ mActivity.setVisibility(true);
+ mActivity.makeActiveIfNeeded(null /* activeActivity */);
assertEquals(STARTED, mActivity.getState());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
index 399cf49..135d005 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
import static android.content.pm.ApplicationInfo.FLAG_SUSPENDED;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
@@ -44,6 +45,7 @@
import androidx.test.filters.SmallTest;
+import com.android.internal.app.BlockedAppActivity;
import com.android.internal.app.HarmfulAppWarningActivity;
import com.android.internal.app.SuspendedAppActivity;
import com.android.internal.app.UnlaunchableAppActivity;
@@ -105,6 +107,8 @@
private PackageManagerService mPackageManager;
@Mock
private ActivityManagerInternal mAmInternal;
+ @Mock
+ private LockTaskController mLockTaskController;
private ActivityStartInterceptor mInterceptor;
private ActivityInfo mAInfo = new ActivityInfo();
@@ -145,6 +149,13 @@
when(mPackageManager.getHarmfulAppWarning(TEST_PACKAGE_NAME, TEST_USER_ID))
.thenReturn(null);
+ // Mock LockTaskController
+ mAInfo.lockTaskLaunchMode = LOCK_TASK_LAUNCH_MODE_DEFAULT;
+ when(mService.getLockTaskController()).thenReturn(mLockTaskController);
+ when(mLockTaskController.isActivityAllowed(
+ TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT))
+ .thenReturn(true);
+
// Initialise activity info
mAInfo.applicationInfo = new ApplicationInfo();
mAInfo.packageName = mAInfo.applicationInfo.packageName = TEST_PACKAGE_NAME;
@@ -196,6 +207,18 @@
}
@Test
+ public void testInterceptLockTaskModeViolationPackage() {
+ when(mLockTaskController.isActivityAllowed(
+ TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT))
+ .thenReturn(false);
+
+ assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null));
+
+ assertTrue(BlockedAppActivity.createIntent(TEST_USER_ID, TEST_PACKAGE_NAME)
+ .filterEquals(mInterceptor.mIntent));
+ }
+
+ @Test
public void testInterceptQuietProfile() {
// GIVEN that the user the activity is starting as is currently in quiet mode
when(mUserManager.isQuietModeEnabled(eq(UserHandle.of(TEST_USER_ID)))).thenReturn(true);
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index 079c49f..d22502d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -27,6 +27,7 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
@@ -153,6 +154,19 @@
}
@Test
+ public void testContainerChanges() {
+ removeGlobalMinSizeRestriction();
+ final ActivityStack stack = new StackBuilder(mRootWindowContainer)
+ .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+ final Task task = stack.getTopMostTask();
+ WindowContainerTransaction t = new WindowContainerTransaction();
+ assertTrue(task.isFocusable());
+ t.setFocusable(stack.mRemoteToken, false);
+ mService.applyContainerTransaction(t);
+ assertFalse(task.isFocusable());
+ }
+
+ @Test
public void testDisplayWindowListener() {
final ArrayList<Integer> added = new ArrayList<>();
final ArrayList<Integer> changed = new ArrayList<>();
diff --git a/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java b/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java
index 574517a..71390db 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AnimatingActivityRegistryTest.java
@@ -40,7 +40,7 @@
* Tests for the {@link ActivityStack} class.
*
* Build/Install/Run:
- * atest FrameworksServicesTests:AnimatingActivityRegistryTest
+ * atest WmTests:AnimatingActivityRegistryTest
*/
@SmallTest
@Presubmit
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index 1311889..f6213bd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -42,7 +42,7 @@
/**
* Build/Install/Run:
- * atest FrameworksServicesTests:AppTransitionControllerTest
+ * atest WmTests:AppTransitionControllerTest
*/
@SmallTest
@Presubmit
diff --git a/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java
index 0ad0f95..1dda535 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BoundsAnimationControllerTests.java
@@ -60,7 +60,7 @@
* appropriately.
*
* Build/Install/Run:
- * atest FrameworksServicesTests:BoundsAnimationControllerTests
+ * atest WmTests:BoundsAnimationControllerTests
*/
@SmallTest
@Presubmit
diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
index 0aa6961..e8f7849 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
@@ -40,7 +40,7 @@
/**
* Build/Install/Run:
- * atest FrameworksServicesTests:DimmerTests
+ * atest WmTests:DimmerTests
*/
@Presubmit
@RunWith(WindowTestRunner.class)
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 73dd2df..f2ba97c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -57,6 +57,8 @@
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -86,6 +88,7 @@
import android.view.MotionEvent;
import android.view.Surface;
import android.view.ViewRootImpl;
+import android.view.WindowManager;
import android.view.test.InsetsModeSession;
import androidx.test.filters.SmallTest;
@@ -561,8 +564,7 @@
final DisplayContent dc = createNewDisplay();
final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, dc, "w");
- dc.setLayoutNeeded();
- dc.performLayout(true /* initial */, false /* updateImeWindows */);
+ performLayout(dc);
assertThat(win.mLayoutSeq, is(dc.mLayoutSeq));
}
@@ -829,8 +831,7 @@
win.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR;
win.setSystemGestureExclusion(Collections.singletonList(new Rect(10, 20, 30, 40)));
- dc.setLayoutNeeded();
- dc.performLayout(true /* initial */, false /* updateImeWindows */);
+ performLayout(dc);
win.setHasSurface(true);
dc.updateSystemGestureExclusion();
@@ -866,8 +867,7 @@
win2.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR;
win2.setSystemGestureExclusion(Collections.singletonList(new Rect(20, 30, 40, 50)));
- dc.setLayoutNeeded();
- dc.performLayout(true /* initial */, false /* updateImeWindows */);
+ performLayout(dc);
win.setHasSurface(true);
win2.setHasSurface(true);
@@ -898,8 +898,7 @@
win2.getAttrs().height = 10;
win2.setSystemGestureExclusion(Collections.emptyList());
- dc.setLayoutNeeded();
- dc.performLayout(true /* initial */, false /* updateImeWindows */);
+ performLayout(dc);
win.setHasSurface(true);
win2.setHasSurface(true);
@@ -922,8 +921,7 @@
| SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
win.mActivityRecord.mTargetSdk = P;
- dc.setLayoutNeeded();
- dc.performLayout(true /* initial */, false /* updateImeWindows */);
+ performLayout(dc);
win.setHasSurface(true);
@@ -935,6 +933,22 @@
}
@Test
+ public void testRequestResizeForEmptyFrames() {
+ final WindowState win = mChildAppWindowAbove;
+ makeWindowVisible(win, win.getParentWindow());
+ win.setRequestedSize(mDisplayContent.mBaseDisplayWidth, 0 /* height */);
+ win.mAttrs.width = win.mAttrs.height = WindowManager.LayoutParams.WRAP_CONTENT;
+ win.mAttrs.gravity = Gravity.CENTER;
+ performLayout(mDisplayContent);
+
+ // The frame is empty because the requested height is zero.
+ assertTrue(win.getFrameLw().isEmpty());
+ // The window should be scheduled to resize then the client may report a new non-empty size.
+ win.updateResizingWindowIfNeeded();
+ assertThat(mWm.mResizingWindows).contains(win);
+ }
+
+ @Test
public void testOrientationChangeLogging() {
MetricsLogger mockLogger = mock(MetricsLogger.class);
Configuration oldConfig = new Configuration();
@@ -1011,6 +1025,11 @@
mWm.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, false /* updateInputWindows */);
}
+ private void performLayout(DisplayContent dc) {
+ dc.setLayoutNeeded();
+ dc.performLayout(true /* initial */, false /* updateImeWindows */);
+ }
+
/**
* Create DisplayContent that does not update display base/initial values from device to keep
* the values set by test.
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 5aece45..0527561 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -594,6 +594,8 @@
@Test
public void layoutWindowLw_withForwardInset_SoftInputAdjustResize() {
+ assumeTrue(ViewRootImpl.sNewInsetsMode == ViewRootImpl.NEW_INSETS_MODE_NONE);
+
mWindow.mAttrs.flags =
FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR | FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
mWindow.mAttrs.softInputMode = SOFT_INPUT_ADJUST_RESIZE;
diff --git a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
new file mode 100644
index 0000000..032edde
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java
@@ -0,0 +1,161 @@
+/*
+ * 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.wm;
+
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * This file tests WM setting the priority on windows that is used in SF to determine at what
+ * frame rate the Display should run. Any changes to the algorithm should be reflected in these
+ * tests.
+ *
+ * Build/Install/Run: atest FrameRateSelectionPriority
+ */
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class FrameRateSelectionPriorityTests extends WindowTestsBase {
+
+ @Test
+ public void basicTest() {
+ final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow");
+ assertNotNull("Window state is created", appWindow);
+ assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+
+ appWindow.updateFrameRateSelectionPriorityIfNeeded();
+ // Priority doesn't change.
+ assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+
+ // Call the function a few times.
+ appWindow.updateFrameRateSelectionPriorityIfNeeded();
+ appWindow.updateFrameRateSelectionPriorityIfNeeded();
+
+ // Since nothing changed in the priority state, the transaction should not be updating.
+ verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority(
+ appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+ }
+
+ @Test
+ public void testApplicationInFocusWithoutModeId() {
+ final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow");
+ assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+ assertEquals(appWindow.getDisplayContent().getDisplayPolicy().getRefreshRatePolicy()
+ .getPreferredModeId(appWindow), 0);
+
+ appWindow.updateFrameRateSelectionPriorityIfNeeded();
+ // Priority stays MAX_VALUE.
+ assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+ verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority(
+ appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+
+ // Application is in focus.
+ appWindow.mToken.mDisplayContent.mCurrentFocus = appWindow;
+ appWindow.updateFrameRateSelectionPriorityIfNeeded();
+ // Priority changes to 1.
+ assertEquals(appWindow.mFrameRateSelectionPriority, 1);
+ verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
+ appWindow.getSurfaceControl(), 1);
+ }
+
+ @Test
+ public void testApplicationInFocusWithModeId() {
+ final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow");
+ assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+
+ // Application is in focus.
+ appWindow.mToken.mDisplayContent.mCurrentFocus = appWindow;
+ appWindow.updateFrameRateSelectionPriorityIfNeeded();
+ // Priority changes.
+ assertEquals(appWindow.mFrameRateSelectionPriority, 1);
+ // Update the mode ID to a requested number.
+ appWindow.mAttrs.preferredDisplayModeId = 1;
+ appWindow.updateFrameRateSelectionPriorityIfNeeded();
+ // Priority changes.
+ assertEquals(appWindow.mFrameRateSelectionPriority, 0);
+
+ // Remove the mode ID request.
+ appWindow.mAttrs.preferredDisplayModeId = 0;
+ appWindow.updateFrameRateSelectionPriorityIfNeeded();
+ // Priority changes.
+ assertEquals(appWindow.mFrameRateSelectionPriority, 1);
+
+ // Verify we called actions on Transactions correctly.
+ verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionPriority(
+ appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+ verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
+ appWindow.getSurfaceControl(), 0);
+ verify(appWindow.getPendingTransaction(), times(2)).setFrameRateSelectionPriority(
+ appWindow.getSurfaceControl(), 1);
+ }
+
+ @Test
+ public void testApplicationNotInFocusWithModeId() {
+ final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow");
+ assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+
+ final WindowState inFocusWindow = createWindow(null, TYPE_APPLICATION, "inFocus");
+ appWindow.mToken.mDisplayContent.mCurrentFocus = inFocusWindow;
+
+ appWindow.updateFrameRateSelectionPriorityIfNeeded();
+ // The window is not in focus.
+ assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+
+ // Update the mode ID to a requested number.
+ appWindow.mAttrs.preferredDisplayModeId = 1;
+ appWindow.updateFrameRateSelectionPriorityIfNeeded();
+ // Priority changes.
+ assertEquals(appWindow.mFrameRateSelectionPriority, 2);
+
+ verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
+ appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+ verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
+ appWindow.getSurfaceControl(), 2);
+ }
+
+ @Test
+ public void testApplicationNotInFocusWithoutModeId() {
+ final WindowState appWindow = createWindow(null, TYPE_APPLICATION, "appWindow");
+ assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+
+ final WindowState inFocusWindow = createWindow(null, TYPE_APPLICATION, "inFocus");
+ appWindow.mToken.mDisplayContent.mCurrentFocus = inFocusWindow;
+
+ appWindow.updateFrameRateSelectionPriorityIfNeeded();
+ // The window is not in focus.
+ assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+
+ // Make sure that the mode ID is not set.
+ appWindow.mAttrs.preferredDisplayModeId = 0;
+ appWindow.updateFrameRateSelectionPriorityIfNeeded();
+ // Priority doesn't change.
+ assertEquals(appWindow.mFrameRateSelectionPriority, RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+
+ verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority(
+ appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET);
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
index 09ac9ce..d819b1a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java
@@ -63,6 +63,26 @@
assertEquals(Insets.of(0, 100, 0, 0),
mProvider.getSource().calculateInsets(new Rect(0, 0, 500, 500),
false /* ignoreVisibility */));
+ assertEquals(Insets.of(0, 100, 0, 0),
+ mProvider.getSource().calculateVisibleInsets(new Rect(0, 0, 500, 500)));
+ }
+
+ @Test
+ public void testPostLayout_givenInsets() {
+ final WindowState ime = createWindow(null, TYPE_APPLICATION, "ime");
+ ime.getFrameLw().set(0, 0, 500, 100);
+ ime.getGivenContentInsetsLw().set(0, 0, 0, 60);
+ ime.getGivenVisibleInsetsLw().set(0, 0, 0, 75);
+ ime.mHasSurface = true;
+ mProvider.setWindow(ime, null);
+ mProvider.onPostLayout();
+ assertEquals(new Rect(0, 0, 500, 40), mProvider.getSource().getFrame());
+ assertEquals(new Rect(0, 0, 500, 25), mProvider.getSource().getVisibleFrame());
+ assertEquals(Insets.of(0, 40, 0, 0),
+ mProvider.getSource().calculateInsets(new Rect(0, 0, 500, 500),
+ false /* ignoreVisibility */));
+ assertEquals(Insets.of(0, 25, 0, 0),
+ mProvider.getSource().calculateVisibleInsets(new Rect(0, 0, 500, 500)));
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
index 039ff60..75ec53d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LockTaskControllerTest.java
@@ -29,6 +29,9 @@
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_KEYGUARD;
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NONE;
import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_NOTIFICATIONS;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
+import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_NEVER;
import static android.os.Process.SYSTEM_UID;
import static android.telecom.TelecomManager.EMERGENCY_DIALER_COMPONENT;
@@ -693,6 +696,38 @@
assertTrue((StatusBarManager.DISABLE2_QUICK_SETTINGS & flags.second) != 0);
}
+ @Test
+ public void testIsActivityAllowed() {
+ // WHEN lock task mode is not enabled
+ assertTrue(mLockTaskController.isActivityAllowed(
+ TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT));
+
+ // WHEN lock task mode is enabled
+ Task tr = getTask(Task.LOCK_TASK_AUTH_WHITELISTED);
+ mLockTaskController.startLockTaskMode(tr, false, TEST_UID);
+
+
+ // package with LOCK_TASK_LAUNCH_MODE_ALWAYS should always be allowed
+ assertTrue(mLockTaskController.isActivityAllowed(
+ TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_ALWAYS));
+
+ // unwhitelisted package should not be allowed
+ assertFalse(mLockTaskController.isActivityAllowed(
+ TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT));
+
+ // update the whitelist
+ String[] whitelist = new String[] { TEST_PACKAGE_NAME };
+ mLockTaskController.updateLockTaskPackages(TEST_USER_ID, whitelist);
+
+ // whitelisted package should be allowed
+ assertTrue(mLockTaskController.isActivityAllowed(
+ TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT));
+
+ // package with LOCK_TASK_LAUNCH_MODE_NEVER should never be allowed
+ assertFalse(mLockTaskController.isActivityAllowed(
+ TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_NEVER));
+ }
+
private Task getTask(int lockTaskAuth) {
return getTask(TEST_PACKAGE_NAME, lockTaskAuth);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 112479b..1a57596 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -61,7 +61,7 @@
/**
* Build/Install/Run:
- * atest FrameworksServicesTests:RemoteAnimationControllerTest
+ * atest WmTests:RemoteAnimationControllerTest
*/
@SmallTest
@Presubmit
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
index 04d79ca..ea8d082 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootActivityContainerTests.java
@@ -253,12 +253,12 @@
// Under split screen primary we should be focusable when not minimized
mRootWindowContainer.setDockedStackMinimized(false);
- assertTrue(stack.isFocusable());
+ assertTrue(stack.isTopActivityFocusable());
assertTrue(activity.isFocusable());
// Under split screen primary we should not be focusable when minimized
mRootWindowContainer.setDockedStackMinimized(true);
- assertFalse(stack.isFocusable());
+ assertFalse(stack.isTopActivityFocusable());
assertFalse(activity.isFocusable());
final ActivityStack pinnedStack = mRootWindowContainer.getDefaultDisplay().createStack(
@@ -267,19 +267,19 @@
.setStack(pinnedStack).build();
// We should not be focusable when in pinned mode
- assertFalse(pinnedStack.isFocusable());
+ assertFalse(pinnedStack.isTopActivityFocusable());
assertFalse(pinnedActivity.isFocusable());
// Add flag forcing focusability.
pinnedActivity.info.flags |= FLAG_ALWAYS_FOCUSABLE;
// We should not be focusable when in pinned mode
- assertTrue(pinnedStack.isFocusable());
+ assertTrue(pinnedStack.isTopActivityFocusable());
assertTrue(pinnedActivity.isFocusable());
// Without the overridding activity, stack should not be focusable.
pinnedStack.removeChild(pinnedActivity.getTask(), "testFocusability");
- assertFalse(pinnedStack.isFocusable());
+ assertFalse(pinnedStack.isTopActivityFocusable());
}
/**
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 64db897..890e4ab 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -391,6 +391,33 @@
assertEquals(null, compatTokens.get(0));
}
+ @Test
+ public void testShouldUseSizeCompatModeOnResizableTask() {
+ setUpApp(new TestDisplayContent.Builder(mService, 1000, 2500).build());
+
+ // Make the task root resizable.
+ mActivity.info.resizeMode = ActivityInfo.RESIZE_MODE_RESIZEABLE;
+
+ // Create a size compat activity on the same task.
+ final ActivityRecord activity = new ActivityBuilder(mService)
+ .setTask(mTask)
+ .setResizeMode(ActivityInfo.RESIZE_MODE_UNRESIZEABLE)
+ .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
+ .build();
+ assertTrue(activity.shouldUseSizeCompatMode());
+
+ // The non-resizable activity should not be size compat because it is on a resizable task
+ // in multi-window mode.
+ mStack.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
+ assertFalse(activity.shouldUseSizeCompatMode());
+
+ // The non-resizable activity should not be size compat because the display support
+ // changing windowing mode from fullscreen to freeform.
+ mStack.mDisplayContent.setDisplayWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM);
+ mStack.setWindowingMode(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
+ assertFalse(activity.shouldUseSizeCompatMode());
+ }
+
/**
* Setup {@link #mActivity} as a size-compat-mode-able activity with fixed aspect and/or
* orientation.
diff --git a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
index f5d08dc..eda1fb8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
+++ b/services/tests/wmtests/src/com/android/server/wm/StubTransaction.java
@@ -250,4 +250,9 @@
return this;
}
+ @Override
+ public SurfaceControl.Transaction setFrameRateSelectionPriority(SurfaceControl sc,
+ int priority) {
+ return this;
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
index e011280..bac2bca 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SurfaceAnimationRunnerTest.java
@@ -60,7 +60,7 @@
* Test class for {@link SurfaceAnimationRunner}.
*
* Build/Install/Run:
- * atest FrameworksServicesTests:SurfaceAnimationRunnerTest
+ * atest WmTests:SurfaceAnimationRunnerTest
*/
@SmallTest
@Presubmit
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 05d1c76..e507508 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -408,6 +408,16 @@
}
}
+ /**
+ * Throws if caller doesn't hold the given lock.
+ * @param lock the lock
+ */
+ static void checkHoldsLock(Object lock) {
+ if (!Thread.holdsLock(lock)) {
+ throw new IllegalStateException("Caller doesn't hold global lock.");
+ }
+ }
+
protected class TestActivityTaskManagerService extends ActivityTaskManagerService {
// ActivityStackSupervisor may be created more than once while setting up AMS and ATMS.
// We keep the reference in order to prevent creating it twice.
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
new file mode 100644
index 0000000..8d2da1e
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
@@ -0,0 +1,170 @@
+/*
+ * 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 com.android.server.wm;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.view.ITaskOrganizer;
+import android.view.SurfaceControl;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test class for {@link TaskOrganizer}.
+ *
+ * Build/Install/Run:
+ * atest WmTests:TaskOrganizerTests
+ */
+@SmallTest
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class TaskOrganizerTests extends WindowTestsBase {
+ private ITaskOrganizer makeAndRegisterMockOrganizer() {
+ final ITaskOrganizer organizer = mock(ITaskOrganizer.class);
+ when(organizer.asBinder()).thenReturn(new Binder());
+
+ mWm.mAtmService.registerTaskOrganizer(organizer, WINDOWING_MODE_PINNED);
+
+ return organizer;
+ }
+
+ @Test
+ public void testAppearVanish() throws RemoteException {
+ final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+ final Task task = createTaskInStack(stack, 0 /* userId */);
+ final ITaskOrganizer organizer = makeAndRegisterMockOrganizer();
+
+ task.setTaskOrganizer(organizer);
+ verify(organizer).taskAppeared(any(), any());
+ assertTrue(task.isControlledByTaskOrganizer());
+
+ task.removeImmediately();
+ verify(organizer).taskVanished(any());
+ }
+
+ @Test
+ public void testSwapOrganizer() throws RemoteException {
+ final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+ final Task task = createTaskInStack(stack, 0 /* userId */);
+ final ITaskOrganizer organizer = makeAndRegisterMockOrganizer();
+ final ITaskOrganizer organizer2 = makeAndRegisterMockOrganizer();
+
+ task.setTaskOrganizer(organizer);
+ verify(organizer).taskAppeared(any(), any());
+ task.setTaskOrganizer(organizer2);
+ verify(organizer).taskVanished(any());
+ verify(organizer2).taskAppeared(any(), any());
+ }
+
+ @Test
+ public void testClearOrganizer() throws RemoteException {
+ final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+ final Task task = createTaskInStack(stack, 0 /* userId */);
+ final ITaskOrganizer organizer = makeAndRegisterMockOrganizer();
+
+ task.setTaskOrganizer(organizer);
+ verify(organizer).taskAppeared(any(), any());
+ assertTrue(task.isControlledByTaskOrganizer());
+
+ task.setTaskOrganizer(null);
+ verify(organizer).taskVanished(any());
+ assertFalse(task.isControlledByTaskOrganizer());
+ }
+
+ @Test
+ public void testTransferStackToOrganizer() throws RemoteException {
+ final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+ final Task task = createTaskInStack(stack, 0 /* userId */);
+ final Task task2 = createTaskInStack(stack, 0 /* userId */);
+ final ITaskOrganizer organizer = makeAndRegisterMockOrganizer();
+
+ stack.transferToTaskOrganizer(organizer);
+
+ verify(organizer, times(2)).taskAppeared(any(), any());
+ assertTrue(task.isControlledByTaskOrganizer());
+ assertTrue(task2.isControlledByTaskOrganizer());
+
+ stack.transferToTaskOrganizer(null);
+
+ verify(organizer, times(2)).taskVanished(any());
+ assertFalse(task.isControlledByTaskOrganizer());
+ assertFalse(task2.isControlledByTaskOrganizer());
+ }
+
+ @Test
+ public void testRegisterTaskOrganizerTaskWindowingModeChanges() throws RemoteException {
+ final ITaskOrganizer organizer = makeAndRegisterMockOrganizer();
+
+ final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+ final Task task = createTaskInStack(stack, 0 /* userId */);
+ task.setWindowingMode(WINDOWING_MODE_PINNED);
+ verify(organizer).taskAppeared(any(), any());
+ assertTrue(task.isControlledByTaskOrganizer());
+
+ task.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ verify(organizer).taskVanished(any());
+ assertFalse(task.isControlledByTaskOrganizer());
+ }
+
+ @Test
+ public void testRegisterTaskOrganizerStackWindowingModeChanges() throws RemoteException {
+ final ITaskOrganizer organizer = makeAndRegisterMockOrganizer();
+
+ final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+ final Task task = createTaskInStack(stack, 0 /* userId */);
+ final Task task2 = createTaskInStack(stack, 0 /* userId */);
+ stack.setWindowingMode(WINDOWING_MODE_PINNED);
+ verify(organizer, times(2)).taskAppeared(any(), any());
+ assertTrue(task.isControlledByTaskOrganizer());
+ assertTrue(task2.isControlledByTaskOrganizer());
+
+ stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ verify(organizer, times(2)).taskVanished(any());
+ assertFalse(task.isControlledByTaskOrganizer());
+ assertFalse(task2.isControlledByTaskOrganizer());
+ }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
index 8e32dad..ca84932 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
@@ -84,7 +84,7 @@
mTarget.finishTaskPositioning();
// Wait until the looper processes finishTaskPositioning.
- assertTrue(waitHandlerIdle(mWm.mH, TIMEOUT_MS));
+ assertTrue(waitHandlerIdle(mWm.mAnimationHandler, TIMEOUT_MS));
assertFalse(mTarget.isPositioningLocked());
assertNull(mTarget.getDragWindowHandleLocked());
@@ -103,7 +103,7 @@
mTarget.finishTaskPositioning(mWindow.mClient);
// Wait until the looper processes finishTaskPositioning.
- assertTrue(waitHandlerIdle(mWm.mH, TIMEOUT_MS));
+ assertTrue(waitHandlerIdle(mWm.mAnimationHandler, TIMEOUT_MS));
assertFalse(mTarget.isPositioningLocked());
assertNull(mTarget.getDragWindowHandleLocked());
@@ -119,7 +119,7 @@
assertNotNull(mWindow.getTask().getTopVisibleAppMainWindow());
mTarget.handleTapOutsideTask(content, 0, 0);
- // Wait until the looper processes finishTaskPositioning.
+ // Wait until the looper processes handleTapOutsideTask.
assertTrue(waitHandlerIdle(mWm.mH, TIMEOUT_MS));
assertTrue(mTarget.isPositioningLocked());
@@ -127,7 +127,7 @@
mTarget.finishTaskPositioning();
// Wait until the looper processes finishTaskPositioning.
- assertTrue(waitHandlerIdle(mWm.mH, TIMEOUT_MS));
+ assertTrue(waitHandlerIdle(mWm.mAnimationHandler, TIMEOUT_MS));
assertFalse(mTarget.isPositioningLocked());
assertNull(mTarget.getDragWindowHandleLocked());
@@ -145,7 +145,7 @@
mWindow.getTask().setResizeMode(RESIZE_MODE_UNRESIZEABLE);
mTarget.handleTapOutsideTask(content, 0, 0);
- // Wait until the looper processes finishTaskPositioning.
+ // Wait until the looper processes handleTapOutsideTask.
assertTrue(waitHandlerIdle(mWm.mH, TIMEOUT_MS));
assertFalse(mTarget.isPositioningLocked());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java
index 0274b7d..e712471 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotCacheTest.java
@@ -38,7 +38,7 @@
* Test class for {@link TaskSnapshotCache}.
*
* Build/Install/Run:
- * atest FrameworksServicesTests:TaskSnapshotCacheTest
+ * atest WmTests:TaskSnapshotCacheTest
*/
@SmallTest
@Presubmit
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
index 8fe0cdb..2e485dd 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotControllerTest.java
@@ -53,7 +53,7 @@
* Test class for {@link TaskSnapshotController}.
*
* Build/Install/Run:
- * * atest FrameworksServicesTests:TaskSnapshotControllerTest
+ * * atest WmTests:TaskSnapshotControllerTest
*/
@SmallTest
@Presubmit
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
index b5a5790..eb8eb98 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotPersisterLoaderTest.java
@@ -47,7 +47,7 @@
* Test class for {@link TaskSnapshotPersister} and {@link TaskSnapshotLoader}
*
* Build/Install/Run:
- * atest FrameworksServicesTests:TaskSnapshotPersisterLoaderTest
+ * atest WmTests:TaskSnapshotPersisterLoaderTest
*/
@MediumTest
@Presubmit
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
index 2d418ff..ed87f3a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskSnapshotSurfaceTest.java
@@ -53,7 +53,7 @@
* Test class for {@link TaskSnapshotSurface}.
*
* Build/Install/Run:
- * atest FrameworksServicesTests:TaskSnapshotSurfaceTest
+ * atest WmTests:TaskSnapshotSurfaceTest
*/
@SmallTest
@Presubmit
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
index e5121b9..77af5ee 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestDisplayContent.java
@@ -72,7 +72,7 @@
private final DisplayInfo mInfo;
private boolean mCanRotate = true;
private int mWindowingMode = WINDOWING_MODE_FULLSCREEN;
- private int mPosition = POSITION_TOP;
+ private int mPosition = POSITION_BOTTOM;
private final ActivityTaskManagerService mService;
private boolean mSystemDecorations = false;
@@ -127,13 +127,13 @@
return this;
}
TestDisplayContent build() {
+ SystemServicesTestRule.checkHoldsLock(mService.mGlobalLock);
+
final int displayId = SystemServicesTestRule.sNextDisplayId++;
final Display display = new Display(DisplayManagerGlobal.getInstance(), displayId,
mInfo, DEFAULT_DISPLAY_ADJUSTMENTS);
- final TestDisplayContent newDisplay;
- synchronized (mService.mGlobalLock) {
- newDisplay = new TestDisplayContent(mService.mStackSupervisor, display);
- }
+ final TestDisplayContent newDisplay =
+ new TestDisplayContent(mService.mStackSupervisor, display);
// disable the normal system decorations
final DisplayPolicy displayPolicy = newDisplay.mDisplayContent.getDisplayPolicy();
spyOn(displayPolicy);
@@ -153,6 +153,10 @@
doReturn(false).when(newDisplay.mDisplayContent)
.handlesOrientationChangeFromDescendant();
}
+ // Please add stubbing before this line. Services will start using this display in other
+ // threads immediately after adding it to hierarchy. Calling doAnswer() type of stubbing
+ // reduces chance of races, but still doesn't eliminate race conditions.
+ mService.mRootWindowContainer.addChild(newDisplay, mPosition);
return newDisplay;
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 248b06b..d3cd3cb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -21,6 +21,7 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.view.WindowManager.TRANSIT_TASK_OPEN;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyFloat;
@@ -49,7 +50,12 @@
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.IBinder;
+import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationAdapter;
+import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
@@ -782,6 +788,63 @@
assertEquals(newDc, activity.mDisplayContent);
}
+ @Test
+ public void testTaskCanApplyAnimation() {
+ final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+ final Task task = createTaskInStack(stack, 0 /* userId */);
+ final ActivityRecord activity =
+ WindowTestUtils.createActivityRecordInTask(mDisplayContent, task);
+ verifyWindowContainerApplyAnimation(task, activity);
+ }
+
+ @Test
+ public void testStackCanApplyAnimation() {
+ final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+ final ActivityRecord activity = WindowTestUtils.createActivityRecordInTask(mDisplayContent,
+ createTaskInStack(stack, 0 /* userId */));
+ verifyWindowContainerApplyAnimation(stack, activity);
+ }
+
+ private void verifyWindowContainerApplyAnimation(WindowContainer wc, ActivityRecord act) {
+ // Initial remote animation for app transition.
+ final RemoteAnimationAdapter adapter = new RemoteAnimationAdapter(
+ new IRemoteAnimationRunner.Stub() {
+ @Override
+ public void onAnimationStart(RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers,
+ IRemoteAnimationFinishedCallback finishedCallback) {
+ try {
+ finishedCallback.onAnimationFinished();
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void onAnimationCancelled() {
+ }
+ }, 0, 0, false);
+ adapter.setCallingPidUid(123, 456);
+ wc.getDisplayContent().prepareAppTransition(TRANSIT_TASK_OPEN, false);
+ wc.getDisplayContent().mAppTransition.overridePendingAppTransitionRemote(adapter);
+ spyOn(wc);
+ doReturn(true).when(wc).okToAnimate();
+
+ // Make sure animating state is as expected after applied animation.
+ assertTrue(wc.applyAnimation(null, TRANSIT_TASK_OPEN, true, false));
+ assertEquals(wc.getTopMostActivity(), act);
+ assertTrue(wc.isAnimating());
+ assertTrue(act.isAnimating(PARENTS));
+
+ // Make sure animation finish callback will be received and reset animating state after
+ // animation finish.
+ wc.getDisplayContent().mAppTransition.goodToGo(TRANSIT_TASK_OPEN, act,
+ mDisplayContent.mOpeningApps);
+ verify(wc).onAnimationFinished();
+ assertFalse(wc.isAnimating());
+ assertFalse(act.isAnimating(PARENTS));
+ }
+
/* Used so we can gain access to some protected members of the {@link WindowContainer} class */
private static class TestWindowContainer extends WindowContainer<TestWindowContainer> {
private final int mLayer;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
index 8936aad..3c0dd1e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTraversalTests.java
@@ -39,7 +39,7 @@
* Tests for {@link WindowContainer#forAllWindows} and various implementations.
*
* Build/Install/Run:
- * atest FrameworksServicesTests:WindowContainerTraversalTests
+ * atest WmTests:WindowContainerTraversalTests
*/
@SmallTest
@Presubmit
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 5028585..7e248f8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -92,7 +92,7 @@
* Tests for the {@link WindowState} class.
*
* Build/Install/Run:
- * atest FrameworksServicesTests:WindowStateTests
+ * atest WmTests:WindowStateTests
*/
@SmallTest
@Presubmit
@@ -421,8 +421,10 @@
.setWindow(statusBar, null /* frameProvider */);
mDisplayContent.getInsetsStateController().onBarControlTargetChanged(
app, null /* fakeTopControlling */, app, null /* fakeNavControlling */);
+ final InsetsSource source = new InsetsSource(ITYPE_STATUS_BAR);
+ source.setVisible(false);
mDisplayContent.getInsetsStateController().getSourceProvider(ITYPE_STATUS_BAR)
- .onInsetsModified(app, new InsetsSource(ITYPE_STATUS_BAR));
+ .onInsetsModified(app, source);
waitUntilHandlersIdle();
assertFalse(statusBar.isVisible());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index ea88315..31a7f24 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -44,6 +44,7 @@
import android.view.DisplayInfo;
import android.view.IWindow;
import android.view.SurfaceControl.Transaction;
+import android.view.View;
import android.view.WindowManager;
import com.android.server.AttributeCache;
@@ -312,6 +313,16 @@
}
}
+ static void makeWindowVisible(WindowState... windows) {
+ for (WindowState win : windows) {
+ win.mViewVisibility = View.VISIBLE;
+ win.mRelayoutCalled = true;
+ win.mHasSurface = true;
+ win.mHidden = false;
+ win.showLw(false /* doAnimation */, false /* requestAnim */);
+ }
+ }
+
/** Creates a {@link ActivityStack} and adds it to the specified {@link DisplayContent}. */
ActivityStack createTaskStackOnDisplay(DisplayContent dc) {
return createTaskStackOnDisplay(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, dc);
diff --git a/services/usage/OWNERS b/services/usage/OWNERS
new file mode 100644
index 0000000..9daa093
--- /dev/null
+++ b/services/usage/OWNERS
@@ -0,0 +1,4 @@
+mwachens@google.com
+varunshah@google.com
+huiyu@google.com
+yamasani@google.com
diff --git a/services/usage/java/com/android/server/usage/StorageStatsManagerInternal.java b/services/usage/java/com/android/server/usage/StorageStatsManagerInternal.java
new file mode 100644
index 0000000..a532548
--- /dev/null
+++ b/services/usage/java/com/android/server/usage/StorageStatsManagerInternal.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.usage;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.pm.PackageStats;
+
+/**
+ * StorageStatsManager local system service interface.
+ *
+ * Only for use within the system server.
+ */
+public abstract class StorageStatsManagerInternal {
+ /**
+ * Class used to augment {@link PackageStats} with the data stored by the system on
+ * behalf of apps in system specific directories
+ * ({@link android.os.Environment#getDataSystemDirectory},
+ * {@link android.os.Environment#getDataSystemCeDirectory}, etc).
+ */
+ public interface StorageStatsAugmenter {
+ void augmentStatsForPackage(@NonNull PackageStats stats,
+ @NonNull String packageName, @UserIdInt int userId,
+ @NonNull String callingPackage);
+ void augmentStatsForUid(@NonNull PackageStats stats, int uid,
+ @NonNull String callingPackage);
+ void augmentStatsForUser(@NonNull PackageStats stats, @UserIdInt int userId,
+ @NonNull String callingPackage);
+ }
+
+ /**
+ * Register a {@link StorageStatsAugmenter}.
+ *
+ * @param augmenter the {@link StorageStatsAugmenter} object to be registered.
+ * @param tag the identifier to be used for debugging in logs/trace.
+ */
+ public abstract void registerStorageStatsAugmenter(@NonNull StorageStatsAugmenter augmenter,
+ @NonNull String tag);
+}
diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java
index 531a931..18b640f 100644
--- a/services/usage/java/com/android/server/usage/StorageStatsService.java
+++ b/services/usage/java/com/android/server/usage/StorageStatsService.java
@@ -18,6 +18,7 @@
import static com.android.internal.util.ArrayUtils.defeatNullable;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
+import static com.android.server.usage.StorageStatsManagerInternal.StorageStatsAugmenter;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -46,6 +47,7 @@
import android.os.ParcelableException;
import android.os.StatFs;
import android.os.SystemProperties;
+import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.CrateInfo;
@@ -58,6 +60,7 @@
import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.DataUnit;
+import android.util.Pair;
import android.util.Slog;
import android.util.SparseLongArray;
@@ -77,6 +80,8 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Consumer;
public class StorageStatsService extends IStorageStatsManager.Stub {
private static final String TAG = "StorageStatsService";
@@ -111,6 +116,9 @@
private final Installer mInstaller;
private final H mHandler;
+ private final CopyOnWriteArrayList<Pair<String, StorageStatsAugmenter>>
+ mStorageStatsAugmenters = new CopyOnWriteArrayList<>();
+
public StorageStatsService(Context context) {
mContext = Preconditions.checkNotNull(context);
mAppOps = Preconditions.checkNotNull(context.getSystemService(AppOpsManager.class));
@@ -139,6 +147,8 @@
}
}
});
+
+ LocalServices.addService(StorageStatsManagerInternal.class, new LocalService());
}
private void invalidateMounts() {
@@ -300,6 +310,12 @@
} catch (InstallerException e) {
throw new ParcelableException(new IOException(e.getMessage()));
}
+ if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) {
+ forEachStorageStatsAugmenter((storageStatsAugmenter) -> {
+ storageStatsAugmenter.augmentStatsForPackage(stats,
+ packageName, userId, callingPackage);
+ }, "queryStatsForPackage");
+ }
return translate(stats);
}
}
@@ -353,6 +369,12 @@
} catch (InstallerException e) {
throw new ParcelableException(new IOException(e.getMessage()));
}
+
+ if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) {
+ forEachStorageStatsAugmenter((storageStatsAugmenter) -> {
+ storageStatsAugmenter.augmentStatsForUid(stats, uid, callingPackage);
+ }, "queryStatsForUid");
+ }
return translate(stats);
}
@@ -379,6 +401,12 @@
} catch (InstallerException e) {
throw new ParcelableException(new IOException(e.getMessage()));
}
+
+ if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) {
+ forEachStorageStatsAugmenter((storageStatsAugmenter) -> {
+ storageStatsAugmenter.augmentStatsForUser(stats, userId, callingPackage);
+ }, "queryStatsForUser");
+ }
return translate(stats);
}
@@ -705,4 +733,29 @@
throw new ParcelableException(new IOException(e.getMessage()));
}
}
+
+ void forEachStorageStatsAugmenter(@NonNull Consumer<StorageStatsAugmenter> consumer,
+ @NonNull String queryTag) {
+ for (int i = 0, count = mStorageStatsAugmenters.size(); i < count; ++i) {
+ final Pair<String, StorageStatsAugmenter> pair = mStorageStatsAugmenters.get(i);
+ final String augmenterTag = pair.first;
+ final StorageStatsAugmenter storageStatsAugmenter = pair.second;
+
+ Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, queryTag + ":" + augmenterTag);
+ try {
+ consumer.accept(storageStatsAugmenter);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_SYSTEM_SERVER);
+ }
+ }
+ }
+
+ private class LocalService extends StorageStatsManagerInternal {
+ @Override
+ public void registerStorageStatsAugmenter(
+ @NonNull StorageStatsAugmenter storageStatsAugmenter,
+ @NonNull String tag) {
+ mStorageStatsAugmenters.add(Pair.create(tag, storageStatsAugmenter));
+ }
+ }
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index c900f38..b8cd378 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -1568,44 +1568,16 @@
}
@Override
- public void setAppStandbyBucket(String packageName,
- int bucket, int userId) {
+ public void setAppStandbyBucket(String packageName, int bucket, int userId) {
getContext().enforceCallingPermission(Manifest.permission.CHANGE_APP_IDLE_STATE,
"No permission to change app standby state");
- if (bucket < UsageStatsManager.STANDBY_BUCKET_ACTIVE
- || bucket > UsageStatsManager.STANDBY_BUCKET_NEVER) {
- throw new IllegalArgumentException("Cannot set the standby bucket to " + bucket);
- }
final int callingUid = Binder.getCallingUid();
- try {
- userId = ActivityManager.getService().handleIncomingUser(
- Binder.getCallingPid(), callingUid, userId, false, true,
- "setAppStandbyBucket", null);
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
- final boolean shellCaller = callingUid == 0 || callingUid == Process.SHELL_UID;
- final boolean systemCaller = UserHandle.isCore(callingUid);
- final int reason = systemCaller
- ? UsageStatsManager.REASON_MAIN_FORCED
- : UsageStatsManager.REASON_MAIN_PREDICTED;
+ final int callingPid = Binder.getCallingPid();
final long token = Binder.clearCallingIdentity();
try {
- final int packageUid = mPackageManagerInternal.getPackageUid(packageName,
- PackageManager.MATCH_ANY_USER | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
- | PackageManager.MATCH_DIRECT_BOOT_AWARE, userId);
- // Caller cannot set their own standby state
- if (packageUid == callingUid) {
- throw new IllegalArgumentException("Cannot set your own standby bucket");
- }
- if (packageUid < 0) {
- throw new IllegalArgumentException(
- "Cannot set standby bucket for non existent package (" + packageName
- + ")");
- }
- mAppStandby.setAppStandbyBucket(packageName, userId, bucket, reason,
- SystemClock.elapsedRealtime(), shellCaller);
+ mAppStandby.setAppStandbyBucket(packageName, bucket, userId,
+ callingUid, callingPid);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -1643,37 +1615,11 @@
"No permission to change app standby state");
final int callingUid = Binder.getCallingUid();
- try {
- userId = ActivityManager.getService().handleIncomingUser(
- Binder.getCallingPid(), callingUid, userId, false, true,
- "setAppStandbyBucket", null);
- } catch (RemoteException re) {
- throw re.rethrowFromSystemServer();
- }
- final boolean shellCaller = callingUid == 0 || callingUid == Process.SHELL_UID;
- final int reason = shellCaller
- ? UsageStatsManager.REASON_MAIN_FORCED
- : UsageStatsManager.REASON_MAIN_PREDICTED;
+ final int callingPid = Binder.getCallingPid();
final long token = Binder.clearCallingIdentity();
try {
- final long elapsedRealtime = SystemClock.elapsedRealtime();
- List<AppStandbyInfo> bucketList = appBuckets.getList();
- for (AppStandbyInfo bucketInfo : bucketList) {
- final String packageName = bucketInfo.mPackageName;
- final int bucket = bucketInfo.mStandbyBucket;
- if (bucket < UsageStatsManager.STANDBY_BUCKET_ACTIVE
- || bucket > UsageStatsManager.STANDBY_BUCKET_NEVER) {
- throw new IllegalArgumentException(
- "Cannot set the standby bucket to " + bucket);
- }
- // Caller cannot set their own standby state
- if (mPackageManagerInternal.getPackageUid(packageName,
- PackageManager.MATCH_ANY_USER, userId) == callingUid) {
- throw new IllegalArgumentException("Cannot set your own standby bucket");
- }
- mAppStandby.setAppStandbyBucket(packageName, userId, bucket, reason,
- elapsedRealtime, shellCaller);
- }
+ mAppStandby.setAppStandbyBuckets(appBuckets.getList(), userId,
+ callingUid, callingPid);
} finally {
Binder.restoreCallingIdentity(token);
}
@@ -2123,6 +2069,13 @@
}
@Override
+ public UsageEvents queryEventsForUser(int userId, long beginTime, long endTime,
+ boolean shouldObfuscateInstantApps) {
+ return UsageStatsService.this.queryEvents(
+ userId, beginTime, endTime, shouldObfuscateInstantApps);
+ }
+
+ @Override
public void setLastJobRunTime(String packageName, int userId, long elapsedRealtime) {
mAppStandby.setLastJobRunTime(packageName, userId, elapsedRealtime);
}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java
index f7cd6a3..9906585 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerDbHelper.java
@@ -21,12 +21,10 @@
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
-import android.hardware.soundtrigger.SoundTrigger;
import android.hardware.soundtrigger.SoundTrigger.GenericSoundModel;
-import android.text.TextUtils;
import android.util.Slog;
-import java.util.Locale;
+import java.io.PrintWriter;
import java.util.UUID;
/**
@@ -39,7 +37,7 @@
static final boolean DBG = false;
private static final String NAME = "st_sound_model.db";
- private static final int VERSION = 1;
+ private static final int VERSION = 2;
// Sound trigger-based sound models.
public static interface GenericSoundModelContract {
@@ -47,15 +45,16 @@
public static final String KEY_MODEL_UUID = "model_uuid";
public static final String KEY_VENDOR_UUID = "vendor_uuid";
public static final String KEY_DATA = "data";
+ public static final String KEY_MODEL_VERSION = "model_version";
}
-
// Table Create Statement for the sound trigger table
private static final String CREATE_TABLE_ST_SOUND_MODEL = "CREATE TABLE "
+ GenericSoundModelContract.TABLE + "("
+ GenericSoundModelContract.KEY_MODEL_UUID + " TEXT PRIMARY KEY,"
+ GenericSoundModelContract.KEY_VENDOR_UUID + " TEXT,"
- + GenericSoundModelContract.KEY_DATA + " BLOB" + " )";
+ + GenericSoundModelContract.KEY_DATA + " BLOB,"
+ + GenericSoundModelContract.KEY_MODEL_VERSION + " INTEGER" + " )";
public SoundTriggerDbHelper(Context context) {
@@ -70,9 +69,13 @@
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
- // TODO: For now, drop older tables and recreate new ones.
- db.execSQL("DROP TABLE IF EXISTS " + GenericSoundModelContract.TABLE);
- onCreate(db);
+ if (oldVersion == 1) {
+ // In version 2, a model version number was added.
+ Slog.d(TAG, "Adding model version column");
+ db.execSQL("ALTER TABLE " + GenericSoundModelContract.TABLE + " ADD COLUMN "
+ + GenericSoundModelContract.KEY_MODEL_VERSION + " INTEGER DEFAULT -1");
+ oldVersion++;
+ }
}
/**
@@ -86,6 +89,7 @@
values.put(GenericSoundModelContract.KEY_MODEL_UUID, soundModel.uuid.toString());
values.put(GenericSoundModelContract.KEY_VENDOR_UUID, soundModel.vendorUuid.toString());
values.put(GenericSoundModelContract.KEY_DATA, soundModel.data);
+ values.put(GenericSoundModelContract.KEY_MODEL_VERSION, soundModel.version);
try {
return db.insertWithOnConflict(GenericSoundModelContract.TABLE, null, values,
@@ -113,8 +117,10 @@
GenericSoundModelContract.KEY_DATA));
String vendor_uuid = c.getString(
c.getColumnIndex(GenericSoundModelContract.KEY_VENDOR_UUID));
+ int version = c.getInt(
+ c.getColumnIndex(GenericSoundModelContract.KEY_MODEL_VERSION));
return new GenericSoundModel(model_uuid, UUID.fromString(vendor_uuid),
- data);
+ data, version);
} while (c.moveToNext());
}
} finally {
@@ -142,4 +148,48 @@
}
}
}
+
+ public void dump(PrintWriter pw) {
+ synchronized(this) {
+ String selectQuery = "SELECT * FROM " + GenericSoundModelContract.TABLE;
+ SQLiteDatabase db = getReadableDatabase();
+ Cursor c = db.rawQuery(selectQuery, null);
+ try {
+ pw.println(" Enrolled GenericSoundModels:");
+ if (c.moveToFirst()) {
+ String[] columnNames = c.getColumnNames();
+ do {
+ for (String name : columnNames) {
+ int colNameIndex = c.getColumnIndex(name);
+ int type = c.getType(colNameIndex);
+ switch (type) {
+ case Cursor.FIELD_TYPE_STRING:
+ pw.printf(" %s: %s\n", name,
+ c.getString(colNameIndex));
+ break;
+ case Cursor.FIELD_TYPE_BLOB:
+ pw.printf(" %s: data blob\n", name);
+ break;
+ case Cursor.FIELD_TYPE_INTEGER:
+ pw.printf(" %s: %d\n", name,
+ c.getInt(colNameIndex));
+ break;
+ case Cursor.FIELD_TYPE_FLOAT:
+ pw.printf(" %s: %f\n", name,
+ c.getFloat(colNameIndex));
+ break;
+ case Cursor.FIELD_TYPE_NULL:
+ pw.printf(" %s: null\n", name);
+ break;
+ }
+ }
+ pw.println();
+ } while (c.moveToNext());
+ }
+ } finally {
+ c.close();
+ db.close();
+ }
+ }
+ }
}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
index 198b4c3..7099c09 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerHelper.java
@@ -611,65 +611,91 @@
int setParameter(UUID modelId, @ModelParams int modelParam, int value) {
synchronized (mLock) {
- MetricsLogger.count(mContext, "sth_set_parameter", 1);
- if (modelId == null || mModule == null) {
- return SoundTrigger.STATUS_ERROR;
- }
- ModelData modelData = mModelDataMap.get(modelId);
- if (modelData == null) {
- Slog.w(TAG, "SetParameter: Invalid model id:" + modelId);
- return SoundTrigger.STATUS_BAD_VALUE;
- }
- if (!modelData.isModelLoaded()) {
- Slog.i(TAG, "SetParameter: Given model is not loaded:" + modelId);
- return SoundTrigger.STATUS_BAD_VALUE;
- }
-
- return mModule.setParameter(modelData.getHandle(), modelParam, value);
+ return setParameterLocked(mModelDataMap.get(modelId), modelParam, value);
}
}
- int getParameter(@NonNull UUID modelId, @ModelParams int modelParam)
- throws UnsupportedOperationException, IllegalArgumentException {
+ int setKeyphraseParameter(int keyphraseId, @ModelParams int modelParam, int value) {
synchronized (mLock) {
- MetricsLogger.count(mContext, "sth_get_parameter", 1);
- if (mModule == null) {
- throw new UnsupportedOperationException("SoundTriggerModule not initialized");
- }
-
- ModelData modelData = mModelDataMap.get(modelId);
- if (modelData == null) {
- throw new IllegalArgumentException("Invalid model id:" + modelId);
- }
- if (!modelData.isModelLoaded()) {
- throw new UnsupportedOperationException("Given model is not loaded:" + modelId);
- }
-
- return mModule.getParameter(modelData.getHandle(), modelParam);
+ return setParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam, value);
}
}
+ private int setParameterLocked(@Nullable ModelData modelData, @ModelParams int modelParam,
+ int value) {
+ MetricsLogger.count(mContext, "sth_set_parameter", 1);
+ if (mModule == null) {
+ return SoundTrigger.STATUS_NO_INIT;
+ }
+ if (modelData == null || !modelData.isModelLoaded()) {
+ Slog.i(TAG, "SetParameter: Given model is not loaded:" + modelData);
+ return SoundTrigger.STATUS_BAD_VALUE;
+ }
+
+ return mModule.setParameter(modelData.getHandle(), modelParam, value);
+ }
+
+ int getParameter(@NonNull UUID modelId, @ModelParams int modelParam) {
+ synchronized (mLock) {
+ return getParameterLocked(mModelDataMap.get(modelId), modelParam);
+ }
+ }
+
+ int getKeyphraseParameter(int keyphraseId, @ModelParams int modelParam) {
+ synchronized (mLock) {
+ return getParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam);
+ }
+ }
+
+ private int getParameterLocked(@Nullable ModelData modelData, @ModelParams int modelParam) {
+ MetricsLogger.count(mContext, "sth_get_parameter", 1);
+ if (mModule == null) {
+ throw new UnsupportedOperationException("SoundTriggerModule not initialized");
+ }
+
+ if (modelData == null) {
+ throw new IllegalArgumentException("Invalid model id");
+ }
+ if (!modelData.isModelLoaded()) {
+ throw new UnsupportedOperationException("Given model is not loaded:" + modelData);
+ }
+
+ return mModule.getParameter(modelData.getHandle(), modelParam);
+ }
+
@Nullable
ModelParamRange queryParameter(@NonNull UUID modelId, @ModelParams int modelParam) {
synchronized (mLock) {
- MetricsLogger.count(mContext, "sth_query_parameter", 1);
- if (mModule == null) {
- return null;
- }
- ModelData modelData = mModelDataMap.get(modelId);
- if (modelData == null) {
- Slog.w(TAG, "queryParameter: Invalid model id:" + modelId);
- return null;
- }
- if (!modelData.isModelLoaded()) {
- Slog.i(TAG, "queryParameter: Given model is not loaded:" + modelId);
- return null;
- }
-
- return mModule.queryParameter(modelData.getHandle(), modelParam);
+ return queryParameterLocked(mModelDataMap.get(modelId), modelParam);
}
}
+ @Nullable
+ ModelParamRange queryKeyphraseParameter(int keyphraseId, @ModelParams int modelParam) {
+ synchronized (mLock) {
+ return queryParameterLocked(getKeyphraseModelDataLocked(keyphraseId), modelParam);
+ }
+ }
+
+ @Nullable
+ private ModelParamRange queryParameterLocked(@Nullable ModelData modelData,
+ @ModelParams int modelParam) {
+ MetricsLogger.count(mContext, "sth_query_parameter", 1);
+ if (mModule == null) {
+ return null;
+ }
+ if (modelData == null) {
+ Slog.w(TAG, "queryParameter: Invalid model id");
+ return null;
+ }
+ if (!modelData.isModelLoaded()) {
+ Slog.i(TAG, "queryParameter: Given model is not loaded:" + modelData);
+ return null;
+ }
+
+ return mModule.queryParameter(modelData.getHandle(), modelParam);
+ }
+
//---- SoundTrigger.StatusListener methods
@Override
public void onRecognition(RecognitionEvent event) {
@@ -730,7 +756,9 @@
return;
}
- model.setStopped();
+ if (event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) {
+ model.setStopped();
+ }
try {
callback.onGenericSoundTriggerDetected((GenericRecognitionEvent) event);
@@ -874,7 +902,9 @@
return;
}
- modelData.setStopped();
+ if (event.status != SoundTrigger.RECOGNITION_STATUS_GET_STATE_RESPONSE) {
+ modelData.setStopped();
+ }
try {
modelData.getCallback().onKeyphraseDetected((KeyphraseRecognitionEvent) event);
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
index d05e044..54dffdc 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerInternal.java
@@ -16,17 +16,17 @@
package com.android.server.soundtrigger;
+import android.annotation.Nullable;
import android.hardware.soundtrigger.IRecognitionStatusCallback;
+import android.hardware.soundtrigger.ModelParams;
import android.hardware.soundtrigger.SoundTrigger;
import android.hardware.soundtrigger.SoundTrigger.Keyphrase;
-import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionEvent;
-import android.hardware.soundtrigger.SoundTrigger.KeyphraseRecognitionExtra;
import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
+import android.hardware.soundtrigger.SoundTrigger.ModelParamRange;
import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
-import android.hardware.soundtrigger.SoundTrigger.RecognitionEvent;
-import android.hardware.soundtrigger.SoundTrigger.SoundModelEvent;
-import android.hardware.soundtrigger.SoundTriggerModule;
+
+import com.android.server.voiceinteraction.VoiceInteractionManagerService;
import java.io.FileDescriptor;
import java.io.PrintWriter;
@@ -71,6 +71,58 @@
public abstract ModuleProperties getModuleProperties();
/**
+ * Set a model specific {@link ModelParams} with the given value. This
+ * parameter will keep its value for the duration the model is loaded regardless of starting and
+ * stopping recognition. Once the model is unloaded, the value will be lost.
+ * {@link SoundTriggerInternal#queryParameter} should be checked first before calling this
+ * method.
+ *
+ * @param keyphraseId The identifier of the keyphrase for which
+ * to modify model parameters
+ * @param modelParam {@link ModelParams}
+ * @param value Value to set
+ * @return - {@link SoundTrigger#STATUS_OK} in case of success
+ * - {@link SoundTrigger#STATUS_NO_INIT} if the native service cannot be reached
+ * - {@link SoundTrigger#STATUS_BAD_VALUE} invalid input parameter
+ * - {@link SoundTrigger#STATUS_INVALID_OPERATION} if the call is out of sequence or
+ * if API is not supported by HAL
+ */
+ public abstract int setParameter(int keyphraseId, @ModelParams int modelParam, int value);
+
+ /**
+ * Get a model specific {@link ModelParams}. This parameter will keep its value
+ * for the duration the model is loaded regardless of starting and stopping recognition.
+ * Once the model is unloaded, the value will be lost. If the value is not set, a default
+ * value is returned. See ModelParams for parameter default values.
+ * {@link SoundTriggerInternal#queryParameter} should be checked first before calling this
+ * method.
+ *
+ * @param keyphraseId The identifier of the keyphrase for which
+ * to modify model parameters
+ * @param modelParam {@link ModelParams}
+ * @return value of parameter
+ * @throws UnsupportedOperationException if hal or model do not support this API.
+ * queryParameter should be checked first.
+ * @throws IllegalArgumentException if invalid model handle or parameter is passed.
+ * queryParameter should be checked first.
+ */
+ public abstract int getParameter(int keyphraseId, @ModelParams int modelParam);
+
+ /**
+ * Determine if parameter control is supported for the given model handle.
+ * This method should be checked prior to calling {@link SoundTriggerInternal#setParameter}
+ * or {@link SoundTriggerInternal#getParameter}.
+ *
+ * @param keyphraseId The identifier of the keyphrase for which
+ * to modify model parameters
+ * @param modelParam {@link ModelParams}
+ * @return supported range of parameter, null if not supported
+ */
+ @Nullable
+ public abstract ModelParamRange queryParameter(int keyphraseId,
+ @ModelParams int modelParam);
+
+ /**
* Unloads (and stops if running) the given keyphraseId
*/
public abstract int unloadKeyphraseModel(int keyphaseId);
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index 68b16f3..767010a 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -1446,26 +1446,45 @@
@Override
public int startRecognition(int keyphraseId, KeyphraseSoundModel soundModel,
IRecognitionStatusCallback listener, RecognitionConfig recognitionConfig) {
- if (!isInitialized()) return STATUS_ERROR;
+ if (!isInitialized()) throw new UnsupportedOperationException();
return mSoundTriggerHelper.startKeyphraseRecognition(keyphraseId, soundModel, listener,
recognitionConfig);
}
@Override
public synchronized int stopRecognition(int keyphraseId, IRecognitionStatusCallback listener) {
- if (!isInitialized()) return STATUS_ERROR;
+ if (!isInitialized()) throw new UnsupportedOperationException();
return mSoundTriggerHelper.stopKeyphraseRecognition(keyphraseId, listener);
}
@Override
public ModuleProperties getModuleProperties() {
- if (!isInitialized()) return null;
+ if (!isInitialized()) throw new UnsupportedOperationException();
return mSoundTriggerHelper.getModuleProperties();
}
@Override
+ public int setParameter(int keyphraseId, @ModelParams int modelParam, int value) {
+ if (!isInitialized()) throw new UnsupportedOperationException();
+ return mSoundTriggerHelper.setKeyphraseParameter(keyphraseId, modelParam, value);
+ }
+
+ @Override
+ public int getParameter(int keyphraseId, @ModelParams int modelParam) {
+ if (!isInitialized()) throw new UnsupportedOperationException();
+ return mSoundTriggerHelper.getKeyphraseParameter(keyphraseId, modelParam);
+ }
+
+ @Override
+ @Nullable
+ public ModelParamRange queryParameter(int keyphraseId, @ModelParams int modelParam) {
+ if (!isInitialized()) throw new UnsupportedOperationException();
+ return mSoundTriggerHelper.queryKeyphraseParameter(keyphraseId, modelParam);
+ }
+
+ @Override
public int unloadKeyphraseModel(int keyphraseId) {
- if (!isInitialized()) return STATUS_ERROR;
+ if (!isInitialized()) throw new UnsupportedOperationException();
return mSoundTriggerHelper.unloadKeyphraseSoundModel(keyphraseId);
}
@@ -1476,6 +1495,9 @@
// log
sEventLogger.dump(pw);
+ // enrolled models
+ mDbHelper.dump(pw);
+
// stats
mSoundModelStatTracker.dump(pw);
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
index dd7b5a8..c58b6da 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/DatabaseHelper.java
@@ -27,6 +27,7 @@
import android.text.TextUtils;
import android.util.Slog;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -43,7 +44,7 @@
static final boolean DBG = false;
private static final String NAME = "sound_model.db";
- private static final int VERSION = 6;
+ private static final int VERSION = 7;
public static interface SoundModelContract {
public static final String TABLE = "sound_model";
@@ -56,6 +57,7 @@
public static final String KEY_LOCALE = "locale";
public static final String KEY_HINT_TEXT = "hint_text";
public static final String KEY_USERS = "users";
+ public static final String KEY_MODEL_VERSION = "model_version";
}
// Table Create Statement
@@ -70,6 +72,7 @@
+ SoundModelContract.KEY_LOCALE + " TEXT,"
+ SoundModelContract.KEY_HINT_TEXT + " TEXT,"
+ SoundModelContract.KEY_USERS + " TEXT,"
+ + SoundModelContract.KEY_MODEL_VERSION + " INTEGER,"
+ "PRIMARY KEY (" + SoundModelContract.KEY_KEYPHRASE_ID + ","
+ SoundModelContract.KEY_LOCALE + ","
+ SoundModelContract.KEY_USERS + ")"
@@ -138,6 +141,13 @@
}
oldVersion++;
}
+ if (oldVersion == 6) {
+ // In version 7, a model version number was added.
+ Slog.d(TAG, "Adding model version column");
+ db.execSQL("ALTER TABLE " + SoundModelContract.TABLE + " ADD COLUMN "
+ + SoundModelContract.KEY_MODEL_VERSION + " INTEGER DEFAULT -1");
+ oldVersion++;
+ }
}
/**
@@ -155,6 +165,7 @@
}
values.put(SoundModelContract.KEY_TYPE, SoundTrigger.SoundModel.TYPE_KEYPHRASE);
values.put(SoundModelContract.KEY_DATA, soundModel.data);
+ values.put(SoundModelContract.KEY_MODEL_VERSION, soundModel.version);
if (soundModel.keyphrases != null && soundModel.keyphrases.length == 1) {
values.put(SoundModelContract.KEY_KEYPHRASE_ID, soundModel.keyphrases[0].id);
@@ -250,6 +261,8 @@
c.getColumnIndex(SoundModelContract.KEY_LOCALE));
String text = c.getString(
c.getColumnIndex(SoundModelContract.KEY_HINT_TEXT));
+ int version = c.getInt(
+ c.getColumnIndex(SoundModelContract.KEY_MODEL_VERSION));
// Only add keyphrases meant for the current user.
if (users == null) {
@@ -282,7 +295,7 @@
vendorUuid = UUID.fromString(vendorUuidString);
}
KeyphraseSoundModel model = new KeyphraseSoundModel(
- UUID.fromString(modelUuid), vendorUuid, data, keyphrases);
+ UUID.fromString(modelUuid), vendorUuid, data, keyphrases, version);
if (DBG) {
Slog.d(TAG, "Found SoundModel for the given keyphrase/locale/user: "
+ model);
@@ -325,6 +338,10 @@
return users;
}
+ /**
+ * SoundModelRecord is no longer used, and it should only be used on database migration.
+ * This class does not need to be modified when modifying the database scheme.
+ */
private static class SoundModelRecord {
public final String modelUuid;
public final String vendorUuid;
@@ -413,4 +430,48 @@
return a == b;
}
}
+
+ public void dump(PrintWriter pw) {
+ synchronized(this) {
+ String selectQuery = "SELECT * FROM " + SoundModelContract.TABLE;
+ SQLiteDatabase db = getReadableDatabase();
+ Cursor c = db.rawQuery(selectQuery, null);
+ try {
+ pw.println(" Enrolled KeyphraseSoundModels:");
+ if (c.moveToFirst()) {
+ String[] columnNames = c.getColumnNames();
+ do {
+ for (String name : columnNames) {
+ int colNameIndex = c.getColumnIndex(name);
+ int type = c.getType(colNameIndex);
+ switch (type) {
+ case Cursor.FIELD_TYPE_STRING:
+ pw.printf(" %s: %s\n", name,
+ c.getString(colNameIndex));
+ break;
+ case Cursor.FIELD_TYPE_BLOB:
+ pw.printf(" %s: data blob\n", name);
+ break;
+ case Cursor.FIELD_TYPE_INTEGER:
+ pw.printf(" %s: %d\n", name,
+ c.getInt(colNameIndex));
+ break;
+ case Cursor.FIELD_TYPE_FLOAT:
+ pw.printf(" %s: %f\n", name,
+ c.getFloat(colNameIndex));
+ break;
+ case Cursor.FIELD_TYPE_NULL:
+ pw.printf(" %s: null\n", name);
+ break;
+ }
+ }
+ pw.println();
+ } while (c.moveToNext());
+ }
+ } finally {
+ c.close();
+ db.close();
+ }
+ }
+ }
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index e9db31b..506c67e 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -41,7 +41,9 @@
import android.content.res.Resources;
import android.database.ContentObserver;
import android.hardware.soundtrigger.IRecognitionStatusCallback;
+import android.hardware.soundtrigger.ModelParams;
import android.hardware.soundtrigger.SoundTrigger.KeyphraseSoundModel;
+import android.hardware.soundtrigger.SoundTrigger.ModelParamRange;
import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
import android.os.Binder;
@@ -157,9 +159,13 @@
}
}
+ private boolean isSupported(UserInfo user) {
+ return user.isFull();
+ }
+
@Override
- public boolean isSupported(UserInfo userInfo) {
- return userInfo.isFull();
+ public boolean isSupportedUser(TargetUser user) {
+ return isSupported(user.getUserInfo());
}
@Override
@@ -1084,6 +1090,55 @@
}
}
+ @Override
+ public int setParameter(IVoiceInteractionService service, int keyphraseId,
+ @ModelParams int modelParam, int value) {
+ // Allow the call if this is the current voice interaction service.
+ synchronized (this) {
+ enforceIsCurrentVoiceInteractionService(service);
+ }
+
+ final long caller = Binder.clearCallingIdentity();
+ try {
+ return mSoundTriggerInternal.setParameter(keyphraseId, modelParam, value);
+ } finally {
+ Binder.restoreCallingIdentity(caller);
+ }
+ }
+
+ @Override
+ public int getParameter(IVoiceInteractionService service, int keyphraseId,
+ @ModelParams int modelParam) {
+ // Allow the call if this is the current voice interaction service.
+ synchronized (this) {
+ enforceIsCurrentVoiceInteractionService(service);
+ }
+
+ final long caller = Binder.clearCallingIdentity();
+ try {
+ return mSoundTriggerInternal.getParameter(keyphraseId, modelParam);
+ } finally {
+ Binder.restoreCallingIdentity(caller);
+ }
+ }
+
+ @Override
+ @Nullable
+ public ModelParamRange queryParameter(IVoiceInteractionService service,
+ int keyphraseId, @ModelParams int modelParam) {
+ // Allow the call if this is the current voice interaction service.
+ synchronized (this) {
+ enforceIsCurrentVoiceInteractionService(service);
+ }
+
+ final long caller = Binder.clearCallingIdentity();
+ try {
+ return mSoundTriggerInternal.queryParameter(keyphraseId, modelParam);
+ } finally {
+ Binder.restoreCallingIdentity(caller);
+ }
+ }
+
private synchronized void unloadAllKeyphraseModels() {
for (int i = 0; i < mLoadedKeyphraseIds.size(); i++) {
final long caller = Binder.clearCallingIdentity();
@@ -1292,6 +1347,7 @@
pw.println(" mCurUserUnlocked: " + mCurUserUnlocked);
pw.println(" mCurUserSupported: " + mCurUserSupported);
dumpSupportedUsers(pw, " ");
+ mDbHelper.dump(pw);
if (mImpl == null) {
pw.println(" (No active implementation)");
return;
diff --git a/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java b/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java
index b75510b..488ee78 100644
--- a/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java
+++ b/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java
@@ -83,7 +83,7 @@
* could transition to INTENT_STARTED.
*
* <p> If any bad transition happened, the state becomse UNKNOWN. The UNKNOWN state
- * could be * accumulated, because during the UNKNOWN state more IntentStarted may
+ * could be accumulated, because during the UNKNOWN state more IntentStarted may
* be triggered. To recover from UNKNOWN to INIT, all the accumualted IntentStarted
* should termniate.
*
@@ -100,7 +100,7 @@
@Override
public void onIntentStarted(@NonNull Intent intent, long timestampNs) {
if (state == State.UNKNOWN) {
- Log.e(TAG, "IntentStarted during UNKNOWN." + intent);
+ Log.wtf(TAG, "IntentStarted during UNKNOWN." + intent);
incAccIntentStartedEvents();
return;
}
@@ -110,32 +110,32 @@
state != State.ACTIVITY_CANCELLED &&
state != State.ACTIVITY_FINISHED &&
state != State.REPORT_FULLY_DRAWN) {
- Log.e(TAG,
+ Log.wtf(TAG,
String.format("Cannot transition from %s to %s", state, State.INTENT_STARTED));
incAccIntentStartedEvents();
incAccIntentStartedEvents();
return;
}
- Log.i(TAG, String.format("Tansition from %s to %s", state, State.INTENT_STARTED));
+ Log.i(TAG, String.format("Transition from %s to %s", state, State.INTENT_STARTED));
state = State.INTENT_STARTED;
}
@Override
public void onIntentFailed() {
if (state == State.UNKNOWN) {
- Log.e(TAG, "IntentFailed during UNKNOWN.");
+ Log.wtf(TAG, "IntentFailed during UNKNOWN.");
decAccIntentStartedEvents();
return;
}
if (state != State.INTENT_STARTED) {
- Log.e(TAG,
+ Log.wtf(TAG,
String.format("Cannot transition from %s to %s", state, State.INTENT_FAILED));
incAccIntentStartedEvents();
return;
}
- Log.i(TAG, String.format("Tansition from %s to %s", state, State.INTENT_FAILED));
+ Log.i(TAG, String.format("Transition from %s to %s", state, State.INTENT_FAILED));
state = State.INTENT_FAILED;
}
@@ -143,11 +143,11 @@
public void onActivityLaunched(@NonNull @ActivityRecordProto byte[] activity,
@Temperature int temperature) {
if (state == State.UNKNOWN) {
- Log.e(TAG, "onActivityLaunched during UNKNOWN.");
+ Log.wtf(TAG, "onActivityLaunched during UNKNOWN.");
return;
}
if (state != State.INTENT_STARTED) {
- Log.e(TAG,
+ Log.wtf(TAG,
String.format("Cannot transition from %s to %s", state, State.ACTIVITY_LAUNCHED));
incAccIntentStartedEvents();
return;
@@ -160,12 +160,12 @@
@Override
public void onActivityLaunchCancelled(@Nullable @ActivityRecordProto byte[] activity) {
if (state == State.UNKNOWN) {
- Log.e(TAG, "onActivityLaunchCancelled during UNKNOWN.");
+ Log.wtf(TAG, "onActivityLaunchCancelled during UNKNOWN.");
decAccIntentStartedEvents();
return;
}
if (state != State.ACTIVITY_LAUNCHED) {
- Log.e(TAG,
+ Log.wtf(TAG,
String.format("Cannot transition from %s to %s", state, State.ACTIVITY_CANCELLED));
incAccIntentStartedEvents();
return;
@@ -179,13 +179,13 @@
public void onActivityLaunchFinished(@NonNull @ActivityRecordProto byte[] activity,
long timestampNs) {
if (state == State.UNKNOWN) {
- Log.e(TAG, "onActivityLaunchFinished during UNKNOWN.");
+ Log.wtf(TAG, "onActivityLaunchFinished during UNKNOWN.");
decAccIntentStartedEvents();
return;
}
if (state != State.ACTIVITY_LAUNCHED) {
- Log.e(TAG,
+ Log.wtf(TAG,
String.format("Cannot transition from %s to %s", state, State.ACTIVITY_FINISHED));
incAccIntentStartedEvents();
return;
@@ -199,7 +199,7 @@
public void onReportFullyDrawn(@NonNull @ActivityRecordProto byte[] activity,
long timestampNs) {
if (state == State.UNKNOWN) {
- Log.e(TAG, "onReportFullyDrawn during UNKNOWN.");
+ Log.wtf(TAG, "onReportFullyDrawn during UNKNOWN.");
return;
}
if (state == State.INIT) {
@@ -207,7 +207,7 @@
}
if (state != State.ACTIVITY_FINISHED) {
- Log.e(TAG,
+ Log.wtf(TAG,
String.format("Cannot transition from %s to %s", state, State.REPORT_FULLY_DRAWN));
return;
}
@@ -230,7 +230,7 @@
private void incAccIntentStartedEvents() {
if (accIntentStartedEvents < 0) {
throw new AssertionError(
- String.format("The number of unknows cannot be negative"));
+ String.format("The number of unknowns cannot be negative"));
}
if (accIntentStartedEvents == 0) {
state = State.UNKNOWN;
@@ -243,7 +243,7 @@
private void decAccIntentStartedEvents() {
if (accIntentStartedEvents <= 0) {
throw new AssertionError(
- String.format("The number of unknows cannot be negative"));
+ String.format("The number of unknowns cannot be negative"));
}
if(accIntentStartedEvents == 1) {
state = State.INIT;
diff --git a/telecomm/TEST_MAPPING b/telecomm/TEST_MAPPING
new file mode 100644
index 0000000..d585666
--- /dev/null
+++ b/telecomm/TEST_MAPPING
@@ -0,0 +1,29 @@
+{
+ "presubmit": [
+ {
+ "name": "TeleServiceTests",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
+ "name": "TelecomUnitTests",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
+ "name": "TelephonyProviderTests",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ]
+}
+
diff --git a/telecomm/java/android/telecom/AudioState.java b/telecomm/java/android/telecom/AudioState.java
index 8b8c86b..ea641f8 100644
--- a/telecomm/java/android/telecom/AudioState.java
+++ b/telecomm/java/android/telecom/AudioState.java
@@ -19,7 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/telecomm/java/android/telecom/Call.java b/telecomm/java/android/telecom/Call.java
index 86ad795..826a89e 100644
--- a/telecomm/java/android/telecom/Call.java
+++ b/telecomm/java/android/telecom/Call.java
@@ -20,12 +20,11 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
-import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import com.android.internal.telecom.IVideoProvider;
@@ -569,6 +568,7 @@
private final Bundle mExtras;
private final Bundle mIntentExtras;
private final long mCreationTimeMillis;
+ private final String mContactDisplayName;
private final @CallDirection int mCallDirection;
private final @Connection.VerificationStatus int mCallerNumberVerificationStatus;
@@ -873,6 +873,17 @@
}
/**
+ * Returns the name of the caller on the remote end, as derived from a
+ * {@link android.provider.ContactsContract} lookup of the call's handle.
+ * @return The name of the caller, or {@code null} if the lookup is not yet complete, if
+ * there's no contacts entry for the caller, or if the {@link InCallService} does
+ * not hold the {@link android.Manifest.permission#READ_CONTACTS} permission.
+ */
+ public @Nullable String getContactDisplayName() {
+ return mContactDisplayName;
+ }
+
+ /**
* Indicates whether the call is an incoming or outgoing call.
* @return The call's direction.
*/
@@ -910,6 +921,7 @@
areBundlesEqual(mExtras, d.mExtras) &&
areBundlesEqual(mIntentExtras, d.mIntentExtras) &&
Objects.equals(mCreationTimeMillis, d.mCreationTimeMillis) &&
+ Objects.equals(mContactDisplayName, d.mContactDisplayName) &&
Objects.equals(mCallDirection, d.mCallDirection) &&
Objects.equals(mCallerNumberVerificationStatus,
d.mCallerNumberVerificationStatus);
@@ -934,6 +946,7 @@
mExtras,
mIntentExtras,
mCreationTimeMillis,
+ mContactDisplayName,
mCallDirection,
mCallerNumberVerificationStatus);
}
@@ -956,6 +969,7 @@
Bundle extras,
Bundle intentExtras,
long creationTimeMillis,
+ String contactDisplayName,
int callDirection,
int callerNumberVerificationStatus) {
mTelecomCallId = telecomCallId;
@@ -974,6 +988,7 @@
mExtras = extras;
mIntentExtras = intentExtras;
mCreationTimeMillis = creationTimeMillis;
+ mContactDisplayName = contactDisplayName;
mCallDirection = callDirection;
mCallerNumberVerificationStatus = callerNumberVerificationStatus;
}
@@ -997,6 +1012,7 @@
parcelableCall.getExtras(),
parcelableCall.getIntentExtras(),
parcelableCall.getCreationTimeMillis(),
+ parcelableCall.getContactDisplayName(),
parcelableCall.getCallDirection(),
parcelableCall.getCallerNumberVerificationStatus());
}
@@ -1446,6 +1462,7 @@
private boolean mChildrenCached;
private String mParentId = null;
+ private String mActiveGenericConferenceChild = null;
private int mState;
private List<String> mCannedTextResponses = null;
private String mCallingPackage;
@@ -1944,6 +1961,20 @@
}
/**
+ * Returns the child {@link Call} in a generic conference that is currently active.
+ * For calls that are not generic conferences, or when the generic conference has more than
+ * 2 children, returns {@code null}.
+ * @see Details#PROPERTY_GENERIC_CONFERENCE
+ * @return The active child call.
+ */
+ public @Nullable Call getGenericConferenceActiveChildCall() {
+ if (mActiveGenericConferenceChild != null) {
+ return mPhone.internalGetCallByTelecomId(mActiveGenericConferenceChild);
+ }
+ return null;
+ }
+
+ /**
* Obtains a list of canned, pre-configured message responses to present to the user as
* ways of rejecting this {@code Call} using via a text message.
*
@@ -2191,6 +2222,13 @@
mChildrenCached = false;
}
+ String activeChildCallId = parcelableCall.getActiveChildCallId();
+ boolean activeChildChanged = !Objects.equals(activeChildCallId,
+ mActiveGenericConferenceChild);
+ if (activeChildChanged) {
+ mActiveGenericConferenceChild = activeChildCallId;
+ }
+
List<String> conferenceableCallIds = parcelableCall.getConferenceableCallIds();
List<Call> conferenceableCalls = new ArrayList<Call>(conferenceableCallIds.size());
for (String otherId : conferenceableCallIds) {
@@ -2250,7 +2288,7 @@
if (parentChanged) {
fireParentChanged(getParent());
}
- if (childrenChanged) {
+ if (childrenChanged || activeChildChanged) {
fireChildrenChanged(getChildren());
}
if (isRttChanged) {
diff --git a/telecomm/java/android/telecom/CallerInfo.java b/telecomm/java/android/telecom/CallerInfo.java
index a5d25e2..fb6f994 100644
--- a/telecomm/java/android/telecom/CallerInfo.java
+++ b/telecomm/java/android/telecom/CallerInfo.java
@@ -17,7 +17,7 @@
package android.telecom;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
@@ -41,8 +41,8 @@
import com.android.i18n.phonenumbers.PhoneNumberUtil;
import com.android.i18n.phonenumbers.Phonenumber.PhoneNumber;
import com.android.i18n.phonenumbers.geocoding.PhoneNumberOfflineGeocoder;
-
import com.android.internal.annotations.VisibleForTesting;
+
import java.util.Locale;
diff --git a/telecomm/java/android/telecom/Connection.java b/telecomm/java/android/telecom/Connection.java
index 0becaf2..f205ec6 100644
--- a/telecomm/java/android/telecom/Connection.java
+++ b/telecomm/java/android/telecom/Connection.java
@@ -21,9 +21,9 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.Notification;
import android.bluetooth.BluetoothDevice;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Intent;
import android.hardware.camera2.CameraManager;
import android.net.Uri;
@@ -502,51 +502,116 @@
//**********************************************************************************************
/**
- * Define IMS Audio Codec
+ * Indicates that the audio codec is currently not specified or is unknown.
*/
- // Current audio codec is NONE
public static final int AUDIO_CODEC_NONE = ImsStreamMediaProfile.AUDIO_QUALITY_NONE; // 0
- // Current audio codec is AMR
+ /**
+ * Adaptive Multi-rate audio codec.
+ */
public static final int AUDIO_CODEC_AMR = ImsStreamMediaProfile.AUDIO_QUALITY_AMR; // 1
- // Current audio codec is AMR_WB
+ /**
+ * Adaptive Multi-rate wideband audio codec.
+ */
public static final int AUDIO_CODEC_AMR_WB = ImsStreamMediaProfile.AUDIO_QUALITY_AMR_WB; // 2
- // Current audio codec is QCELP13K
+ /**
+ * Qualcomm code-excited linear prediction 13 kilobit audio codec.
+ */
public static final int AUDIO_CODEC_QCELP13K = ImsStreamMediaProfile.AUDIO_QUALITY_QCELP13K; //3
- // Current audio codec is EVRC
+ /**
+ * Enhanced Variable Rate Codec. See 3GPP2 C.S0014-A.
+ */
public static final int AUDIO_CODEC_EVRC = ImsStreamMediaProfile.AUDIO_QUALITY_EVRC; // 4
- // Current audio codec is EVRC_B
+ /**
+ * Enhanced Variable Rate Codec B. Commonly used on CDMA networks.
+ */
public static final int AUDIO_CODEC_EVRC_B = ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_B; // 5
- // Current audio codec is EVRC_WB
+ /**
+ * Enhanced Variable Rate Wideband Codec. See RFC5188.
+ */
public static final int AUDIO_CODEC_EVRC_WB = ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_WB; // 6
- // Current audio codec is EVRC_NW
+ /**
+ * Enhanced Variable Rate Narrowband-Wideband Codec.
+ */
public static final int AUDIO_CODEC_EVRC_NW = ImsStreamMediaProfile.AUDIO_QUALITY_EVRC_NW; // 7
- // Current audio codec is GSM_EFR
+ /**
+ * GSM Enhanced Full-Rate audio codec, also known as GSM-EFR, GSM 06.60, or simply EFR.
+ */
public static final int AUDIO_CODEC_GSM_EFR = ImsStreamMediaProfile.AUDIO_QUALITY_GSM_EFR; // 8
- // Current audio codec is GSM_FR
+ /**
+ * GSM Full-Rate audio codec, also known as GSM-FR, GSM 06.10, GSM, or simply FR.
+ */
public static final int AUDIO_CODEC_GSM_FR = ImsStreamMediaProfile.AUDIO_QUALITY_GSM_FR; // 9
- // Current audio codec is GSM_HR
+ /**
+ * GSM Half Rate audio codec.
+ */
public static final int AUDIO_CODEC_GSM_HR = ImsStreamMediaProfile.AUDIO_QUALITY_GSM_HR; // 10
- // Current audio codec is G711U
+ /**
+ * ITU-T G711U audio codec.
+ */
public static final int AUDIO_CODEC_G711U = ImsStreamMediaProfile.AUDIO_QUALITY_G711U; // 11
- // Current audio codec is G723
+ /**
+ * ITU-T G723 audio codec.
+ */
public static final int AUDIO_CODEC_G723 = ImsStreamMediaProfile.AUDIO_QUALITY_G723; // 12
- // Current audio codec is G711A
+ /**
+ * ITU-T G711A audio codec.
+ */
public static final int AUDIO_CODEC_G711A = ImsStreamMediaProfile.AUDIO_QUALITY_G711A; // 13
- // Current audio codec is G722
+ /**
+ * ITU-T G722 audio codec.
+ */
public static final int AUDIO_CODEC_G722 = ImsStreamMediaProfile.AUDIO_QUALITY_G722; // 14
- // Current audio codec is G711AB
+ /**
+ * ITU-T G711AB audio codec.
+ */
public static final int AUDIO_CODEC_G711AB = ImsStreamMediaProfile.AUDIO_QUALITY_G711AB; // 15
- // Current audio codec is G729
+ /**
+ * ITU-T G729 audio codec.
+ */
public static final int AUDIO_CODEC_G729 = ImsStreamMediaProfile.AUDIO_QUALITY_G729; // 16
- // Current audio codec is EVS_NB
+ /**
+ * Enhanced Voice Services Narrowband audio codec. See 3GPP TS 26.441.
+ */
public static final int AUDIO_CODEC_EVS_NB = ImsStreamMediaProfile.AUDIO_QUALITY_EVS_NB; // 17
- // Current audio codec is EVS_WB
+ /**
+ * Enhanced Voice Services Wideband audio codec. See 3GPP TS 26.441.
+ */
public static final int AUDIO_CODEC_EVS_WB = ImsStreamMediaProfile.AUDIO_QUALITY_EVS_WB; // 18
- // Current audio codec is EVS_SWB
+ /**
+ * Enhanced Voice Services Super-Wideband audio codec. See 3GPP TS 26.441.
+ */
public static final int AUDIO_CODEC_EVS_SWB = ImsStreamMediaProfile.AUDIO_QUALITY_EVS_SWB; // 19
- // Current audio codec is EVS_FB
+ /**
+ * Enhanced Voice Services Fullband audio codec. See 3GPP TS 26.441.
+ */
public static final int AUDIO_CODEC_EVS_FB = ImsStreamMediaProfile.AUDIO_QUALITY_EVS_FB; // 20
+ /**@hide*/
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "AUDIO_CODEC_", value = {
+ AUDIO_CODEC_NONE,
+ AUDIO_CODEC_AMR,
+ AUDIO_CODEC_AMR_WB,
+ AUDIO_CODEC_QCELP13K,
+ AUDIO_CODEC_EVRC,
+ AUDIO_CODEC_EVRC_B,
+ AUDIO_CODEC_EVRC_WB,
+ AUDIO_CODEC_EVRC_NW,
+ AUDIO_CODEC_GSM_EFR,
+ AUDIO_CODEC_GSM_FR,
+ AUDIO_CODEC_GSM_HR,
+ AUDIO_CODEC_G711U,
+ AUDIO_CODEC_G723,
+ AUDIO_CODEC_G711A,
+ AUDIO_CODEC_G722,
+ AUDIO_CODEC_G711AB,
+ AUDIO_CODEC_G729,
+ AUDIO_CODEC_EVS_NB,
+ AUDIO_CODEC_EVS_SWB,
+ AUDIO_CODEC_EVS_FB
+ })
+ public @interface AudioCodec {}
+
/**
* Connection extra key used to store the last forwarded number associated with the current
* connection. Used to communicate to the user interface that the connection was forwarded via
@@ -640,10 +705,10 @@
"android.telecom.extra.IS_RTT_AUDIO_PRESENT";
/**
- * The audio codec in use for the current {@link Connection}, if known. Valid values include
- * {@link #AUDIO_CODEC_AMR_WB} and {@link #AUDIO_CODEC_EVS_WB}.
+ * The audio codec in use for the current {@link Connection}, if known. Examples of valid
+ * values include {@link #AUDIO_CODEC_AMR_WB} and {@link #AUDIO_CODEC_EVS_WB}.
*/
- public static final String EXTRA_AUDIO_CODEC =
+ public static final @AudioCodec String EXTRA_AUDIO_CODEC =
"android.telecom.extra.AUDIO_CODEC";
/**
diff --git a/telecomm/java/android/telecom/Log.java b/telecomm/java/android/telecom/Log.java
index 7d4ee76..4f6a9d6 100644
--- a/telecomm/java/android/telecom/Log.java
+++ b/telecomm/java/android/telecom/Log.java
@@ -16,7 +16,7 @@
package android.telecom;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
diff --git a/telecomm/java/android/telecom/ParcelableCall.java b/telecomm/java/android/telecom/ParcelableCall.java
index a234bb0..415a817 100644
--- a/telecomm/java/android/telecom/ParcelableCall.java
+++ b/telecomm/java/android/telecom/ParcelableCall.java
@@ -16,27 +16,286 @@
package android.telecom;
-import android.annotation.UnsupportedAppUsage;
+import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
-import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
import android.telecom.Call.Details.CallDirection;
+import com.android.internal.telecom.IVideoProvider;
+
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import com.android.internal.telecom.IVideoProvider;
-
/**
* Information about a call that is used between InCallService and Telecom.
* @hide
*/
public final class ParcelableCall implements Parcelable {
+
+ public static class ParcelableCallBuilder {
+ private String mId;
+ private int mState;
+ private DisconnectCause mDisconnectCause;
+ private List<String> mCannedSmsResponses;
+ private int mCapabilities;
+ private int mProperties;
+ private int mSupportedAudioRoutes;
+ private long mConnectTimeMillis;
+ private Uri mHandle;
+ private int mHandlePresentation;
+ private String mCallerDisplayName;
+ private int mCallerDisplayNamePresentation;
+ private GatewayInfo mGatewayInfo;
+ private PhoneAccountHandle mAccountHandle;
+ private boolean mIsVideoCallProviderChanged;
+ private IVideoProvider mVideoCallProvider;
+ private boolean mIsRttCallChanged;
+ private ParcelableRttCall mRttCall;
+ private String mParentCallId;
+ private List<String> mChildCallIds;
+ private StatusHints mStatusHints;
+ private int mVideoState;
+ private List<String> mConferenceableCallIds;
+ private Bundle mIntentExtras;
+ private Bundle mExtras;
+ private long mCreationTimeMillis;
+ private int mCallDirection;
+ private int mCallerNumberVerificationStatus;
+ private String mContactDisplayName;
+ private String mActiveChildCallId;
+
+ public ParcelableCallBuilder setId(String id) {
+ mId = id;
+ return this;
+ }
+
+ public ParcelableCallBuilder setState(int state) {
+ mState = state;
+ return this;
+ }
+
+ public ParcelableCallBuilder setDisconnectCause(DisconnectCause disconnectCause) {
+ mDisconnectCause = disconnectCause;
+ return this;
+ }
+
+ public ParcelableCallBuilder setCannedSmsResponses(List<String> cannedSmsResponses) {
+ mCannedSmsResponses = cannedSmsResponses;
+ return this;
+ }
+
+ public ParcelableCallBuilder setCapabilities(int capabilities) {
+ mCapabilities = capabilities;
+ return this;
+ }
+
+ public ParcelableCallBuilder setProperties(int properties) {
+ mProperties = properties;
+ return this;
+ }
+
+ public ParcelableCallBuilder setSupportedAudioRoutes(int supportedAudioRoutes) {
+ mSupportedAudioRoutes = supportedAudioRoutes;
+ return this;
+ }
+
+ public ParcelableCallBuilder setConnectTimeMillis(long connectTimeMillis) {
+ mConnectTimeMillis = connectTimeMillis;
+ return this;
+ }
+
+ public ParcelableCallBuilder setHandle(Uri handle) {
+ mHandle = handle;
+ return this;
+ }
+
+ public ParcelableCallBuilder setHandlePresentation(int handlePresentation) {
+ mHandlePresentation = handlePresentation;
+ return this;
+ }
+
+ public ParcelableCallBuilder setCallerDisplayName(String callerDisplayName) {
+ mCallerDisplayName = callerDisplayName;
+ return this;
+ }
+
+ public ParcelableCallBuilder setCallerDisplayNamePresentation(
+ int callerDisplayNamePresentation) {
+ mCallerDisplayNamePresentation = callerDisplayNamePresentation;
+ return this;
+ }
+
+ public ParcelableCallBuilder setGatewayInfo(GatewayInfo gatewayInfo) {
+ mGatewayInfo = gatewayInfo;
+ return this;
+ }
+
+ public ParcelableCallBuilder setAccountHandle(PhoneAccountHandle accountHandle) {
+ mAccountHandle = accountHandle;
+ return this;
+ }
+
+ public ParcelableCallBuilder setIsVideoCallProviderChanged(
+ boolean isVideoCallProviderChanged) {
+ mIsVideoCallProviderChanged = isVideoCallProviderChanged;
+ return this;
+ }
+
+ public ParcelableCallBuilder setVideoCallProvider(IVideoProvider videoCallProvider) {
+ mVideoCallProvider = videoCallProvider;
+ return this;
+ }
+
+ public ParcelableCallBuilder setIsRttCallChanged(boolean isRttCallChanged) {
+ mIsRttCallChanged = isRttCallChanged;
+ return this;
+ }
+
+ public ParcelableCallBuilder setRttCall(ParcelableRttCall rttCall) {
+ mRttCall = rttCall;
+ return this;
+ }
+
+ public ParcelableCallBuilder setParentCallId(String parentCallId) {
+ mParentCallId = parentCallId;
+ return this;
+ }
+
+ public ParcelableCallBuilder setChildCallIds(List<String> childCallIds) {
+ mChildCallIds = childCallIds;
+ return this;
+ }
+
+ public ParcelableCallBuilder setStatusHints(StatusHints statusHints) {
+ mStatusHints = statusHints;
+ return this;
+ }
+
+ public ParcelableCallBuilder setVideoState(int videoState) {
+ mVideoState = videoState;
+ return this;
+ }
+
+ public ParcelableCallBuilder setConferenceableCallIds(
+ List<String> conferenceableCallIds) {
+ mConferenceableCallIds = conferenceableCallIds;
+ return this;
+ }
+
+ public ParcelableCallBuilder setIntentExtras(Bundle intentExtras) {
+ mIntentExtras = intentExtras;
+ return this;
+ }
+
+ public ParcelableCallBuilder setExtras(Bundle extras) {
+ mExtras = extras;
+ return this;
+ }
+
+ public ParcelableCallBuilder setCreationTimeMillis(long creationTimeMillis) {
+ mCreationTimeMillis = creationTimeMillis;
+ return this;
+ }
+
+ public ParcelableCallBuilder setCallDirection(int callDirection) {
+ mCallDirection = callDirection;
+ return this;
+ }
+
+ public ParcelableCallBuilder setCallerNumberVerificationStatus(
+ int callerNumberVerificationStatus) {
+ mCallerNumberVerificationStatus = callerNumberVerificationStatus;
+ return this;
+ }
+
+ public ParcelableCallBuilder setContactDisplayName(String contactDisplayName) {
+ mContactDisplayName = contactDisplayName;
+ return this;
+ }
+
+ public ParcelableCallBuilder setActiveChildCallId(String activeChildCallId) {
+ mActiveChildCallId = activeChildCallId;
+ return this;
+ }
+
+ public ParcelableCall createParcelableCall() {
+ return new ParcelableCall(
+ mId,
+ mState,
+ mDisconnectCause,
+ mCannedSmsResponses,
+ mCapabilities,
+ mProperties,
+ mSupportedAudioRoutes,
+ mConnectTimeMillis,
+ mHandle,
+ mHandlePresentation,
+ mCallerDisplayName,
+ mCallerDisplayNamePresentation,
+ mGatewayInfo,
+ mAccountHandle,
+ mIsVideoCallProviderChanged,
+ mVideoCallProvider,
+ mIsRttCallChanged,
+ mRttCall,
+ mParentCallId,
+ mChildCallIds,
+ mStatusHints,
+ mVideoState,
+ mConferenceableCallIds,
+ mIntentExtras,
+ mExtras,
+ mCreationTimeMillis,
+ mCallDirection,
+ mCallerNumberVerificationStatus,
+ mContactDisplayName,
+ mActiveChildCallId);
+ }
+
+ public static ParcelableCallBuilder fromParcelableCall(ParcelableCall parcelableCall) {
+ ParcelableCallBuilder newBuilder = new ParcelableCallBuilder();
+ newBuilder.mId = parcelableCall.mId;
+ newBuilder.mState = parcelableCall.mState;
+ newBuilder.mDisconnectCause = parcelableCall.mDisconnectCause;
+ newBuilder.mCannedSmsResponses = parcelableCall.mCannedSmsResponses;
+ newBuilder.mCapabilities = parcelableCall.mCapabilities;
+ newBuilder.mProperties = parcelableCall.mProperties;
+ newBuilder.mSupportedAudioRoutes = parcelableCall.mSupportedAudioRoutes;
+ newBuilder.mConnectTimeMillis = parcelableCall.mConnectTimeMillis;
+ newBuilder.mHandle = parcelableCall.mHandle;
+ newBuilder.mHandlePresentation = parcelableCall.mHandlePresentation;
+ newBuilder.mCallerDisplayName = parcelableCall.mCallerDisplayName;
+ newBuilder.mCallerDisplayNamePresentation =
+ parcelableCall.mCallerDisplayNamePresentation;
+ newBuilder.mGatewayInfo = parcelableCall.mGatewayInfo;
+ newBuilder.mAccountHandle = parcelableCall.mAccountHandle;
+ newBuilder.mIsVideoCallProviderChanged = parcelableCall.mIsVideoCallProviderChanged;
+ newBuilder.mVideoCallProvider = parcelableCall.mVideoCallProvider;
+ newBuilder.mIsRttCallChanged = parcelableCall.mIsRttCallChanged;
+ newBuilder.mRttCall = parcelableCall.mRttCall;
+ newBuilder.mParentCallId = parcelableCall.mParentCallId;
+ newBuilder.mChildCallIds = parcelableCall.mChildCallIds;
+ newBuilder.mStatusHints = parcelableCall.mStatusHints;
+ newBuilder.mVideoState = parcelableCall.mVideoState;
+ newBuilder.mConferenceableCallIds = parcelableCall.mConferenceableCallIds;
+ newBuilder.mIntentExtras = parcelableCall.mIntentExtras;
+ newBuilder.mExtras = parcelableCall.mExtras;
+ newBuilder.mCreationTimeMillis = parcelableCall.mCreationTimeMillis;
+ newBuilder.mCallDirection = parcelableCall.mCallDirection;
+ newBuilder.mCallerNumberVerificationStatus =
+ parcelableCall.mCallerNumberVerificationStatus;
+ newBuilder.mContactDisplayName = parcelableCall.mContactDisplayName;
+ newBuilder.mActiveChildCallId = parcelableCall.mActiveChildCallId;
+ return newBuilder;
+ }
+ }
+
private final String mId;
private final int mState;
private final DisconnectCause mDisconnectCause;
@@ -66,6 +325,8 @@
private final long mCreationTimeMillis;
private final int mCallDirection;
private final int mCallerNumberVerificationStatus;
+ private final String mContactDisplayName;
+ private final String mActiveChildCallId; // Only valid for CDMA conferences
public ParcelableCall(
String id,
@@ -95,7 +356,10 @@
Bundle extras,
long creationTimeMillis,
int callDirection,
- int callerNumberVerificationStatus) {
+ int callerNumberVerificationStatus,
+ String contactDisplayName,
+ String activeChildCallId
+ ) {
mId = id;
mState = state;
mDisconnectCause = disconnectCause;
@@ -124,6 +388,8 @@
mCreationTimeMillis = creationTimeMillis;
mCallDirection = callDirection;
mCallerNumberVerificationStatus = callerNumberVerificationStatus;
+ mContactDisplayName = contactDisplayName;
+ mActiveChildCallId = activeChildCallId;
}
/** The unique ID of the call. */
@@ -333,6 +599,21 @@
return mCallerNumberVerificationStatus;
}
+ /**
+ * @return the name of the remote party as derived from a contacts DB lookup.
+ */
+ public @Nullable String getContactDisplayName() {
+ return mContactDisplayName;
+ }
+
+ /**
+ * @return On a CDMA conference with two participants, returns the ID of the child call that's
+ * currently active.
+ */
+ public @Nullable String getActiveChildCallId() {
+ return mActiveChildCallId;
+ }
+
/** Responsible for creating ParcelableCall objects for deserialized Parcels. */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
public static final @android.annotation.NonNull Parcelable.Creator<ParcelableCall> CREATOR =
@@ -372,35 +653,40 @@
long creationTimeMillis = source.readLong();
int callDirection = source.readInt();
int callerNumberVerificationStatus = source.readInt();
- return new ParcelableCall(
- id,
- state,
- disconnectCause,
- cannedSmsResponses,
- capabilities,
- properties,
- supportedAudioRoutes,
- connectTimeMillis,
- handle,
- handlePresentation,
- callerDisplayName,
- callerDisplayNamePresentation,
- gatewayInfo,
- accountHandle,
- isVideoCallProviderChanged,
- videoCallProvider,
- isRttCallChanged,
- rttCall,
- parentCallId,
- childCallIds,
- statusHints,
- videoState,
- conferenceableCallIds,
- intentExtras,
- extras,
- creationTimeMillis,
- callDirection,
- callerNumberVerificationStatus);
+ String contactDisplayName = source.readString();
+ String activeChildCallId = source.readString();
+ return new ParcelableCallBuilder()
+ .setId(id)
+ .setState(state)
+ .setDisconnectCause(disconnectCause)
+ .setCannedSmsResponses(cannedSmsResponses)
+ .setCapabilities(capabilities)
+ .setProperties(properties)
+ .setSupportedAudioRoutes(supportedAudioRoutes)
+ .setConnectTimeMillis(connectTimeMillis)
+ .setHandle(handle)
+ .setHandlePresentation(handlePresentation)
+ .setCallerDisplayName(callerDisplayName)
+ .setCallerDisplayNamePresentation(callerDisplayNamePresentation)
+ .setGatewayInfo(gatewayInfo)
+ .setAccountHandle(accountHandle)
+ .setIsVideoCallProviderChanged(isVideoCallProviderChanged)
+ .setVideoCallProvider(videoCallProvider)
+ .setIsRttCallChanged(isRttCallChanged)
+ .setRttCall(rttCall)
+ .setParentCallId(parentCallId)
+ .setChildCallIds(childCallIds)
+ .setStatusHints(statusHints)
+ .setVideoState(videoState)
+ .setConferenceableCallIds(conferenceableCallIds)
+ .setIntentExtras(intentExtras)
+ .setExtras(extras)
+ .setCreationTimeMillis(creationTimeMillis)
+ .setCallDirection(callDirection)
+ .setCallerNumberVerificationStatus(callerNumberVerificationStatus)
+ .setContactDisplayName(contactDisplayName)
+ .setActiveChildCallId(activeChildCallId)
+ .createParcelableCall();
}
@Override
@@ -447,6 +733,8 @@
destination.writeLong(mCreationTimeMillis);
destination.writeInt(mCallDirection);
destination.writeInt(mCallerNumberVerificationStatus);
+ destination.writeString(mContactDisplayName);
+ destination.writeString(mActiveChildCallId);
}
@Override
diff --git a/telecomm/java/android/telecom/Phone.java b/telecomm/java/android/telecom/Phone.java
index 61a639a1..a427ed6 100644
--- a/telecomm/java/android/telecom/Phone.java
+++ b/telecomm/java/android/telecom/Phone.java
@@ -17,8 +17,8 @@
package android.telecom;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
import android.bluetooth.BluetoothDevice;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Bundle;
import android.util.ArrayMap;
diff --git a/telecomm/java/android/telecom/PhoneAccountHandle.java b/telecomm/java/android/telecom/PhoneAccountHandle.java
index eb568e0..e1bcb5f 100644
--- a/telecomm/java/android/telecom/PhoneAccountHandle.java
+++ b/telecomm/java/android/telecom/PhoneAccountHandle.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.os.Build;
import android.os.Parcel;
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index af3c55a..c4c1e21 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -26,7 +26,7 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -49,6 +49,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
import java.util.concurrent.Executor;
/**
@@ -882,7 +883,8 @@
*/
public TelecomManager(Context context, ITelecomService telecomServiceImpl) {
Context appContext = context.getApplicationContext();
- if (appContext != null) {
+ if (appContext != null && Objects.equals(context.getFeatureId(),
+ appContext.getFeatureId())) {
mContext = appContext;
} else {
mContext = context;
@@ -916,7 +918,7 @@
try {
if (isServiceConnected()) {
return getTelecomService().getDefaultOutgoingPhoneAccount(uriScheme,
- mContext.getOpPackageName());
+ mContext.getOpPackageName(), mContext.getFeatureId());
}
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelecomService#getDefaultOutgoingPhoneAccount", e);
@@ -1113,7 +1115,8 @@
public List<PhoneAccountHandle> getSelfManagedPhoneAccounts() {
try {
if (isServiceConnected()) {
- return getTelecomService().getSelfManagedPhoneAccounts(mContext.getOpPackageName());
+ return getTelecomService().getSelfManagedPhoneAccounts(mContext.getOpPackageName(),
+ mContext.getFeatureId());
}
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelecomService#getSelfManagedPhoneAccounts()", e);
@@ -1138,8 +1141,8 @@
boolean includeDisabledAccounts) {
try {
if (isServiceConnected()) {
- return getTelecomService().getCallCapablePhoneAccounts(
- includeDisabledAccounts, mContext.getOpPackageName());
+ return getTelecomService().getCallCapablePhoneAccounts(includeDisabledAccounts,
+ mContext.getOpPackageName(), mContext.getFeatureId());
}
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelecomService#getCallCapablePhoneAccounts(" +
@@ -1442,7 +1445,7 @@
try {
if (isServiceConnected()) {
return getTelecomService().isVoiceMailNumber(accountHandle, number,
- mContext.getOpPackageName());
+ mContext.getOpPackageName(), mContext.getFeatureId());
}
} catch (RemoteException e) {
Log.e(TAG, "RemoteException calling ITelecomService#isVoiceMailNumber.", e);
@@ -1464,7 +1467,7 @@
try {
if (isServiceConnected()) {
return getTelecomService().getVoiceMailNumber(accountHandle,
- mContext.getOpPackageName());
+ mContext.getOpPackageName(), mContext.getFeatureId());
}
} catch (RemoteException e) {
Log.e(TAG, "RemoteException calling ITelecomService#hasVoiceMailNumber.", e);
@@ -1485,7 +1488,7 @@
try {
if (isServiceConnected()) {
return getTelecomService().getLine1Number(accountHandle,
- mContext.getOpPackageName());
+ mContext.getOpPackageName(), mContext.getFeatureId());
}
} catch (RemoteException e) {
Log.e(TAG, "RemoteException calling ITelecomService#getLine1Number.", e);
@@ -1506,7 +1509,8 @@
public boolean isInCall() {
try {
if (isServiceConnected()) {
- return getTelecomService().isInCall(mContext.getOpPackageName());
+ return getTelecomService().isInCall(mContext.getOpPackageName(),
+ mContext.getFeatureId());
}
} catch (RemoteException e) {
Log.e(TAG, "RemoteException calling isInCall().", e);
@@ -1531,7 +1535,8 @@
public boolean isInManagedCall() {
try {
if (isServiceConnected()) {
- return getTelecomService().isInManagedCall(mContext.getOpPackageName());
+ return getTelecomService().isInManagedCall(mContext.getOpPackageName(),
+ mContext.getFeatureId());
}
} catch (RemoteException e) {
Log.e(TAG, "RemoteException calling isInManagedCall().", e);
@@ -1711,7 +1716,8 @@
public boolean isTtySupported() {
try {
if (isServiceConnected()) {
- return getTelecomService().isTtySupported(mContext.getOpPackageName());
+ return getTelecomService().isTtySupported(mContext.getOpPackageName(),
+ mContext.getFeatureId());
}
} catch (RemoteException e) {
Log.e(TAG, "RemoteException attempting to get TTY supported state.", e);
@@ -1735,7 +1741,8 @@
public @TtyMode int getCurrentTtyMode() {
try {
if (isServiceConnected()) {
- return getTelecomService().getCurrentTtyMode(mContext.getOpPackageName());
+ return getTelecomService().getCurrentTtyMode(mContext.getOpPackageName(),
+ mContext.getFeatureId());
}
} catch (RemoteException e) {
Log.e(TAG, "RemoteException attempting to get the current TTY mode.", e);
@@ -1925,7 +1932,8 @@
ITelecomService service = getTelecomService();
if (service != null) {
try {
- service.showInCallScreen(showDialpad, mContext.getOpPackageName());
+ service.showInCallScreen(showDialpad, mContext.getOpPackageName(),
+ mContext.getFeatureId());
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelecomService#showCallScreen", e);
}
@@ -1988,7 +1996,7 @@
}
try {
service.placeCall(address, extras == null ? new Bundle() : extras,
- mContext.getOpPackageName());
+ mContext.getOpPackageName(), mContext.getFeatureId());
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelecomService#placeCall", e);
}
diff --git a/telecomm/java/android/telecom/VideoCallImpl.java b/telecomm/java/android/telecom/VideoCallImpl.java
index 4a1aa0a..109e7f8 100644
--- a/telecomm/java/android/telecom/VideoCallImpl.java
+++ b/telecomm/java/android/telecom/VideoCallImpl.java
@@ -16,7 +16,7 @@
package android.telecom;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
diff --git a/telecomm/java/android/telecom/VideoProfile.java b/telecomm/java/android/telecom/VideoProfile.java
index 64e6ca3..4197f3c 100644
--- a/telecomm/java/android/telecom/VideoProfile.java
+++ b/telecomm/java/android/telecom/VideoProfile.java
@@ -19,7 +19,6 @@
import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.annotation.IntRange;
-import android.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
index 204c37e..c54da6b 100644
--- a/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
+++ b/telecomm/java/com/android/internal/telecom/ITelecomService.aidl
@@ -35,12 +35,13 @@
*
* @param showDialpad if true, make the dialpad visible initially.
*/
- void showInCallScreen(boolean showDialpad, String callingPackage);
+ void showInCallScreen(boolean showDialpad, String callingPackage, String callingFeatureId);
/**
* @see TelecomServiceImpl#getDefaultOutgoingPhoneAccount
*/
- PhoneAccountHandle getDefaultOutgoingPhoneAccount(in String uriScheme, String callingPackage);
+ PhoneAccountHandle getDefaultOutgoingPhoneAccount(in String uriScheme, String callingPackage,
+ String callingFeatureId);
/**
* @see TelecomServiceImpl#getUserSelectedOutgoingPhoneAccount
@@ -56,12 +57,13 @@
* @see TelecomServiceImpl#getCallCapablePhoneAccounts
*/
List<PhoneAccountHandle> getCallCapablePhoneAccounts(
- boolean includeDisabledAccounts, String callingPackage);
+ boolean includeDisabledAccounts, String callingPackage, String callingFeatureId);
/**
* @see TelecomServiceImpl#getSelfManagedPhoneAccounts
*/
- List<PhoneAccountHandle> getSelfManagedPhoneAccounts(String callingPackage);
+ List<PhoneAccountHandle> getSelfManagedPhoneAccounts(String callingPackage,
+ String callingFeatureId);
/**
* @see TelecomManager#getPhoneAccountsSupportingScheme
@@ -123,17 +125,19 @@
* @see TelecomServiceImpl#isVoiceMailNumber
*/
boolean isVoiceMailNumber(in PhoneAccountHandle accountHandle, String number,
- String callingPackage);
+ String callingPackage, String callingFeatureId);
/**
* @see TelecomServiceImpl#getVoiceMailNumber
*/
- String getVoiceMailNumber(in PhoneAccountHandle accountHandle, String callingPackage);
+ String getVoiceMailNumber(in PhoneAccountHandle accountHandle, String callingPackage,
+ String callingFeatureId);
/**
* @see TelecomServiceImpl#getLine1Number
*/
- String getLine1Number(in PhoneAccountHandle accountHandle, String callingPackage);
+ String getLine1Number(in PhoneAccountHandle accountHandle, String callingPackage,
+ String callingFeatureId);
/**
* @see TelecomServiceImpl#getDefaultPhoneApp
@@ -172,12 +176,12 @@
/**
* @see TelecomServiceImpl#isInCall
*/
- boolean isInCall(String callingPackage);
+ boolean isInCall(String callingPackage, String callingFeatureId);
/**
* @see TelecomServiceImpl#isInManagedCall
*/
- boolean isInManagedCall(String callingPackage);
+ boolean isInManagedCall(String callingPackage, String callingFeatureId);
/**
* @see TelecomServiceImpl#isRinging
@@ -229,12 +233,12 @@
/**
* @see TelecomServiceImpl#isTtySupported
*/
- boolean isTtySupported(String callingPackage);
+ boolean isTtySupported(String callingPackage, String callingFeatureId);
/**
* @see TelecomServiceImpl#getCurrentTtyMode
*/
- int getCurrentTtyMode(String callingPackage);
+ int getCurrentTtyMode(String callingPackage, String callingFeatureId);
/**
* @see TelecomServiceImpl#addNewIncomingCall
@@ -249,7 +253,7 @@
/**
* @see TelecomServiceImpl#placeCall
*/
- void placeCall(in Uri handle, in Bundle extras, String callingPackage);
+ void placeCall(in Uri handle, in Bundle extras, String callingPackage, String callingFeatureId);
/**
* @see TelecomServiceImpl#enablePhoneAccount
diff --git a/telephony/OWNERS b/telephony/OWNERS
index 2a6e8de..58a7ea0 100644
--- a/telephony/OWNERS
+++ b/telephony/OWNERS
@@ -14,4 +14,5 @@
refuhoo@google.com
paulye@google.com
nazaninb@google.com
-sarahchin@google.com
\ No newline at end of file
+sarahchin@google.com
+dbright@google.com
diff --git a/telephony/TEST_MAPPING b/telephony/TEST_MAPPING
new file mode 100644
index 0000000..d585666
--- /dev/null
+++ b/telephony/TEST_MAPPING
@@ -0,0 +1,29 @@
+{
+ "presubmit": [
+ {
+ "name": "TeleServiceTests",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
+ "name": "TelecomUnitTests",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
+ "name": "TelephonyProviderTests",
+ "options": [
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ }
+ ]
+}
+
diff --git a/telephony/java/android/telephony/LocationAccessPolicy.java b/telephony/common/android/telephony/LocationAccessPolicy.java
similarity index 97%
rename from telephony/java/android/telephony/LocationAccessPolicy.java
rename to telephony/common/android/telephony/LocationAccessPolicy.java
index 95aa101..f39981f 100644
--- a/telephony/java/android/telephony/LocationAccessPolicy.java
+++ b/telephony/common/android/telephony/LocationAccessPolicy.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2017 The Android Open Source Project
+ * 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.
@@ -11,7 +11,7 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
*/
package android.telephony;
@@ -60,6 +60,7 @@
DENIED_HARD,
}
+ /** Data structure for location permission query */
public static class LocationPermissionQuery {
public final String callingPackage;
public final String callingFeatureId;
@@ -83,6 +84,7 @@
this.method = method;
}
+ /** Builder for LocationPermissionQuery */
public static class Builder {
private String mCallingPackage;
private String mCallingFeatureId;
@@ -161,6 +163,7 @@
return this;
}
+ /** build LocationPermissionQuery */
public LocationPermissionQuery build() {
return new LocationPermissionQuery(mCallingPackage, mCallingFeatureId,
mCallingUid, mCallingPid, mMinSdkVersionForCoarse, mMinSdkVersionForFine,
@@ -258,6 +261,7 @@
}
}
+ /** Check if location permissions have been granted */
public static LocationPermissionResult checkLocationPermission(
Context context, LocationPermissionQuery query) {
// Always allow the phone process and system server to access location. This avoid
diff --git a/telephony/java/com/android/internal/telephony/CarrierAppUtils.java b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
similarity index 76%
rename from telephony/java/com/android/internal/telephony/CarrierAppUtils.java
rename to telephony/common/com/android/internal/telephony/CarrierAppUtils.java
index 2dc6ae3..9bc534c 100644
--- a/telephony/java/com/android/internal/telephony/CarrierAppUtils.java
+++ b/telephony/common/com/android/internal/telephony/CarrierAppUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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.
@@ -11,24 +11,26 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
*/
package com.android.internal.telephony;
import android.annotation.Nullable;
import android.content.ContentResolver;
+import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.os.RemoteException;
+import android.os.UserHandle;
import android.permission.IPermissionManager;
import android.provider.Settings;
-import android.telephony.Rlog;
import android.telephony.TelephonyManager;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.Log;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
@@ -74,17 +76,18 @@
* system startup prior to any application running, as well as any time the set of carrier
* privileged apps may have changed.
*/
- public synchronized static void disableCarrierAppsUntilPrivileged(String callingPackage,
+ public static synchronized void disableCarrierAppsUntilPrivileged(String callingPackage,
IPackageManager packageManager, IPermissionManager permissionManager,
- TelephonyManager telephonyManager, ContentResolver contentResolver, int userId) {
+ TelephonyManager telephonyManager, int userId, Context context) {
if (DEBUG) {
- Rlog.d(TAG, "disableCarrierAppsUntilPrivileged");
+ Log.d(TAG, "disableCarrierAppsUntilPrivileged");
}
SystemConfig config = SystemConfig.getInstance();
ArraySet<String> systemCarrierAppsDisabledUntilUsed =
config.getDisabledUntilUsedPreinstalledCarrierApps();
ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed =
config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
+ ContentResolver contentResolver = getContentResolverForUser(context, userId);
disableCarrierAppsUntilPrivileged(callingPackage, packageManager, permissionManager,
telephonyManager, contentResolver, userId, systemCarrierAppsDisabledUntilUsed,
systemCarrierAssociatedAppsDisabledUntilUsed);
@@ -101,11 +104,11 @@
* broadcasts. The app will continue to run (briefly) after being disabled, before the Package
* Manager can kill it, and this can lead to crashes as the app is in an unexpected state.
*/
- public synchronized static void disableCarrierAppsUntilPrivileged(String callingPackage,
- IPackageManager packageManager, IPermissionManager permissionManager,
- ContentResolver contentResolver, int userId) {
+ public static synchronized void disableCarrierAppsUntilPrivileged(String callingPackage,
+ IPackageManager packageManager, IPermissionManager permissionManager, int userId,
+ Context context) {
if (DEBUG) {
- Rlog.d(TAG, "disableCarrierAppsUntilPrivileged");
+ Log.d(TAG, "disableCarrierAppsUntilPrivileged");
}
SystemConfig config = SystemConfig.getInstance();
ArraySet<String> systemCarrierAppsDisabledUntilUsed =
@@ -114,11 +117,22 @@
ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed =
config.getDisabledUntilUsedPreinstalledCarrierAssociatedApps();
+ ContentResolver contentResolver = getContentResolverForUser(context, userId);
disableCarrierAppsUntilPrivileged(callingPackage, packageManager, permissionManager,
null /* telephonyManager */, contentResolver, userId,
systemCarrierAppsDisabledUntilUsed, systemCarrierAssociatedAppsDisabledUntilUsed);
}
+ private static ContentResolver getContentResolverForUser(Context context, int userId) {
+ Context userContext = context.createContextAsUser(UserHandle.getUserHandleForUid(userId),
+ 0);
+ return userContext.getContentResolver();
+ }
+
+ /**
+ * Disable carrier apps until they are privileged
+ * Must be public b/c framework unit tests can't access package-private methods.
+ */
// Must be public b/c framework unit tests can't access package-private methods.
@VisibleForTesting
public static void disableCarrierAppsUntilPrivileged(String callingPackage,
@@ -127,8 +141,8 @@
ContentResolver contentResolver, int userId,
ArraySet<String> systemCarrierAppsDisabledUntilUsed,
ArrayMap<String, List<String>> systemCarrierAssociatedAppsDisabledUntilUsed) {
- List<ApplicationInfo> candidates = getDefaultCarrierAppCandidatesHelper(packageManager,
- userId, systemCarrierAppsDisabledUntilUsed);
+ List<ApplicationInfo> candidates = getDefaultNotUpdatedCarrierAppCandidatesHelper(
+ packageManager, userId, systemCarrierAppsDisabledUntilUsed);
if (candidates == null || candidates.isEmpty()) {
return;
}
@@ -139,9 +153,8 @@
systemCarrierAssociatedAppsDisabledUntilUsed);
List<String> enabledCarrierPackages = new ArrayList<>();
-
- boolean hasRunOnce = Settings.Secure.getIntForUser(
- contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 0, userId) == 1;
+ boolean hasRunOnce = Settings.Secure.getInt(contentResolver,
+ Settings.Secure.CARRIER_APPS_HANDLED, 0) == 1;
try {
for (ApplicationInfo ai : candidates) {
@@ -165,16 +178,17 @@
}
}
+ int enabledSetting = packageManager.getApplicationEnabledSetting(packageName,
+ userId);
if (hasPrivileges) {
// Only update enabled state for the app on /system. Once it has been
// updated we shouldn't touch it.
- if (!ai.isUpdatedSystemApp()
- && (ai.enabledSetting ==
- PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
- || ai.enabledSetting ==
- PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
- || (ai.flags & ApplicationInfo.FLAG_INSTALLED) == 0)) {
- Rlog.i(TAG, "Update state(" + packageName + "): ENABLED for user "
+ if (enabledSetting
+ == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
+ || enabledSetting
+ == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
+ || (ai.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
+ Log.i(TAG, "Update state(" + packageName + "): ENABLED for user "
+ userId);
packageManager.setSystemAppInstallState(
packageName,
@@ -191,13 +205,16 @@
// Also enable any associated apps for this carrier app.
if (associatedAppList != null) {
for (ApplicationInfo associatedApp : associatedAppList) {
- if (associatedApp.enabledSetting ==
- PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
- || associatedApp.enabledSetting ==
- PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
+ int associatedAppEnabledSetting =
+ packageManager.getApplicationEnabledSetting(
+ associatedApp.packageName, userId);
+ if (associatedAppEnabledSetting
+ == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
+ || associatedAppEnabledSetting
+ == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
|| (associatedApp.flags
& ApplicationInfo.FLAG_INSTALLED) == 0) {
- Rlog.i(TAG, "Update associated state(" + associatedApp.packageName
+ Log.i(TAG, "Update associated state(" + associatedApp.packageName
+ "): ENABLED for user " + userId);
packageManager.setSystemAppInstallState(
associatedApp.packageName,
@@ -218,11 +235,10 @@
} else { // No carrier privileges
// Only update enabled state for the app on /system. Once it has been
// updated we shouldn't touch it.
- if (!ai.isUpdatedSystemApp()
- && ai.enabledSetting ==
- PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
+ if (enabledSetting
+ == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
&& (ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0) {
- Rlog.i(TAG, "Update state(" + packageName
+ Log.i(TAG, "Update state(" + packageName
+ "): DISABLED_UNTIL_USED for user " + userId);
packageManager.setSystemAppInstallState(
packageName,
@@ -236,11 +252,14 @@
if (!hasRunOnce) {
if (associatedAppList != null) {
for (ApplicationInfo associatedApp : associatedAppList) {
- if (associatedApp.enabledSetting
+ int associatedAppEnabledSetting =
+ packageManager.getApplicationEnabledSetting(
+ associatedApp.packageName, userId);
+ if (associatedAppEnabledSetting
== PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
&& (associatedApp.flags
& ApplicationInfo.FLAG_INSTALLED) != 0) {
- Rlog.i(TAG,
+ Log.i(TAG,
"Update associated state(" + associatedApp.packageName
+ "): DISABLED_UNTIL_USED for user " + userId);
packageManager.setSystemAppInstallState(
@@ -256,8 +275,7 @@
// Mark the execution so we do not disable apps again.
if (!hasRunOnce) {
- Settings.Secure.putIntForUser(
- contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 1, userId);
+ Settings.Secure.putInt(contentResolver, Settings.Secure.CARRIER_APPS_HANDLED, 1);
}
if (!enabledCarrierPackages.isEmpty()) {
@@ -268,7 +286,7 @@
permissionManager.grantDefaultPermissionsToEnabledCarrierApps(packageNames, userId);
}
} catch (RemoteException e) {
- Rlog.w(TAG, "Could not reach PackageManager", e);
+ Log.w(TAG, "Could not reach PackageManager", e);
}
}
@@ -294,8 +312,8 @@
ApplicationInfo ai = candidates.get(i);
String packageName = ai.packageName;
boolean hasPrivileges =
- telephonyManager.checkCarrierPrivilegesForPackageAnyPhone(packageName) ==
- TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
+ telephonyManager.checkCarrierPrivilegesForPackageAnyPhone(packageName)
+ == TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
if (!hasPrivileges) {
candidates.remove(i);
}
@@ -348,6 +366,31 @@
return apps;
}
+ private static List<ApplicationInfo> getDefaultNotUpdatedCarrierAppCandidatesHelper(
+ IPackageManager packageManager,
+ int userId,
+ ArraySet<String> systemCarrierAppsDisabledUntilUsed) {
+ if (systemCarrierAppsDisabledUntilUsed == null) {
+ return null;
+ }
+
+ int size = systemCarrierAppsDisabledUntilUsed.size();
+ if (size == 0) {
+ return null;
+ }
+
+ List<ApplicationInfo> apps = new ArrayList<>(size);
+ for (int i = 0; i < size; i++) {
+ String packageName = systemCarrierAppsDisabledUntilUsed.valueAt(i);
+ ApplicationInfo ai =
+ getApplicationInfoIfNotUpdatedSystemApp(packageManager, userId, packageName);
+ if (ai != null) {
+ apps.add(ai);
+ }
+ }
+ return apps;
+ }
+
private static Map<String, List<ApplicationInfo>> getDefaultCarrierAssociatedAppsHelper(
IPackageManager packageManager,
int userId,
@@ -360,11 +403,11 @@
systemCarrierAssociatedAppsDisabledUntilUsed.valueAt(i);
for (int j = 0; j < associatedAppPackages.size(); j++) {
ApplicationInfo ai =
- getApplicationInfoIfSystemApp(
+ getApplicationInfoIfNotUpdatedSystemApp(
packageManager, userId, associatedAppPackages.get(j));
// Only update enabled state for the app on /system. Once it has been updated we
// shouldn't touch it.
- if (ai != null && !ai.isUpdatedSystemApp()) {
+ if (ai != null) {
List<ApplicationInfo> appList = associatedApps.get(carrierAppPackage);
if (appList == null) {
appList = new ArrayList<>();
@@ -378,6 +421,26 @@
}
@Nullable
+ private static ApplicationInfo getApplicationInfoIfNotUpdatedSystemApp(
+ IPackageManager packageManager,
+ int userId,
+ String packageName) {
+ try {
+ ApplicationInfo ai = packageManager.getApplicationInfo(packageName,
+ PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
+ | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+ | PackageManager.MATCH_SYSTEM_ONLY
+ | PackageManager.MATCH_FACTORY_ONLY, userId);
+ if (ai != null) {
+ return ai;
+ }
+ } catch (RemoteException e) {
+ Log.w(TAG, "Could not reach PackageManager", e);
+ }
+ return null;
+ }
+
+ @Nullable
private static ApplicationInfo getApplicationInfoIfSystemApp(
IPackageManager packageManager,
int userId,
@@ -385,12 +448,13 @@
try {
ApplicationInfo ai = packageManager.getApplicationInfo(packageName,
PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS
- | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS, userId);
- if (ai != null && ai.isSystemApp()) {
+ | PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS
+ | PackageManager.MATCH_SYSTEM_ONLY, userId);
+ if (ai != null) {
return ai;
}
} catch (RemoteException e) {
- Rlog.w(TAG, "Could not reach PackageManager", e);
+ Log.w(TAG, "Could not reach PackageManager", e);
}
return null;
}
diff --git a/telephony/common/com/android/internal/telephony/EncodeException.java b/telephony/common/com/android/internal/telephony/EncodeException.java
index cdc853e..bb723a0 100644
--- a/telephony/common/com/android/internal/telephony/EncodeException.java
+++ b/telephony/common/com/android/internal/telephony/EncodeException.java
@@ -16,7 +16,7 @@
package com.android.internal.telephony;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* {@hide}
diff --git a/telephony/common/com/android/internal/telephony/GsmAlphabet.java b/telephony/common/com/android/internal/telephony/GsmAlphabet.java
index 5fb4e90..5c53f7e 100644
--- a/telephony/common/com/android/internal/telephony/GsmAlphabet.java
+++ b/telephony/common/com/android/internal/telephony/GsmAlphabet.java
@@ -16,10 +16,10 @@
package com.android.internal.telephony;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.Resources;
import android.os.Build;
-import android.telephony.Rlog;
+import android.util.Log;
import android.text.TextUtils;
import android.util.SparseIntArray;
@@ -498,11 +498,11 @@
StringBuilder ret = new StringBuilder(lengthSeptets);
if (languageTable < 0 || languageTable > sLanguageTables.length) {
- Rlog.w(TAG, "unknown language table " + languageTable + ", using default");
+ Log.w(TAG, "unknown language table " + languageTable + ", using default");
languageTable = 0;
}
if (shiftTable < 0 || shiftTable > sLanguageShiftTables.length) {
- Rlog.w(TAG, "unknown single shift table " + shiftTable + ", using default");
+ Log.w(TAG, "unknown single shift table " + shiftTable + ", using default");
shiftTable = 0;
}
@@ -512,11 +512,11 @@
String shiftTableToChar = sLanguageShiftTables[shiftTable];
if (languageTableToChar.isEmpty()) {
- Rlog.w(TAG, "no language table for code " + languageTable + ", using default");
+ Log.w(TAG, "no language table for code " + languageTable + ", using default");
languageTableToChar = sLanguageTables[0];
}
if (shiftTableToChar.isEmpty()) {
- Rlog.w(TAG, "no single shift table for code " + shiftTable + ", using default");
+ Log.w(TAG, "no single shift table for code " + shiftTable + ", using default");
shiftTableToChar = sLanguageShiftTables[0];
}
@@ -556,7 +556,7 @@
}
}
} catch (RuntimeException ex) {
- Rlog.e(TAG, "Error GSM 7 bit packed: ", ex);
+ Log.e(TAG, "Error GSM 7 bit packed: ", ex);
return null;
}
@@ -813,7 +813,7 @@
for (int i = 0; i < sz; i++) {
char c = s.charAt(i);
if (c == GSM_EXTENDED_ESCAPE) {
- Rlog.w(TAG, "countGsmSeptets() string contains Escape character, skipping.");
+ Log.w(TAG, "countGsmSeptets() string contains Escape character, skipping.");
continue;
}
if (charToLanguageTable.get(c, -1) != -1) {
@@ -892,7 +892,7 @@
for (int i = 0; i < sz && !lpcList.isEmpty(); i++) {
char c = s.charAt(i);
if (c == GSM_EXTENDED_ESCAPE) {
- Rlog.w(TAG, "countGsmSeptets() string contains Escape character, ignoring!");
+ Log.w(TAG, "countGsmSeptets() string contains Escape character, ignoring!");
continue;
}
// iterate through enabled locking shift tables
@@ -1496,7 +1496,7 @@
int numTables = sLanguageTables.length;
int numShiftTables = sLanguageShiftTables.length;
if (numTables != numShiftTables) {
- Rlog.e(TAG, "Error: language tables array length " + numTables +
+ Log.e(TAG, "Error: language tables array length " + numTables +
" != shift tables array length " + numShiftTables);
}
@@ -1506,7 +1506,7 @@
int tableLen = table.length();
if (tableLen != 0 && tableLen != 128) {
- Rlog.e(TAG, "Error: language tables index " + i +
+ Log.e(TAG, "Error: language tables index " + i +
" length " + tableLen + " (expected 128 or 0)");
}
@@ -1524,7 +1524,7 @@
int shiftTableLen = shiftTable.length();
if (shiftTableLen != 0 && shiftTableLen != 128) {
- Rlog.e(TAG, "Error: language shift tables index " + i +
+ Log.e(TAG, "Error: language shift tables index " + i +
" length " + shiftTableLen + " (expected 128 or 0)");
}
diff --git a/telephony/common/com/android/internal/telephony/HbpcdUtils.java b/telephony/common/com/android/internal/telephony/HbpcdUtils.java
index 2f31942..714f5a6 100644
--- a/telephony/common/com/android/internal/telephony/HbpcdUtils.java
+++ b/telephony/common/com/android/internal/telephony/HbpcdUtils.java
@@ -19,7 +19,7 @@
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
-import android.telephony.Rlog;
+import android.util.Log;
import com.android.internal.telephony.HbpcdLookup.ArbitraryMccSidMatch;
import com.android.internal.telephony.HbpcdLookup.MccIdd;
@@ -54,16 +54,16 @@
if (c2 != null) {
int c2Counter = c2.getCount();
if (DBG) {
- Rlog.d(LOG_TAG, "Query unresolved arbitrary table, entries are " + c2Counter);
+ Log.d(LOG_TAG, "Query unresolved arbitrary table, entries are " + c2Counter);
}
if (c2Counter == 1) {
if (DBG) {
- Rlog.d(LOG_TAG, "Query Unresolved arbitrary returned the cursor " + c2);
+ Log.d(LOG_TAG, "Query Unresolved arbitrary returned the cursor " + c2);
}
c2.moveToFirst();
tmpMcc = c2.getInt(0);
if (DBG) {
- Rlog.d(LOG_TAG, "MCC found in arbitrary_mcc_sid_match: " + tmpMcc);
+ Log.d(LOG_TAG, "MCC found in arbitrary_mcc_sid_match: " + tmpMcc);
}
c2.close();
return tmpMcc;
@@ -85,18 +85,18 @@
int c3Counter = c3.getCount();
if (c3Counter > 0) {
if (c3Counter > 1) {
- Rlog.w(LOG_TAG, "something wrong, get more results for 1 conflict SID: " + c3);
+ Log.w(LOG_TAG, "something wrong, get more results for 1 conflict SID: " + c3);
}
- if (DBG) Rlog.d(LOG_TAG, "Query conflict sid returned the cursor " + c3);
+ if (DBG) Log.d(LOG_TAG, "Query conflict sid returned the cursor " + c3);
c3.moveToFirst();
tmpMcc = c3.getInt(0);
if (DBG) {
- Rlog.d(LOG_TAG, "MCC found in mcc_lookup_table. Return tmpMcc = " + tmpMcc);
+ Log.d(LOG_TAG, "MCC found in mcc_lookup_table. Return tmpMcc = " + tmpMcc);
}
if (!isNitzTimeZone) {
// time zone is not accurate, it may get wrong mcc, ignore it.
if (DBG) {
- Rlog.d(LOG_TAG, "time zone is not accurate, mcc may be " + tmpMcc);
+ Log.d(LOG_TAG, "time zone is not accurate, mcc may be " + tmpMcc);
}
tmpMcc = 0;
}
@@ -115,18 +115,18 @@
null, null);
if (c5 != null) {
if (c5.getCount() > 0) {
- if (DBG) Rlog.d(LOG_TAG, "Query Range returned the cursor " + c5);
+ if (DBG) Log.d(LOG_TAG, "Query Range returned the cursor " + c5);
c5.moveToFirst();
tmpMcc = c5.getInt(0);
- if (DBG) Rlog.d(LOG_TAG, "SID found in mcc_sid_range. Return tmpMcc = " + tmpMcc);
+ if (DBG) Log.d(LOG_TAG, "SID found in mcc_sid_range. Return tmpMcc = " + tmpMcc);
c5.close();
return tmpMcc;
}
c5.close();
}
- if (DBG) Rlog.d(LOG_TAG, "SID NOT found in mcc_sid_range.");
+ if (DBG) Log.d(LOG_TAG, "SID NOT found in mcc_sid_range.");
- if (DBG) Rlog.d(LOG_TAG, "Exit getMccByOtherFactors. Return tmpMcc = " + tmpMcc);
+ if (DBG) Log.d(LOG_TAG, "Exit getMccByOtherFactors. Return tmpMcc = " + tmpMcc);
// If unknown MCC still could not be resolved,
return tmpMcc;
}
@@ -135,7 +135,7 @@
* Gets country information with given MCC.
*/
public String getIddByMcc(int mcc) {
- if (DBG) Rlog.d(LOG_TAG, "Enter getHbpcdInfoByMCC.");
+ if (DBG) Log.d(LOG_TAG, "Enter getHbpcdInfoByMCC.");
String idd = "";
Cursor c = null;
@@ -145,19 +145,19 @@
MccIdd.MCC + "=" + mcc, null, null);
if (cur != null) {
if (cur.getCount() > 0) {
- if (DBG) Rlog.d(LOG_TAG, "Query Idd returned the cursor " + cur);
+ if (DBG) Log.d(LOG_TAG, "Query Idd returned the cursor " + cur);
// TODO: for those country having more than 1 IDDs, need more information
// to decide which IDD would be used. currently just use the first 1.
cur.moveToFirst();
idd = cur.getString(0);
- if (DBG) Rlog.d(LOG_TAG, "IDD = " + idd);
+ if (DBG) Log.d(LOG_TAG, "IDD = " + idd);
}
cur.close();
}
if (c != null) c.close();
- if (DBG) Rlog.d(LOG_TAG, "Exit getHbpcdInfoByMCC.");
+ if (DBG) Log.d(LOG_TAG, "Exit getHbpcdInfoByMCC.");
return idd;
}
}
diff --git a/telephony/common/com/android/internal/telephony/PackageChangeReceiver.java b/telephony/common/com/android/internal/telephony/PackageChangeReceiver.java
index 922af12..0b47547 100644
--- a/telephony/common/com/android/internal/telephony/PackageChangeReceiver.java
+++ b/telephony/common/com/android/internal/telephony/PackageChangeReceiver.java
@@ -24,17 +24,17 @@
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Handler;
+import android.os.HandlerThread;
import android.os.Looper;
import android.os.UserHandle;
-import com.android.internal.os.BackgroundThread;
-
/**
* Helper class for monitoring the state of packages: adding, removing,
* updating, and disappearing and reappearing on the SD card.
*/
public abstract class PackageChangeReceiver extends BroadcastReceiver {
static final IntentFilter sPackageIntentFilter = new IntentFilter();
+ private static HandlerThread sHandlerThread;
static {
sPackageIntentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
sPackageIntentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
@@ -43,28 +43,24 @@
sPackageIntentFilter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
sPackageIntentFilter.addDataScheme("package");
}
+ // Keep an instance of Context around as long as we still want the receiver:
+ // if the instance of Context gets garbage-collected, it'll unregister the receiver, so only
+ // unset when we want to unregister.
Context mRegisteredContext;
/**
- * To register the intents that needed for monitoring the state of packages
+ * To register the intents that needed for monitoring the state of packages. Once this method
+ * has been called on an instance of {@link PackageChangeReceiver}, all subsequent calls must
+ * have the same {@code user} argument.
*/
public void register(@NonNull Context context, @Nullable Looper thread,
@Nullable UserHandle user) {
if (mRegisteredContext != null) {
throw new IllegalStateException("Already registered");
}
- Handler handler = (thread == null) ? BackgroundThread.getHandler() : new Handler(thread);
- mRegisteredContext = context;
- if (handler != null) {
- if (user != null) {
- context.registerReceiverAsUser(this, user, sPackageIntentFilter, null, handler);
- } else {
- context.registerReceiver(this, sPackageIntentFilter,
- null, handler);
- }
- } else {
- throw new NullPointerException();
- }
+ Handler handler = new Handler(thread == null ? getStaticLooper() : thread);
+ mRegisteredContext = user == null ? context : context.createContextAsUser(user, 0);
+ mRegisteredContext.registerReceiver(this, sPackageIntentFilter, null, handler);
}
/**
@@ -78,6 +74,14 @@
mRegisteredContext = null;
}
+ private static synchronized Looper getStaticLooper() {
+ if (sHandlerThread == null) {
+ sHandlerThread = new HandlerThread(PackageChangeReceiver.class.getSimpleName());
+ sHandlerThread.start();
+ }
+ return sHandlerThread.getLooper();
+ }
+
/**
* This method is invoked when receive the Intent.ACTION_PACKAGE_ADDED
*/
diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java
index 70ce1bf..9b82828 100644
--- a/telephony/common/com/android/internal/telephony/SmsApplication.java
+++ b/telephony/common/com/android/internal/telephony/SmsApplication.java
@@ -40,10 +40,11 @@
import android.provider.Telephony;
import android.provider.Telephony.Sms.Intents;
import android.telephony.PackageChangeReceiver;
-import android.telephony.Rlog;
+import android.util.Log;
import android.telephony.TelephonyManager;
import android.util.Log;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
@@ -57,6 +58,7 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
+import java.util.stream.Collectors;
/**
* Class for managing the primary application that we will deliver SMS/MMS messages to
@@ -65,10 +67,10 @@
*/
public final class SmsApplication {
static final String LOG_TAG = "SmsApplication";
- private static final String PHONE_PACKAGE_NAME = "com.android.phone";
- private static final String BLUETOOTH_PACKAGE_NAME = "com.android.bluetooth";
- private static final String MMS_SERVICE_PACKAGE_NAME = "com.android.mms.service";
- private static final String TELEPHONY_PROVIDER_PACKAGE_NAME = "com.android.providers.telephony";
+ public static final String PHONE_PACKAGE_NAME = "com.android.phone";
+ public static final String BLUETOOTH_PACKAGE_NAME = "com.android.bluetooth";
+ public static final String MMS_SERVICE_PACKAGE_NAME = "com.android.mms.service";
+ public static final String TELEPHONY_PROVIDER_PACKAGE_NAME = "com.android.providers.telephony";
private static final String SCHEME_SMS = "sms";
private static final String SCHEME_SMSTO = "smsto";
@@ -77,13 +79,13 @@
private static final boolean DEBUG = false;
private static final boolean DEBUG_MULTIUSER = false;
- private static final int[] DEFAULT_APP_EXCLUSIVE_APPOPS = {
- AppOpsManager.OP_READ_SMS,
- AppOpsManager.OP_WRITE_SMS,
- AppOpsManager.OP_RECEIVE_SMS,
- AppOpsManager.OP_RECEIVE_WAP_PUSH,
- AppOpsManager.OP_SEND_SMS,
- AppOpsManager.OP_READ_CELL_BROADCASTS
+ private static final String[] DEFAULT_APP_EXCLUSIVE_APPOPS = {
+ AppOpsManager.OPSTR_READ_SMS,
+ AppOpsManager.OPSTR_WRITE_SMS,
+ AppOpsManager.OPSTR_RECEIVE_SMS,
+ AppOpsManager.OPSTR_RECEIVE_WAP_PUSH,
+ AppOpsManager.OPSTR_SEND_SMS,
+ AppOpsManager.OPSTR_READ_CELL_BROADCASTS
};
private static SmsPackageMonitor sSmsPackageMonitor = null;
@@ -247,6 +249,7 @@
private static Collection<SmsApplicationData> getApplicationCollectionInternal(
Context context, int userId) {
PackageManager packageManager = context.getPackageManager();
+ UserHandle userHandle = UserHandle.of(userId);
// Get the list of apps registered for SMS
Intent intent = new Intent(Intents.SMS_DELIVER_ACTION);
@@ -255,7 +258,7 @@
}
List<ResolveInfo> smsReceivers = packageManager.queryBroadcastReceiversAsUser(intent,
PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
- userId);
+ userHandle);
HashMap<String, SmsApplicationData> receivers = new HashMap<String, SmsApplicationData>();
@@ -282,7 +285,7 @@
intent.setDataAndType(null, "application/vnd.wap.mms-message");
List<ResolveInfo> mmsReceivers = packageManager.queryBroadcastReceiversAsUser(intent,
PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
- userId);
+ userHandle);
for (ResolveInfo resolveInfo : mmsReceivers) {
final ActivityInfo activityInfo = resolveInfo.activityInfo;
if (activityInfo == null) {
@@ -324,7 +327,7 @@
Uri.fromParts(SCHEME_SMSTO, "", null));
List<ResolveInfo> sendToActivities = packageManager.queryIntentActivitiesAsUser(intent,
PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
- userId);
+ userHandle);
for (ResolveInfo resolveInfo : sendToActivities) {
final ActivityInfo activityInfo = resolveInfo.activityInfo;
if (activityInfo == null) {
@@ -342,7 +345,7 @@
List<ResolveInfo> smsAppChangedReceivers =
packageManager.queryBroadcastReceiversAsUser(intent,
PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userHandle);
if (DEBUG_MULTIUSER) {
Log.i(LOG_TAG, "getApplicationCollectionInternal smsAppChangedActivities=" +
smsAppChangedReceivers);
@@ -369,7 +372,7 @@
List<ResolveInfo> providerChangedReceivers =
packageManager.queryBroadcastReceiversAsUser(intent,
PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userHandle);
if (DEBUG_MULTIUSER) {
Log.i(LOG_TAG, "getApplicationCollectionInternal providerChangedActivities=" +
providerChangedReceivers);
@@ -396,7 +399,7 @@
List<ResolveInfo> simFullReceivers =
packageManager.queryBroadcastReceiversAsUser(intent,
PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userHandle);
if (DEBUG_MULTIUSER) {
Log.i(LOG_TAG, "getApplicationCollectionInternal simFullReceivers="
+ simFullReceivers);
@@ -496,7 +499,7 @@
// If we found a package, make sure AppOps permissions are set up correctly
if (applicationData != null) {
- // We can only call checkOp if we are privileged (updateIfNeeded) or if the app we
+ // We can only call unsafeCheckOp if we are privileged (updateIfNeeded) or if the app we
// are checking is for our current uid. Doing this check from the unprivileged current
// SMS app allows us to tell the current SMS app that it is not in a good state and
// needs to ask to be the current SMS app again to work properly.
@@ -550,23 +553,23 @@
// apps, all of them should be able to write to telephony provider.
// This is to allow the proxy package permission check in telephony provider
// to pass.
- for (int appop : DEFAULT_APP_EXCLUSIVE_APPOPS) {
- appOps.setUidMode(appop, Process.PHONE_UID, AppOpsManager.MODE_ALLOWED);
+ for (String opStr : DEFAULT_APP_EXCLUSIVE_APPOPS) {
+ appOps.setUidMode(opStr, Process.PHONE_UID, AppOpsManager.MODE_ALLOWED);
}
}
private static boolean tryFixExclusiveSmsAppops(Context context,
SmsApplicationData applicationData, boolean updateIfNeeded) {
AppOpsManager appOps = context.getSystemService(AppOpsManager.class);
- for (int appOp : DEFAULT_APP_EXCLUSIVE_APPOPS) {
- int mode = appOps.checkOp(appOp, applicationData.mUid,
+ for (String opStr : DEFAULT_APP_EXCLUSIVE_APPOPS) {
+ int mode = appOps.unsafeCheckOp(opStr, applicationData.mUid,
applicationData.mPackageName);
if (mode != AppOpsManager.MODE_ALLOWED) {
- Rlog.e(LOG_TAG, applicationData.mPackageName + " lost "
- + AppOpsManager.modeToName(appOp) + ": "
+ Log.e(LOG_TAG, applicationData.mPackageName + " lost "
+ + opStr + ": "
+ (updateIfNeeded ? " (fixing)" : " (no permission to fix)"));
if (updateIfNeeded) {
- appOps.setUidMode(appOp, applicationData.mUid, AppOpsManager.MODE_ALLOWED);
+ appOps.setUidMode(opStr, applicationData.mUid, AppOpsManager.MODE_ALLOWED);
} else {
return false;
}
@@ -625,7 +628,8 @@
}
// We only make the change if the new package is valid
- PackageManager packageManager = context.getPackageManager();
+ PackageManager packageManager =
+ context.createContextAsUser(userHandle, 0).getPackageManager();
Collection<SmsApplicationData> applications = getApplicationCollectionInternal(
context, userId);
SmsApplicationData oldAppData = oldPackageName != null ?
@@ -636,11 +640,10 @@
AppOpsManager appOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
if (oldPackageName != null) {
try {
- int uid = packageManager.getPackageInfoAsUser(
- oldPackageName, 0, userId).applicationInfo.uid;
+ int uid = packageManager.getPackageInfo(oldPackageName, 0).applicationInfo.uid;
setExclusiveAppops(oldPackageName, appOps, uid, AppOpsManager.MODE_DEFAULT);
} catch (NameNotFoundException e) {
- Rlog.w(LOG_TAG, "Old SMS package not found: " + oldPackageName);
+ Log.w(LOG_TAG, "Old SMS package not found: " + oldPackageName);
}
}
@@ -747,29 +750,29 @@
// the package signature matches system signature.
final int result = packageManager.checkSignatures(context.getPackageName(), packageName);
if (result != PackageManager.SIGNATURE_MATCH) {
- Rlog.e(LOG_TAG, packageName + " does not have system signature");
+ Log.e(LOG_TAG, packageName + " does not have system signature");
return;
}
try {
PackageInfo info = packageManager.getPackageInfo(packageName, 0);
- int mode = appOps.checkOp(AppOpsManager.OP_WRITE_SMS, info.applicationInfo.uid,
+ int mode = appOps.unsafeCheckOp(AppOpsManager.OPSTR_WRITE_SMS, info.applicationInfo.uid,
packageName);
if (mode != AppOpsManager.MODE_ALLOWED) {
- Rlog.w(LOG_TAG, packageName + " does not have OP_WRITE_SMS: (fixing)");
+ Log.w(LOG_TAG, packageName + " does not have OP_WRITE_SMS: (fixing)");
setExclusiveAppops(packageName, appOps, info.applicationInfo.uid,
AppOpsManager.MODE_ALLOWED);
}
} catch (NameNotFoundException e) {
// No whitelisted system app on this device
- Rlog.e(LOG_TAG, "Package not found: " + packageName);
+ Log.e(LOG_TAG, "Package not found: " + packageName);
}
}
private static void setExclusiveAppops(String pkg, AppOpsManager appOpsManager, int uid,
int mode) {
- for (int appop : DEFAULT_APP_EXCLUSIVE_APPOPS) {
- appOpsManager.setUidMode(appop, uid, mode);
+ for (String opStr : DEFAULT_APP_EXCLUSIVE_APPOPS) {
+ appOpsManager.setUidMode(opStr, uid, mode);
}
}
@@ -801,9 +804,16 @@
}
private void onPackageChanged() {
- PackageManager packageManager = mContext.getPackageManager();
+ int userId;
+ try {
+ userId = getSendingUser().getIdentifier();
+ } catch (NullPointerException e) {
+ // This should never happen in prod -- unit tests will put the receiver into a
+ // unusual state where the pending result is null, which produces a NPE when calling
+ // getSendingUserId. Just pretend like it's the system user for testing.
+ userId = UserHandle.USER_SYSTEM;
+ }
Context userContext = mContext;
- final int userId = getSendingUserId();
if (userId != UserHandle.USER_SYSTEM) {
try {
userContext = mContext.createPackageContextAsUser(mContext.getPackageName(), 0,
@@ -814,10 +824,11 @@
}
}
}
+ PackageManager packageManager = userContext.getPackageManager();
// Ensure this component is still configured as the preferred activity
ComponentName componentName = getDefaultSendToApplication(userContext, true);
if (componentName != null) {
- configurePreferredActivity(packageManager, componentName, userId);
+ configurePreferredActivity(packageManager, componentName);
}
}
}
@@ -829,41 +840,36 @@
@UnsupportedAppUsage
private static void configurePreferredActivity(PackageManager packageManager,
- ComponentName componentName, int userId) {
+ ComponentName componentName) {
// Add the four activity preferences we want to direct to this app.
- replacePreferredActivity(packageManager, componentName, userId, SCHEME_SMS);
- replacePreferredActivity(packageManager, componentName, userId, SCHEME_SMSTO);
- replacePreferredActivity(packageManager, componentName, userId, SCHEME_MMS);
- replacePreferredActivity(packageManager, componentName, userId, SCHEME_MMSTO);
+ replacePreferredActivity(packageManager, componentName, SCHEME_SMS);
+ replacePreferredActivity(packageManager, componentName, SCHEME_SMSTO);
+ replacePreferredActivity(packageManager, componentName, SCHEME_MMS);
+ replacePreferredActivity(packageManager, componentName, SCHEME_MMSTO);
}
/**
* Updates the ACTION_SENDTO activity to the specified component for the specified scheme.
*/
private static void replacePreferredActivity(PackageManager packageManager,
- ComponentName componentName, int userId, String scheme) {
+ ComponentName componentName, String scheme) {
// Build the set of existing activities that handle this scheme
Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts(scheme, "", null));
- List<ResolveInfo> resolveInfoList = packageManager.queryIntentActivitiesAsUser(
- intent, PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_RESOLVED_FILTER,
- userId);
+ List<ResolveInfo> resolveInfoList = packageManager.queryIntentActivities(
+ intent, PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_RESOLVED_FILTER);
- // Build the set of ComponentNames for these activities
- final int n = resolveInfoList.size();
- ComponentName[] set = new ComponentName[n];
- for (int i = 0; i < n; i++) {
- ResolveInfo info = resolveInfoList.get(i);
- set[i] = new ComponentName(info.activityInfo.packageName, info.activityInfo.name);
- }
+ List<ComponentName> components = resolveInfoList.stream().map(info ->
+ new ComponentName(info.activityInfo.packageName, info.activityInfo.name))
+ .collect(Collectors.toList());
// Update the preferred SENDTO activity for the specified scheme
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_SENDTO);
intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
intentFilter.addDataScheme(scheme);
- packageManager.replacePreferredActivityAsUser(intentFilter,
+ packageManager.replacePreferredActivity(intentFilter,
IntentFilter.MATCH_CATEGORY_SCHEME + IntentFilter.MATCH_ADJUSTMENT_NORMAL,
- set, componentName, userId);
+ components, componentName);
}
/**
@@ -894,6 +900,7 @@
* @param userId target user ID.
* @return component name of the app and class to deliver SMS messages to
*/
+ @VisibleForTesting
public static ComponentName getDefaultSmsApplicationAsUser(Context context,
boolean updateIfNeeded, int userId) {
final long token = Binder.clearCallingIdentity();
diff --git a/telephony/common/com/android/internal/telephony/SmsConstants.java b/telephony/common/com/android/internal/telephony/SmsConstants.java
index 19f52b0..3aa8bbf 100644
--- a/telephony/common/com/android/internal/telephony/SmsConstants.java
+++ b/telephony/common/com/android/internal/telephony/SmsConstants.java
@@ -15,7 +15,7 @@
*/
package com.android.internal.telephony;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* SMS Constants and must be the same as the corresponding
diff --git a/telephony/common/com/android/internal/telephony/SmsNumberUtils.java b/telephony/common/com/android/internal/telephony/SmsNumberUtils.java
index 06c08f5..cd365a1 100644
--- a/telephony/common/com/android/internal/telephony/SmsNumberUtils.java
+++ b/telephony/common/com/android/internal/telephony/SmsNumberUtils.java
@@ -24,13 +24,16 @@
import android.os.SystemProperties;
import android.telephony.CarrierConfigManager;
import android.telephony.PhoneNumberUtils;
-import android.telephony.Rlog;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Log;
import com.android.internal.telephony.HbpcdLookup.MccIdd;
import com.android.internal.telephony.HbpcdLookup.MccLookup;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
@@ -143,7 +146,7 @@
// First check whether the number is a NANP number.
int nanpState = checkNANP(numberEntry, allIDDs);
- if (DBG) Rlog.d(TAG, "NANP type: " + getNumberPlanType(nanpState));
+ if (DBG) Log.d(TAG, "NANP type: " + getNumberPlanType(nanpState));
if ((nanpState == NP_NANP_LOCAL)
|| (nanpState == NP_NANP_AREA_LOCAL)
@@ -173,7 +176,7 @@
int internationalState = checkInternationalNumberPlan(context, numberEntry, allIDDs,
NANP_IDD);
- if (DBG) Rlog.d(TAG, "International type: " + getNumberPlanType(internationalState));
+ if (DBG) Log.d(TAG, "International type: " + getNumberPlanType(internationalState));
String returnNumber = null;
switch (internationalState) {
@@ -272,7 +275,7 @@
}
}
} catch (SQLException e) {
- Rlog.e(TAG, "Can't access HbpcdLookup database", e);
+ Log.e(TAG, "Can't access HbpcdLookup database", e);
} finally {
if (cursor != null) {
cursor.close();
@@ -281,7 +284,7 @@
IDDS_MAPS.put(mcc, allIDDs);
- if (DBG) Rlog.d(TAG, "MCC = " + mcc + ", all IDDs = " + allIDDs);
+ if (DBG) Log.d(TAG, "MCC = " + mcc + ", all IDDs = " + allIDDs);
return allIDDs;
}
@@ -472,7 +475,7 @@
int tempCC = allCCs[i];
for (int j = 0; j < MAX_COUNTRY_CODES_LENGTH; j ++) {
if (tempCC == ccArray[j]) {
- if (DBG) Rlog.d(TAG, "Country code = " + tempCC);
+ if (DBG) Log.d(TAG, "Country code = " + tempCC);
return tempCC;
}
}
@@ -509,7 +512,7 @@
}
}
} catch (SQLException e) {
- Rlog.e(TAG, "Can't access HbpcdLookup database", e);
+ Log.e(TAG, "Can't access HbpcdLookup database", e);
} finally {
if (cursor != null) {
cursor.close();
@@ -561,10 +564,10 @@
* Filter the destination number if using VZW sim card.
*/
public static String filterDestAddr(Context context, int subId, String destAddr) {
- if (DBG) Rlog.d(TAG, "enter filterDestAddr. destAddr=\"" + Rlog.pii(TAG, destAddr) + "\"" );
+ if (DBG) Log.d(TAG, "enter filterDestAddr. destAddr=\"" + pii(TAG, destAddr) + "\"" );
if (destAddr == null || !PhoneNumberUtils.isGlobalPhoneNumber(destAddr)) {
- Rlog.w(TAG, "destAddr" + Rlog.pii(TAG, destAddr) +
+ Log.w(TAG, "destAddr" + pii(TAG, destAddr) +
" is not a global phone number! Nothing changed.");
return destAddr;
}
@@ -585,9 +588,9 @@
}
if (DBG) {
- Rlog.d(TAG, "destAddr is " + ((result != null)?"formatted.":"not formatted."));
- Rlog.d(TAG, "leave filterDestAddr, new destAddr=\"" + (result != null ? Rlog.pii(TAG,
- result) : Rlog.pii(TAG, destAddr)) + "\"");
+ Log.d(TAG, "destAddr is " + ((result != null)?"formatted.":"not formatted."));
+ Log.d(TAG, "leave filterDestAddr, new destAddr=\"" + (result != null ? pii(TAG,
+ result) : pii(TAG, destAddr)) + "\"");
}
return result != null ? result : destAddr;
}
@@ -608,7 +611,7 @@
networkType = CDMA_HOME_NETWORK;
}
} else {
- if (DBG) Rlog.w(TAG, "warning! unknown mPhoneType value=" + phoneType);
+ if (DBG) Log.w(TAG, "warning! unknown mPhoneType value=" + phoneType);
}
return networkType;
@@ -650,4 +653,44 @@
// by default this value is false
return false;
}
+
+ /**
+ * Redact personally identifiable information for production users.
+ * @param tag used to identify the source of a log message
+ * @param pii the personally identifiable information we want to apply secure hash on.
+ * @return If tag is loggable in verbose mode or pii is null, return the original input.
+ * otherwise return a secure Hash of input pii
+ */
+ private static String pii(String tag, Object pii) {
+ String val = String.valueOf(pii);
+ if (pii == null || TextUtils.isEmpty(val) || Log.isLoggable(tag, Log.VERBOSE)) {
+ return val;
+ }
+ return "[" + secureHash(val.getBytes()) + "]";
+ }
+
+ /**
+ * Returns a secure hash (using the SHA1 algorithm) of the provided input.
+ *
+ * @return "****" if the build type is user, otherwise the hash
+ * @param input the bytes for which the secure hash should be computed.
+ */
+ private static String secureHash(byte[] input) {
+ // Refrain from logging user personal information in user build.
+ if (android.os.Build.IS_USER) {
+ return "****";
+ }
+
+ MessageDigest messageDigest;
+
+ try {
+ messageDigest = MessageDigest.getInstance("SHA-1");
+ } catch (NoSuchAlgorithmException e) {
+ return "####";
+ }
+
+ byte[] result = messageDigest.digest(input);
+ return Base64.encodeToString(
+ result, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP);
+ }
}
diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
index 80a55b2..b8b203d 100644
--- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
+++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java
@@ -28,7 +28,6 @@
import android.os.Build;
import android.os.Process;
import android.os.UserHandle;
-import android.telephony.Rlog;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.util.Log;
@@ -521,7 +520,7 @@
return;
}
- if (DBG) Rlog.d(LOG_TAG, "No modify permission, check carrier privilege next.");
+ if (DBG) Log.d(LOG_TAG, "No modify permission, check carrier privilege next.");
enforceCallingOrSelfCarrierPrivilege(context, subId, message);
}
@@ -539,7 +538,7 @@
}
if (DBG) {
- Rlog.d(LOG_TAG, "No READ_PHONE_STATE permission, check carrier privilege next.");
+ Log.d(LOG_TAG, "No READ_PHONE_STATE permission, check carrier privilege next.");
}
enforceCallingOrSelfCarrierPrivilege(context, subId, message);
@@ -559,7 +558,7 @@
}
if (DBG) {
- Rlog.d(LOG_TAG, "No READ_PRIVILEDED_PHONE_STATE permission, "
+ Log.d(LOG_TAG, "No READ_PRIVILEDED_PHONE_STATE permission, "
+ "check carrier privilege next.");
}
@@ -584,7 +583,7 @@
Context context, int subId, int uid, String message) {
if (getCarrierPrivilegeStatus(context, subId, uid)
!= TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
- if (DBG) Rlog.e(LOG_TAG, "No Carrier Privilege.");
+ if (DBG) Log.e(LOG_TAG, "No Carrier Privilege.");
throw new SecurityException(message);
}
}
@@ -593,7 +592,7 @@
private static boolean checkCarrierPrivilegeForAnySubId(Context context, int uid) {
SubscriptionManager sm = (SubscriptionManager) context.getSystemService(
Context.TELEPHONY_SUBSCRIPTION_SERVICE);
- int[] activeSubIds = sm.getActiveSubscriptionIdList(/* visibleOnly */ false);
+ int[] activeSubIds = sm.getActiveAndHiddenSubscriptionIdList();
for (int activeSubId : activeSubIds) {
if (getCarrierPrivilegeStatus(context, activeSubId, uid)
== TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
diff --git a/telephony/java/com/android/internal/telephony/util/ArrayUtils.java b/telephony/common/com/android/internal/telephony/util/ArrayUtils.java
similarity index 86%
rename from telephony/java/com/android/internal/telephony/util/ArrayUtils.java
rename to telephony/common/com/android/internal/telephony/util/ArrayUtils.java
index 2905125..28401a6 100644
--- a/telephony/java/com/android/internal/telephony/util/ArrayUtils.java
+++ b/telephony/common/com/android/internal/telephony/util/ArrayUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -29,17 +29,30 @@
/**
* Adds value to given array if not already present, providing set-like behavior.
+ *
+ * @param kind The class of the array elements.
+ * @param array The array to append to.
+ * @param element The array element to append.
+ * @return The array containing the appended element.
*/
@SuppressWarnings("unchecked")
- public static @NonNull <T> T[] appendElement(Class<T> kind, @Nullable T[] array, T element) {
+ @NonNull
+ public static <T> T[] appendElement(Class<T> kind, @Nullable T[] array, T element) {
return appendElement(kind, array, element, false);
}
/**
* Adds value to given array.
+ *
+ * @param kind The class of the array elements.
+ * @param array The array to append to.
+ * @param element The array element to append.
+ * @param allowDuplicates Whether to allow duplicated elements in array.
+ * @return The array containing the appended element.
*/
@SuppressWarnings("unchecked")
- public static @NonNull <T> T[] appendElement(Class<T> kind, @Nullable T[] array, T element,
+ @NonNull
+ public static <T> T[] appendElement(Class<T> kind, @Nullable T[] array, T element,
boolean allowDuplicates) {
final T[] result;
final int end;
@@ -59,13 +72,14 @@
/**
* Combine multiple arrays into a single array.
*
- * @param kind The class of the array elements
+ * @param kind The class of the array elements
* @param arrays The arrays to combine
- * @param <T> The class of the array elements (inferred from kind).
+ * @param <T> The class of the array elements (inferred from kind).
* @return A single array containing all the elements of the parameter arrays.
*/
@SuppressWarnings("unchecked")
- public static @NonNull <T> T[] concatElements(Class<T> kind, @Nullable T[]... arrays) {
+ @NonNull
+ public static <T> T[] concatElements(Class<T> kind, @Nullable T[]... arrays) {
if (arrays == null || arrays.length == 0) {
return createEmptyArray(kind);
}
diff --git a/telephony/java/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
similarity index 93%
rename from telephony/java/com/android/internal/telephony/util/TelephonyUtils.java
rename to telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
index 2abcc76..a7ad884 100644
--- a/telephony/java/com/android/internal/telephony/util/TelephonyUtils.java
+++ b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java
@@ -28,6 +28,8 @@
import android.os.SystemProperties;
import java.io.PrintWriter;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
/**
@@ -137,4 +139,12 @@
}
return ret;
}
+
+ /** Wait for latch to trigger */
+ public static void waitUntilReady(CountDownLatch latch, long timeoutMs) {
+ try {
+ latch.await(timeoutMs, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException ignored) {
+ }
+ }
}
diff --git a/telephony/common/com/google/android/mms/pdu/PduPersister.java b/telephony/common/com/google/android/mms/pdu/PduPersister.java
index b237705..8efca0e 100755
--- a/telephony/common/com/google/android/mms/pdu/PduPersister.java
+++ b/telephony/common/com/google/android/mms/pdu/PduPersister.java
@@ -34,6 +34,7 @@
import android.provider.Telephony.MmsSms.PendingMessages;
import android.provider.Telephony.Threads;
import android.telephony.PhoneNumberUtils;
+import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -1448,9 +1449,9 @@
final Set<String> myPhoneNumbers = new HashSet<String>();
if (excludeMyNumber) {
// Build a list of my phone numbers from the various sims.
- for (int subid : subscriptionManager.getActiveSubscriptionIdList()) {
+ for (SubscriptionInfo subInfo : subscriptionManager.getActiveSubscriptionInfoList()) {
final String myNumber = mContext.getSystemService(TelephonyManager.class).
- createForSubscriptionId(subid).getLine1Number();
+ createForSubscriptionId(subInfo.getSubscriptionId()).getLine1Number();
if (myNumber != null) {
myPhoneNumbers.add(myNumber);
}
diff --git a/telephony/framework-telephony-jarjar-rules.txt b/telephony/framework-telephony-jarjar-rules.txt
new file mode 100644
index 0000000..7cab806
--- /dev/null
+++ b/telephony/framework-telephony-jarjar-rules.txt
@@ -0,0 +1,4 @@
+rule android.telephony.Annotation* android.telephony.framework.Annotation@1
+rule com.android.i18n.phonenumbers.** com.android.telephony.framework.phonenumbers.@1
+#TODO: add jarjar rules for statically linked util classes
+
diff --git a/telephony/java/android/service/carrier/CarrierIdentifier.java b/telephony/java/android/service/carrier/CarrierIdentifier.java
index af5bf74..7957c25 100644
--- a/telephony/java/android/service/carrier/CarrierIdentifier.java
+++ b/telephony/java/android/service/carrier/CarrierIdentifier.java
@@ -20,7 +20,7 @@
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
import android.telephony.TelephonyManager;
import com.android.internal.telephony.uicc.IccUtils;
diff --git a/telephony/java/android/service/euicc/EuiccProfileInfo.java b/telephony/java/android/service/euicc/EuiccProfileInfo.java
index 6c357cc..8450a90 100644
--- a/telephony/java/android/service/euicc/EuiccProfileInfo.java
+++ b/telephony/java/android/service/euicc/EuiccProfileInfo.java
@@ -19,7 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
import android.service.carrier.CarrierIdentifier;
diff --git a/telephony/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.java b/telephony/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.java
index c7a9851..2382f65 100644
--- a/telephony/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.java
+++ b/telephony/java/android/service/euicc/GetDefaultDownloadableSubscriptionListResult.java
@@ -17,7 +17,7 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.euicc.DownloadableSubscription;
diff --git a/telephony/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.java b/telephony/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.java
index abd4065..d0fb511 100644
--- a/telephony/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.java
+++ b/telephony/java/android/service/euicc/GetDownloadableSubscriptionMetadataResult.java
@@ -17,7 +17,7 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.euicc.DownloadableSubscription;
diff --git a/telephony/java/android/telephony/AnomalyReporter.java b/telephony/java/android/telephony/AnomalyReporter.java
index 9753d8b..097041f 100644
--- a/telephony/java/android/telephony/AnomalyReporter.java
+++ b/telephony/java/android/telephony/AnomalyReporter.java
@@ -16,6 +16,8 @@
package android.telephony;
+import com.android.telephony.Rlog;
+
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
import android.content.Context;
diff --git a/media/java/android/media/RouteSessionInfo.aidl b/telephony/java/android/telephony/BarringInfo.aidl
similarity index 89%
copy from media/java/android/media/RouteSessionInfo.aidl
copy to telephony/java/android/telephony/BarringInfo.aidl
index fb5d836..50ddf6b 100644
--- a/media/java/android/media/RouteSessionInfo.aidl
+++ b/telephony/java/android/telephony/BarringInfo.aidl
@@ -14,6 +14,7 @@
* limitations under the License.
*/
-package android.media;
+/** @hide */
+package android.telephony;
-parcelable RouteSessionInfo;
+parcelable BarringInfo;
diff --git a/telephony/java/android/telephony/BarringInfo.java b/telephony/java/android/telephony/BarringInfo.java
new file mode 100644
index 0000000..5419c3c
--- /dev/null
+++ b/telephony/java/android/telephony/BarringInfo.java
@@ -0,0 +1,398 @@
+/*
+ * 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.telephony;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.SparseArray;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Provides the barring configuration for a particular service type.
+ *
+ * Provides indication about the barring of a particular service for use. Certain barring types
+ * are only valid for certain technology families. Any service that does not have a barring
+ * configuration is unbarred by default.
+ */
+public final class BarringInfo implements Parcelable {
+
+ /**
+ * Barring Service Type
+ *
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "BARRING_SERVICE_TYPE_", value = {
+ BARRING_SERVICE_TYPE_CS_SERVICE,
+ BARRING_SERVICE_TYPE_PS_SERVICE,
+ BARRING_SERVICE_TYPE_CS_VOICE,
+ BARRING_SERVICE_TYPE_MO_SIGNALLING,
+ BARRING_SERVICE_TYPE_MO_DATA,
+ BARRING_SERVICE_TYPE_CS_FALLBACK,
+ BARRING_SERVICE_TYPE_MMTEL_VOICE,
+ BARRING_SERVICE_TYPE_MMTEL_VIDEO,
+ BARRING_SERVICE_TYPE_EMERGENCY,
+ BARRING_SERVICE_TYPE_SMS})
+ public @interface BarringServiceType {}
+
+ /* Applicabe to UTRAN */
+ /** Barring indicator for circuit-switched service; applicable to UTRAN */
+ public static final int BARRING_SERVICE_TYPE_CS_SERVICE =
+ android.hardware.radio.V1_5.BarringServiceType.CS_SERVICE;
+ /** Barring indicator for packet-switched service; applicable to UTRAN */
+ public static final int BARRING_SERVICE_TYPE_PS_SERVICE =
+ android.hardware.radio.V1_5.BarringServiceType.PS_SERVICE;
+ /** Barring indicator for circuit-switched voice service; applicable to UTRAN */
+ public static final int BARRING_SERVICE_TYPE_CS_VOICE =
+ android.hardware.radio.V1_5.BarringServiceType.CS_VOICE;
+
+ /* Applicable to EUTRAN, NGRAN */
+ /** Barring indicator for mobile-originated signalling; applicable to EUTRAN and NGRAN */
+ public static final int BARRING_SERVICE_TYPE_MO_SIGNALLING =
+ android.hardware.radio.V1_5.BarringServiceType.MO_SIGNALLING;
+ /** Barring indicator for mobile-originated data traffic; applicable to EUTRAN and NGRAN */
+ public static final int BARRING_SERVICE_TYPE_MO_DATA =
+ android.hardware.radio.V1_5.BarringServiceType.MO_DATA;
+ /** Barring indicator for circuit-switched fallback for voice; applicable to EUTRAN and NGRAN */
+ public static final int BARRING_SERVICE_TYPE_CS_FALLBACK =
+ android.hardware.radio.V1_5.BarringServiceType.CS_FALLBACK;
+ /** Barring indicator for MMTEL (IMS) voice; applicable to EUTRAN and NGRAN */
+ public static final int BARRING_SERVICE_TYPE_MMTEL_VOICE =
+ android.hardware.radio.V1_5.BarringServiceType.MMTEL_VOICE;
+ /** Barring indicator for MMTEL (IMS) video; applicable to EUTRAN and NGRAN */
+ public static final int BARRING_SERVICE_TYPE_MMTEL_VIDEO =
+ android.hardware.radio.V1_5.BarringServiceType.MMTEL_VIDEO;
+
+ /* Applicable to UTRAN, EUTRAN, NGRAN */
+ /** Barring indicator for emergency services; applicable to UTRAN, EUTRAN, and NGRAN */
+ public static final int BARRING_SERVICE_TYPE_EMERGENCY =
+ android.hardware.radio.V1_5.BarringServiceType.EMERGENCY;
+ /** Barring indicator for SMS sending; applicable to UTRAN, EUTRAN, and NGRAN */
+ public static final int BARRING_SERVICE_TYPE_SMS =
+ android.hardware.radio.V1_5.BarringServiceType.SMS;
+
+ //TODO: add barring constants for Operator-Specific barring codes
+
+ /** Describe the current barring configuration of a cell */
+ public static final class BarringServiceInfo implements Parcelable {
+ /**
+ * Barring Type
+ * @hide
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "BARRING_TYPE_", value =
+ {BARRING_TYPE_NONE,
+ BARRING_TYPE_UNCONDITIONAL,
+ BARRING_TYPE_CONDITIONAL,
+ BARRING_TYPE_UNKNOWN})
+ public @interface BarringType {}
+
+ /** Barring is inactive */
+ public static final int BARRING_TYPE_NONE = android.hardware.radio.V1_5.BarringType.NONE;
+ /** The service is barred */
+ public static final int BARRING_TYPE_UNCONDITIONAL =
+ android.hardware.radio.V1_5.BarringType.UNCONDITIONAL;
+ /** The service may be barred based on additional factors */
+ public static final int BARRING_TYPE_CONDITIONAL =
+ android.hardware.radio.V1_5.BarringType.CONDITIONAL;
+
+ /** If a modem does not report barring info, then the barring type will be UNKNOWN */
+ public static final int BARRING_TYPE_UNKNOWN = -1;
+
+ private final @BarringType int mBarringType;
+
+ private final boolean mIsConditionallyBarred;
+ private final int mConditionalBarringFactor;
+ private final int mConditionalBarringTimeSeconds;
+
+ /** @hide */
+ public BarringServiceInfo(@BarringType int type) {
+ this(type, false, 0, 0);
+ }
+
+ /** @hide */
+ @TestApi
+ public BarringServiceInfo(@BarringType int barringType, boolean isConditionallyBarred,
+ int conditionalBarringFactor, int conditionalBarringTimeSeconds) {
+ mBarringType = barringType;
+ mIsConditionallyBarred = isConditionallyBarred;
+ mConditionalBarringFactor = conditionalBarringFactor;
+ mConditionalBarringTimeSeconds = conditionalBarringTimeSeconds;
+ }
+
+ public @BarringType int getBarringType() {
+ return mBarringType;
+ }
+
+ /**
+ * @return true if the conditional barring parameters have resulted in the service being
+ * barred; false if the service has either not been evaluated for conditional
+ * barring or has been evaluated and isn't barred.
+ */
+ public boolean isConditionallyBarred() {
+ return mIsConditionallyBarred;
+ }
+
+ /**
+ * @return the conditional barring factor as a percentage 0-100, which is the probability of
+ * a random device being barred for the service type.
+ */
+ public int getConditionalBarringFactor() {
+ return mConditionalBarringFactor;
+ }
+
+ /**
+ * @return the conditional barring time seconds, which is the interval between successive
+ * evaluations for conditional barring based on the barring factor.
+ */
+ @SuppressLint("MethodNameUnits")
+ public int getConditionalBarringTimeSeconds() {
+ return mConditionalBarringTimeSeconds;
+ }
+
+ /**
+ * Return whether a service is currently barred based on the BarringInfo
+ *
+ * @return true if the service is currently being barred, otherwise false
+ */
+ public boolean isBarred() {
+ return mBarringType == BarringServiceInfo.BARRING_TYPE_UNCONDITIONAL
+ || (mBarringType == BarringServiceInfo.BARRING_TYPE_CONDITIONAL
+ && mIsConditionallyBarred);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mBarringType, mIsConditionallyBarred,
+ mConditionalBarringFactor, mConditionalBarringTimeSeconds);
+ }
+
+ @Override
+ public boolean equals(Object rhs) {
+ if (!(rhs instanceof BarringServiceInfo)) return false;
+
+ BarringServiceInfo other = (BarringServiceInfo) rhs;
+ return mBarringType == other.mBarringType
+ && mIsConditionallyBarred == other.mIsConditionallyBarred
+ && mConditionalBarringFactor == other.mConditionalBarringFactor
+ && mConditionalBarringTimeSeconds == other.mConditionalBarringTimeSeconds;
+ }
+
+ /** @hide */
+ public BarringServiceInfo(Parcel p) {
+ mBarringType = p.readInt();
+ mIsConditionallyBarred = p.readBoolean();
+ mConditionalBarringFactor = p.readInt();
+ mConditionalBarringTimeSeconds = p.readInt();
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeInt(mBarringType);
+ dest.writeBoolean(mIsConditionallyBarred);
+ dest.writeInt(mConditionalBarringFactor);
+ dest.writeInt(mConditionalBarringTimeSeconds);
+ }
+
+ /* @inheritDoc */
+ public static final @NonNull Parcelable.Creator<BarringServiceInfo> CREATOR =
+ new Parcelable.Creator<BarringServiceInfo>() {
+ @Override
+ public BarringServiceInfo createFromParcel(Parcel source) {
+ return new BarringServiceInfo(source);
+ }
+
+ @Override
+ public BarringServiceInfo[] newArray(int size) {
+ return new BarringServiceInfo[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+ }
+
+ private CellIdentity mCellIdentity;
+
+ // A SparseArray potentially mapping each BarringService type to a BarringServiceInfo config
+ // that describes the current barring status of that particular service.
+ private SparseArray<BarringServiceInfo> mBarringServiceInfos;
+
+ /** @hide */
+ @TestApi
+ @SystemApi
+ public BarringInfo() {
+ mBarringServiceInfos = new SparseArray<>();
+ }
+
+ /**
+ * Constructor for new BarringInfo instances.
+ *
+ * @hide
+ */
+ @TestApi
+ public BarringInfo(@Nullable CellIdentity barringCellId,
+ @NonNull SparseArray<BarringServiceInfo> barringServiceInfos) {
+ mCellIdentity = barringCellId;
+ mBarringServiceInfos = barringServiceInfos;
+ }
+
+ /** @hide */
+ public static BarringInfo create(
+ @NonNull android.hardware.radio.V1_5.CellIdentity halBarringCellId,
+ @NonNull List<android.hardware.radio.V1_5.BarringInfo> halBarringInfos) {
+ CellIdentity ci = CellIdentity.create(halBarringCellId);
+ SparseArray<BarringServiceInfo> serviceInfos = new SparseArray<>();
+
+ for (android.hardware.radio.V1_5.BarringInfo halBarringInfo : halBarringInfos) {
+ if (halBarringInfo.type == android.hardware.radio.V1_5.BarringType.CONDITIONAL) {
+ if (halBarringInfo.typeSpecificInfo.getDiscriminator()
+ != android.hardware.radio.V1_5.BarringTypeSpecificInfo
+ .hidl_discriminator.conditionalBarringInfo) {
+ // this is an error case where the barring info is conditional but the
+ // conditional barring fields weren't included
+ continue;
+ }
+ android.hardware.radio.V1_5.ConditionalBarringInfo conditionalInfo =
+ halBarringInfo.typeSpecificInfo.conditionalBarringInfo();
+ serviceInfos.put(
+ halBarringInfo.service, new BarringServiceInfo(
+ halBarringInfo.type, // will always be CONDITIONAL here
+ conditionalInfo.isBarred,
+ conditionalInfo.barringFactor,
+ conditionalInfo.barringTimeSeconds));
+ } else {
+ // Barring type is either NONE or UNCONDITIONAL
+ serviceInfos.put(
+ halBarringInfo.service, new BarringServiceInfo(halBarringInfo.type,
+ false, 0, 0));
+ }
+ }
+ return new BarringInfo(ci, serviceInfos);
+ }
+
+ /**
+ * Return whether a service is currently barred based on the BarringInfo
+ *
+ * @param service the service to be checked.
+ * @return true if the service is currently being barred, otherwise false
+ */
+ public boolean isServiceBarred(@BarringServiceType int service) {
+ BarringServiceInfo bsi = mBarringServiceInfos.get(service);
+ return bsi != null && (bsi.isBarred());
+ }
+
+ /**
+ * Get the BarringServiceInfo for a specified service.
+ *
+ * @return a BarringServiceInfo struct describing the current barring status for a service
+ */
+ public @NonNull BarringServiceInfo getBarringServiceInfo(@BarringServiceType int service) {
+ BarringServiceInfo bsi = mBarringServiceInfos.get(service);
+ // If barring is reported but not for a particular service, then we report the barring
+ // type as UNKNOWN; if the modem reports barring info but doesn't report for a particular
+ // service then we can safely assume that the service isn't barred (for instance because
+ // that particular service isn't applicable to the current RAN).
+ return (bsi != null) ? bsi : new BarringServiceInfo(
+ mBarringServiceInfos.size() > 0 ? BarringServiceInfo.BARRING_TYPE_NONE :
+ BarringServiceInfo.BARRING_TYPE_UNKNOWN);
+ }
+
+ /** @hide */
+ @SystemApi
+ public @NonNull BarringInfo createLocationInfoSanitizedCopy() {
+ return new BarringInfo(mCellIdentity.sanitizeLocationInfo(), mBarringServiceInfos);
+ }
+
+ /** @hide */
+ public BarringInfo(Parcel p) {
+ mCellIdentity = p.readParcelable(CellIdentity.class.getClassLoader());
+ mBarringServiceInfos = p.readSparseArray(BarringServiceInfo.class.getClassLoader());
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeParcelable(mCellIdentity, flags);
+ dest.writeSparseArray(mBarringServiceInfos);
+ }
+
+ public static final @NonNull Parcelable.Creator<BarringInfo> CREATOR =
+ new Parcelable.Creator<BarringInfo>() {
+ @Override
+ public BarringInfo createFromParcel(Parcel source) {
+ return new BarringInfo(source);
+ }
+
+ @Override
+ public BarringInfo[] newArray(int size) {
+ return new BarringInfo[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = mCellIdentity != null ? mCellIdentity.hashCode() : 7;
+ for (int i = 0; i < mBarringServiceInfos.size(); i++) {
+ hash = hash + 15 * mBarringServiceInfos.keyAt(i);
+ hash = hash + 31 * mBarringServiceInfos.valueAt(i).hashCode();
+ }
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object rhs) {
+ if (!(rhs instanceof BarringInfo)) return false;
+
+ BarringInfo bi = (BarringInfo) rhs;
+
+ if (hashCode() != bi.hashCode()) return false;
+
+ if (mBarringServiceInfos.size() != bi.mBarringServiceInfos.size()) return false;
+
+ for (int i = 0; i < mBarringServiceInfos.size(); i++) {
+ if (mBarringServiceInfos.keyAt(i) != bi.mBarringServiceInfos.keyAt(i)) return false;
+ if (!Objects.equals(mBarringServiceInfos.valueAt(i),
+ bi.mBarringServiceInfos.valueAt(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "BarringInfo {mCellIdentity=" + mCellIdentity
+ + ", mBarringServiceInfos=" + mBarringServiceInfos + "}";
+ }
+}
diff --git a/telephony/java/android/telephony/CallQuality.java b/telephony/java/android/telephony/CallQuality.java
index e01deb2..1e1cdba 100644
--- a/telephony/java/android/telephony/CallQuality.java
+++ b/telephony/java/android/telephony/CallQuality.java
@@ -80,6 +80,9 @@
private int mMaxRelativeJitter;
private int mAverageRoundTripTime;
private int mCodecType;
+ private boolean mRtpInactivityDetected;
+ private boolean mRxSilenceDetected;
+ private boolean mTxSilenceDetected;
/** @hide **/
public CallQuality(Parcel in) {
@@ -94,6 +97,9 @@
mMaxRelativeJitter = in.readInt();
mAverageRoundTripTime = in.readInt();
mCodecType = in.readInt();
+ mRtpInactivityDetected = in.readBoolean();
+ mRxSilenceDetected = in.readBoolean();
+ mTxSilenceDetected = in.readBoolean();
}
/** @hide **/
@@ -109,7 +115,7 @@
* @param numRtpPacketsReceived RTP packets received from network
* @param numRtpPacketsTransmittedLost RTP packets which were lost in network and never
* transmitted
- * @param numRtpPacketsNotReceived RTP packets which were lost in network and never recieved
+ * @param numRtpPacketsNotReceived RTP packets which were lost in network and never received
* @param averageRelativeJitter average relative jitter in milliseconds
* @param maxRelativeJitter maximum relative jitter in milliseconds
* @param averageRoundTripTime average round trip delay in milliseconds
@@ -127,6 +133,48 @@
int maxRelativeJitter,
int averageRoundTripTime,
int codecType) {
+ this(downlinkCallQualityLevel, uplinkCallQualityLevel, callDuration,
+ numRtpPacketsTransmitted, numRtpPacketsReceived, numRtpPacketsTransmittedLost,
+ numRtpPacketsNotReceived, averageRelativeJitter, maxRelativeJitter,
+ averageRoundTripTime, codecType, false, false, false);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param callQualityLevel the call quality level (see #CallQualityLevel)
+ * @param callDuration the call duration in milliseconds
+ * @param numRtpPacketsTransmitted RTP packets sent to network
+ * @param numRtpPacketsReceived RTP packets received from network
+ * @param numRtpPacketsTransmittedLost RTP packets which were lost in network and never
+ * transmitted
+ * @param numRtpPacketsNotReceived RTP packets which were lost in network and never received
+ * @param averageRelativeJitter average relative jitter in milliseconds
+ * @param maxRelativeJitter maximum relative jitter in milliseconds
+ * @param averageRoundTripTime average round trip delay in milliseconds
+ * @param codecType the codec type
+ * @param rtpInactivityDetected True if no incoming RTP is received for a continuous duration of
+ * 4 seconds
+ * @param rxSilenceDetected True if only silence RTP packets are received for 20 seconds
+ * immediately after call is connected
+ * @param txSilenceDetected True if only silence RTP packets are sent for 20 seconds immediately
+ * after call is connected
+ */
+ public CallQuality(
+ @CallQualityLevel int downlinkCallQualityLevel,
+ @CallQualityLevel int uplinkCallQualityLevel,
+ int callDuration,
+ int numRtpPacketsTransmitted,
+ int numRtpPacketsReceived,
+ int numRtpPacketsTransmittedLost,
+ int numRtpPacketsNotReceived,
+ int averageRelativeJitter,
+ int maxRelativeJitter,
+ int averageRoundTripTime,
+ int codecType,
+ boolean rtpInactivityDetected,
+ boolean rxSilenceDetected,
+ boolean txSilenceDetected) {
this.mDownlinkCallQualityLevel = downlinkCallQualityLevel;
this.mUplinkCallQualityLevel = uplinkCallQualityLevel;
this.mCallDuration = callDuration;
@@ -138,6 +186,9 @@
this.mMaxRelativeJitter = maxRelativeJitter;
this.mAverageRoundTripTime = averageRoundTripTime;
this.mCodecType = codecType;
+ this.mRtpInactivityDetected = rtpInactivityDetected;
+ this.mRxSilenceDetected = rxSilenceDetected;
+ this.mTxSilenceDetected = txSilenceDetected;
}
// getters
@@ -226,6 +277,29 @@
}
/**
+ * Returns true if no rtp packets are received continuously for the last 4 seconds
+ */
+ public boolean isRtpInactivityDetected() {
+ return mRtpInactivityDetected;
+ }
+
+ /**
+ * Returns true if only silence rtp packets are received for a duration of 20 seconds starting
+ * at call setup
+ */
+ public boolean isIncomingSilenceDetected() {
+ return mRxSilenceDetected;
+ }
+
+ /**
+ * Returns true if only silence rtp packets are sent for a duration of 20 seconds starting at
+ * call setup
+ */
+ public boolean isOutgoingSilenceDetected() {
+ return mTxSilenceDetected;
+ }
+
+ /**
* Returns the codec type. This value corresponds to the AUDIO_QUALITY_* constants in
* {@link ImsStreamMediaProfile}.
*
@@ -270,6 +344,9 @@
+ " maxRelativeJitter=" + mMaxRelativeJitter
+ " averageRoundTripTime=" + mAverageRoundTripTime
+ " codecType=" + mCodecType
+ + " rtpInactivityDetected=" + mRtpInactivityDetected
+ + " txSilenceDetected=" + mRxSilenceDetected
+ + " rxSilenceDetected=" + mTxSilenceDetected
+ "}";
}
@@ -286,7 +363,10 @@
mAverageRelativeJitter,
mMaxRelativeJitter,
mAverageRoundTripTime,
- mCodecType);
+ mCodecType,
+ mRtpInactivityDetected,
+ mRxSilenceDetected,
+ mTxSilenceDetected);
}
@Override
@@ -311,7 +391,10 @@
&& mAverageRelativeJitter == s.mAverageRelativeJitter
&& mMaxRelativeJitter == s.mMaxRelativeJitter
&& mAverageRoundTripTime == s.mAverageRoundTripTime
- && mCodecType == s.mCodecType);
+ && mCodecType == s.mCodecType
+ && mRtpInactivityDetected == s.mRtpInactivityDetected
+ && mRxSilenceDetected == s.mRxSilenceDetected
+ && mTxSilenceDetected == s.mTxSilenceDetected);
}
/**
@@ -336,6 +419,9 @@
dest.writeInt(mMaxRelativeJitter);
dest.writeInt(mAverageRoundTripTime);
dest.writeInt(mCodecType);
+ dest.writeBoolean(mRtpInactivityDetected);
+ dest.writeBoolean(mRxSilenceDetected);
+ dest.writeBoolean(mTxSilenceDetected);
}
public static final @android.annotation.NonNull Parcelable.Creator<CallQuality> CREATOR = new Parcelable.Creator() {
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index eea08dc..5a7c3b3 100755
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -24,17 +24,17 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.os.PersistableBundle;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.service.carrier.CarrierService;
import android.telecom.TelecomManager;
import android.telephony.ims.ImsReasonInfo;
import com.android.internal.telephony.ICarrierConfigLoader;
+import com.android.telephony.Rlog;
/**
* Provides access to telephony configuration values that are carrier-specific.
@@ -299,7 +299,6 @@
/**
* A string array containing numbers that shouldn't be included in the call log.
- * @hide
*/
public static final String KEY_UNLOGGABLE_NUMBERS_STRING_ARRAY =
"unloggable_numbers_string_array";
@@ -312,12 +311,11 @@
KEY_HIDE_CARRIER_NETWORK_SETTINGS_BOOL = "hide_carrier_network_settings_bool";
/**
- * Do only allow auto selection in Advanced Network Settings when in home network.
+ * Only allow auto selection in Advanced Network Settings when in home network.
* Manual selection is allowed when in roaming network.
- * @hide
*/
- public static final String
- KEY_ONLY_AUTO_SELECT_IN_HOME_NETWORK_BOOL = "only_auto_select_in_home_network";
+ public static final String KEY_ONLY_AUTO_SELECT_IN_HOME_NETWORK_BOOL =
+ "only_auto_select_in_home_network";
/**
* Control whether users receive a simplified network settings UI and improved network
@@ -581,9 +579,6 @@
* registration state to change. That is, turning on or off mobile data will not cause VT to be
* enabled or disabled.
* When {@code false}, disabling mobile data will cause VT to be de-registered.
- * <p>
- * See also {@link #KEY_VILTE_DATA_IS_METERED_BOOL}.
- * @hide
*/
public static final String KEY_IGNORE_DATA_ENABLED_CHANGED_FOR_VIDEO_CALLS =
"ignore_data_enabled_changed_for_video_calls";
@@ -647,7 +642,6 @@
/**
* Default WFC_IMS_enabled: true VoWiFi by default is on
* false VoWiFi by default is off
- * @hide
*/
public static final String KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL =
"carrier_default_wfc_ims_enabled_bool";
@@ -671,6 +665,12 @@
"carrier_promote_wfc_on_call_fail_bool";
/**
+ * Flag specifying whether provisioning is required for RCS.
+ */
+ public static final String KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL =
+ "carrier_rcs_provisioning_required_bool";
+
+ /**
* Flag specifying whether provisioning is required for VoLTE, Video Telephony, and WiFi
* Calling.
*/
@@ -713,9 +713,7 @@
*
* As of now, Verizon is the only carrier enforcing this dependency in their
* WFC awareness and activation requirements.
- *
- * @hide
- * */
+ */
public static final String KEY_CARRIER_VOLTE_OVERRIDE_WFC_PROVISIONING_BOOL
= "carrier_volte_override_wfc_provisioning_bool";
@@ -847,9 +845,12 @@
"carrier_force_disable_etws_cmas_test_bool";
/**
- * The default flag specifying whether "Turn on Notifications" option will be always shown in
- * Settings->More->Emergency broadcasts menu regardless developer options is turned on or not.
+ * The default flag specifying whether "Allow alerts" option will be always shown in
+ * emergency alerts settings regardless developer options is turned on or not.
+ *
+ * @deprecated The allow alerts option is always shown now. No longer need a config for that.
*/
+ @Deprecated
public static final String KEY_ALWAYS_SHOW_EMERGENCY_ALERT_ONOFF_BOOL =
"always_show_emergency_alert_onoff_bool";
@@ -1073,7 +1074,6 @@
*
* When {@code false}, the old behavior is used, where the toggle in accessibility settings is
* used to set the IMS stack's RTT enabled state.
- * @hide
*/
public static final String KEY_IGNORE_RTT_MODE_SETTING_BOOL =
"ignore_rtt_mode_setting_bool";
@@ -1114,7 +1114,6 @@
* Determines whether the IMS conference merge process supports and returns its participants
* data. When {@code true}, on merge complete, conference call would have a list of its
* participants returned in XML format, {@code false otherwise}.
- * @hide
*/
public static final String KEY_SUPPORT_IMS_CONFERENCE_EVENT_PACKAGE_BOOL =
"support_ims_conference_event_package_bool";
@@ -1187,20 +1186,18 @@
public static final String KEY_ENABLE_APPS_STRING_ARRAY = "enable_apps_string_array";
/**
- * Determine whether user can switch Wi-Fi preferred or Cellular preferred in calling preference.
+ * Determine whether user can switch Wi-Fi preferred or Cellular preferred
+ * in calling preference.
* Some operators support Wi-Fi Calling only, not VoLTE.
* They don't need "Cellular preferred" option.
- * In this case, set uneditalbe attribute for preferred preference.
- * @hide
+ * In this case, set uneditable attribute for preferred preference.
*/
public static final String KEY_EDITABLE_WFC_MODE_BOOL = "editable_wfc_mode_bool";
- /**
- * Flag to indicate if Wi-Fi needs to be disabled in ECBM
- * @hide
- **/
- public static final String
- KEY_CONFIG_WIFI_DISABLE_IN_ECBM = "config_wifi_disable_in_ecbm";
+ /**
+ * Flag to indicate if Wi-Fi needs to be disabled in ECBM.
+ */
+ public static final String KEY_CONFIG_WIFI_DISABLE_IN_ECBM = "config_wifi_disable_in_ecbm";
/**
* List operator-specific error codes and indices of corresponding error strings in
@@ -1264,9 +1261,8 @@
public static final String KEY_WFC_SPN_USE_ROOT_LOCALE = "wfc_spn_use_root_locale";
/**
- * The Component Name of the activity that can setup the emergency addrees for WiFi Calling
+ * The Component Name of the activity that can setup the emergency address for WiFi Calling
* as per carrier requirement.
- * @hide
*/
public static final String KEY_WFC_EMERGENCY_ADDRESS_CARRIER_APP_STRING =
"wfc_emergency_address_carrier_app_string";
@@ -1430,22 +1426,19 @@
public static final String KEY_ALLOW_ADDING_APNS_BOOL = "allow_adding_apns_bool";
/**
- * APN types that user is not allowed to modify
- * @hide
+ * APN types that user is not allowed to modify.
*/
public static final String KEY_READ_ONLY_APN_TYPES_STRING_ARRAY =
"read_only_apn_types_string_array";
/**
- * APN fields that user is not allowed to modify
- * @hide
+ * APN fields that user is not allowed to modify.
*/
public static final String KEY_READ_ONLY_APN_FIELDS_STRING_ARRAY =
"read_only_apn_fields_string_array";
/**
* Default value of APN types field if not specified by user when adding/modifying an APN.
- * @hide
*/
public static final String KEY_APN_SETTINGS_DEFAULT_APN_TYPES_STRING_ARRAY =
"apn_settings_default_apn_types_string_array";
@@ -1476,29 +1469,25 @@
"hide_digits_helper_text_on_stk_input_screen_bool";
/**
- * Boolean indicating if show data RAT icon on status bar even when data is disabled
- * @hide
+ * Boolean indicating if show data RAT icon on status bar even when data is disabled.
*/
public static final String KEY_ALWAYS_SHOW_DATA_RAT_ICON_BOOL =
"always_show_data_rat_icon_bool";
/**
- * Boolean indicating if default data account should show LTE or 4G icon
- * @hide
+ * Boolean indicating if default data account should show LTE or 4G icon.
*/
public static final String KEY_SHOW_4G_FOR_LTE_DATA_ICON_BOOL =
"show_4g_for_lte_data_icon_bool";
/**
* Boolean indicating if default data account should show 4G icon when in 3G.
- * @hide
*/
public static final String KEY_SHOW_4G_FOR_3G_DATA_ICON_BOOL =
"show_4g_for_3g_data_icon_bool";
/**
- * Boolean indicating if lte+ icon should be shown if available
- * @hide
+ * Boolean indicating if LTE+ icon should be shown if available.
*/
public static final String KEY_HIDE_LTE_PLUS_DATA_ICON_BOOL =
"hide_lte_plus_data_icon_bool";
@@ -1513,10 +1502,8 @@
"operator_name_filter_pattern_string";
/**
- * The string is used to compare with operator name. If it matches the pattern then show
- * specific data icon.
- *
- * @hide
+ * The string is used to compare with operator name.
+ * If it matches the pattern then show specific data icon.
*/
public static final String KEY_SHOW_CARRIER_DATA_ICON_PATTERN_STRING =
"show_carrier_data_icon_pattern_string";
@@ -1529,33 +1516,28 @@
"show_precise_failed_cause_bool";
/**
- * Boolean to decide whether lte is enabled.
- * @hide
+ * Boolean to decide whether LTE is enabled.
*/
public static final String KEY_LTE_ENABLED_BOOL = "lte_enabled_bool";
/**
* Boolean to decide whether TD-SCDMA is supported.
- * @hide
*/
public static final String KEY_SUPPORT_TDSCDMA_BOOL = "support_tdscdma_bool";
/**
* A list of mcc/mnc that support TD-SCDMA for device when connect to the roaming network.
- * @hide
*/
public static final String KEY_SUPPORT_TDSCDMA_ROAMING_NETWORKS_STRING_ARRAY =
"support_tdscdma_roaming_networks_string_array";
/**
* Boolean to decide whether world mode is enabled.
- * @hide
*/
public static final String KEY_WORLD_MODE_ENABLED_BOOL = "world_mode_enabled_bool";
/**
* Flatten {@link android.content.ComponentName} of the carrier's settings activity.
- * @hide
*/
public static final String KEY_CARRIER_SETTINGS_ACTIVITY_COMPONENT_NAME_STRING =
"carrier_settings_activity_component_name_string";
@@ -1609,25 +1591,23 @@
/**
* Defines carrier-specific actions which act upon
* com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED, used for customization of the
- * default carrier app
+ * default carrier app.
* Format: "CARRIER_ACTION_IDX, ..."
* Where {@code CARRIER_ACTION_IDX} is an integer defined in
- * {@link com.android.carrierdefaultapp.CarrierActionUtils CarrierActionUtils}
+ * com.android.carrierdefaultapp.CarrierActionUtils
* Example:
- * {@link com.android.carrierdefaultapp.CarrierActionUtils#CARRIER_ACTION_DISABLE_METERED_APNS
- * disable_metered_apns}
- * @hide
+ * com.android.carrierdefaultapp.CarrierActionUtils#CARRIER_ACTION_DISABLE_METERED_APNS
+ * disables metered APNs
*/
- @UnsupportedAppUsage
+ @SuppressLint("IntentName")
public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_REDIRECTION_STRING_ARRAY =
"carrier_default_actions_on_redirection_string_array";
/**
- * Defines carrier-specific actions which act upon
- * com.android.internal.telephony.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED
+ * Defines carrier-specific actions which act upon CARRIER_SIGNAL_REQUEST_NETWORK_FAILED
* and configured signal args:
- * {@link com.android.internal.telephony.TelephonyIntents#EXTRA_APN_TYPE_KEY apnType},
- * {@link com.android.internal.telephony.TelephonyIntents#EXTRA_ERROR_CODE_KEY errorCode}
+ * android.telephony.TelephonyManager#EXTRA_APN_TYPE,
+ * android.telephony.TelephonyManager#EXTRA_ERROR_CODE
* used for customization of the default carrier app
* Format:
* {
@@ -1635,42 +1615,41 @@
* "APN_1, ERROR_CODE_2 : CARRIER_ACTION_IDX_1 "
* }
* Where {@code APN_1} is a string defined in
- * {@link com.android.internal.telephony.PhoneConstants PhoneConstants}
+ * com.android.internal.telephony.PhoneConstants
* Example: "default"
*
- * {@code ERROR_CODE_1} is an integer defined in
- * {@link DataFailCause DcFailure}
+ * {@code ERROR_CODE_1} is an integer defined in android.telephony.DataFailCause
* Example:
- * {@link DataFailCause#MISSING_UNKNOWN_APN}
+ * android.telephony.DataFailCause#MISSING_UNKNOWN_APN
*
* {@code CARRIER_ACTION_IDX_1} is an integer defined in
- * {@link com.android.carrierdefaultapp.CarrierActionUtils CarrierActionUtils}
+ * com.android.carrierdefaultapp.CarrierActionUtils
* Example:
- * {@link com.android.carrierdefaultapp.CarrierActionUtils#CARRIER_ACTION_DISABLE_METERED_APNS}
- * @hide
+ * com.android.carrierdefaultapp.CarrierActionUtils#CARRIER_ACTION_DISABLE_METERED_APNS
+ * disables metered APNs
*/
+ @SuppressLint("IntentName")
public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_DCFAILURE_STRING_ARRAY =
"carrier_default_actions_on_dcfailure_string_array";
/**
- * Defines carrier-specific actions which act upon
- * com.android.internal.telephony.CARRIER_SIGNAL_RESET, used for customization of the
- * default carrier app
+ * Defines carrier-specific actions which act upon CARRIER_SIGNAL_RESET,
+ * used for customization of the default carrier app.
* Format: "CARRIER_ACTION_IDX, ..."
* Where {@code CARRIER_ACTION_IDX} is an integer defined in
- * {@link com.android.carrierdefaultapp.CarrierActionUtils CarrierActionUtils}
+ * com.android.carrierdefaultapp.CarrierActionUtils
* Example:
- * {@link com.android.carrierdefaultapp.CarrierActionUtils
- * #CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS clear all notifications on reset}
- * @hide
+ * com.android.carrierdefaultapp.CarrierActionUtils#CARRIER_ACTION_CANCEL_ALL_NOTIFICATIONS
+ * clears all notifications on reset
*/
+ @SuppressLint("IntentName")
public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_RESET =
"carrier_default_actions_on_reset_string_array";
/**
* Defines carrier-specific actions which act upon
* com.android.internal.telephony.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE,
- * used for customization of the default carrier app
+ * used for customization of the default carrier app.
* Format:
* {
* "true : CARRIER_ACTION_IDX_1",
@@ -1678,17 +1657,17 @@
* }
* Where {@code true} is a boolean indicates default network available/unavailable
* Where {@code CARRIER_ACTION_IDX} is an integer defined in
- * {@link com.android.carrierdefaultapp.CarrierActionUtils CarrierActionUtils}
+ * com.android.carrierdefaultapp.CarrierActionUtils CarrierActionUtils
* Example:
- * {@link com.android.carrierdefaultapp.CarrierActionUtils
- * #CARRIER_ACTION_ENABLE_DEFAULT_URL_HANDLER enable the app as the default URL handler}
- * @hide
+ * com.android.carrierdefaultapp.CarrierActionUtils#CARRIER_ACTION_ENABLE_DEFAULT_URL_HANDLER
+ * enables the app as the default URL handler
*/
+ @SuppressLint("IntentName")
public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_DEFAULT_NETWORK_AVAILABLE =
"carrier_default_actions_on_default_network_available_string_array";
+
/**
- * Defines a list of acceptable redirection url for default carrier app
- * @hides
+ * Defines a list of acceptable redirection url for default carrier app.
*/
public static final String KEY_CARRIER_DEFAULT_REDIRECTION_URL_STRING_ARRAY =
"carrier_default_redirection_url_string_array";
@@ -1816,10 +1795,10 @@
/**
* Determines whether to enable enhanced call blocking feature on the device.
- * @see SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED
- * @see SystemContract#ENHANCED_SETTING_KEY_BLOCK_PRIVATE
- * @see SystemContract#ENHANCED_SETTING_KEY_BLOCK_PAYPHONE
- * @see SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNKNOWN
+ * android.provider.BlockedNumberContract.SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNREGISTERED
+ * android.provider.BlockedNumberContract.SystemContract#ENHANCED_SETTING_KEY_BLOCK_PRIVATE
+ * android.provider.BlockedNumberContract.SystemContract#ENHANCED_SETTING_KEY_BLOCK_PAYPHONE
+ * android.provider.BlockedNumberContract.SystemContract#ENHANCED_SETTING_KEY_BLOCK_UNKNOWN
*
* <p>
* 1. For Single SIM(SS) device, it can be customized in both carrier_config_mccmnc.xml
@@ -1829,7 +1808,6 @@
* function is used regardless of SIM.
* <p>
* If {@code true} enable enhanced call blocking feature on the device, {@code false} otherwise.
- * @hide
*/
public static final String KEY_SUPPORT_ENHANCED_CALL_BLOCKING_BOOL =
"support_enhanced_call_blocking_bool";
@@ -1940,7 +1918,6 @@
/**
* Flag indicating whether the carrier supports call deflection for an incoming IMS call.
- * @hide
*/
public static final String KEY_CARRIER_ALLOW_DEFLECT_IMS_CALL_BOOL =
"carrier_allow_deflect_ims_call_bool";
@@ -2005,8 +1982,6 @@
/**
* Whether system apps are allowed to use fallback if carrier video call is not available.
* Defaults to {@code true}.
- *
- * @hide
*/
public static final String KEY_ALLOW_VIDEO_CALLING_FALLBACK_BOOL =
"allow_video_calling_fallback_bool";
@@ -2044,9 +2019,8 @@
"enhanced_4g_lte_title_variant_bool";
/**
- * The index indicates the carrier specified title string of Enahnce 4G LTE Mode settings.
+ * The index indicates the carrier specified title string of Enhanced 4G LTE Mode settings.
* Default value is 0, which indicates the default title string.
- * @hide
*/
public static final String KEY_ENHANCED_4G_LTE_TITLE_VARIANT_INT =
"enhanced_4g_lte_title_variant_int";
@@ -2090,15 +2064,13 @@
* {@link #KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL} is false. If
* {@link #KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL} is true, this
* configuration is ignored and roaming preference cannot be changed.
- * @hide
*/
public static final String KEY_EDITABLE_WFC_ROAMING_MODE_BOOL =
"editable_wfc_roaming_mode_bool";
/**
- * Flag specifying wether to show blocking pay phone option in blocked numbers screen. Only show
- * the option if payphone call presentation represents in the carrier's region.
- * @hide
+ * Flag specifying whether to show blocking pay phone option in blocked numbers screen.
+ * Only show the option if payphone call presentation is present in the carrier's region.
*/
public static final java.lang.String KEY_SHOW_BLOCKING_PAY_PHONE_OPTION_BOOL =
"show_blocking_pay_phone_option_bool";
@@ -2108,7 +2080,6 @@
* {@code false} - roaming preference can be selected separately from the home preference.
* {@code true} - roaming preference is the same as home preference and
* {@link #KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT} is used as the default value.
- * @hide
*/
public static final String KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL =
"use_wfc_home_network_mode_in_roaming_network_bool";
@@ -2136,7 +2107,6 @@
* while the device is registered over WFC. Default value is -1, which indicates
* that this notification is not pertinent for a particular carrier. We've added a delay
* to prevent false positives.
- * @hide
*/
public static final String KEY_EMERGENCY_NOTIFICATION_DELAY_INT =
"emergency_notification_delay_int";
@@ -2187,7 +2157,6 @@
/**
* Flag specifying whether to show an alert dialog for video call charges.
* By default this value is {@code false}.
- * @hide
*/
public static final String KEY_SHOW_VIDEO_CALL_CHARGES_ALERT_DIALOG_BOOL =
"show_video_call_charges_alert_dialog_bool";
@@ -2474,14 +2443,12 @@
/**
* Identifies if the key is available for WLAN or EPDG or both. The value is a bitmask.
* 0 indicates that neither EPDG or WLAN is enabled.
- * 1 indicates that key type {@link TelephonyManager#KEY_TYPE_EPDG} is enabled.
- * 2 indicates that key type {@link TelephonyManager#KEY_TYPE_WLAN} is enabled.
+ * 1 indicates that key type TelephonyManager#KEY_TYPE_EPDG is enabled.
+ * 2 indicates that key type TelephonyManager#KEY_TYPE_WLAN is enabled.
* 3 indicates that both are enabled.
- * @hide
*/
public static final String IMSI_KEY_AVAILABILITY_INT = "imsi_key_availability_int";
-
/**
* Key identifying if the CDMA Caller ID presentation and suppression MMI codes
* should be converted to 3GPP CLIR codes when a multimode (CDMA+UMTS+LTE) device is roaming
@@ -2496,7 +2463,6 @@
/**
* Flag specifying whether IMS registration state menu is shown in Status Info setting,
* default to false.
- * @hide
*/
public static final String KEY_SHOW_IMS_REGISTRATION_STATUS_BOOL =
"show_ims_registration_status_bool";
@@ -2552,7 +2518,6 @@
/**
* The flag to disable the popup dialog which warns the user of data charges.
- * @hide
*/
public static final String KEY_DISABLE_CHARGE_INDICATION_BOOL =
"disable_charge_indication_bool";
@@ -2617,15 +2582,13 @@
/**
* Determines whether any carrier has been identified and its specific config has been applied,
* default to false.
- * @hide
*/
public static final String KEY_CARRIER_CONFIG_APPLIED_BOOL = "carrier_config_applied_bool";
/**
* Determines whether we should show a warning asking the user to check with their carrier
- * on pricing when the user enabled data roaming.
+ * on pricing when the user enabled data roaming,
* default to false.
- * @hide
*/
public static final String KEY_CHECK_PRICING_WITH_CARRIER_FOR_DATA_ROAMING_BOOL =
"check_pricing_with_carrier_data_roaming_bool";
@@ -2777,10 +2740,10 @@
* Specifies a carrier-defined {@link android.telecom.CallRedirectionService} which Telecom
* will bind to for outgoing calls. An empty string indicates that no carrier-defined
* {@link android.telecom.CallRedirectionService} is specified.
- * @hide
*/
public static final String KEY_CALL_REDIRECTION_SERVICE_COMPONENT_NAME_STRING =
"call_redirection_service_component_name_string";
+
/**
* Support for the original string display of CDMA MO call.
* By default, it is disabled.
@@ -2893,8 +2856,8 @@
"call_waiting_service_class_int";
/**
- * This configuration allow the system UI to display different 5G icon for different 5G
- * scenario.
+ * This configuration allows the system UI to display different 5G icons for different 5G
+ * scenarios.
*
* There are five 5G scenarios:
* 1. connected_mmwave: device currently connected to 5G cell as the secondary cell and using
@@ -2911,24 +2874,22 @@
* 5G cell as a secondary cell) but the use of 5G is restricted.
*
* The configured string contains multiple key-value pairs separated by comma. For each pair,
- * the key and value is separated by a colon. The key is corresponded to a 5G status above and
+ * the key and value are separated by a colon. The key corresponds to a 5G status above and
* the value is the icon name. Use "None" as the icon name if no icon should be shown in a
* specific 5G scenario. If the scenario is "None", config can skip this key and value.
*
* Icon name options: "5G_Plus", "5G".
*
* Here is an example:
- * UE want to display 5G_Plus icon for scenario#1, and 5G icon for scenario#2; otherwise no
+ * UE wants to display 5G_Plus icon for scenario#1, and 5G icon for scenario#2; otherwise not
* define.
* The configuration is: "connected_mmwave:5G_Plus,connected:5G"
- *
- * @hide
*/
public static final String KEY_5G_ICON_CONFIGURATION_STRING =
"5g_icon_configuration_string";
/**
- * Timeout in second for displaying 5G icon, default value is 0 which means the timer is
+ * Timeout in seconds for displaying 5G icon, default value is 0 which means the timer is
* disabled.
*
* System UI will show the 5G icon and start a timer with the timeout from this config when the
@@ -2937,8 +2898,6 @@
*
* If 5G is reacquired during this timer, the timer is canceled and restarted when 5G is next
* lost. Allows us to momentarily lose 5G without blinking the icon.
- *
- * @hide
*/
public static final String KEY_5G_ICON_DISPLAY_GRACE_PERIOD_SEC_INT =
"5g_icon_display_grace_period_sec_int";
@@ -3103,8 +3062,6 @@
* signal bar of primary network. By default it will be false, meaning whenever data
* is going over opportunistic network, signal bar will reflect signal strength and rat
* icon of that network.
- *
- * @hide
*/
public static final String KEY_ALWAYS_SHOW_PRIMARY_SIGNAL_BAR_IN_OPPORTUNISTIC_NETWORK_BOOLEAN =
"always_show_primary_signal_bar_in_opportunistic_network_boolean";
@@ -3120,6 +3077,15 @@
"data_switch_validation_timeout_long";
/**
+ * Specifies whether the system should prefix the EAP method to the anonymous identity.
+ * The following prefix will be added if this key is set to TRUE:
+ * EAP-AKA: "0"
+ * EAP-SIM: "1"
+ * EAP-AKA_PRIME: "6"
+ */
+ public static final String ENABLE_EAP_METHOD_PREFIX_BOOL = "enable_eap_method_prefix_bool";
+
+ /**
* GPS configs. See the GNSS HAL documentation for more details.
*/
public static final class Gps {
@@ -3317,8 +3283,6 @@
/**
* Determines whether wifi calling location privacy policy is shown.
- *
- * @hide
*/
public static final String KEY_SHOW_WFC_LOCATION_PRIVACY_POLICY_BOOL =
"show_wfc_location_privacy_policy_bool";
@@ -3390,12 +3354,17 @@
/** Prefix of all Ims.KEY_* constants. */
public static final String KEY_PREFIX = "ims.";
- //TODO: Add configs related to IMS.
+ /**
+ * Delay in milliseconds to turn off wifi when IMS is registered over wifi.
+ */
+ public static final String KEY_WIFI_OFF_DEFERRING_TIME_INT =
+ KEY_PREFIX + "wifi_off_deferring_time_int";
private Ims() {}
private static PersistableBundle getDefaults() {
PersistableBundle defaults = new PersistableBundle();
+ defaults.putInt(KEY_WIFI_OFF_DEFERRING_TIME_INT, 0);
return defaults;
}
}
@@ -3424,8 +3393,8 @@
"support_wps_over_ims_bool";
/**
- * Holds the list of carrier certificate hashes. Note that each carrier has its own certificates
- * @hide
+ * Holds the list of carrier certificate hashes.
+ * Note that each carrier has its own certificates.
*/
public static final String KEY_CARRIER_CERTIFICATE_STRING_ARRAY =
"carrier_certificate_string_array";
@@ -3483,6 +3452,7 @@
sDefaults.putInt(KEY_CARRIER_DEFAULT_WFC_IMS_MODE_INT, 2);
sDefaults.putInt(KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_MODE_INT, 2);
sDefaults.putBoolean(KEY_CARRIER_FORCE_DISABLE_ETWS_CMAS_TEST_BOOL, false);
+ sDefaults.putBoolean(KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL, true);
sDefaults.putBoolean(KEY_CARRIER_VOLTE_PROVISIONING_REQUIRED_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_UT_PROVISIONING_REQUIRED_BOOL, false);
sDefaults.putBoolean(KEY_CARRIER_SUPPORTS_SS_OVER_UT_BOOL, false);
@@ -3934,6 +3904,7 @@
CellSignalStrengthLte.USE_RSRP | CellSignalStrengthLte.USE_RSSNR);
// Default wifi configurations.
sDefaults.putAll(Wifi.getDefaults());
+ sDefaults.putBoolean(ENABLE_EAP_METHOD_PREFIX_BOOL, false);
}
/**
@@ -4195,8 +4166,11 @@
/** @hide */
@Nullable
private ICarrierConfigLoader getICarrierConfigLoader() {
- return ICarrierConfigLoader.Stub
- .asInterface(ServiceManager.getService(Context.CARRIER_CONFIG_SERVICE));
+ return ICarrierConfigLoader.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getCarrierConfigServiceRegisterer()
+ .get());
}
/**
diff --git a/telephony/java/android/telephony/CbGeoUtils.java b/telephony/java/android/telephony/CbGeoUtils.java
index 84be4e8..719ba8d 100644
--- a/telephony/java/android/telephony/CbGeoUtils.java
+++ b/telephony/java/android/telephony/CbGeoUtils.java
@@ -16,6 +16,8 @@
package android.telephony;
+import com.android.telephony.Rlog;
+
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.text.TextUtils;
diff --git a/telephony/java/android/telephony/CellIdentity.java b/telephony/java/android/telephony/CellIdentity.java
index e523fba..3f0aeb5 100644
--- a/telephony/java/android/telephony/CellIdentity.java
+++ b/telephony/java/android/telephony/CellIdentity.java
@@ -20,10 +20,13 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
+import android.hardware.radio.V1_0.CellInfoType;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
+import com.android.telephony.Rlog;
+
import java.util.Objects;
import java.util.UUID;
@@ -185,6 +188,15 @@
@SystemApi
public abstract @NonNull CellLocation asCellLocation();
+ /**
+ * Create and a return a new instance of CellIdentity with location-identifying information
+ * removed.
+ *
+ * @hide
+ */
+ @SystemApi
+ public abstract @NonNull CellIdentity sanitizeLocationInfo();
+
@Override
public boolean equals(Object other) {
if (!(other instanceof CellIdentity)) {
@@ -312,4 +324,103 @@
return true;
}
+ /** @hide */
+ public static CellIdentity create(android.hardware.radio.V1_0.CellIdentity cellIdentity) {
+ if (cellIdentity == null) return null;
+ switch(cellIdentity.cellInfoType) {
+ case CellInfoType.GSM: {
+ if (cellIdentity.cellIdentityGsm.size() == 1) {
+ return new CellIdentityGsm(cellIdentity.cellIdentityGsm.get(0));
+ }
+ break;
+ }
+ case CellInfoType.WCDMA: {
+ if (cellIdentity.cellIdentityWcdma.size() == 1) {
+ return new CellIdentityWcdma(cellIdentity.cellIdentityWcdma.get(0));
+ }
+ break;
+ }
+ case CellInfoType.TD_SCDMA: {
+ if (cellIdentity.cellIdentityTdscdma.size() == 1) {
+ return new CellIdentityTdscdma(cellIdentity.cellIdentityTdscdma.get(0));
+ }
+ break;
+ }
+ case CellInfoType.LTE: {
+ if (cellIdentity.cellIdentityLte.size() == 1) {
+ return new CellIdentityLte(cellIdentity.cellIdentityLte.get(0));
+ }
+ break;
+ }
+ case CellInfoType.CDMA: {
+ if (cellIdentity.cellIdentityCdma.size() == 1) {
+ return new CellIdentityCdma(cellIdentity.cellIdentityCdma.get(0));
+ }
+ break;
+ }
+ case CellInfoType.NONE: break;
+ default: break;
+ }
+ return null;
+ }
+
+ /** @hide */
+ public static CellIdentity create(android.hardware.radio.V1_2.CellIdentity cellIdentity) {
+ if (cellIdentity == null) return null;
+ switch(cellIdentity.cellInfoType) {
+ case CellInfoType.GSM: {
+ if (cellIdentity.cellIdentityGsm.size() == 1) {
+ return new CellIdentityGsm(cellIdentity.cellIdentityGsm.get(0));
+ }
+ break;
+ }
+ case CellInfoType.WCDMA: {
+ if (cellIdentity.cellIdentityWcdma.size() == 1) {
+ return new CellIdentityWcdma(cellIdentity.cellIdentityWcdma.get(0));
+ }
+ break;
+ }
+ case CellInfoType.TD_SCDMA: {
+ if (cellIdentity.cellIdentityTdscdma.size() == 1) {
+ return new CellIdentityTdscdma(cellIdentity.cellIdentityTdscdma.get(0));
+ }
+ break;
+ }
+ case CellInfoType.LTE: {
+ if (cellIdentity.cellIdentityLte.size() == 1) {
+ return new CellIdentityLte(cellIdentity.cellIdentityLte.get(0));
+ }
+ break;
+ }
+ case CellInfoType.CDMA: {
+ if (cellIdentity.cellIdentityCdma.size() == 1) {
+ return new CellIdentityCdma(cellIdentity.cellIdentityCdma.get(0));
+ }
+ break;
+ }
+ case CellInfoType.NONE: break;
+ default: break;
+ }
+ return null;
+ }
+
+ /** @hide */
+ public static CellIdentity create(android.hardware.radio.V1_5.CellIdentity ci) {
+ if (ci == null) return null;
+ switch (ci.getDiscriminator()) {
+ case android.hardware.radio.V1_5.CellIdentity.hidl_discriminator.gsm:
+ return new CellIdentityGsm(ci.gsm());
+ case android.hardware.radio.V1_5.CellIdentity.hidl_discriminator.cdma:
+ return new CellIdentityCdma(ci.cdma());
+ case android.hardware.radio.V1_5.CellIdentity.hidl_discriminator.lte:
+ return new CellIdentityLte(ci.lte());
+ case android.hardware.radio.V1_5.CellIdentity.hidl_discriminator.wcdma:
+ return new CellIdentityWcdma(ci.wcdma());
+ case android.hardware.radio.V1_5.CellIdentity.hidl_discriminator.tdscdma:
+ return new CellIdentityTdscdma(ci.tdscdma());
+ case android.hardware.radio.V1_5.CellIdentity.hidl_discriminator.nr:
+ return new CellIdentityNr(ci.nr());
+ default: return null;
+ }
+ }
}
diff --git a/telephony/java/android/telephony/CellIdentityCdma.java b/telephony/java/android/telephony/CellIdentityCdma.java
index 54236b42..1a6bf33 100644
--- a/telephony/java/android/telephony/CellIdentityCdma.java
+++ b/telephony/java/android/telephony/CellIdentityCdma.java
@@ -128,7 +128,8 @@
}
/** @hide */
- public CellIdentityCdma sanitizeLocationInfo() {
+ @Override
+ public @NonNull CellIdentityCdma sanitizeLocationInfo() {
return new CellIdentityCdma(CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE,
CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE,
mAlphaLong, mAlphaShort);
diff --git a/telephony/java/android/telephony/CellIdentityGsm.java b/telephony/java/android/telephony/CellIdentityGsm.java
index 4e4454d..49f425a 100644
--- a/telephony/java/android/telephony/CellIdentityGsm.java
+++ b/telephony/java/android/telephony/CellIdentityGsm.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.telephony.gsm.GsmCellLocation;
import android.text.TextUtils;
@@ -104,7 +104,8 @@
}
/** @hide */
- public CellIdentityGsm sanitizeLocationInfo() {
+ @Override
+ public @NonNull CellIdentityGsm sanitizeLocationInfo() {
return new CellIdentityGsm(CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE,
CellInfo.UNAVAILABLE, mMccStr, mMncStr, mAlphaLong, mAlphaShort);
}
diff --git a/telephony/java/android/telephony/CellIdentityLte.java b/telephony/java/android/telephony/CellIdentityLte.java
index c3fc73b..bc46550 100644
--- a/telephony/java/android/telephony/CellIdentityLte.java
+++ b/telephony/java/android/telephony/CellIdentityLte.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Parcel;
import android.telephony.gsm.GsmCellLocation;
@@ -121,7 +121,8 @@
}
/** @hide */
- public CellIdentityLte sanitizeLocationInfo() {
+ @Override
+ public @NonNull CellIdentityLte sanitizeLocationInfo() {
return new CellIdentityLte(CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE,
CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE,
mMccStr, mMncStr, mAlphaLong, mAlphaShort);
diff --git a/telephony/java/android/telephony/CellIdentityNr.java b/telephony/java/android/telephony/CellIdentityNr.java
index e3fec7b..f08a580 100644
--- a/telephony/java/android/telephony/CellIdentityNr.java
+++ b/telephony/java/android/telephony/CellIdentityNr.java
@@ -69,7 +69,8 @@
}
/** @hide */
- public CellIdentityNr sanitizeLocationInfo() {
+ @Override
+ public @NonNull CellIdentityNr sanitizeLocationInfo() {
return new CellIdentityNr(CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE,
mMccStr, mMncStr, CellInfo.UNAVAILABLE, mAlphaLong, mAlphaShort);
}
diff --git a/telephony/java/android/telephony/CellIdentityTdscdma.java b/telephony/java/android/telephony/CellIdentityTdscdma.java
index 8f812b6..4bb3a95 100644
--- a/telephony/java/android/telephony/CellIdentityTdscdma.java
+++ b/telephony/java/android/telephony/CellIdentityTdscdma.java
@@ -97,7 +97,8 @@
}
/** @hide */
- public CellIdentityTdscdma sanitizeLocationInfo() {
+ @Override
+ public @NonNull CellIdentityTdscdma sanitizeLocationInfo() {
return new CellIdentityTdscdma(mMccStr, mMncStr, CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE,
CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, mAlphaLong, mAlphaShort);
}
diff --git a/telephony/java/android/telephony/CellIdentityWcdma.java b/telephony/java/android/telephony/CellIdentityWcdma.java
index 556bc32..4ecd134 100644
--- a/telephony/java/android/telephony/CellIdentityWcdma.java
+++ b/telephony/java/android/telephony/CellIdentityWcdma.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.telephony.gsm.GsmCellLocation;
import android.text.TextUtils;
@@ -98,7 +98,8 @@
}
/** @hide */
- public CellIdentityWcdma sanitizeLocationInfo() {
+ @Override
+ public @NonNull CellIdentityWcdma sanitizeLocationInfo() {
return new CellIdentityWcdma(CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE,
CellInfo.UNAVAILABLE, CellInfo.UNAVAILABLE, mMccStr, mMncStr,
mAlphaLong, mAlphaShort);
diff --git a/telephony/java/android/telephony/CellInfo.java b/telephony/java/android/telephony/CellInfo.java
index ae45307..475c99b 100644
--- a/telephony/java/android/telephony/CellInfo.java
+++ b/telephony/java/android/telephony/CellInfo.java
@@ -18,7 +18,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.hardware.radio.V1_4.CellInfo.Info;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/telephony/java/android/telephony/CellInfoCdma.java b/telephony/java/android/telephony/CellInfoCdma.java
index 30b131f..acb21f4 100644
--- a/telephony/java/android/telephony/CellInfoCdma.java
+++ b/telephony/java/android/telephony/CellInfoCdma.java
@@ -16,12 +16,13 @@
package android.telephony;
+import com.android.telephony.Rlog;
+
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
-import android.telephony.Rlog;
/**
* A {@link CellInfo} representing a CDMA cell that provides identity and measurement info.
diff --git a/telephony/java/android/telephony/CellInfoGsm.java b/telephony/java/android/telephony/CellInfoGsm.java
index 137f97e..79a9d44 100644
--- a/telephony/java/android/telephony/CellInfoGsm.java
+++ b/telephony/java/android/telephony/CellInfoGsm.java
@@ -16,11 +16,12 @@
package android.telephony;
+import com.android.telephony.Rlog;
+
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
-import android.telephony.Rlog;
/**
* A {@link CellInfo} representing a GSM cell that provides identity and measurement info.
diff --git a/telephony/java/android/telephony/CellInfoLte.java b/telephony/java/android/telephony/CellInfoLte.java
index da7b7ab..fed3ebf 100644
--- a/telephony/java/android/telephony/CellInfoLte.java
+++ b/telephony/java/android/telephony/CellInfoLte.java
@@ -16,8 +16,10 @@
package android.telephony;
+import com.android.telephony.Rlog;
+
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/telephony/java/android/telephony/CellInfoTdscdma.java b/telephony/java/android/telephony/CellInfoTdscdma.java
index f1305f5..58ff8c9 100644
--- a/telephony/java/android/telephony/CellInfoTdscdma.java
+++ b/telephony/java/android/telephony/CellInfoTdscdma.java
@@ -16,6 +16,8 @@
package android.telephony;
+import com.android.telephony.Rlog;
+
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/telephony/java/android/telephony/CellInfoWcdma.java b/telephony/java/android/telephony/CellInfoWcdma.java
index ee5fec8..33f6a55 100644
--- a/telephony/java/android/telephony/CellInfoWcdma.java
+++ b/telephony/java/android/telephony/CellInfoWcdma.java
@@ -18,7 +18,7 @@
import android.os.Parcel;
import android.os.Parcelable;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
import java.util.Objects;
diff --git a/telephony/java/android/telephony/CellLocation.java b/telephony/java/android/telephony/CellLocation.java
index 0133153..2d0bd52 100644
--- a/telephony/java/android/telephony/CellLocation.java
+++ b/telephony/java/android/telephony/CellLocation.java
@@ -16,13 +16,12 @@
package android.telephony;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Bundle;
import android.os.RemoteException;
-import android.os.ServiceManager;
-
-import android.annotation.UnsupportedAppUsage;
import android.telephony.cdma.CdmaCellLocation;
import android.telephony.gsm.GsmCellLocation;
+
import com.android.internal.telephony.ITelephony;
import com.android.internal.telephony.PhoneConstants;
@@ -38,7 +37,11 @@
*/
public static void requestLocationUpdate() {
try {
- ITelephony phone = ITelephony.Stub.asInterface(ServiceManager.getService("phone"));
+ ITelephony phone = ITelephony.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getTelephonyServiceRegisterer()
+ .get());
if (phone != null) {
phone.updateServiceLocation();
}
@@ -50,8 +53,7 @@
/**
* Create a new CellLocation from a intent notifier Bundle
*
- * This method is used by PhoneStateIntentReceiver and maybe by
- * external applications.
+ * This method maybe used by external applications.
*
* @param bundle Bundle from intent notifier
* @return newly created CellLocation
diff --git a/telephony/java/android/telephony/CellSignalStrengthCdma.java b/telephony/java/android/telephony/CellSignalStrengthCdma.java
index 1998439..cab3b0c 100644
--- a/telephony/java/android/telephony/CellSignalStrengthCdma.java
+++ b/telephony/java/android/telephony/CellSignalStrengthCdma.java
@@ -20,7 +20,7 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PersistableBundle;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
import java.util.Objects;
diff --git a/telephony/java/android/telephony/CellSignalStrengthGsm.java b/telephony/java/android/telephony/CellSignalStrengthGsm.java
index fb16d54..28052aa 100644
--- a/telephony/java/android/telephony/CellSignalStrengthGsm.java
+++ b/telephony/java/android/telephony/CellSignalStrengthGsm.java
@@ -16,13 +16,14 @@
package android.telephony;
+import com.android.telephony.Rlog;
+
import android.annotation.IntRange;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PersistableBundle;
-import android.telephony.Rlog;
import java.util.Objects;
diff --git a/telephony/java/android/telephony/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java
index 8df9d23..2ef2a52 100644
--- a/telephony/java/android/telephony/CellSignalStrengthLte.java
+++ b/telephony/java/android/telephony/CellSignalStrengthLte.java
@@ -16,8 +16,10 @@
package android.telephony;
+import com.android.telephony.Rlog;
+
import android.annotation.IntRange;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PersistableBundle;
diff --git a/telephony/java/android/telephony/CellSignalStrengthNr.java b/telephony/java/android/telephony/CellSignalStrengthNr.java
index f31fafe..4d67bcf 100644
--- a/telephony/java/android/telephony/CellSignalStrengthNr.java
+++ b/telephony/java/android/telephony/CellSignalStrengthNr.java
@@ -16,6 +16,8 @@
package android.telephony;
+import com.android.telephony.Rlog;
+
import android.annotation.IntDef;
import android.annotation.IntRange;
import android.os.Parcel;
@@ -155,7 +157,17 @@
* @param ss signal strength from modem.
*/
public CellSignalStrengthNr(android.hardware.radio.V1_4.NrSignalStrength ss) {
- this(ss.csiRsrp, ss.csiRsrq, ss.csiSinr, ss.ssRsrp, ss.ssRsrq, ss.ssSinr);
+ this(flip(ss.csiRsrp), flip(ss.csiRsrq), ss.csiSinr, flip(ss.ssRsrp), flip(ss.ssRsrq),
+ ss.ssSinr);
+ }
+
+ /**
+ * Flip sign cell strength value when taking in the value from hal
+ * @param val cell strength value
+ * @return flipped value
+ */
+ private static int flip(int val) {
+ return val != CellInfo.UNAVAILABLE ? -val : val;
}
/**
diff --git a/telephony/java/android/telephony/CellSignalStrengthTdscdma.java b/telephony/java/android/telephony/CellSignalStrengthTdscdma.java
index f4a3dbb..3bd9d58 100644
--- a/telephony/java/android/telephony/CellSignalStrengthTdscdma.java
+++ b/telephony/java/android/telephony/CellSignalStrengthTdscdma.java
@@ -16,6 +16,8 @@
package android.telephony;
+import com.android.telephony.Rlog;
+
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.os.Parcel;
diff --git a/telephony/java/android/telephony/CellSignalStrengthWcdma.java b/telephony/java/android/telephony/CellSignalStrengthWcdma.java
index 4dc54f0..535e952 100644
--- a/telephony/java/android/telephony/CellSignalStrengthWcdma.java
+++ b/telephony/java/android/telephony/CellSignalStrengthWcdma.java
@@ -16,13 +16,14 @@
package android.telephony;
+import com.android.telephony.Rlog;
+
import android.annotation.IntRange;
import android.annotation.StringDef;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.PersistableBundle;
-import android.telephony.Rlog;
import android.text.TextUtils;
import java.lang.annotation.Retention;
diff --git a/telephony/java/android/telephony/DisconnectCause.java b/telephony/java/android/telephony/DisconnectCause.java
index 04ec4b6..be85b30 100644
--- a/telephony/java/android/telephony/DisconnectCause.java
+++ b/telephony/java/android/telephony/DisconnectCause.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* Describes the cause of a disconnected call. Those disconnect causes can be converted into a more
@@ -360,6 +360,12 @@
*/
public static final int OUTGOING_EMERGENCY_CALL_PLACED = 80;
+ /**
+ * Indicates that incoming call was rejected by the modem before the call went in ringing
+ */
+ public static final int INCOMING_AUTO_REJECTED = 81;
+
+
//*********************************************************************************************
// When adding a disconnect type:
// 1) Update toString() with the newly added disconnect type.
@@ -536,6 +542,8 @@
return "WFC_SERVICE_NOT_AVAILABLE_IN_THIS_LOCATION";
case OUTGOING_EMERGENCY_CALL_PLACED:
return "OUTGOING_EMERGENCY_CALL_PLACED";
+ case INCOMING_AUTO_REJECTED:
+ return "INCOMING_AUTO_REJECTED";
default:
return "INVALID: " + cause;
}
diff --git a/telephony/java/android/telephony/ImsManager.java b/telephony/java/android/telephony/ImsManager.java
index 39af34c..c706d28 100644
--- a/telephony/java/android/telephony/ImsManager.java
+++ b/telephony/java/android/telephony/ImsManager.java
@@ -17,6 +17,8 @@
package android.telephony.ims;
import android.annotation.NonNull;
+import android.annotation.SdkConstant;
+import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
@@ -35,7 +37,26 @@
private Context mContext;
- /** @hide */
+ /**
+ * <p>Broadcast Action: Indicates that an IMS operation was rejected by the network due to it
+ * not being authorized on the network.
+ * May include the {@link SubscriptionManager#EXTRA_SUBSCRIPTION_INDEX} extra to also specify
+ * which subscription the operation was rejected for.
+ * <p class="note">
+ * Carrier applications may listen to this broadcast to be notified of possible IMS provisioning
+ * issues.
+ */
+ // Moved from TelephonyIntents, need to keep backwards compatibility with OEM apps that have
+ // this value hard-coded in BroadcastReceiver.
+ @SuppressLint("ActionValue")
+ @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION =
+ "com.android.internal.intent.action.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION";
+
+ /**
+ * Use {@link Context#getSystemService(String)} to get an instance of this class.
+ * @hide
+ */
public ImsManager(@NonNull Context context) {
mContext = context;
}
diff --git a/telephony/java/android/telephony/JapanesePhoneNumberFormatter.java b/telephony/java/android/telephony/JapanesePhoneNumberFormatter.java
index 92a674c..1c31368 100644
--- a/telephony/java/android/telephony/JapanesePhoneNumberFormatter.java
+++ b/telephony/java/android/telephony/JapanesePhoneNumberFormatter.java
@@ -16,7 +16,7 @@
package android.telephony;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.text.Editable;
/*
diff --git a/telephony/java/android/telephony/NeighboringCellInfo.java b/telephony/java/android/telephony/NeighboringCellInfo.java
index 023ed30..5f46799 100644
--- a/telephony/java/android/telephony/NeighboringCellInfo.java
+++ b/telephony/java/android/telephony/NeighboringCellInfo.java
@@ -24,7 +24,7 @@
import static android.telephony.TelephonyManager.NETWORK_TYPE_UMTS;
import static android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/telephony/java/android/telephony/NetworkRegistrationInfo.java b/telephony/java/android/telephony/NetworkRegistrationInfo.java
index fc717e7..cbd5ed6 100644
--- a/telephony/java/android/telephony/NetworkRegistrationInfo.java
+++ b/telephony/java/android/telephony/NetworkRegistrationInfo.java
@@ -46,13 +46,17 @@
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = "DOMAIN_", value = {DOMAIN_CS, DOMAIN_PS})
+ @IntDef(prefix = "DOMAIN_", value = {DOMAIN_UNKNOWN, DOMAIN_CS, DOMAIN_PS, DOMAIN_CS_PS})
public @interface Domain {}
+ /** Unknown / Unspecified domain */
+ public static final int DOMAIN_UNKNOWN = 0;
/** Circuit switching domain */
- public static final int DOMAIN_CS = 1;
+ public static final int DOMAIN_CS = android.hardware.radio.V1_5.Domain.CS;
/** Packet switching domain */
- public static final int DOMAIN_PS = 2;
+ public static final int DOMAIN_PS = android.hardware.radio.V1_5.Domain.PS;
+ /** Applicable to both CS and PS Domain */
+ public static final int DOMAIN_CS_PS = DOMAIN_CS | DOMAIN_PS;
/**
* Network registration state
@@ -504,11 +508,21 @@
}
}
+ /** @hide */
+ static @NonNull String domainToString(@Domain int domain) {
+ switch (domain) {
+ case DOMAIN_CS: return "CS";
+ case DOMAIN_PS: return "PS";
+ case DOMAIN_CS_PS: return "CS_PS";
+ default: return "UNKNOWN";
+ }
+ }
+
@NonNull
@Override
public String toString() {
return new StringBuilder("NetworkRegistrationInfo{")
- .append(" domain=").append((mDomain == DOMAIN_CS) ? "CS" : "PS")
+ .append(" domain=").append(domainToString(mDomain))
.append(" transportType=").append(
AccessNetworkConstants.transportTypeToString(mTransportType))
.append(" registrationState=").append(registrationStateToString(mRegistrationState))
@@ -646,7 +660,7 @@
* .build();
* </code></pre>
*/
- public static final class Builder{
+ public static final class Builder {
@Domain
private int mDomain;
diff --git a/telephony/java/android/telephony/NetworkScan.java b/telephony/java/android/telephony/NetworkScan.java
index 202da68..a6dedf7 100644
--- a/telephony/java/android/telephony/NetworkScan.java
+++ b/telephony/java/android/telephony/NetworkScan.java
@@ -16,10 +16,10 @@
package android.telephony;
+import com.android.telephony.Rlog;
+
import android.annotation.IntDef;
-import android.content.Context;
import android.os.RemoteException;
-import android.os.ServiceManager;
import com.android.internal.telephony.ITelephony;
@@ -148,6 +148,9 @@
private ITelephony getITelephony() {
return ITelephony.Stub.asInterface(
- ServiceManager.getService(Context.TELEPHONY_SERVICE));
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getTelephonyServiceRegisterer()
+ .get());
}
}
diff --git a/telephony/java/android/telephony/NetworkService.java b/telephony/java/android/telephony/NetworkService.java
index 8c5e107..844289c 100644
--- a/telephony/java/android/telephony/NetworkService.java
+++ b/telephony/java/android/telephony/NetworkService.java
@@ -16,6 +16,8 @@
package android.telephony;
+import com.android.telephony.Rlog;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
diff --git a/telephony/java/android/telephony/NetworkServiceCallback.java b/telephony/java/android/telephony/NetworkServiceCallback.java
index 89b9665..214ab41 100644
--- a/telephony/java/android/telephony/NetworkServiceCallback.java
+++ b/telephony/java/android/telephony/NetworkServiceCallback.java
@@ -16,6 +16,8 @@
package android.telephony;
+import com.android.telephony.Rlog;
+
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.SystemApi;
diff --git a/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java b/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java
index ac6bcaa..d4ed860 100644
--- a/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java
+++ b/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java
@@ -16,15 +16,14 @@
package android.telephony;
-import com.android.i18n.phonenumbers.AsYouTypeFormatter;
-import com.android.i18n.phonenumbers.PhoneNumberUtil;
-
-import android.annotation.UnsupportedAppUsage;
-import android.telephony.PhoneNumberUtils;
+import android.compat.annotation.UnsupportedAppUsage;
import android.text.Editable;
import android.text.Selection;
import android.text.TextWatcher;
+import com.android.i18n.phonenumbers.AsYouTypeFormatter;
+import com.android.i18n.phonenumbers.PhoneNumberUtil;
+
import java.util.Locale;
/**
diff --git a/telephony/java/android/telephony/PhoneNumberUtils.java b/telephony/java/android/telephony/PhoneNumberUtils.java
index 67afa7d..2f9e6ac 100644
--- a/telephony/java/android/telephony/PhoneNumberUtils.java
+++ b/telephony/java/android/telephony/PhoneNumberUtils.java
@@ -16,12 +16,14 @@
package android.telephony;
+import com.android.telephony.Rlog;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
diff --git a/telephony/java/android/telephony/PreciseCallState.java b/telephony/java/android/telephony/PreciseCallState.java
index bfa6326..98eeacf 100644
--- a/telephony/java/android/telephony/PreciseCallState.java
+++ b/telephony/java/android/telephony/PreciseCallState.java
@@ -19,14 +19,12 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.Annotation.DisconnectCauses;
import android.telephony.Annotation.PreciseCallStates;
import android.telephony.Annotation.PreciseDisconnectCauses;
-import android.telephony.DisconnectCause;
-import android.telephony.PreciseDisconnectCause;
import java.util.Objects;
diff --git a/telephony/java/android/telephony/PreciseDataConnectionState.java b/telephony/java/android/telephony/PreciseDataConnectionState.java
index 0610796..0cfb8c5 100644
--- a/telephony/java/android/telephony/PreciseDataConnectionState.java
+++ b/telephony/java/android/telephony/PreciseDataConnectionState.java
@@ -20,7 +20,10 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.Compatibility;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
+import android.compat.annotation.UnsupportedAppUsage;
import android.net.LinkProperties;
import android.os.Build;
import android.os.Parcel;
@@ -31,8 +34,6 @@
import android.telephony.Annotation.NetworkType;
import android.telephony.data.ApnSetting;
-import dalvik.system.VMRuntime;
-
import java.util.Objects;
@@ -134,6 +135,13 @@
}
/**
+ * To check the SDK version for {@link PreciseDataConnectionState#getDataConnectionState}.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+ private static final long GET_DATA_CONNECTION_STATE_CODE_CHANGE = 147600208L;
+
+ /**
* Returns the state of data connection that supported the apn types returned by
* {@link #getDataConnectionApnTypeBitMask()}
*
@@ -144,7 +152,7 @@
@SystemApi
public @DataState int getDataConnectionState() {
if (mState == TelephonyManager.DATA_DISCONNECTING
- && VMRuntime.getRuntime().getTargetSdkVersion() < Build.VERSION_CODES.R) {
+ && !Compatibility.isChangeEnabled(GET_DATA_CONNECTION_STATE_CODE_CHANGE)) {
return TelephonyManager.DATA_CONNECTED;
}
diff --git a/telephony/java/android/telephony/RadioAccessFamily.java b/telephony/java/android/telephony/RadioAccessFamily.java
index 28f6515..bc84738 100644
--- a/telephony/java/android/telephony/RadioAccessFamily.java
+++ b/telephony/java/android/telephony/RadioAccessFamily.java
@@ -16,9 +16,7 @@
package android.telephony;
-import android.annotation.UnsupportedAppUsage;
-import android.hardware.radio.V1_0.RadioTechnology;
-import android.hardware.radio.V1_4.CellInfo.Info;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java
index 599cd9f..2c8014e 100644
--- a/telephony/java/android/telephony/ServiceState.java
+++ b/telephony/java/android/telephony/ServiceState.java
@@ -21,7 +21,7 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
@@ -34,6 +34,8 @@
import android.telephony.NetworkRegistrationInfo.NRState;
import android.text.TextUtils;
+import com.android.telephony.Rlog;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -351,6 +353,7 @@
private String mOperatorAlphaLongRaw;
private String mOperatorAlphaShortRaw;
+ private boolean mIsDataRoamingFromRegistration;
private boolean mIsIwlanPreferred;
/**
@@ -436,6 +439,7 @@
mNrFrequencyRange = s.mNrFrequencyRange;
mOperatorAlphaLongRaw = s.mOperatorAlphaLongRaw;
mOperatorAlphaShortRaw = s.mOperatorAlphaShortRaw;
+ mIsDataRoamingFromRegistration = s.mIsDataRoamingFromRegistration;
mIsIwlanPreferred = s.mIsIwlanPreferred;
}
@@ -470,6 +474,7 @@
mNrFrequencyRange = in.readInt();
mOperatorAlphaLongRaw = in.readString();
mOperatorAlphaShortRaw = in.readString();
+ mIsDataRoamingFromRegistration = in.readBoolean();
mIsIwlanPreferred = in.readBoolean();
}
@@ -497,6 +502,7 @@
out.writeInt(mNrFrequencyRange);
out.writeString(mOperatorAlphaLongRaw);
out.writeString(mOperatorAlphaShortRaw);
+ out.writeBoolean(mIsDataRoamingFromRegistration);
out.writeBoolean(mIsIwlanPreferred);
}
@@ -545,7 +551,7 @@
* @see #STATE_EMERGENCY_ONLY
* @see #STATE_POWER_OFF
*
- * @return current data registration state {@link RegState}
+ * @return current data registration state
*
* @hide
*/
@@ -562,7 +568,7 @@
* @see #STATE_EMERGENCY_ONLY
* @see #STATE_POWER_OFF
*
- * @return current data registration state {@link RegState}
+ * @return current data registration state
*
* @hide
*/
@@ -582,8 +588,8 @@
*/
@DuplexMode
public int getDuplexMode() {
- // only support LTE duplex mode
- if (!isLte(getRilDataRadioTechnology())) {
+ // support LTE/NR duplex mode
+ if (!isPsOnlyTech(getRilDataRadioTechnology())) {
return DUPLEX_MODE_UNKNOWN;
}
@@ -647,7 +653,9 @@
}
/**
- * Get current data network roaming type
+ * Get whether the current data network is roaming.
+ * This value may be overwritten by resource overlay or carrier configuration.
+ * @see #getDataRoamingFromRegistration() to get the value from the network registration.
* @return roaming type
* @hide
*/
@@ -657,18 +665,25 @@
}
/**
- * Get whether data network registration state is roaming
+ * Set whether the data network registration state is roaming.
+ * This should only be set to the roaming value received
+ * once the data registration phase has completed.
+ * @hide
+ */
+ public void setDataRoamingFromRegistration(boolean dataRoaming) {
+ mIsDataRoamingFromRegistration = dataRoaming;
+ }
+
+ /**
+ * Get whether data network registration state is roaming.
+ * This value is set directly from the modem and will not be overwritten
+ * by resource overlay or carrier configuration.
* @return true if registration indicates roaming, false otherwise
* @hide
*/
+ @SystemApi
public boolean getDataRoamingFromRegistration() {
- final NetworkRegistrationInfo regState = getNetworkRegistrationInfo(
- NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
- if (regState != null) {
- return regState.getRegistrationState()
- == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING;
- }
- return false;
+ return mIsDataRoamingFromRegistration;
}
/**
@@ -871,6 +886,7 @@
mNrFrequencyRange,
mOperatorAlphaLongRaw,
mOperatorAlphaShortRaw,
+ mIsDataRoamingFromRegistration,
mIsIwlanPreferred);
}
}
@@ -901,6 +917,7 @@
&& mNetworkRegistrationInfos.size() == s.mNetworkRegistrationInfos.size()
&& mNetworkRegistrationInfos.containsAll(s.mNetworkRegistrationInfos)
&& mNrFrequencyRange == s.mNrFrequencyRange
+ && mIsDataRoamingFromRegistration == s.mIsDataRoamingFromRegistration
&& mIsIwlanPreferred == s.mIsIwlanPreferred;
}
}
@@ -1060,6 +1077,8 @@
.append(", mNrFrequencyRange=").append(mNrFrequencyRange)
.append(", mOperatorAlphaLongRaw=").append(mOperatorAlphaLongRaw)
.append(", mOperatorAlphaShortRaw=").append(mOperatorAlphaShortRaw)
+ .append(", mIsDataRoamingFromRegistration=")
+ .append(mIsDataRoamingFromRegistration)
.append(", mIsIwlanPreferred=").append(mIsIwlanPreferred)
.append("}").toString();
}
@@ -1100,6 +1119,7 @@
}
mOperatorAlphaLongRaw = null;
mOperatorAlphaShortRaw = null;
+ mIsDataRoamingFromRegistration = false;
mIsIwlanPreferred = false;
}
@@ -1622,7 +1642,8 @@
* @return Current data network type
* @hide
*/
- @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
+ @SystemApi
+ @TestApi
public @NetworkType int getDataNetworkType() {
final NetworkRegistrationInfo iwlanRegInfo = getNetworkRegistrationInfo(
NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
@@ -1715,9 +1736,10 @@
}
/** @hide */
- public static boolean isLte(int radioTechnology) {
- return radioTechnology == RIL_RADIO_TECHNOLOGY_LTE ||
- radioTechnology == RIL_RADIO_TECHNOLOGY_LTE_CA;
+ public static boolean isPsOnlyTech(int radioTechnology) {
+ return radioTechnology == RIL_RADIO_TECHNOLOGY_LTE
+ || radioTechnology == RIL_RADIO_TECHNOLOGY_LTE_CA
+ || radioTechnology == RIL_RADIO_TECHNOLOGY_NR;
}
/** @hide */
@@ -1886,7 +1908,7 @@
synchronized (mNetworkRegistrationInfos) {
for (NetworkRegistrationInfo networkRegistrationInfo : mNetworkRegistrationInfos) {
- if (networkRegistrationInfo.getDomain() == domain) {
+ if ((networkRegistrationInfo.getDomain() & domain) != 0) {
list.add(new NetworkRegistrationInfo(networkRegistrationInfo));
}
}
@@ -1902,7 +1924,6 @@
* @param transportType The transport type
* @return The matching {@link NetworkRegistrationInfo}
* @hide
- *
*/
@Nullable
@SystemApi
@@ -1911,7 +1932,7 @@
synchronized (mNetworkRegistrationInfos) {
for (NetworkRegistrationInfo networkRegistrationInfo : mNetworkRegistrationInfos) {
if (networkRegistrationInfo.getTransportType() == transportType
- && networkRegistrationInfo.getDomain() == domain) {
+ && (networkRegistrationInfo.getDomain() & domain) != 0) {
return new NetworkRegistrationInfo(networkRegistrationInfo);
}
}
@@ -2045,4 +2066,27 @@
public boolean isIwlanPreferred() {
return mIsIwlanPreferred;
}
+ /**
+ * @return {@code true}Returns True whenever the modem is searching for service.
+ * To check both CS and PS domain
+ */
+
+ public boolean isSearching() {
+ NetworkRegistrationInfo psRegState = getNetworkRegistrationInfo(
+ NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+ if (psRegState != null && psRegState.getRegistrationState()
+ == NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_SEARCHING) {
+ return true;
+ }
+
+ NetworkRegistrationInfo csRegState = getNetworkRegistrationInfo(
+ NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+ if (csRegState != null && csRegState.getRegistrationState()
+ == NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_SEARCHING) {
+ return true;
+ }
+ return false;
+ }
}
diff --git a/telephony/java/android/telephony/SignalStrength.java b/telephony/java/android/telephony/SignalStrength.java
index 9aafc1b..1c58f8f 100644
--- a/telephony/java/android/telephony/SignalStrength.java
+++ b/telephony/java/android/telephony/SignalStrength.java
@@ -16,8 +16,11 @@
package android.telephony;
+import com.android.telephony.Rlog;
+
import android.annotation.NonNull;
-import android.annotation.UnsupportedAppUsage;
+import android.annotation.SystemApi;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
@@ -84,8 +87,7 @@
/**
* Create a new SignalStrength from a intent notifier Bundle
*
- * This method is used by PhoneStateIntentReceiver and maybe by
- * external applications.
+ * This method may be used by external applications.
*
* @param m Bundle from intent notifier
* @return newly created SignalStrength
@@ -275,8 +277,8 @@
*
* @hide
*/
- @UnsupportedAppUsage
- public SignalStrength(SignalStrength s) {
+ @SystemApi
+ public SignalStrength(@NonNull SignalStrength s) {
copyFrom(s);
}
@@ -332,17 +334,16 @@
/**
* {@link Parcelable.Creator}
*
- * @hide
*/
- @UnsupportedAppUsage
- public static final @android.annotation.NonNull Parcelable.Creator<SignalStrength> CREATOR = new Parcelable.Creator() {
- public SignalStrength createFromParcel(Parcel in) {
- return new SignalStrength(in);
- }
+ public static final @android.annotation.NonNull Parcelable.Creator<SignalStrength> CREATOR =
+ new Parcelable.Creator<SignalStrength>() {
+ public SignalStrength createFromParcel(Parcel in) {
+ return new SignalStrength(in);
+ }
- public SignalStrength[] newArray(int size) {
- return new SignalStrength[size];
- }
+ public SignalStrength[] newArray(int size) {
+ return new SignalStrength[size];
+ }
};
/**
diff --git a/telephony/java/android/telephony/SmsCbMessage.java b/telephony/java/android/telephony/SmsCbMessage.java
index 045d1eb..3c67094 100644
--- a/telephony/java/android/telephony/SmsCbMessage.java
+++ b/telephony/java/android/telephony/SmsCbMessage.java
@@ -557,7 +557,7 @@
public ContentValues getContentValues() {
ContentValues cv = new ContentValues(16);
cv.put(CellBroadcasts.SLOT_INDEX, mSlotIndex);
- cv.put(CellBroadcasts.SUB_ID, mSubId);
+ cv.put(CellBroadcasts.SUBSCRIPTION_ID, mSubId);
cv.put(CellBroadcasts.GEOGRAPHICAL_SCOPE, mGeographicalScope);
if (mLocation.getPlmn() != null) {
cv.put(CellBroadcasts.PLMN, mLocation.getPlmn());
@@ -621,7 +621,7 @@
int format = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.MESSAGE_FORMAT));
int priority = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.MESSAGE_PRIORITY));
int slotIndex = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.SLOT_INDEX));
- int subId = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.SUB_ID));
+ int subId = cursor.getInt(cursor.getColumnIndexOrThrow(CellBroadcasts.SUBSCRIPTION_ID));
String plmn;
int plmnColumn = cursor.getColumnIndex(CellBroadcasts.PLMN);
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index 7215ef8..fee6d3f 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -25,17 +25,17 @@
import android.annotation.SuppressAutoDoc;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
-import android.app.ActivityThread;
import android.app.PendingIntent;
+import android.compat.Compatibility;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
-import android.content.pm.PackageManager;
import android.database.CursorWindow;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
@@ -282,6 +282,42 @@
*/
public static final int SMS_MESSAGE_PERIOD_NOT_SPECIFIED = -1;
+ /** @hide */
+ @IntDef(prefix = { "PREMIUM_SMS_CONSENT" }, value = {
+ SmsManager.PREMIUM_SMS_CONSENT_UNKNOWN,
+ SmsManager.PREMIUM_SMS_CONSENT_ASK_USER,
+ SmsManager.PREMIUM_SMS_CONSENT_NEVER_ALLOW,
+ SmsManager.PREMIUM_SMS_CONSENT_ALWAYS_ALLOW
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface PremiumSmsConsent {}
+
+ /** Premium SMS Consent for the package is unknown. This indicates that the user
+ * has not set a permission for this package, because this package has never tried
+ * to send a premium SMS.
+ * @hide
+ */
+ @SystemApi
+ public static final int PREMIUM_SMS_CONSENT_UNKNOWN = 0;
+
+ /** Default premium SMS Consent (ask user for each premium SMS sent).
+ * @hide
+ */
+ @SystemApi
+ public static final int PREMIUM_SMS_CONSENT_ASK_USER = 1;
+
+ /** Premium SMS Consent when the owner has denied the app from sending premium SMS.
+ * @hide
+ */
+ @SystemApi
+ public static final int PREMIUM_SMS_CONSENT_NEVER_ALLOW = 2;
+
+ /** Premium SMS Consent when the owner has allowed the app to send premium SMS.
+ * @hide
+ */
+ @SystemApi
+ public static final int PREMIUM_SMS_CONSENT_ALWAYS_ALLOW = 3;
+
// result of asking the user for a subscription to perform an operation.
private interface SubscriptionResolverResult {
void onSuccess(int subId);
@@ -388,7 +424,7 @@
String destinationAddress, String scAddress, String text,
PendingIntent sentIntent, PendingIntent deliveryIntent) {
sendTextMessageInternal(destinationAddress, scAddress, text, sentIntent, deliveryIntent,
- true /* persistMessage*/, ActivityThread.currentPackageName());
+ true /* persistMessage*/, null);
}
/**
@@ -598,7 +634,7 @@
String destinationAddress, String scAddress, String text,
PendingIntent sentIntent, PendingIntent deliveryIntent) {
sendTextMessageInternal(destinationAddress, scAddress, text, sentIntent, deliveryIntent,
- false /* persistMessage */, ActivityThread.currentPackageName());
+ false /* persistMessage */, null);
}
private void sendTextMessageInternal(
@@ -641,7 +677,7 @@
ISms iSms = getISmsServiceOrThrow();
if (iSms != null) {
iSms.sendTextForSubscriberWithOptions(subId,
- ActivityThread.currentPackageName(), destinationAddress,
+ null, destinationAddress,
scAddress,
text, sentIntent, deliveryIntent, persistMessage, finalPriority,
expectMore, finalValidity);
@@ -663,7 +699,7 @@
ISms iSms = getISmsServiceOrThrow();
if (iSms != null) {
iSms.sendTextForSubscriberWithOptions(getSubscriptionId(),
- ActivityThread.currentPackageName(), destinationAddress,
+ null, destinationAddress,
scAddress,
text, sentIntent, deliveryIntent, persistMessage, finalPriority,
expectMore, finalValidity);
@@ -745,7 +781,11 @@
"Invalid pdu format. format must be either 3gpp or 3gpp2");
}
try {
- ISms iSms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+ ISms iSms = ISms.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSmsServiceRegisterer()
+ .get());
if (iSms != null) {
iSms.injectSmsPduForSubscriber(
getSubscriptionId(), pdu, format, receivedIntent);
@@ -881,7 +921,7 @@
String destinationAddress, String scAddress, ArrayList<String> parts,
ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents) {
sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents,
- deliveryIntents, true /* persistMessage*/, ActivityThread.currentPackageName());
+ deliveryIntents, true /* persistMessage*/, null);
}
/**
@@ -898,8 +938,9 @@
* subscription.
* </p>
*
- * @param packageName serves as the default package name if
- * {@link ActivityThread#currentPackageName()} is null.
+ * @param packageName serves as the default package name if the package name that is
+ * associated with the user id is null.
+ *
* @hide
*/
@SystemApi
@@ -909,9 +950,7 @@
@NonNull List<String> parts, @Nullable List<PendingIntent> sentIntents,
@Nullable List<PendingIntent> deliveryIntents, @NonNull String packageName) {
sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents,
- deliveryIntents, true /* persistMessage*/,
- ActivityThread.currentPackageName() == null
- ? packageName : ActivityThread.currentPackageName());
+ deliveryIntents, true /* persistMessage*/, packageName);
}
private void sendMultipartTextMessageInternal(
@@ -1012,7 +1051,7 @@
String destinationAddress, String scAddress, List<String> parts,
List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents) {
sendMultipartTextMessageInternal(destinationAddress, scAddress, parts, sentIntents,
- deliveryIntents, false /* persistMessage*/, ActivityThread.currentPackageName());
+ deliveryIntents, false /* persistMessage*/, null);
}
/**
@@ -1174,7 +1213,7 @@
ISms iSms = getISmsServiceOrThrow();
if (iSms != null) {
iSms.sendMultipartTextForSubscriberWithOptions(subId,
- ActivityThread.currentPackageName(), destinationAddress,
+ null, destinationAddress,
scAddress, parts, sentIntents, deliveryIntents,
persistMessage, finalPriority, expectMore, finalValidity);
}
@@ -1196,7 +1235,7 @@
ISms iSms = getISmsServiceOrThrow();
if (iSms != null) {
iSms.sendMultipartTextForSubscriberWithOptions(getSubscriptionId(),
- ActivityThread.currentPackageName(), destinationAddress,
+ null, destinationAddress,
scAddress, parts, sentIntents, deliveryIntents,
persistMessage, finalPriority, expectMore, finalValidity);
}
@@ -1327,7 +1366,7 @@
public void onSuccess(int subId) {
try {
ISms iSms = getISmsServiceOrThrow();
- iSms.sendDataForSubscriber(subId, ActivityThread.currentPackageName(),
+ iSms.sendDataForSubscriber(subId, null,
destinationAddress, scAddress, destinationPort & 0xFFFF, data,
sentIntent, deliveryIntent);
} catch (RemoteException e) {
@@ -1453,7 +1492,6 @@
private void resolveSubscriptionForOperation(SubscriptionResolverResult resolverResult) {
int subId = getSubscriptionId();
boolean isSmsSimPickActivityNeeded = false;
- final Context context = ActivityThread.currentApplication().getApplicationContext();
try {
ISms iSms = getISmsService();
if (iSms != null) {
@@ -1475,14 +1513,14 @@
return;
}
// We need to ask the user pick an appropriate subid for the operation.
- Log.d(TAG, "resolveSubscriptionForOperation isSmsSimPickActivityNeeded is true for package "
- + context.getPackageName());
+ Log.d(TAG, "resolveSubscriptionForOperation isSmsSimPickActivityNeeded is true for calling"
+ + " package. ");
try {
// Create the SMS pick activity and call back once the activity is complete. Can't do
// it here because we do not have access to the activity context that is performing this
// operation.
// Requires that the calling process has the SEND_SMS permission.
- getITelephony().enqueueSmsPickResult(context.getOpPackageName(),
+ getITelephony().enqueueSmsPickResult(null,
new IIntegerConsumer.Stub() {
@Override
public void accept(int subId) {
@@ -1500,6 +1538,13 @@
}
}
+ /**
+ * To check the SDK version for SmsManager.sendResolverResult method.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.P)
+ private static final long GET_TARGET_SDK_VERSION_CODE_CHANGE = 145147528L;
+
private void sendResolverResult(SubscriptionResolverResult resolverResult, int subId,
boolean pickActivityShown) {
if (SubscriptionManager.isValidSubscriptionId(subId)) {
@@ -1507,7 +1552,8 @@
return;
}
- if (getTargetSdkVersion() <= Build.VERSION_CODES.P && !pickActivityShown) {
+ if (!Compatibility.isChangeEnabled(GET_TARGET_SDK_VERSION_CODE_CHANGE)
+ && !pickActivityShown) {
// Do not fail, return a success with an INVALID subid for apps targeting P or below
// that tried to perform an operation and the SMS disambiguation dialog was never shown,
// as these applications may not have been written to handle the failure case properly.
@@ -1520,22 +1566,12 @@
}
}
- private static int getTargetSdkVersion() {
- final Context context = ActivityThread.currentApplication().getApplicationContext();
- int targetSdk;
- try {
- targetSdk = context.getPackageManager().getApplicationInfo(
- context.getOpPackageName(), 0).targetSdkVersion;
- } catch (PackageManager.NameNotFoundException e) {
- // Default to old behavior if we can not find this.
- targetSdk = -1;
- }
- return targetSdk;
- }
-
private static ITelephony getITelephony() {
ITelephony binder = ITelephony.Stub.asInterface(
- ServiceManager.getService(Context.TELEPHONY_SERVICE));
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getTelephonyServiceRegisterer()
+ .get());
if (binder == null) {
throw new RuntimeException("Could not find Telephony Service.");
}
@@ -1573,7 +1609,11 @@
}
private static ISms getISmsService() {
- return ISms.Stub.asInterface(ServiceManager.getService("isms"));
+ return ISms.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSmsServiceRegisterer()
+ .get());
}
/**
@@ -1611,7 +1651,7 @@
ISms iSms = getISmsService();
if (iSms != null) {
success = iSms.copyMessageToIccEfForSubscriber(getSubscriptionId(),
- ActivityThread.currentPackageName(),
+ null,
status, pdu, smsc);
}
} catch (RemoteException ex) {
@@ -1652,7 +1692,7 @@
ISms iSms = getISmsService();
if (iSms != null) {
success = iSms.updateMessageOnIccEfForSubscriber(getSubscriptionId(),
- ActivityThread.currentPackageName(),
+ null,
messageIndex, STATUS_ON_ICC_FREE, null /* pdu */);
}
} catch (RemoteException ex) {
@@ -1695,7 +1735,7 @@
ISms iSms = getISmsService();
if (iSms != null) {
success = iSms.updateMessageOnIccEfForSubscriber(getSubscriptionId(),
- ActivityThread.currentPackageName(),
+ null,
messageIndex, newStatus, pdu);
}
} catch (RemoteException ex) {
@@ -1747,7 +1787,7 @@
if (iSms != null) {
records = iSms.getAllMessagesFromIccEfForSubscriber(
getSubscriptionId(),
- ActivityThread.currentPackageName());
+ null);
}
} catch (RemoteException ex) {
// ignore it
@@ -2007,7 +2047,11 @@
public boolean isSMSPromptEnabled() {
ISms iSms = null;
try {
- iSms = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+ iSms = ISms.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSmsServiceRegisterer()
+ .get());
return iSms.isSMSPromptEnabled();
} catch (RemoteException ex) {
return false;
@@ -2434,14 +2478,18 @@
* @param sentIntent if not NULL this <code>PendingIntent</code> is
* broadcast when the message is successfully sent, or failed
* @throws IllegalArgumentException if contentUri is empty
+ * @deprecated use {@link MmsManager#sendMultimediaMessage} instead.
*/
public void sendMultimediaMessage(Context context, Uri contentUri, String locationUrl,
Bundle configOverrides, PendingIntent sentIntent) {
if (contentUri == null) {
throw new IllegalArgumentException("Uri contentUri null");
}
- MmsManager.getInstance().sendMultimediaMessage(getSubscriptionId(), contentUri,
- locationUrl, configOverrides, sentIntent);
+ MmsManager m = (MmsManager) context.getSystemService(Context.MMS_SERVICE);
+ if (m != null) {
+ m.sendMultimediaMessage(getSubscriptionId(), contentUri, locationUrl, configOverrides,
+ sentIntent);
+ }
}
/**
@@ -2465,6 +2513,7 @@
* @param downloadedIntent if not NULL this <code>PendingIntent</code> is
* broadcast when the message is downloaded, or the download is failed
* @throws IllegalArgumentException if locationUrl or contentUri is empty
+ * @deprecated use {@link MmsManager#downloadMultimediaMessage} instead.
*/
public void downloadMultimediaMessage(Context context, String locationUrl, Uri contentUri,
Bundle configOverrides, PendingIntent downloadedIntent) {
@@ -2474,8 +2523,11 @@
if (contentUri == null) {
throw new IllegalArgumentException("Uri contentUri null");
}
- MmsManager.getInstance().downloadMultimediaMessage(getSubscriptionId(), locationUrl,
- contentUri, configOverrides, downloadedIntent);
+ MmsManager m = (MmsManager) context.getSystemService(Context.MMS_SERVICE);
+ if (m != null) {
+ m.downloadMultimediaMessage(getSubscriptionId(), locationUrl, contentUri,
+ configOverrides, downloadedIntent);
+ }
}
// MMS send/download failure result codes
@@ -2504,22 +2556,31 @@
public static final String MESSAGE_STATUS_READ = "read";
/**
- * Get carrier-dependent configuration values.
+ * Get carrier-dependent MMS configuration values.
*
* <p class="note"><strong>Note:</strong> This method is intended for internal use by carrier
- * applications or the Telephony framework and will never trigger an SMS disambiguation
- * dialog. If this method is called on a device that has multiple active subscriptions, this
- * {@link SmsManager} instance has been created with {@link #getDefault()}, and no user-defined
- * default subscription is defined, the subscription ID associated with this message will be
- * INVALID, which will result in the operation being completed on the subscription associated
- * with logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the
- * operation is performed on the correct subscription.
+ * applications or the Telephony framework and will never trigger an SMS disambiguation dialog.
+ * If this method is called on a device that has multiple active subscriptions, this {@link
+ * SmsManager} instance has been created with {@link #getDefault()}, and no user-defined default
+ * subscription is defined, the subscription ID associated with this message will be INVALID,
+ * which will result in the operation being completed on the subscription associated with
+ * logical slot 0. Use {@link #getSmsManagerForSubscriptionId(int)} to ensure the operation is
+ * performed on the correct subscription.
* </p>
*
- * @return bundle key/values pairs of configuration values
+ * @return the bundle key/values pairs that contains MMS configuration values
+ * or an empty Bundle if they cannot be found.
*/
- public Bundle getCarrierConfigValues() {
- return MmsManager.getInstance().getCarrierConfigValues(getSubscriptionId());
+ @NonNull public Bundle getCarrierConfigValues() {
+ try {
+ ISms iSms = getISmsService();
+ if (iSms != null) {
+ return iSms.getCarrierConfigValuesForSubscriber(getSubscriptionId());
+ }
+ } catch (RemoteException ex) {
+ // ignore it
+ }
+ return new Bundle();
}
/**
@@ -2550,7 +2611,7 @@
try {
ISms iccSms = getISmsServiceOrThrow();
return iccSms.createAppSpecificSmsToken(getSubscriptionId(),
- ActivityThread.currentPackageName(), intent);
+ null, intent);
} catch (RemoteException ex) {
ex.rethrowFromSystemServer();
@@ -2670,7 +2731,7 @@
try {
ISms iccSms = getISmsServiceOrThrow();
return iccSms.createAppSpecificSmsTokenWithPackageInfo(getSubscriptionId(),
- ActivityThread.currentPackageName(), prefixes, intent);
+ null, prefixes, intent);
} catch (RemoteException ex) {
ex.rethrowFromSystemServer();
@@ -2761,7 +2822,7 @@
ISms iccISms = getISmsServiceOrThrow();
if (iccISms != null) {
return iccISms.checkSmsShortCodeDestination(getSubscriptionId(),
- ActivityThread.currentPackageName(), null, destAddress, countryIso);
+ null, null, destAddress, countryIso);
}
} catch (RemoteException e) {
Log.e(TAG, "checkSmsShortCodeDestination() RemoteException", e);
@@ -2797,7 +2858,7 @@
ISms iSms = getISmsService();
if (iSms != null) {
smsc = iSms.getSmscAddressFromIccEfForSubscriber(
- getSubscriptionId(), ActivityThread.currentPackageName());
+ getSubscriptionId(), null);
}
} catch (RemoteException ex) {
// ignore it
@@ -2831,11 +2892,60 @@
ISms iSms = getISmsService();
if (iSms != null) {
return iSms.setSmscAddressOnIccEfForSubscriber(
- smsc, getSubscriptionId(), ActivityThread.currentPackageName());
+ smsc, getSubscriptionId(), null);
}
} catch (RemoteException ex) {
// ignore it
}
return false;
}
+
+ /**
+ * Gets the premium SMS permission for the specified package. If the package has never
+ * been seen before, the default {@link SmsManager#PREMIUM_SMS_PERMISSION_ASK_USER}
+ * will be returned.
+ * @param packageName the name of the package to query permission
+ * @return one of {@link SmsManager#PREMIUM_SMS_CONSENT_UNKNOWN},
+ * {@link SmsManager#PREMIUM_SMS_CONSENT_ASK_USER},
+ * {@link SmsManager#PREMIUM_SMS_CONSENT_NEVER_ALLOW}, or
+ * {@link SmsManager#PREMIUM_SMS_CONSENT_ALWAYS_ALLOW}
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public @PremiumSmsConsent int getPremiumSmsConsent(@NonNull String packageName) {
+ int permission = 0;
+ try {
+ ISms iSms = getISmsService();
+ if (iSms != null) {
+ permission = iSms.getPremiumSmsPermission(packageName);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "getPremiumSmsPermission() RemoteException", e);
+ }
+ return permission;
+ }
+
+ /**
+ * Sets the premium SMS permission for the specified package and save the value asynchronously
+ * to persistent storage.
+ * @param packageName the name of the package to set permission
+ * @param permission one of {@link SmsManager#PREMIUM_SMS_CONSENT_ASK_USER},
+ * {@link SmsManager#PREMIUM_SMS_CONSENT_NEVER_ALLOW}, or
+ * {@link SmsManager#PREMIUM_SMS_CONSENT_ALWAYS_ALLOW}
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ public void setPremiumSmsConsent(
+ @NonNull String packageName, @PremiumSmsConsent int permission) {
+ try {
+ ISms iSms = getISmsService();
+ if (iSms != null) {
+ iSms.setPremiumSmsPermission(packageName, permission);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "setPremiumSmsPermission() RemoteException", e);
+ }
+ }
}
diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java
index 2897358..eefbd44 100644
--- a/telephony/java/android/telephony/SmsMessage.java
+++ b/telephony/java/android/telephony/SmsMessage.java
@@ -16,11 +16,18 @@
package android.telephony;
+import com.android.telephony.Rlog;
+
import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA;
+import android.Manifest;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.annotation.StringDef;
-import android.annotation.UnsupportedAppUsage;
+import android.annotation.SystemApi;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.Resources;
import android.os.Binder;
import android.text.TextUtils;
@@ -29,8 +36,10 @@
import com.android.internal.telephony.GsmAlphabet.TextEncodingDetails;
import com.android.internal.telephony.Sms7BitEncodingTranslator;
import com.android.internal.telephony.SmsConstants;
+import com.android.internal.telephony.SmsHeader;
import com.android.internal.telephony.SmsMessageBase;
import com.android.internal.telephony.SmsMessageBase.SubmitPduBase;
+import com.android.internal.telephony.cdma.sms.UserData;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -54,6 +63,16 @@
UNKNOWN, CLASS_0, CLASS_1, CLASS_2, CLASS_3;
}
+ /** @hide */
+ @IntDef(prefix = { "ENCODING_" }, value = {
+ ENCODING_UNKNOWN,
+ ENCODING_7BIT,
+ ENCODING_8BIT,
+ ENCODING_16BIT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface EncodingSize {}
+
/** User data text encoding code unit size */
public static final int ENCODING_UNKNOWN = 0;
public static final int ENCODING_7BIT = 1;
@@ -313,6 +332,34 @@
}
/**
+ * Create an SmsMessage from a native SMS-Submit PDU, specified by Bluetooth Message Access
+ * Profile Specification v1.4.2 5.8.
+ * This is used by Bluetooth MAP profile to decode message when sending non UTF-8 SMS messages.
+ *
+ * @param data Message data.
+ * @param isCdma Indicates weather the type of the SMS is CDMA.
+ * @return An SmsMessage representing the message.
+ *
+ * @hide
+ */
+ @SystemApi
+ @Nullable
+ public static SmsMessage createFromNativeSmsSubmitPdu(@NonNull byte[] data, boolean isCdma) {
+ SmsMessageBase wrappedMessage;
+
+ if (isCdma) {
+ wrappedMessage = com.android.internal.telephony.cdma.SmsMessage.createFromEfRecord(
+ 0, data);
+ } else {
+ // Bluetooth uses its own method to decode GSM PDU so this part is not called.
+ wrappedMessage = com.android.internal.telephony.gsm.SmsMessage.createFromEfRecord(
+ 0, data);
+ }
+
+ return wrappedMessage != null ? new SmsMessage(wrappedMessage) : null;
+ }
+
+ /**
* Get the TP-Layer-Length for the given SMS-SUBMIT PDU Basically, the
* length in bytes (not hex chars) less the SMSC header
*
@@ -631,6 +678,83 @@
}
/**
+ * Get an SMS-SUBMIT PDU's encoded message.
+ * This is used by Bluetooth MAP profile to handle long non UTF-8 SMS messages.
+ *
+ * @param isTypeGsm true when message's type is GSM, false when type is CDMA
+ * @param destinationAddress the address of the destination for the message
+ * @param message message content
+ * @param encoding User data text encoding code unit size
+ * @param languageTable GSM national language table to use, specified by 3GPP
+ * 23.040 9.2.3.24.16
+ * @param languageShiftTable GSM national language shift table to use, specified by 3GPP
+ * 23.040 9.2.3.24.15
+ * @param refNumber parameter to create SmsHeader
+ * @param seqNumber parameter to create SmsHeader
+ * @param msgCount parameter to create SmsHeader
+ * @return a byte[] containing the encoded message
+ *
+ * @hide
+ */
+ @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+ @SystemApi
+ @NonNull
+ public static byte[] getSubmitPduEncodedMessage(boolean isTypeGsm,
+ @NonNull String destinationAddress,
+ @NonNull String message,
+ @EncodingSize int encoding, int languageTable,
+ int languageShiftTable, int refNumber,
+ int seqNumber, int msgCount) {
+ byte[] data;
+ SmsHeader.ConcatRef concatRef = new SmsHeader.ConcatRef();
+ concatRef.refNumber = refNumber;
+ concatRef.seqNumber = seqNumber; // 1-based sequence
+ concatRef.msgCount = msgCount;
+ // We currently set this to true since our messaging app will never
+ // send more than 255 parts (it converts the message to MMS well before that).
+ // However, we should support 3rd party messaging apps that might need 16-bit
+ // references
+ // Note: It's not sufficient to just flip this bit to true; it will have
+ // ripple effects (several calculations assume 8-bit ref).
+ concatRef.isEightBits = true;
+ SmsHeader smsHeader = new SmsHeader();
+ smsHeader.concatRef = concatRef;
+
+ /* Depending on the type, call either GSM or CDMA getSubmitPdu(). The encoding
+ * will be determined(again) by getSubmitPdu().
+ * All packets need to be encoded using the same encoding, as the bMessage
+ * only have one filed to describe the encoding for all messages in a concatenated
+ * SMS... */
+ if (encoding == ENCODING_7BIT) {
+ smsHeader.languageTable = languageTable;
+ smsHeader.languageShiftTable = languageShiftTable;
+ }
+
+ if (isTypeGsm) {
+ data = com.android.internal.telephony.gsm.SmsMessage.getSubmitPdu(null,
+ destinationAddress, message, false,
+ SmsHeader.toByteArray(smsHeader), encoding, languageTable,
+ languageShiftTable).encodedMessage;
+ } else { // SMS_TYPE_CDMA
+ UserData uData = new UserData();
+ uData.payloadStr = message;
+ uData.userDataHeader = smsHeader;
+ if (encoding == ENCODING_7BIT) {
+ uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET;
+ } else { // assume UTF-16
+ uData.msgEncoding = UserData.ENCODING_UNICODE_16;
+ }
+ uData.msgEncodingSet = true;
+ data = com.android.internal.telephony.cdma.SmsMessage.getSubmitPdu(
+ destinationAddress, uData, false).encodedMessage;
+ }
+ if (data == null) {
+ return new byte[0];
+ }
+ return data;
+ }
+
+ /**
* Returns the address of the SMS service center that relayed this message
* or null if there is none.
*/
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index 94085e9..c24eeb7 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -16,9 +16,11 @@
package android.telephony;
+import com.android.telephony.Rlog;
+
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 7380b31..b42ce35 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -33,9 +33,9 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.BroadcastOptions;
import android.app.PendingIntent;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
@@ -53,7 +53,6 @@
import android.os.ParcelUuid;
import android.os.Process;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.provider.Telephony.SimInfo;
import android.telephony.euicc.EuiccManager;
import android.telephony.ims.ImsMmTelManager;
@@ -66,6 +65,7 @@
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.util.HandlerExecutor;
import com.android.internal.util.Preconditions;
+import com.android.telephony.Rlog;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -265,7 +265,7 @@
* <P>Type: TEXT (String)</P>
*/
/** @hide */
- public static final String UNIQUE_KEY_SUBSCRIPTION_ID = "_id";
+ public static final String UNIQUE_KEY_SUBSCRIPTION_ID = SimInfo.UNIQUE_KEY_SUBSCRIPTION_ID;
/**
* TelephonyProvider column name for a unique identifier for the subscription within the
@@ -274,18 +274,18 @@
* <P>Type: TEXT (String)</P>
*/
/** @hide */
- public static final String ICC_ID = "icc_id";
+ public static final String ICC_ID = SimInfo.ICC_ID;
/**
* TelephonyProvider column name for user SIM_SlOT_INDEX
* <P>Type: INTEGER (int)</P>
*/
/** @hide */
- public static final String SIM_SLOT_INDEX = "sim_id";
+ public static final String SIM_SLOT_INDEX = SimInfo.SIM_SLOT_INDEX;
/** SIM is not inserted */
/** @hide */
- public static final int SIM_NOT_INSERTED = -1;
+ public static final int SIM_NOT_INSERTED = SimInfo.SIM_NOT_INSERTED;
/**
* The slot-index for Bluetooth Remote-SIM subscriptions
@@ -300,23 +300,7 @@
* Default value is 0.
*/
/** @hide */
- public static final String SUBSCRIPTION_TYPE = "subscription_type";
-
- /**
- * TelephonyProvider column name white_listed_apn_data.
- * It's a bitmask of APN types that will be allowed on this subscription even if it's metered
- * and mobile data is turned off by the user.
- * <P>Type: INTEGER (int)</P> For example, if TYPE_MMS is is true, Telephony will allow MMS
- * data connection to setup even if MMS is metered and mobile_data is turned off on that
- * subscription.
- *
- * Default value is 0.
- *
- * @deprecated Replaced by {@link #DATA_ENABLED_OVERRIDE_RULES}
- * @hide
- */
- @Deprecated
- public static final String WHITE_LISTED_APN_DATA = "white_listed_apn_data";
+ public static final String SUBSCRIPTION_TYPE = SimInfo.SUBSCRIPTION_TYPE;
/**
* TelephonyProvider column name data_enabled_override_rules.
@@ -329,7 +313,15 @@
*
* @hide
*/
- public static final String DATA_ENABLED_OVERRIDE_RULES = "data_enabled_override_rules";
+ public static final String DATA_ENABLED_OVERRIDE_RULES = SimInfo.DATA_ENABLED_OVERRIDE_RULES;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = {"SUBSCRIPTION_TYPE_"},
+ value = {
+ SUBSCRIPTION_TYPE_LOCAL_SIM,
+ SUBSCRIPTION_TYPE_REMOTE_SIM})
+ public @interface SubscriptionType {}
/**
* This constant is to designate a subscription as a Local-SIM Subscription.
@@ -337,7 +329,7 @@
* device.
* </p>
*/
- public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0;
+ public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = SimInfo.SUBSCRIPTION_TYPE_LOCAL_SIM;
/**
* This constant is to designate a subscription as a Remote-SIM Subscription.
@@ -363,29 +355,21 @@
* was never seen before.
* </p>
*/
- public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1;
-
- /** @hide */
- @Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = {"SUBSCRIPTION_TYPE_"},
- value = {
- SUBSCRIPTION_TYPE_LOCAL_SIM,
- SUBSCRIPTION_TYPE_REMOTE_SIM})
- public @interface SubscriptionType {}
+ public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = SimInfo.SUBSCRIPTION_TYPE_REMOTE_SIM;
/**
* TelephonyProvider column name for user displayed name.
* <P>Type: TEXT (String)</P>
*/
/** @hide */
- public static final String DISPLAY_NAME = "display_name";
+ public static final String DISPLAY_NAME = SimInfo.DISPLAY_NAME;
/**
* TelephonyProvider column name for the service provider name for the SIM.
* <P>Type: TEXT (String)</P>
*/
/** @hide */
- public static final String CARRIER_NAME = "carrier_name";
+ public static final String CARRIER_NAME = SimInfo.CARRIER_NAME;
/**
* Default name resource
@@ -399,44 +383,44 @@
*
* @hide
*/
- public static final String NAME_SOURCE = "name_source";
+ public static final String NAME_SOURCE = SimInfo.NAME_SOURCE;
/**
* The name_source is the default, which is from the carrier id.
* @hide
*/
- public static final int NAME_SOURCE_DEFAULT_SOURCE = 0;
+ public static final int NAME_SOURCE_DEFAULT = SimInfo.NAME_SOURCE_DEFAULT;
/**
* The name_source is from SIM EF_SPN.
* @hide
*/
- public static final int NAME_SOURCE_SIM_SPN = 1;
+ public static final int NAME_SOURCE_SIM_SPN = SimInfo.NAME_SOURCE_SIM_SPN;
/**
* The name_source is from user input
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
- public static final int NAME_SOURCE_USER_INPUT = 2;
+ public static final int NAME_SOURCE_USER_INPUT = SimInfo.NAME_SOURCE_USER_INPUT;
/**
* The name_source is carrier (carrier app, carrier config, etc.)
* @hide
*/
- public static final int NAME_SOURCE_CARRIER = 3;
+ public static final int NAME_SOURCE_CARRIER = SimInfo.NAME_SOURCE_CARRIER;
/**
* The name_source is from SIM EF_PNN.
* @hide
*/
- public static final int NAME_SOURCE_SIM_PNN = 4;
+ public static final int NAME_SOURCE_SIM_PNN = SimInfo.NAME_SOURCE_SIM_PNN;
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"NAME_SOURCE_"},
value = {
- NAME_SOURCE_DEFAULT_SOURCE,
+ NAME_SOURCE_DEFAULT,
NAME_SOURCE_SIM_SPN,
NAME_SOURCE_USER_INPUT,
NAME_SOURCE_CARRIER,
@@ -449,67 +433,30 @@
* <P>Type: INTEGER (int)</P>
*/
/** @hide */
- public static final String COLOR = "color";
-
- /** @hide */
- public static final int COLOR_1 = 0;
-
- /** @hide */
- public static final int COLOR_2 = 1;
-
- /** @hide */
- public static final int COLOR_3 = 2;
-
- /** @hide */
- public static final int COLOR_4 = 3;
-
- /** @hide */
- public static final int COLOR_DEFAULT = COLOR_1;
+ public static final String COLOR = SimInfo.COLOR;
/**
* TelephonyProvider column name for the phone number of a SIM.
* <P>Type: TEXT (String)</P>
*/
/** @hide */
- public static final String NUMBER = "number";
+ public static final String NUMBER = SimInfo.NUMBER;
/**
- * TelephonyProvider column name for the number display format of a SIM.
+ * TelephonyProvider column name for whether data roaming is enabled.
* <P>Type: INTEGER (int)</P>
*/
/** @hide */
- public static final String DISPLAY_NUMBER_FORMAT = "display_number_format";
-
- /** @hide */
- public static final int DISPLAY_NUMBER_NONE = 0;
-
- /** @hide */
- public static final int DISPLAY_NUMBER_FIRST = 1;
-
- /** @hide */
- public static final int DISPLAY_NUMBER_LAST = 2;
-
- /** @hide */
- public static final int DISPLAY_NUMBER_DEFAULT = DISPLAY_NUMBER_FIRST;
-
- /**
- * TelephonyProvider column name for permission for data roaming of a SIM.
- * <P>Type: INTEGER (int)</P>
- */
- /** @hide */
- public static final String DATA_ROAMING = "data_roaming";
+ public static final String DATA_ROAMING = SimInfo.DATA_ROAMING;
/** Indicates that data roaming is enabled for a subscription */
- public static final int DATA_ROAMING_ENABLE = 1;
+ public static final int DATA_ROAMING_ENABLE = SimInfo.DATA_ROAMING_ENABLE;
/** Indicates that data roaming is disabled for a subscription */
- public static final int DATA_ROAMING_DISABLE = 0;
+ public static final int DATA_ROAMING_DISABLE = SimInfo.DATA_ROAMING_DISABLE;
/** @hide */
- public static final int DATA_ROAMING_DEFAULT = DATA_ROAMING_DISABLE;
-
- /** @hide */
- public static final int SIM_PROVISIONED = 0;
+ public static final int DATA_ROAMING_DEFAULT = SimInfo.DATA_ROAMING_DEFAULT;
/**
* TelephonyProvider column name for subscription carrier id.
@@ -517,61 +464,61 @@
* <p>Type: INTEGER (int) </p>
* @hide
*/
- public static final String CARRIER_ID = "carrier_id";
+ public static final String CARRIER_ID = SimInfo.CARRIER_ID;
/**
* @hide A comma-separated list of EHPLMNs associated with the subscription
* <P>Type: TEXT (String)</P>
*/
- public static final String EHPLMNS = "ehplmns";
+ public static final String EHPLMNS = SimInfo.EHPLMNS;
/**
* @hide A comma-separated list of HPLMNs associated with the subscription
* <P>Type: TEXT (String)</P>
*/
- public static final String HPLMNS = "hplmns";
+ public static final String HPLMNS = SimInfo.HPLMNS;
/**
* TelephonyProvider column name for the MCC associated with a SIM, stored as a string.
* <P>Type: TEXT (String)</P>
* @hide
*/
- public static final String MCC_STRING = "mcc_string";
+ public static final String MCC_STRING = SimInfo.MCC_STRING;
/**
* TelephonyProvider column name for the MNC associated with a SIM, stored as a string.
* <P>Type: TEXT (String)</P>
* @hide
*/
- public static final String MNC_STRING = "mnc_string";
+ public static final String MNC_STRING = SimInfo.MNC_STRING;
/**
* TelephonyProvider column name for the MCC associated with a SIM.
* <P>Type: INTEGER (int)</P>
* @hide
*/
- public static final String MCC = "mcc";
+ public static final String MCC = SimInfo.MCC;
/**
* TelephonyProvider column name for the MNC associated with a SIM.
* <P>Type: INTEGER (int)</P>
* @hide
*/
- public static final String MNC = "mnc";
+ public static final String MNC = SimInfo.MNC;
/**
* TelephonyProvider column name for the iso country code associated with a SIM.
* <P>Type: TEXT (String)</P>
* @hide
*/
- public static final String ISO_COUNTRY_CODE = "iso_country_code";
+ public static final String ISO_COUNTRY_CODE = SimInfo.ISO_COUNTRY_CODE;
/**
* TelephonyProvider column name for the sim provisioning status associated with a SIM.
* <P>Type: INTEGER (int)</P>
* @hide
*/
- public static final String SIM_PROVISIONING_STATUS = "sim_provisioning_status";
+ public static final String SIM_PROVISIONING_STATUS = SimInfo.SIM_PROVISIONING_STATUS;
/**
* TelephonyProvider column name for whether a subscription is embedded (that is, present on an
@@ -579,7 +526,7 @@
* <p>Type: INTEGER (int), 1 for embedded or 0 for non-embedded.
* @hide
*/
- public static final String IS_EMBEDDED = "is_embedded";
+ public static final String IS_EMBEDDED = SimInfo.IS_EMBEDDED;
/**
* TelephonyProvider column name for SIM card identifier. For UICC card it is the ICCID of the
@@ -587,7 +534,7 @@
* <P>Type: TEXT (String)</P>
* @hide
*/
- public static final String CARD_ID = "card_id";
+ public static final String CARD_ID = SimInfo.CARD_ID;
/**
* TelephonyProvider column name for the encoded {@link UiccAccessRule}s from
@@ -595,7 +542,7 @@
* <p>TYPE: BLOB
* @hide
*/
- public static final String ACCESS_RULES = "access_rules";
+ public static final String ACCESS_RULES = SimInfo.ACCESS_RULES;
/**
* TelephonyProvider column name for the encoded {@link UiccAccessRule}s from
@@ -605,7 +552,7 @@
* @hide
*/
public static final String ACCESS_RULES_FROM_CARRIER_CONFIGS =
- "access_rules_from_carrier_configs";
+ SimInfo.ACCESS_RULES_FROM_CARRIER_CONFIGS;
/**
* TelephonyProvider column name identifying whether an embedded subscription is on a removable
@@ -615,79 +562,79 @@
* <p>TYPE: INTEGER (int), 1 for removable or 0 for non-removable.
* @hide
*/
- public static final String IS_REMOVABLE = "is_removable";
+ public static final String IS_REMOVABLE = SimInfo.IS_REMOVABLE;
/**
* TelephonyProvider column name for extreme threat in CB settings
* @hide
*/
- public static final String CB_EXTREME_THREAT_ALERT = "enable_cmas_extreme_threat_alerts";
+ public static final String CB_EXTREME_THREAT_ALERT = SimInfo.CB_EXTREME_THREAT_ALERT;
/**
* TelephonyProvider column name for severe threat in CB settings
*@hide
*/
- public static final String CB_SEVERE_THREAT_ALERT = "enable_cmas_severe_threat_alerts";
+ public static final String CB_SEVERE_THREAT_ALERT = SimInfo.CB_SEVERE_THREAT_ALERT;
/**
* TelephonyProvider column name for amber alert in CB settings
*@hide
*/
- public static final String CB_AMBER_ALERT = "enable_cmas_amber_alerts";
+ public static final String CB_AMBER_ALERT = SimInfo.CB_AMBER_ALERT;
/**
* TelephonyProvider column name for emergency alert in CB settings
*@hide
*/
- public static final String CB_EMERGENCY_ALERT = "enable_emergency_alerts";
+ public static final String CB_EMERGENCY_ALERT = SimInfo.CB_EMERGENCY_ALERT;
/**
* TelephonyProvider column name for alert sound duration in CB settings
*@hide
*/
- public static final String CB_ALERT_SOUND_DURATION = "alert_sound_duration";
+ public static final String CB_ALERT_SOUND_DURATION = SimInfo.CB_ALERT_SOUND_DURATION;
/**
* TelephonyProvider column name for alert reminder interval in CB settings
*@hide
*/
- public static final String CB_ALERT_REMINDER_INTERVAL = "alert_reminder_interval";
+ public static final String CB_ALERT_REMINDER_INTERVAL = SimInfo.CB_ALERT_REMINDER_INTERVAL;
/**
* TelephonyProvider column name for enabling vibrate in CB settings
*@hide
*/
- public static final String CB_ALERT_VIBRATE = "enable_alert_vibrate";
+ public static final String CB_ALERT_VIBRATE = SimInfo.CB_ALERT_VIBRATE;
/**
* TelephonyProvider column name for enabling alert speech in CB settings
*@hide
*/
- public static final String CB_ALERT_SPEECH = "enable_alert_speech";
+ public static final String CB_ALERT_SPEECH = SimInfo.CB_ALERT_SPEECH;
/**
* TelephonyProvider column name for ETWS test alert in CB settings
*@hide
*/
- public static final String CB_ETWS_TEST_ALERT = "enable_etws_test_alerts";
+ public static final String CB_ETWS_TEST_ALERT = SimInfo.CB_ETWS_TEST_ALERT;
/**
* TelephonyProvider column name for enable channel50 alert in CB settings
*@hide
*/
- public static final String CB_CHANNEL_50_ALERT = "enable_channel_50_alerts";
+ public static final String CB_CHANNEL_50_ALERT = SimInfo.CB_CHANNEL_50_ALERT;
/**
* TelephonyProvider column name for CMAS test alert in CB settings
*@hide
*/
- public static final String CB_CMAS_TEST_ALERT= "enable_cmas_test_alerts";
+ public static final String CB_CMAS_TEST_ALERT = SimInfo.CB_CMAS_TEST_ALERT;
/**
* TelephonyProvider column name for Opt out dialog in CB settings
*@hide
*/
- public static final String CB_OPT_OUT_DIALOG = "show_cmas_opt_out_dialog";
+ public static final String CB_OPT_OUT_DIALOG = SimInfo.CB_OPT_OUT_DIALOG;
/**
* TelephonyProvider column name for enable Volte.
@@ -696,37 +643,37 @@
* {@link CarrierConfigManager#KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL}.
*@hide
*/
- public static final String ENHANCED_4G_MODE_ENABLED = "volte_vt_enabled";
+ public static final String ENHANCED_4G_MODE_ENABLED = SimInfo.ENHANCED_4G_MODE_ENABLED;
/**
* TelephonyProvider column name for enable VT (Video Telephony over IMS)
*@hide
*/
- public static final String VT_IMS_ENABLED = "vt_ims_enabled";
+ public static final String VT_IMS_ENABLED = SimInfo.VT_IMS_ENABLED;
/**
* TelephonyProvider column name for enable Wifi calling
*@hide
*/
- public static final String WFC_IMS_ENABLED = "wfc_ims_enabled";
+ public static final String WFC_IMS_ENABLED = SimInfo.WFC_IMS_ENABLED;
/**
* TelephonyProvider column name for Wifi calling mode
*@hide
*/
- public static final String WFC_IMS_MODE = "wfc_ims_mode";
+ public static final String WFC_IMS_MODE = SimInfo.WFC_IMS_MODE;
/**
* TelephonyProvider column name for Wifi calling mode in roaming
*@hide
*/
- public static final String WFC_IMS_ROAMING_MODE = "wfc_ims_roaming_mode";
+ public static final String WFC_IMS_ROAMING_MODE = SimInfo.WFC_IMS_ROAMING_MODE;
/**
* TelephonyProvider column name for enable Wifi calling in roaming
*@hide
*/
- public static final String WFC_IMS_ROAMING_ENABLED = "wfc_ims_roaming_enabled";
+ public static final String WFC_IMS_ROAMING_ENABLED = SimInfo.WFC_IMS_ROAMING_ENABLED;
/**
* TelephonyProvider column name for whether a subscription is opportunistic, that is,
@@ -735,7 +682,7 @@
* <p>Type: INTEGER (int), 1 for opportunistic or 0 for non-opportunistic.
* @hide
*/
- public static final String IS_OPPORTUNISTIC = "is_opportunistic";
+ public static final String IS_OPPORTUNISTIC = SimInfo.IS_OPPORTUNISTIC;
/**
* TelephonyProvider column name for group ID. Subscriptions with same group ID
@@ -744,7 +691,7 @@
*
* @hide
*/
- public static final String GROUP_UUID = "group_uuid";
+ public static final String GROUP_UUID = SimInfo.GROUP_UUID;
/**
* TelephonyProvider column name for group owner. It's the package name who created
@@ -752,14 +699,7 @@
*
* @hide
*/
- public static final String GROUP_OWNER = "group_owner";
-
- /**
- * TelephonyProvider column name for whether a subscription is metered or not, that is, whether
- * the network it connects to charges for subscription or not. For example, paid CBRS or unpaid.
- * @hide
- */
- public static final String IS_METERED = "is_metered";
+ public static final String GROUP_OWNER = SimInfo.GROUP_OWNER;
/**
* TelephonyProvider column name for the profile class of a subscription
@@ -767,7 +707,7 @@
* <P>Type: INTEGER (int)</P>
* @hide
*/
- public static final String PROFILE_CLASS = "profile_class";
+ public static final String PROFILE_CLASS = SimInfo.PROFILE_CLASS;
/**
* Profile class of the subscription
@@ -775,11 +715,11 @@
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = { "PROFILE_CLASS_" }, value = {
- PROFILE_CLASS_TESTING,
- PROFILE_CLASS_PROVISIONING,
- PROFILE_CLASS_OPERATIONAL,
- PROFILE_CLASS_UNSET,
- PROFILE_CLASS_DEFAULT
+ SimInfo.PROFILE_CLASS_TESTING,
+ SimInfo.PROFILE_CLASS_PROVISIONING,
+ SimInfo.PROFILE_CLASS_OPERATIONAL,
+ SimInfo.PROFILE_CLASS_UNSET,
+ SimInfo.PROFILE_CLASS_DEFAULT
})
public @interface ProfileClass {}
@@ -791,7 +731,7 @@
* @hide
*/
@SystemApi
- public static final int PROFILE_CLASS_TESTING = 0;
+ public static final int PROFILE_CLASS_TESTING = SimInfo.PROFILE_CLASS_TESTING;
/**
* A provisioning profile is pre-loaded onto the eUICC and
@@ -800,7 +740,7 @@
* @hide
*/
@SystemApi
- public static final int PROFILE_CLASS_PROVISIONING = 1;
+ public static final int PROFILE_CLASS_PROVISIONING = SimInfo.PROFILE_CLASS_PROVISIONING;
/**
* An operational profile can be pre-loaded or downloaded
@@ -809,7 +749,7 @@
* @hide
*/
@SystemApi
- public static final int PROFILE_CLASS_OPERATIONAL = 2;
+ public static final int PROFILE_CLASS_OPERATIONAL = SimInfo.PROFILE_CLASS_OPERATIONAL;
/**
* The profile class is unset. This occurs when profile class
@@ -818,14 +758,14 @@
* @hide
*/
@SystemApi
- public static final int PROFILE_CLASS_UNSET = -1;
+ public static final int PROFILE_CLASS_UNSET = SimInfo.PROFILE_CLASS_UNSET;
/**
* Default profile class
* @hide
*/
@SystemApi
- public static final int PROFILE_CLASS_DEFAULT = PROFILE_CLASS_UNSET;
+ public static final int PROFILE_CLASS_DEFAULT = SimInfo.PROFILE_CLASS_DEFAULT;
/**
* IMSI (International Mobile Subscriber Identity).
@@ -833,13 +773,13 @@
* @hide
*/
//TODO: add @SystemApi
- public static final String IMSI = "imsi";
+ public static final String IMSI = SimInfo.IMSI;
/**
* Whether uicc applications is set to be enabled or disabled. By default it's enabled.
* @hide
*/
- public static final String UICC_APPLICATIONS_ENABLED = "uicc_applications_enabled";
+ public static final String UICC_APPLICATIONS_ENABLED = SimInfo.UICC_APPLICATIONS_ENABLED;
/**
* Broadcast Action: The user has changed one of the default subs related to
@@ -1023,8 +963,11 @@
private final INetworkPolicyManager getNetworkPolicy() {
if (mNetworkPolicy == null) {
- mNetworkPolicy = INetworkPolicyManager.Stub
- .asInterface(ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
+ mNetworkPolicy = INetworkPolicyManager.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getNetworkPolicyServiceRegisterer()
+ .get());
}
return mNetworkPolicy;
}
@@ -1041,6 +984,23 @@
*/
public void addOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) {
if (listener == null) return;
+ addOnSubscriptionsChangedListener(listener.mExecutor, listener);
+ }
+
+ /**
+ * Register for changes to the list of active {@link SubscriptionInfo} records or to the
+ * individual records themselves. When a change occurs the onSubscriptionsChanged method of
+ * the listener will be invoked immediately if there has been a notification. The
+ * onSubscriptionChanged method will also be triggered once initially when calling this
+ * function.
+ *
+ * @param listener an instance of {@link OnSubscriptionsChangedListener} with
+ * onSubscriptionsChanged overridden.
+ * @param executor the executor that will execute callbacks.
+ */
+ public void addOnSubscriptionsChangedListener(
+ @NonNull @CallbackExecutor Executor executor,
+ @NonNull OnSubscriptionsChangedListener listener) {
String pkgName = mContext != null ? mContext.getOpPackageName() : "<unknown>";
if (DBG) {
logd("register OnSubscriptionsChangedListener pkgName=" + pkgName
@@ -1052,7 +1012,7 @@
mContext.getSystemService(Context.TELEPHONY_REGISTRY_SERVICE);
if (telephonyRegistryManager != null) {
telephonyRegistryManager.addOnSubscriptionsChangedListener(listener,
- listener.mExecutor);
+ executor);
}
}
@@ -1185,7 +1145,11 @@
SubscriptionInfo subInfo = null;
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
subInfo = iSub.getActiveSubscriptionInfo(subId, mContext.getOpPackageName(),
mContext.getFeatureId());
@@ -1199,12 +1163,17 @@
}
/**
- * Get the active SubscriptionInfo associated with the iccId
+ * Gets an active SubscriptionInfo {@link SubscriptionInfo} associated with the Sim IccId.
+ *
* @param iccId the IccId of SIM card
* @return SubscriptionInfo, maybe null if its not active
+ *
* @hide
*/
- public SubscriptionInfo getActiveSubscriptionInfoForIccIndex(String iccId) {
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @Nullable
+ @SystemApi
+ public SubscriptionInfo getActiveSubscriptionInfoForIcc(@NonNull String iccId) {
if (VDBG) logd("[getActiveSubscriptionInfoForIccIndex]+ iccId=" + iccId);
if (iccId == null) {
logd("[getActiveSubscriptionInfoForIccIndex]- null iccid");
@@ -1214,7 +1183,11 @@
SubscriptionInfo result = null;
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
result = iSub.getActiveSubscriptionInfoForIccId(iccId, mContext.getOpPackageName(),
mContext.getFeatureId());
@@ -1248,7 +1221,11 @@
SubscriptionInfo result = null;
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
result = iSub.getActiveSubscriptionInfoForSimSlotIndex(slotIndex,
mContext.getOpPackageName(), mContext.getFeatureId());
@@ -1272,7 +1249,11 @@
List<SubscriptionInfo> result = null;
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
result = iSub.getAllSubInfoList(mContext.getOpPackageName(),
mContext.getFeatureId());
@@ -1323,6 +1304,11 @@
* The records will be sorted by {@link SubscriptionInfo#getSimSlotIndex}
* then by {@link SubscriptionInfo#getSubscriptionId}.
*
+ * Hidden subscriptions refer to those are not meant visible to the users.
+ * For example, an opportunistic subscription that is grouped with other
+ * subscriptions should remain invisible to users as they are only functionally
+ * supplementary to primary ones.
+ *
* <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
* or that the calling app has carrier privileges (see
* {@link TelephonyManager#hasCarrierPrivileges}). In the latter case, only records accessible
@@ -1348,7 +1334,11 @@
List<SubscriptionInfo> activeList = null;
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
activeList = iSub.getActiveSubscriptionInfoList(mContext.getOpPackageName(),
mContext.getFeatureId());
@@ -1399,7 +1389,11 @@
List<SubscriptionInfo> result = null;
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
result = iSub.getAvailableSubscriptionInfoList(mContext.getOpPackageName(),
mContext.getFeatureId());
@@ -1438,7 +1432,11 @@
List<SubscriptionInfo> result = null;
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
result = iSub.getAccessibleSubscriptionInfoList(mContext.getOpPackageName());
}
@@ -1467,7 +1465,11 @@
public void requestEmbeddedSubscriptionInfoListRefresh() {
int cardId = TelephonyManager.from(mContext).getCardIdForDefaultEuicc();
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
iSub.requestEmbeddedSubscriptionInfoListRefresh(cardId);
}
@@ -1496,7 +1498,11 @@
@SystemApi
public void requestEmbeddedSubscriptionInfoListRefresh(int cardId) {
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
iSub.requestEmbeddedSubscriptionInfoListRefresh(cardId);
}
@@ -1517,7 +1523,11 @@
int result = 0;
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
result = iSub.getAllSubInfoCount(mContext.getOpPackageName(),
mContext.getFeatureId());
@@ -1546,7 +1556,11 @@
int result = 0;
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
result = iSub.getActiveSubInfoCount(mContext.getOpPackageName(),
mContext.getFeatureId());
@@ -1567,7 +1581,11 @@
int result = 0;
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
result = iSub.getActiveSubInfoCountMax();
}
@@ -1624,7 +1642,11 @@
}
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub == null) {
Log.e(LOG_TAG, "[addSubscriptionInfoRecord]- ISub service is null");
return;
@@ -1658,7 +1680,11 @@
}
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub == null) {
Log.e(LOG_TAG, "[removeSubscriptionInfoRecord]- ISub service is null");
return;
@@ -1761,7 +1787,11 @@
int result = INVALID_SIM_SLOT_INDEX;
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
result = iSub.getSlotIndex(subscriptionId);
}
@@ -1795,7 +1825,11 @@
int[] subId = null;
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
subId = iSub.getSubId(slotIndex);
}
@@ -1819,7 +1853,11 @@
int result = INVALID_PHONE_INDEX;
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
result = iSub.getPhoneId(subId);
}
@@ -1853,7 +1891,11 @@
int subId = INVALID_SUBSCRIPTION_ID;
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
subId = iSub.getDefaultSubId();
}
@@ -1876,7 +1918,11 @@
int subId = INVALID_SUBSCRIPTION_ID;
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
subId = iSub.getDefaultVoiceSubId();
}
@@ -1906,7 +1952,11 @@
public void setDefaultVoiceSubscriptionId(int subscriptionId) {
if (VDBG) logd("setDefaultVoiceSubId sub id = " + subscriptionId);
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
iSub.setDefaultVoiceSubId(subscriptionId);
}
@@ -1954,7 +2004,11 @@
int subId = INVALID_SUBSCRIPTION_ID;
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
subId = iSub.getDefaultSmsSubId();
}
@@ -1980,7 +2034,11 @@
public void setDefaultSmsSubId(int subscriptionId) {
if (VDBG) logd("setDefaultSmsSubId sub id = " + subscriptionId);
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
iSub.setDefaultSmsSubId(subscriptionId);
}
@@ -2018,7 +2076,11 @@
int subId = INVALID_SUBSCRIPTION_ID;
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
subId = iSub.getDefaultDataSubId();
}
@@ -2044,7 +2106,11 @@
public void setDefaultDataSubId(int subscriptionId) {
if (VDBG) logd("setDataSubscription sub id = " + subscriptionId);
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
iSub.setDefaultDataSubId(subscriptionId);
}
@@ -2075,7 +2141,11 @@
/** @hide */
public void clearSubscriptionInfo() {
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
iSub.clearSubInfo();
}
@@ -2162,6 +2232,7 @@
} else {
logd("putPhoneIdAndSubIdExtra: no valid subs");
intent.putExtra(PhoneConstants.PHONE_KEY, phoneId);
+ intent.putExtra(EXTRA_SLOT_INDEX, phoneId);
}
}
@@ -2169,34 +2240,52 @@
@UnsupportedAppUsage
public static void putPhoneIdAndSubIdExtra(Intent intent, int phoneId, int subId) {
if (VDBG) logd("putPhoneIdAndSubIdExtra: phoneId=" + phoneId + " subId=" + subId);
- intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
- intent.putExtra(EXTRA_SUBSCRIPTION_INDEX, subId);
intent.putExtra(EXTRA_SLOT_INDEX, phoneId);
intent.putExtra(PhoneConstants.PHONE_KEY, phoneId);
+ putSubscriptionIdExtra(intent, subId);
}
/**
- * TODO(b/137102918) Make this static, tests use this as an instance method currently.
+ * Get visible subscription Id(s) of the currently active SIM(s).
*
* @return the list of subId's that are active,
- * is never null but the length maybe 0.
+ * is never null but the length may be 0.
* @hide
*/
- @UnsupportedAppUsage
+ @SystemApi
public @NonNull int[] getActiveSubscriptionIdList() {
return getActiveSubscriptionIdList(/* visibleOnly */ true);
}
/**
- * TODO(b/137102918) Make this static, tests use this as an instance method currently.
+ * Get both hidden and visible subscription Id(s) of the currently active SIM(s).
*
+ * Hidden subscriptions refer to those are not meant visible to the users.
+ * For example, an opportunistic subscription that is grouped with other
+ * subscriptions should remain invisible to users as they are only functionally
+ * supplementary to primary ones.
+ *
+ * @return the list of subId's that are active,
+ * is never null but the length may be 0.
+ * @hide
+ */
+ @SystemApi
+ public @NonNull int[] getActiveAndHiddenSubscriptionIdList() {
+ return getActiveSubscriptionIdList(/* visibleOnly */false);
+ }
+
+ /**
* @return a non-null list of subId's that are active.
*
* @hide
*/
public @NonNull int[] getActiveSubscriptionIdList(boolean visibleOnly) {
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
int[] subId = iSub.getActiveSubIdList(visibleOnly);
if (subId != null) return subId;
@@ -2247,7 +2336,11 @@
int simState = TelephonyManager.SIM_STATE_UNKNOWN;
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
simState = iSub.getSimStateForSlotIndex(slotIndex);
}
@@ -2266,7 +2359,11 @@
*/
public static void setSubscriptionProperty(int subId, String propKey, String propValue) {
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
iSub.setSubscriptionProperty(subId, propKey, propValue);
}
@@ -2286,7 +2383,11 @@
Context context) {
String resultValue = null;
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
resultValue = iSub.getSubscriptionProperty(subId, propKey,
context.getOpPackageName(), context.getFeatureId());
@@ -2428,7 +2529,11 @@
@UnsupportedAppUsage
public boolean isActiveSubId(int subId) {
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
return iSub.isActiveSubId(subId, mContext.getOpPackageName(),
mContext.getFeatureId());
@@ -2731,7 +2836,11 @@
@TelephonyManager.SetOpportunisticSubscriptionResult Consumer<Integer> callback) {
if (VDBG) logd("[setPreferredDataSubscriptionId]+ subId:" + subId);
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub == null) return;
ISetOpportunisticDataCallback callbackStub = new ISetOpportunisticDataCallback.Stub() {
@@ -2774,7 +2883,11 @@
public int getPreferredDataSubscriptionId() {
int preferredSubId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID;
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
preferredSubId = iSub.getPreferredDataSubscriptionId();
}
@@ -2805,7 +2918,11 @@
List<SubscriptionInfo> subInfoList = null;
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
subInfoList = iSub.getOpportunisticSubscriptions(contextPkg, contextFeature);
}
@@ -2906,7 +3023,11 @@
ParcelUuid groupUuid = null;
int[] subIdArray = subIdList.stream().mapToInt(i->i).toArray();
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
groupUuid = iSub.createSubscriptionGroup(subIdArray, pkgForDebug);
} else {
@@ -2956,7 +3077,11 @@
int[] subIdArray = subIdList.stream().mapToInt(i->i).toArray();
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
iSub.addSubscriptionsIntoGroup(subIdArray, groupUuid, pkgForDebug);
} else {
@@ -3008,7 +3133,11 @@
int[] subIdArray = subIdList.stream().mapToInt(i->i).toArray();
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
iSub.removeSubscriptionsFromGroup(subIdArray, groupUuid, pkgForDebug);
} else {
@@ -3053,7 +3182,11 @@
List<SubscriptionInfo> result = null;
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
result = iSub.getSubscriptionsInGroup(groupUuid, contextPkg, contextFeature);
} else {
@@ -3166,7 +3299,11 @@
logd("setSubscriptionActivated subId= " + subscriptionId + " enable " + enable);
}
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
return iSub.setSubscriptionEnabled(enable, subscriptionId);
}
@@ -3196,7 +3333,11 @@
logd("setUiccApplicationsEnabled subId= " + subscriptionId + " enable " + enabled);
}
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
iSub.setUiccApplicationsEnabled(enabled, subscriptionId);
}
@@ -3226,7 +3367,11 @@
logd("canDisablePhysicalSubscription");
}
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
return iSub.canDisablePhysicalSubscription();
}
@@ -3247,7 +3392,11 @@
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public boolean isSubscriptionEnabled(int subscriptionId) {
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
return iSub.isSubscriptionEnabled(subscriptionId);
}
@@ -3270,7 +3419,11 @@
int subId = INVALID_SUBSCRIPTION_ID;
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
subId = iSub.getEnabledSubscriptionId(slotIndex);
}
@@ -3282,31 +3435,6 @@
return subId;
}
- /**
- * Set whether a subscription always allows MMS connection. If true, MMS network
- * request will be accepted by telephony even if user turns "mobile data" off
- * on this subscription.
- *
- * @param subId which subscription it's setting to.
- * @param alwaysAllow whether Mms data is always allowed.
- * @return whether operation is successful.
- *
- * @hide
- */
- public boolean setAlwaysAllowMmsData(int subId, boolean alwaysAllow) {
- try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
- if (iSub != null) {
- return iSub.setAlwaysAllowMmsData(subId, alwaysAllow);
- }
- } catch (RemoteException ex) {
- if (!isSystemProcess()) {
- ex.rethrowAsRuntimeException();
- }
- }
- return false;
- }
-
private interface CallISubMethodHelper {
int callMethod(ISub iSub) throws RemoteException;
}
@@ -3321,7 +3449,11 @@
int result = 0;
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
result = helper.callMethod(iSub);
}
@@ -3344,7 +3476,11 @@
*/
public static int getActiveDataSubscriptionId() {
try {
- ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
+ ISub iSub = ISub.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getSubscriptionServiceRegisterer()
+ .get());
if (iSub != null) {
return iSub.getActiveDataSubscriptionId();
}
@@ -3352,4 +3488,19 @@
}
return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
}
+
+ /**
+ * Helper method that puts a subscription id on an intent with the constants:
+ * PhoneConstant.SUBSCRIPTION_KEY and SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX.
+ * Both constants are used to support backwards compatibility. Once we know we got all places,
+ * we can remove PhoneConstants.SUBSCRIPTION_KEY.
+ * @param intent Intent to put sub id on.
+ * @param subId SubscriptionId to put on intent.
+ *
+ * @hide
+ */
+ public static void putSubscriptionIdExtra(Intent intent, int subId) {
+ intent.putExtra(SubscriptionManager.EXTRA_SUBSCRIPTION_INDEX, subId);
+ intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
+ }
}
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 20c9f6c..56ca8c7 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -36,16 +36,16 @@
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
import android.annotation.WorkerThread;
-import android.app.ActivityThread;
import android.app.PendingIntent;
import android.compat.Compatibility;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.NetworkStats;
@@ -60,7 +60,6 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
-import android.os.ServiceManager;
import android.os.SystemProperties;
import android.os.WorkSource;
import android.provider.Settings.SettingNotFoundException;
@@ -107,8 +106,7 @@
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.RILConstants;
import com.android.internal.telephony.SmsApplication;
-
-import dalvik.system.VMRuntime;
+import com.android.telephony.Rlog;
import java.io.FileInputStream;
import java.io.IOException;
@@ -381,8 +379,17 @@
// effort and get the context from the current activity thread.
if (mContext != null) {
return mContext.getOpPackageName();
+ } else {
+ ITelephony telephony = getITelephony();
+ if (telephony == null) return null;
+ try {
+ return telephony.getCurrentPackageName();
+ } catch (RemoteException ex) {
+ return null;
+ } catch (NullPointerException ex) {
+ return null;
+ }
}
- return ActivityThread.currentOpPackageName();
}
private String getFeatureId() {
@@ -449,12 +456,8 @@
case UNKNOWN:
modemCount = MODEM_COUNT_SINGLE_MODEM;
// check for voice and data support, 0 if not supported
- if (!isVoiceCapable() && !isSmsCapable() && mContext != null) {
- ConnectivityManager cm = (ConnectivityManager) mContext
- .getSystemService(Context.CONNECTIVITY_SERVICE);
- if (cm != null && !cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE)) {
- modemCount = MODEM_COUNT_NO_MODEM;
- }
+ if (!isVoiceCapable() && !isSmsCapable() && !isDataCapable()) {
+ modemCount = MODEM_COUNT_NO_MODEM;
}
break;
case DSDS:
@@ -785,30 +788,6 @@
public static final String EXTRA_PRECISE_DISCONNECT_CAUSE = "precise_disconnect_cause";
/**
- * The lookup key used with the {@link #ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED} broadcast
- * for an String containing the data APN type.
- *
- * <p class="note">
- * Retrieve with
- * {@link android.content.Intent#getStringExtra(String name)}.
- *
- * @hide
- */
- public static final String EXTRA_DATA_APN_TYPE = PhoneConstants.DATA_APN_TYPE_KEY;
-
- /**
- * The lookup key used with the {@link #ACTION_PRECISE_DATA_CONNECTION_STATE_CHANGED} broadcast
- * for an String containing the data APN.
- *
- * <p class="note">
- * Retrieve with
- * {@link android.content.Intent#getStringExtra(String name)}.
- *
- * @hide
- */
- public static final String EXTRA_DATA_APN = PhoneConstants.DATA_APN_KEY;
-
- /**
* Broadcast intent action for letting the default dialer to know to show voicemail
* notification.
*
@@ -1133,6 +1112,16 @@
*/
public static final int CDMA_ROAMING_MODE_ANY = 2;
+ /** @hide */
+ @IntDef(prefix = { "CDMA_ROAMING_MODE_" }, value = {
+ CDMA_ROAMING_MODE_RADIO_DEFAULT,
+ CDMA_ROAMING_MODE_HOME,
+ CDMA_ROAMING_MODE_AFFILIATED,
+ CDMA_ROAMING_MODE_ANY
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CdmaRoamingMode{}
+
/**
* An unknown carrier id. It could either be subscription unavailable or the subscription
* carrier cannot be recognized. Unrecognized carriers here means
@@ -1471,27 +1460,10 @@
"android.telephony.extra.SIM_COMBINATION_NAMES";
/**
- * Broadcast Action: The time was set by the carrier (typically by the NITZ string).
- * This is a sticky broadcast.
- * The intent will have the following extra values:</p>
- * <ul>
- * <li><em>time</em> - The time as a long in UTC milliseconds.</li>
- * </ul>
- *
- * <p class="note">
- * Requires the READ_PHONE_STATE permission.
- *
- * <p class="note">This is a protected intent that can only be sent by the system.
- *
- * @hide
- */
- @SystemApi
- public static final String ACTION_NETWORK_SET_TIME = "android.telephony.action.NETWORK_SET_TIME";
-
- /**
* <p>Broadcast Action: The emergency callback mode is changed.
* <ul>
- * <li><em>phoneinECMState</em> - A boolean value,true=phone in ECM, false=ECM off</li>
+ * <li><em>EXTRA_PHONE_IN_ECM_STATE</em> - A boolean value,true=phone in ECM,
+ * false=ECM off</li>
* </ul>
* <p class="note">
* You can <em>not</em> receive this through components declared
@@ -1501,18 +1473,218 @@
*
* <p class="note">This is a protected intent that can only be sent by the system.
*
+ * @see #EXTRA_PHONE_IN_ECM_STATE
+ *
* @hide
*/
@SystemApi
@SuppressLint("ActionValue")
- public static final String ACTION_EMERGENCY_CALLBACK_MODE_CHANGED
- = "android.intent.action.EMERGENCY_CALLBACK_MODE_CHANGED";
+ public static final String ACTION_EMERGENCY_CALLBACK_MODE_CHANGED =
+ "android.intent.action.EMERGENCY_CALLBACK_MODE_CHANGED";
+
+
+ /**
+ * Extra included in {@link #ACTION_EMERGENCY_CALLBACK_MODE_CHANGED}.
+ * Indicates whether the phone is in an emergency phone state.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_PHONE_IN_ECM_STATE =
+ "android.telephony.extra.PHONE_IN_ECM_STATE";
+
+ /**
+ * <p>Broadcast Action: when data connections get redirected with validation failure.
+ * intended for sim/account status checks and only sent to the specified carrier app
+ * The intent will have the following extra values:</p>
+ * <ul>
+ * <li>{@link #EXTRA_APN_TYPE}</li><dd>A string with the apn type.</dd>
+ * <li>{@link #EXTRA_APN_TYPE_INT}</li><dd>A integer with the apn type.</dd>
+ * <li>{@link #EXTRA_REDIRECTION_URL}</li><dd>redirection url string</dd>
+ * <li>subId</li><dd>Sub Id which associated the data connection failure.</dd>
+ * </ul>
+ * <p class="note">This is a protected intent that can only be sent by the system.</p>
+ * @hide
+ */
+ @SystemApi
+ @SuppressLint("ActionValue")
+ public static final String ACTION_CARRIER_SIGNAL_REDIRECTED =
+ "com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED";
+
+ /**
+ * <p>Broadcast Action: when data connections setup fails.
+ * intended for sim/account status checks and only sent to the specified carrier app
+ * The intent will have the following extra values:</p>
+ * <ul>
+ * <li>{@link #EXTRA_APN_TYPE}</li><dd>A string with the apn type.</dd>
+ * <li>{@link #EXTRA_APN_TYPE_INT}</li><dd>A integer with the apn type.</dd>
+ * <li>{@link #EXTRA_ERROR_CODE}</li><dd>A integer with dataFailCause.</dd>
+ * <li>subId</li><dd>Sub Id which associated the data connection failure.</dd>
+ * </ul>
+ * <p class="note">This is a protected intent that can only be sent by the system. </p>
+ * @hide
+ */
+ @SystemApi
+ @SuppressLint("ActionValue")
+ public static final String ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED =
+ "com.android.internal.telephony.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED";
+
+ /**
+ * <p>Broadcast Action: when pco value is available.
+ * intended for sim/account status checks and only sent to the specified carrier app
+ * The intent will have the following extra values:</p>
+ * <ul>
+ * <li>{@link #EXTRA_APN_TYPE}</li><dd>A string with the apn type.</dd>
+ * <li>{@link #EXTRA_APN_TYPE_INT}</li><dd>A integer with the apn type.</dd>
+ * <li>{@link #EXTRA_APN_PROTOCOL}</li><dd>A string with the protocol of the apn connection
+ * (IP,IPV6, IPV4V6)</dd>
+ * <li>{@link #EXTRA_APN_PROTOCOL_INT}</li><dd>A integer with the protocol of the apn
+ * connection (IP,IPV6, IPV4V6)</dd>
+ * <li>{@link #EXTRA_PCO_ID}</li><dd>An integer indicating the pco id for the data.</dd>
+ * <li>{@link #EXTRA_PCO_VALUE}</li><dd>A byte array of pco data read from modem.</dd>
+ * <li>subId</li><dd>Sub Id which associated the data connection.</dd>
+ * </ul>
+ * <p class="note">This is a protected intent that can only be sent by the system. </p>
+ * @hide
+ */
+ @SystemApi
+ @SuppressLint("ActionValue")
+ public static final String ACTION_CARRIER_SIGNAL_PCO_VALUE =
+ "com.android.internal.telephony.CARRIER_SIGNAL_PCO_VALUE";
+
+ /**
+ * <p>Broadcast Action: when system default network available/unavailable with
+ * carrier-disabled mobile data. Intended for carrier apps to set/reset carrier actions when
+ * other network becomes system default network, Wi-Fi for example.
+ * The intent will have the following extra values:</p>
+ * <ul>
+ * <li>{@link #EXTRA_DEFAULT_NETWORK_AVAILABLE}</li>
+ * <dd>A boolean indicates default network available.</dd>
+ * <li>subId</li><dd>Sub Id which associated the default data.</dd>
+ * </ul>
+ * <p class="note">This is a protected intent that can only be sent by the system. </p>
+ * @hide
+ */
+ @SystemApi
+ @SuppressLint("ActionValue")
+ public static final String ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE =
+ "com.android.internal.telephony.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE";
+
+ /**
+ * <p>Broadcast Action: when framework reset all carrier actions on sim load or absent.
+ * intended for carrier apps clean up (clear UI e.g.) and only sent to the specified carrier app
+ * The intent will have the following extra values:</p>
+ * <ul>
+ * <li>subId</li><dd>Sub Id which associated the data connection failure.</dd>
+ * </ul>
+ * <p class="note">This is a protected intent that can only be sent by the system.</p>
+ * @hide
+ */
+ @SystemApi
+ @SuppressLint("ActionValue")
+ public static final String ACTION_CARRIER_SIGNAL_RESET =
+ "com.android.internal.telephony.CARRIER_SIGNAL_RESET";
+
+ // CARRIER_SIGNAL_ACTION extra keys
+ /**
+ * An string extra of redirected url upon {@link #ACTION_CARRIER_SIGNAL_REDIRECTED}.
+ * @hide
+ */
+ @SystemApi
+ @SuppressLint("ActionValue")
+ public static final String EXTRA_REDIRECTION_URL = "redirectionUrl";
+
+ /**
+ * An integer extra of error code upon {@link #ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED}.
+ * Check {@link DataFailCause} for all possible values.
+ * @hide
+ */
+ @SystemApi
+ @SuppressLint("ActionValue")
+ public static final String EXTRA_ERROR_CODE = "errorCode";
+
+ /**
+ * An string extra of corresponding apn type upon
+ * {@link #ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED},
+ * {@link #ACTION_CARRIER_SIGNAL_REDIRECTED} and
+ * {@link #ACTION_CARRIER_SIGNAL_PCO_VALUE} broadcasts.
+ * @deprecated This is kept for backward compatibility reason. Use {@link #EXTRA_APN_TYPE_INT}
+ * instead.
+ *
+ * @hide
+ */
+ @SystemApi
+ @Deprecated
+ @SuppressLint("ActionValue")
+ public static final String EXTRA_APN_TYPE = "apnType";
+
+ /**
+ * An string integer of corresponding apn type upon
+ * {@link #ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED},
+ * {@link #ACTION_CARRIER_SIGNAL_REDIRECTED} and
+ * {@link #ACTION_CARRIER_SIGNAL_PCO_VALUE} broadcasts.
+ * Check {@link ApnSetting} TYPE_* for its values.
+ * @hide
+ */
+ @SystemApi
+ @SuppressLint("ActionValue")
+ public static final String EXTRA_APN_TYPE_INT = "apnTypeInt";
+
+ /**
+ * An string extra with the protocol of the apn connection (IP,IPV6, IPV4V6) upon
+ * {@link #ACTION_CARRIER_SIGNAL_PCO_VALUE} broadcasts.
+ * @deprecated This is kept for backward compatibility reason.
+ * Use {@link #EXTRA_APN_PROTOCOL_INT} instead.
+ *
+ * @hide
+ */
+ @SystemApi
+ @Deprecated
+ @SuppressLint("ActionValue")
+ public static final String EXTRA_APN_PROTOCOL = "apnProto";
+
+ /**
+ * An integer extra with the protocol of the apn connection (IP,IPV6, IPV4V6) upon
+ * {@link #ACTION_CARRIER_SIGNAL_PCO_VALUE} broadcasts.
+ * Check {@link ApnSetting} PROTOCOL_* for its values.
+ * @hide
+ */
+ @SystemApi
+ @SuppressLint("ActionValue")
+ public static final String EXTRA_APN_PROTOCOL_INT = "apnProtoInt";
+
+ /**
+ * An integer extra indicating the pco id for the data upon
+ * {@link #ACTION_CARRIER_SIGNAL_PCO_VALUE} broadcasts.
+ * @hide
+ */
+ @SystemApi
+ @SuppressLint("ActionValue")
+ public static final String EXTRA_PCO_ID = "pcoId";
+
+ /**
+ * An extra of byte array of pco data read from modem upon
+ * {@link #ACTION_CARRIER_SIGNAL_PCO_VALUE} broadcasts.
+ * @hide
+ */
+ @SystemApi
+ @SuppressLint("ActionValue")
+ public static final String EXTRA_PCO_VALUE = "pcoValue";
+
+ /**
+ * An boolean extra indicating default network available upon
+ * {@link #ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE} broadcasts.
+ * @hide
+ */
+ @SystemApi
+ @SuppressLint("ActionValue")
+ public static final String EXTRA_DEFAULT_NETWORK_AVAILABLE = "defaultNetworkAvailable";
/**
* <p>Broadcast Action: The emergency call state is changed.
* <ul>
- * <li><em>phoneInEmergencyCall</em> - A boolean value, true if phone in emergency call,
- * false otherwise</li>
+ * <li><em>EXTRA_PHONE_IN_EMERGENCY_CALL</em> - A boolean value, true if phone in emergency
+ * call, false otherwise</li>
* </ul>
* <p class="note">
* You can <em>not</em> receive this through components declared
@@ -1522,12 +1694,25 @@
*
* <p class="note">This is a protected intent that can only be sent by the system.
*
+ * @see #EXTRA_PHONE_IN_EMERGENCY_CALL
+ *
* @hide
*/
@SystemApi
@SuppressLint("ActionValue")
- public static final String ACTION_EMERGENCY_CALL_STATE_CHANGED
- = "android.intent.action.EMERGENCY_CALL_STATE_CHANGED";
+ public static final String ACTION_EMERGENCY_CALL_STATE_CHANGED =
+ "android.intent.action.EMERGENCY_CALL_STATE_CHANGED";
+
+
+ /**
+ * Extra included in {@link #ACTION_EMERGENCY_CALL_STATE_CHANGED}.
+ * It indicates whether the phone is making an emergency call.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final String EXTRA_PHONE_IN_EMERGENCY_CALL =
+ "android.telephony.extra.PHONE_IN_EMERGENCY_CALL";
/**
* <p>Broadcast Action: It indicates the Emergency callback mode blocks datacall/sms
@@ -1540,8 +1725,50 @@
* @hide
*/
@SystemApi
- public static final String ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS
- = "android.telephony.action.SHOW_NOTICE_ECM_BLOCK_OTHERS";
+ public static final String ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS =
+ "android.telephony.action.SHOW_NOTICE_ECM_BLOCK_OTHERS";
+
+ /**
+ * Broadcast Action: The default data subscription has changed in a multi-SIM device.
+ * This has the following extra values:</p>
+ * <ul>
+ * <li><em>subscription</em> - A int, the current data default subscription.</li>
+ * </ul>
+ *
+ * @hide
+ */
+ @SystemApi
+ @SuppressLint("ActionValue")
+ public static final String ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED =
+ "android.intent.action.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED";
+
+ /**
+ * Broadcast Action: The default voice subscription has changed in a mult-SIm device.
+ * This has the following extra values:</p>
+ * <ul>
+ * <li><em>subscription</em> - A int, the current voice default subscription.</li>
+ * </ul>
+ *
+ * @hide
+ */
+ @SystemApi
+ @SuppressLint("ActionValue")
+ public static final String ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED =
+ "android.intent.action.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED";
+
+ /**
+ * Broadcast Action: This triggers a client initiated OMA-DM session to the OMA server.
+ * <p class="note">
+ * Open Mobile Alliance (OMA) Device Management (DM).
+ *
+ * This intent is used by the system components to trigger OMA-DM
+ *
+ * @hide
+ */
+ @SystemApi
+ @SuppressLint("ActionValue")
+ public static final String ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE =
+ "com.android.omadm.service.CONFIGURATION_UPDATE";
//
//
@@ -2057,6 +2284,16 @@
public static final int PHONE_TYPE_CDMA = PhoneConstants.PHONE_TYPE_CDMA;
/** Phone is via SIP. */
public static final int PHONE_TYPE_SIP = PhoneConstants.PHONE_TYPE_SIP;
+ /** Phone is via IMS. */
+ public static final int PHONE_TYPE_IMS = PhoneConstants.PHONE_TYPE_IMS;
+
+ /**
+ * Phone is via Third Party.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int PHONE_TYPE_THIRD_PARTY = PhoneConstants.PHONE_TYPE_THIRD_PARTY;
/**
* Returns the current phone type.
@@ -5043,7 +5280,9 @@
* not present or not loaded
* @hide
*/
- @UnsupportedAppUsage
+ @Nullable
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public String[] getIsimImpu() {
try {
IPhoneSubInfo info = getSubscriberInfo();
@@ -5065,7 +5304,11 @@
@UnsupportedAppUsage
private IPhoneSubInfo getSubscriberInfo() {
// get it each time because that process crashes a lot
- return IPhoneSubInfo.Stub.asInterface(ServiceManager.getService("iphonesubinfo"));
+ return IPhoneSubInfo.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getPhoneSubServiceRegisterer()
+ .get());
}
/**
@@ -5221,6 +5464,13 @@
public static final int DATA_DISCONNECTING = 4;
/**
+ * To check the SDK version for {@link TelephonyManager#getDataState}.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q)
+ private static final long GET_DATA_STATE_CODE_CHANGE = 147600208L;
+
+ /**
* Returns a constant indicating the current data connection state
* (cellular).
*
@@ -5238,7 +5488,7 @@
int state = telephony.getDataStateForSubId(
getSubId(SubscriptionManager.getActiveDataSubscriptionId()));
if (state == TelephonyManager.DATA_DISCONNECTING
- && VMRuntime.getRuntime().getTargetSdkVersion() < Build.VERSION_CODES.R) {
+ && !Compatibility.isChangeEnabled(GET_DATA_STATE_CODE_CHANGE)) {
return TelephonyManager.DATA_CONNECTED;
}
@@ -5278,11 +5528,19 @@
}
private ITelephonyRegistry getTelephonyRegistry() {
- return ITelephonyRegistry.Stub.asInterface(ServiceManager.getService("telephony.registry"));
+ return ITelephonyRegistry.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getTelephonyRegistryServiceRegisterer()
+ .get());
}
private IOns getIOns() {
- return IOns.Stub.asInterface(ServiceManager.getService("ions"));
+ return IOns.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getOpportunisticNetworkServiceRegisterer()
+ .get());
}
//
@@ -5292,6 +5550,13 @@
//
/**
+ * To check the SDK version for {@link TelephonyManager#listen}.
+ */
+ @ChangeId
+ @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.P)
+ private static final long LISTEN_CODE_CHANGE = 147600208L;
+
+ /**
* Registers a listener object to receive notification of changes
* in specified telephony states.
* <p>
@@ -5330,7 +5595,7 @@
// subId from PhoneStateListener is deprecated Q on forward, use the subId from
// TelephonyManager instance. keep using subId from PhoneStateListener for pre-Q.
int subId = mSubId;
- if (VMRuntime.getRuntime().getTargetSdkVersion() >= Build.VERSION_CODES.Q) {
+ if (Compatibility.isChangeEnabled(LISTEN_CODE_CHANGE)) {
// since mSubId in PhoneStateListener is deprecated from Q on forward, this is
// the only place to set mSubId and its for "informational" only.
// TODO: remove this once we completely get rid of mSubId in PhoneStateListener
@@ -5838,7 +6103,10 @@
* @param AID Application id. See ETSI 102.221 and 101.220.
* @param p2 P2 parameter (described in ISO 7816-4).
* @return an IccOpenLogicalChannelResponse object.
+ * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
*/
+ // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+ @Deprecated
public IccOpenLogicalChannelResponse iccOpenLogicalChannel(String AID, int p2) {
return iccOpenLogicalChannel(getSubId(), AID, p2);
}
@@ -5869,7 +6137,10 @@
* @param p2 P2 parameter (described in ISO 7816-4).
* @return an IccOpenLogicalChannelResponse object.
* @hide
+ * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
*/
+ // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+ @Deprecated
public IccOpenLogicalChannelResponse iccOpenLogicalChannel(int subId, String AID, int p2) {
try {
ITelephony telephony = getITelephony();
@@ -5897,7 +6168,10 @@
* iccOpenLogicalChannel.
* @return true if the channel was closed successfully.
* @hide
+ * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
*/
+ // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+ @Deprecated
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@SystemApi
public boolean iccCloseLogicalChannelBySlot(int slotIndex, int channel) {
@@ -5924,7 +6198,10 @@
* @param channel is the channel id to be closed as returned by a successful
* iccOpenLogicalChannel.
* @return true if the channel was closed successfully.
+ * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
*/
+ // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+ @Deprecated
public boolean iccCloseLogicalChannel(int channel) {
return iccCloseLogicalChannel(getSubId(), channel);
}
@@ -5943,7 +6220,10 @@
* iccOpenLogicalChannel.
* @return true if the channel was closed successfully.
* @hide
+ * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
*/
+ // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+ @Deprecated
public boolean iccCloseLogicalChannel(int subId, int channel) {
try {
ITelephony telephony = getITelephony();
@@ -5979,7 +6259,10 @@
* @return The APDU response from the ICC card with the status appended at the end, or null if
* there is an issue connecting to the Telephony service.
* @hide
+ * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
*/
+ // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+ @Deprecated
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@SystemApi
@Nullable
@@ -6017,7 +6300,10 @@
* @param data Data to be sent with the APDU.
* @return The APDU response from the ICC card with the status appended at
* the end.
+ * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
*/
+ // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+ @Deprecated
public String iccTransmitApduLogicalChannel(int channel, int cla,
int instruction, int p1, int p2, int p3, String data) {
return iccTransmitApduLogicalChannel(getSubId(), channel, cla,
@@ -6046,7 +6332,10 @@
* @return The APDU response from the ICC card with the status appended at
* the end.
* @hide
+ * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
*/
+ // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+ @Deprecated
public String iccTransmitApduLogicalChannel(int subId, int channel, int cla,
int instruction, int p1, int p2, int p3, String data) {
try {
@@ -6082,7 +6371,10 @@
* @return The APDU response from the ICC card with the status appended at
* the end.
* @hide
+ * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
*/
+ // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+ @Deprecated
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
@SystemApi
@NonNull
@@ -6118,7 +6410,10 @@
* @param data Data to be sent with the APDU.
* @return The APDU response from the ICC card with the status appended at
* the end.
+ * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
*/
+ // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+ @Deprecated
public String iccTransmitApduBasicChannel(int cla,
int instruction, int p1, int p2, int p3, String data) {
return iccTransmitApduBasicChannel(getSubId(), cla,
@@ -6145,7 +6440,10 @@
* @return The APDU response from the ICC card with the status appended at
* the end.
* @hide
+ * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
*/
+ // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+ @Deprecated
public String iccTransmitApduBasicChannel(int subId, int cla,
int instruction, int p1, int p2, int p3, String data) {
try {
@@ -6173,7 +6471,10 @@
* @param p3 P3 value of the APDU command.
* @param filePath
* @return The APDU response.
+ * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
*/
+ // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+ @Deprecated
public byte[] iccExchangeSimIO(int fileID, int command, int p1, int p2, int p3,
String filePath) {
return iccExchangeSimIO(getSubId(), fileID, command, p1, p2, p3, filePath);
@@ -6195,7 +6496,10 @@
* @param filePath
* @return The APDU response.
* @hide
+ * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
*/
+ // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+ @Deprecated
public byte[] iccExchangeSimIO(int subId, int fileID, int command, int p1, int p2,
int p3, String filePath) {
try {
@@ -6221,7 +6525,10 @@
* @return The APDU response from the ICC card in hexadecimal format
* with the last 4 bytes being the status word. If the command fails,
* returns an empty string.
+ * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
*/
+ // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+ @Deprecated
public String sendEnvelopeWithStatus(String content) {
return sendEnvelopeWithStatus(getSubId(), content);
}
@@ -6241,7 +6548,10 @@
* with the last 4 bytes being the status word. If the command fails,
* returns an empty string.
* @hide
+ * @deprecated Use {@link android.se.omapi.SEService} APIs instead.
*/
+ // TODO(b/147153909): Update Javadoc to link to specific SEService API once integrated.
+ @Deprecated
public String sendEnvelopeWithStatus(int subId, String content) {
try {
ITelephony telephony = getITelephony();
@@ -7357,6 +7667,18 @@
RILConstants.NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA;
/**
+ * The default preferred network mode constant.
+ *
+ * <p> This constant is used in case of nothing is set in
+ * TelephonyProperties#default_network().
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int DEFAULT_PREFERRED_NETWORK_MODE =
+ RILConstants.DEFAULT_PREFERRED_NETWORK_MODE;
+
+ /**
* Get the preferred network type.
* Used for device configuration by some CDMA operators.
*
@@ -7731,12 +8053,12 @@
/**
* Check whether DUN APN is required for tethering.
* <p>
- * Requires Permission: READ_PRIVILEGED_PHONE_STATE.
+ * Requires Permission: MODIFY_PHONE_STATE.
*
* @return {@code true} if DUN APN is required for tethering.
* @hide
*/
- @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@SystemApi
public boolean isTetheringApnRequired() {
return isTetheringApnRequired(getSubId(SubscriptionManager.getActiveDataSubscriptionId()));
@@ -8692,8 +9014,9 @@
*
* @hide
*/
- @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
- public int getCdmaRoamingMode() {
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public @CdmaRoamingMode int getCdmaRoamingMode() {
int mode = CDMA_ROAMING_MODE_RADIO_DEFAULT;
try {
ITelephony telephony = getITelephony();
@@ -8720,8 +9043,9 @@
*
* @hide
*/
+ @SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- public boolean setCdmaRoamingMode(int mode) {
+ public boolean setCdmaRoamingMode(@CdmaRoamingMode int mode) {
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
@@ -8733,6 +9057,36 @@
return false;
}
+ /** @hide */
+ @IntDef(flag = true, prefix = { "CDMA_SUBSCRIPTION_" }, value = {
+ CDMA_SUBSCRIPTION_UNKNOWN,
+ CDMA_SUBSCRIPTION_RUIM_SIM,
+ CDMA_SUBSCRIPTION_NV
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CdmaSubscription{}
+
+ /** Used for CDMA subscription mode, it'll be UNKNOWN if there is no Subscription source.
+ * @hide
+ */
+ @SystemApi
+ public static final int CDMA_SUBSCRIPTION_UNKNOWN = -1;
+
+ /** Used for CDMA subscription mode: RUIM/SIM (default)
+ * @hide
+ */
+ @SystemApi
+ public static final int CDMA_SUBSCRIPTION_RUIM_SIM = 0;
+
+ /** Used for CDMA subscription mode: NV -> non-volatile memory
+ * @hide
+ */
+ @SystemApi
+ public static final int CDMA_SUBSCRIPTION_NV = 1;
+
+ /** @hide */
+ public static final int PREFERRED_CDMA_SUBSCRIPTION = CDMA_SUBSCRIPTION_RUIM_SIM;
+
/**
* Sets the subscription mode for CDMA phone to the given mode {@code mode}.
*
@@ -8740,14 +9094,15 @@
*
* @return {@code true} if successed.
*
- * @see Phone#CDMA_SUBSCRIPTION_UNKNOWN
- * @see Phone#CDMA_SUBSCRIPTION_RUIM_SIM
- * @see Phone#CDMA_SUBSCRIPTION_NV
+ * @see #CDMA_SUBSCRIPTION_UNKNOWN
+ * @see #CDMA_SUBSCRIPTION_RUIM_SIM
+ * @see #CDMA_SUBSCRIPTION_NV
*
* @hide
*/
+ @SystemApi
@RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
- public boolean setCdmaSubscriptionMode(int mode) {
+ public boolean setCdmaSubscriptionMode(@CdmaSubscription int mode) {
try {
ITelephony telephony = getITelephony();
if (telephony != null) {
@@ -10360,6 +10715,7 @@
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}.
*
* @param enabled control enable or disable carrier data.
+ * @see #resetAllCarrierActions()
* @hide
*/
@SystemApi
@@ -10386,6 +10742,7 @@
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}.
*
* @param enabled control enable or disable radio.
+ * @see #resetAllCarrierActions()
* @hide
*/
@SystemApi
@@ -10412,6 +10769,7 @@
* {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}.
*
* @param report control start/stop reporting network status.
+ * @see #resetAllCarrierActions()
* @hide
*/
@SystemApi
@@ -10478,14 +10836,15 @@
/**
* Policy control of data connection. Usually used when data limit is passed.
* @param enabled True if enabling the data, otherwise disabling.
- * @param subId sub id
* @hide
*/
- public void setPolicyDataEnabled(boolean enabled, int subId) {
+ @SystemApi
+ @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+ public void setPolicyDataEnabled(boolean enabled) {
try {
ITelephony service = getITelephony();
if (service != null) {
- service.setPolicyDataEnabled(enabled, subId);
+ service.setPolicyDataEnabled(enabled, getSubId());
}
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelephony#setPolicyDataEnabled", e);
@@ -10558,7 +10917,8 @@
*
* @hide
*/
- @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public boolean isManualNetworkSelectionAllowed() {
try {
ITelephony telephony = getITelephony();
@@ -10592,12 +10952,21 @@
}
/**
+ * Checks whether cellular data connection is enabled in the device.
+ *
+ * Whether cellular data connection is enabled, meaning upon request whether will try to setup
+ * metered data connection considering all factors below:
+ * 1) User turned on data setting {@link #isDataEnabled}.
+ * 2) Carrier allows data to be on.
+ * 3) Network policy.
+ * And possibly others.
+ *
+ * @return {@code true} if the overall data connection is capable; {@code false} if not.
* @hide
- * It's similar to isDataEnabled, but unlike isDataEnabled, this API also evaluates
- * carrierDataEnabled, policyDataEnabled etc to give a final decision of whether mobile data is
- * capable of using.
*/
- public boolean isDataCapable() {
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public boolean isDataConnectionEnabled() {
boolean retVal = false;
try {
int subId = getSubId(SubscriptionManager.getDefaultDataSubscriptionId());
@@ -10605,13 +10974,24 @@
if (telephony != null)
retVal = telephony.isDataEnabled(subId);
} catch (RemoteException e) {
- Log.e(TAG, "Error calling ITelephony#isDataEnabled", e);
+ Log.e(TAG, "Error isDataConnectionEnabled", e);
} catch (NullPointerException e) {
}
return retVal;
}
/**
+ * Checks if FEATURE_TELEPHONY_DATA is enabled.
+ *
+ * @hide
+ */
+ public boolean isDataCapable() {
+ if (mContext == null) return true;
+ return mContext.getPackageManager().hasSystemFeature(
+ PackageManager.FEATURE_TELEPHONY_DATA);
+ }
+
+ /**
* In this mode, modem will not send specified indications when screen is off.
* @hide
*/
@@ -11958,7 +12338,7 @@
* 1) User data is turned on, or
* 2) APN is un-metered for this subscription, or
* 3) APN type is whitelisted. E.g. MMS is whitelisted if
- * {@link SubscriptionManager#setAlwaysAllowMmsData} is turned on.
+ * {@link #setAlwaysAllowMmsData(boolean)} is turned on.
*
* @param apnType Value indicating the apn type. Apn types are defined in {@link ApnSetting}.
* @return whether data is enabled for a apn type.
@@ -12082,4 +12462,30 @@
}
return false;
}
+
+ /**
+ * Set whether the specific sim card always allows MMS connection. If true, MMS network
+ * request will be accepted by telephony even if user turns "mobile data" off
+ * on this specific sim card.
+ *
+ * @param alwaysAllow whether Mms data is always allowed.
+ * @return whether operation is successful.
+ *
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+ public boolean setAlwaysAllowMmsData(boolean alwaysAllow) {
+ try {
+ ITelephony service = getITelephony();
+ if (service != null) {
+ return service.setAlwaysAllowMmsData(getSubId(), alwaysAllow);
+ }
+ } catch (RemoteException ex) {
+ if (!isSystemProcess()) {
+ ex.rethrowAsRuntimeException();
+ }
+ }
+ return false;
+ }
}
diff --git a/telephony/java/android/telephony/TelephonyScanManager.java b/telephony/java/android/telephony/TelephonyScanManager.java
index 96b6db7..a1d40e8 100644
--- a/telephony/java/android/telephony/TelephonyScanManager.java
+++ b/telephony/java/android/telephony/TelephonyScanManager.java
@@ -16,10 +16,11 @@
package android.telephony;
+import com.android.telephony.Rlog;
+
import static com.android.internal.util.Preconditions.checkNotNull;
import android.annotation.Nullable;
-import android.content.Context;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
@@ -29,7 +30,6 @@
import android.os.Messenger;
import android.os.Parcelable;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.util.SparseArray;
import com.android.internal.telephony.ITelephony;
@@ -234,6 +234,9 @@
private ITelephony getITelephony() {
return ITelephony.Stub.asInterface(
- ServiceManager.getService(Context.TELEPHONY_SERVICE));
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getTelephonyServiceRegisterer()
+ .get());
}
}
diff --git a/telephony/java/android/telephony/UiccAccessRule.java b/telephony/java/android/telephony/UiccAccessRule.java
index 93ccba1..81a09c6 100644
--- a/telephony/java/android/telephony/UiccAccessRule.java
+++ b/telephony/java/android/telephony/UiccAccessRule.java
@@ -15,6 +15,8 @@
*/
package android.telephony;
+import com.android.telephony.Rlog;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
diff --git a/telephony/java/android/telephony/VoLteServiceState.java b/telephony/java/android/telephony/VoLteServiceState.java
index f65c7b0..d4a27d9 100644
--- a/telephony/java/android/telephony/VoLteServiceState.java
+++ b/telephony/java/android/telephony/VoLteServiceState.java
@@ -16,12 +16,13 @@
package android.telephony;
-import android.annotation.UnsupportedAppUsage;
+import com.android.telephony.Rlog;
+
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
-import android.telephony.Rlog;
/**
* Contains LTE network state related information.
@@ -52,8 +53,7 @@
/**
* Create a new VoLteServiceState from a intent notifier Bundle
*
- * This method is used by PhoneStateIntentReceiver and maybe by
- * external applications.
+ * This method is maybe used by external applications.
*
* @param m Bundle from intent notifier
* @return newly created VoLteServiceState
diff --git a/telephony/java/android/telephony/cdma/CdmaCellLocation.java b/telephony/java/android/telephony/cdma/CdmaCellLocation.java
index 45b1e47..9bc39a0 100644
--- a/telephony/java/android/telephony/cdma/CdmaCellLocation.java
+++ b/telephony/java/android/telephony/cdma/CdmaCellLocation.java
@@ -16,7 +16,7 @@
package android.telephony.cdma;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Bundle;
import android.telephony.CellLocation;
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index 034fc22..fab1bf2 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -28,7 +28,7 @@
import android.provider.Telephony.Carriers;
import android.telephony.Annotation.ApnType;
import android.telephony.Annotation.NetworkType;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
@@ -1056,6 +1056,11 @@
}
/** @hide */
+ public boolean isEmergencyApn() {
+ return hasApnType(TYPE_EMERGENCY);
+ }
+
+ /** @hide */
public boolean canHandleType(@ApnType int type) {
if (!mCarrierEnabled) {
return false;
diff --git a/telephony/java/android/telephony/data/DataService.java b/telephony/java/android/telephony/data/DataService.java
index 372bdf1..bff12b6 100644
--- a/telephony/java/android/telephony/data/DataService.java
+++ b/telephony/java/android/telephony/data/DataService.java
@@ -31,7 +31,7 @@
import android.os.Message;
import android.os.RemoteException;
import android.telephony.AccessNetworkConstants;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
diff --git a/telephony/java/android/telephony/data/DataServiceCallback.java b/telephony/java/android/telephony/data/DataServiceCallback.java
index 11dc78a..d33d3f9 100644
--- a/telephony/java/android/telephony/data/DataServiceCallback.java
+++ b/telephony/java/android/telephony/data/DataServiceCallback.java
@@ -22,7 +22,7 @@
import android.annotation.SystemApi;
import android.net.LinkProperties;
import android.os.RemoteException;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
import android.telephony.data.DataService.DataServiceProvider;
import java.lang.annotation.Retention;
diff --git a/telephony/java/android/telephony/data/QualifiedNetworksService.java b/telephony/java/android/telephony/data/QualifiedNetworksService.java
index e793979..8220b16 100644
--- a/telephony/java/android/telephony/data/QualifiedNetworksService.java
+++ b/telephony/java/android/telephony/data/QualifiedNetworksService.java
@@ -28,7 +28,7 @@
import android.os.RemoteException;
import android.telephony.AccessNetworkConstants.AccessNetworkType;
import android.telephony.Annotation.ApnType;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
import android.util.SparseArray;
import com.android.internal.annotations.VisibleForTesting;
diff --git a/telephony/java/android/telephony/emergency/EmergencyNumber.java b/telephony/java/android/telephony/emergency/EmergencyNumber.java
index 1666265..cd3fc95 100644
--- a/telephony/java/android/telephony/emergency/EmergencyNumber.java
+++ b/telephony/java/android/telephony/emergency/EmergencyNumber.java
@@ -25,7 +25,7 @@
import android.os.Parcelable;
import android.telephony.CarrierConfigManager;
import android.telephony.PhoneNumberUtils;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
diff --git a/telephony/java/android/telephony/euicc/DownloadableSubscription.java b/telephony/java/android/telephony/euicc/DownloadableSubscription.java
index cb27f64..23d46ba 100644
--- a/telephony/java/android/telephony/euicc/DownloadableSubscription.java
+++ b/telephony/java/android/telephony/euicc/DownloadableSubscription.java
@@ -17,17 +17,18 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
import android.app.PendingIntent;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
import android.telephony.UiccAccessRule;
+
+import com.android.internal.util.Preconditions;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import com.android.internal.util.Preconditions;
-
/**
* Information about a subscription which is downloadable to an eUICC using
* {@link EuiccManager#downloadSubscription(DownloadableSubscription, boolean, PendingIntent).
diff --git a/telephony/java/android/telephony/euicc/EuiccCardManager.java b/telephony/java/android/telephony/euicc/EuiccCardManager.java
index 994c49c..e16fffa 100644
--- a/telephony/java/android/telephony/euicc/EuiccCardManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccCardManager.java
@@ -21,8 +21,8 @@
import android.annotation.SystemApi;
import android.content.Context;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.service.euicc.EuiccProfileInfo;
+import android.telephony.TelephonyFrameworkInitializer;
import android.util.Log;
import com.android.internal.telephony.euicc.IAuthenticateServerCallback;
@@ -148,7 +148,10 @@
private IEuiccCardController getIEuiccCardController() {
return IEuiccCardController.Stub.asInterface(
- ServiceManager.getService("euicc_card_controller"));
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getEuiccCardControllerServiceRegisterer()
+ .get());
}
/**
diff --git a/telephony/java/android/telephony/euicc/EuiccInfo.java b/telephony/java/android/telephony/euicc/EuiccInfo.java
index 91ebb6c..467d268 100644
--- a/telephony/java/android/telephony/euicc/EuiccInfo.java
+++ b/telephony/java/android/telephony/euicc/EuiccInfo.java
@@ -16,7 +16,7 @@
package android.telephony.euicc;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index cb66a96..d5a48df 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -30,7 +30,7 @@
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.RemoteException;
-import android.os.ServiceManager;
+import android.telephony.TelephonyFrameworkInitializer;
import android.telephony.TelephonyManager;
import android.telephony.euicc.EuiccCardManager.ResetOption;
@@ -968,6 +968,10 @@
}
private static IEuiccController getIEuiccController() {
- return IEuiccController.Stub.asInterface(ServiceManager.getService("econtroller"));
+ return IEuiccController.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getEuiccControllerService()
+ .get());
}
}
diff --git a/telephony/java/android/telephony/gsm/GsmCellLocation.java b/telephony/java/android/telephony/gsm/GsmCellLocation.java
index d6780ce..30cea0e 100644
--- a/telephony/java/android/telephony/gsm/GsmCellLocation.java
+++ b/telephony/java/android/telephony/gsm/GsmCellLocation.java
@@ -16,7 +16,7 @@
package android.telephony.gsm;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Bundle;
import android.telephony.CellLocation;
diff --git a/telephony/java/android/telephony/ims/ImsCallForwardInfo.java b/telephony/java/android/telephony/ims/ImsCallForwardInfo.java
index 9f09d7a..d53a2e6 100644
--- a/telephony/java/android/telephony/ims/ImsCallForwardInfo.java
+++ b/telephony/java/android/telephony/ims/ImsCallForwardInfo.java
@@ -20,7 +20,7 @@
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/telephony/java/android/telephony/ims/ImsCallProfile.java b/telephony/java/android/telephony/ims/ImsCallProfile.java
index 87e5391..bc60d81 100644
--- a/telephony/java/android/telephony/ims/ImsCallProfile.java
+++ b/telephony/java/android/telephony/ims/ImsCallProfile.java
@@ -20,7 +20,7 @@
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
@@ -279,6 +279,14 @@
"android.telephony.ims.extra.ADDITIONAL_SIP_INVITE_FIELDS";
/**
+ * CallDisconnectCause: Specify call disconnect cause. This extra should be a code
+ * corresponding to ImsReasonInfo and should only be populated in the case that the
+ * call has already been missed
+ */
+ public static final String EXTRA_CALL_DISCONNECT_CAUSE =
+ "android.telephony.ims.extra.CALL_DISCONNECT_CAUSE";
+
+ /**
* Extra key which the RIL can use to indicate the radio technology used for a call.
* Valid values are:
* {@link android.telephony.ServiceState#RIL_RADIO_TECHNOLOGY_LTE},
diff --git a/telephony/java/android/telephony/ims/ImsConferenceState.java b/telephony/java/android/telephony/ims/ImsConferenceState.java
index 8d2049b..abfee61 100644
--- a/telephony/java/android/telephony/ims/ImsConferenceState.java
+++ b/telephony/java/android/telephony/ims/ImsConferenceState.java
@@ -24,7 +24,7 @@
import android.os.Parcelable;
import android.telecom.Call;
import android.telecom.Connection;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
import android.util.Log;
import java.util.HashMap;
diff --git a/telephony/java/android/telephony/ims/ImsExternalCallState.java b/telephony/java/android/telephony/ims/ImsExternalCallState.java
index dcb9c9d..136a83e 100644
--- a/telephony/java/android/telephony/ims/ImsExternalCallState.java
+++ b/telephony/java/android/telephony/ims/ImsExternalCallState.java
@@ -24,7 +24,7 @@
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
diff --git a/telephony/java/android/telephony/ims/ImsMmTelManager.java b/telephony/java/android/telephony/ims/ImsMmTelManager.java
index 057d22c..91514e9 100644
--- a/telephony/java/android/telephony/ims/ImsMmTelManager.java
+++ b/telephony/java/android/telephony/ims/ImsMmTelManager.java
@@ -25,14 +25,13 @@
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.content.Context;
import android.os.Binder;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.os.ServiceSpecificException;
import android.telephony.AccessNetworkConstants;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyFrameworkInitializer;
import android.telephony.ims.aidl.IImsCapabilityCallback;
import android.telephony.ims.feature.ImsFeature;
import android.telephony.ims.feature.MmTelFeature;
@@ -1018,7 +1017,10 @@
private static ITelephony getITelephony() {
ITelephony binder = ITelephony.Stub.asInterface(
- ServiceManager.getService(Context.TELEPHONY_SERVICE));
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getTelephonyServiceRegisterer()
+ .get());
if (binder == null) {
throw new RuntimeException("Could not find Telephony Service.");
}
diff --git a/telephony/java/android/telephony/ims/ImsRcsManager.java b/telephony/java/android/telephony/ims/ImsRcsManager.java
index d3fb37f..d483291 100644
--- a/telephony/java/android/telephony/ims/ImsRcsManager.java
+++ b/telephony/java/android/telephony/ims/ImsRcsManager.java
@@ -26,8 +26,9 @@
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.telephony.AccessNetworkConstants;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyFrameworkInitializer;
import android.telephony.ims.aidl.IImsCapabilityCallback;
import android.telephony.ims.aidl.IImsRcsController;
import android.telephony.ims.feature.ImsFeature;
@@ -35,6 +36,8 @@
import android.telephony.ims.stub.ImsRegistrationImplBase;
import android.util.Log;
+import com.android.internal.telephony.IIntegerConsumer;
+
import java.util.concurrent.Executor;
import java.util.function.Consumer;
@@ -158,9 +161,20 @@
if (executor == null) {
throw new IllegalArgumentException("Must include a non-null Executor.");
}
+
+ IImsRcsController imsRcsController = getIImsRcsController();
+ if (imsRcsController == null) {
+ Log.e(TAG, "Register registration callback: IImsRcsController is null");
+ throw new ImsException("Cannot find remote IMS service",
+ ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+ }
+
c.setExecutor(executor);
- throw new UnsupportedOperationException("registerImsRegistrationCallback is not"
- + "supported.");
+ try {
+ imsRcsController.registerImsRegistrationCallback(mSubId, c.getBinder());
+ } catch (RemoteException | IllegalStateException e) {
+ throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
+ }
}
/**{@inheritDoc}*/
@@ -171,8 +185,18 @@
if (c == null) {
throw new IllegalArgumentException("Must include a non-null RegistrationCallback.");
}
- throw new UnsupportedOperationException("unregisterImsRegistrationCallback is not"
- + "supported.");
+
+ IImsRcsController imsRcsController = getIImsRcsController();
+ if (imsRcsController == null) {
+ Log.e(TAG, "Unregister registration callback: IImsRcsController is null");
+ throw new IllegalStateException("Cannot find remote IMS service");
+ }
+
+ try {
+ imsRcsController.unregisterImsRegistrationCallback(mSubId, c.getBinder());
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
}
/**{@inheritDoc}*/
@@ -186,8 +210,23 @@
if (executor == null) {
throw new IllegalArgumentException("Must include a non-null Executor.");
}
- throw new UnsupportedOperationException("getRegistrationState is not"
- + "supported.");
+
+ IImsRcsController imsRcsController = getIImsRcsController();
+ if (imsRcsController == null) {
+ Log.e(TAG, "Get registration state error: IImsRcsController is null");
+ throw new IllegalStateException("Cannot find remote IMS service");
+ }
+
+ try {
+ imsRcsController.getImsRcsRegistrationState(mSubId, new IIntegerConsumer.Stub() {
+ @Override
+ public void accept(int result) {
+ executor.execute(() -> stateCallback.accept(result));
+ }
+ });
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
}
/**{@inheritDoc}*/
@@ -202,10 +241,25 @@
if (executor == null) {
throw new IllegalArgumentException("Must include a non-null Executor.");
}
- throw new UnsupportedOperationException("getRegistrationTransportType is not"
- + "supported.");
- }
+ IImsRcsController imsRcsController = getIImsRcsController();
+ if (imsRcsController == null) {
+ Log.e(TAG, "Get registration transport type error: IImsRcsController is null");
+ throw new IllegalStateException("Cannot find remote IMS service");
+ }
+
+ try {
+ imsRcsController.getImsRcsRegistrationTransportType(mSubId,
+ new IIntegerConsumer.Stub() {
+ @Override
+ public void accept(int result) {
+ executor.execute(() -> transportTypeCallback.accept(result));
+ }
+ });
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
/**
* Registers an {@link AvailabilityCallback} with the system, which will provide RCS
@@ -362,7 +416,10 @@
}
private IImsRcsController getIImsRcsController() {
- IBinder binder = ServiceManager.getService(Context.TELEPHONY_IMS_SERVICE);
+ IBinder binder = TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getTelephonyImsServiceRegisterer()
+ .get();
return IImsRcsController.Stub.asInterface(binder);
}
}
diff --git a/telephony/java/android/telephony/ims/ImsReasonInfo.java b/telephony/java/android/telephony/ims/ImsReasonInfo.java
index f4b2cef..0d6b31d 100644
--- a/telephony/java/android/telephony/ims/ImsReasonInfo.java
+++ b/telephony/java/android/telephony/ims/ImsReasonInfo.java
@@ -20,7 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/telephony/java/android/telephony/ims/ImsSsData.java b/telephony/java/android/telephony/ims/ImsSsData.java
index 6b72859..2d2e638 100644
--- a/telephony/java/android/telephony/ims/ImsSsData.java
+++ b/telephony/java/android/telephony/ims/ImsSsData.java
@@ -22,7 +22,7 @@
import android.annotation.TestApi;
import android.os.Parcel;
import android.os.Parcelable;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
diff --git a/telephony/java/android/telephony/ims/ImsSsInfo.java b/telephony/java/android/telephony/ims/ImsSsInfo.java
index 77bd984..9cce95f 100644
--- a/telephony/java/android/telephony/ims/ImsSsInfo.java
+++ b/telephony/java/android/telephony/ims/ImsSsInfo.java
@@ -21,7 +21,7 @@
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/telephony/java/android/telephony/ims/ImsStreamMediaProfile.java b/telephony/java/android/telephony/ims/ImsStreamMediaProfile.java
index b7ab0a0..b70fd64 100644
--- a/telephony/java/android/telephony/ims/ImsStreamMediaProfile.java
+++ b/telephony/java/android/telephony/ims/ImsStreamMediaProfile.java
@@ -19,7 +19,7 @@
import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/telephony/java/android/telephony/ims/ImsVideoCallProvider.java b/telephony/java/android/telephony/ims/ImsVideoCallProvider.java
index 270e693..569c6d5 100644
--- a/telephony/java/android/telephony/ims/ImsVideoCallProvider.java
+++ b/telephony/java/android/telephony/ims/ImsVideoCallProvider.java
@@ -18,7 +18,7 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
diff --git a/telephony/java/android/telephony/ims/ProvisioningManager.java b/telephony/java/android/telephony/ims/ProvisioningManager.java
index e4d6335..9b739d3 100644
--- a/telephony/java/android/telephony/ims/ProvisioningManager.java
+++ b/telephony/java/android/telephony/ims/ProvisioningManager.java
@@ -25,16 +25,14 @@
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.WorkerThread;
-import android.content.Context;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyFrameworkInitializer;
import android.telephony.ims.aidl.IImsConfigCallback;
import android.telephony.ims.feature.MmTelFeature;
+import android.telephony.ims.feature.RcsFeature;
import android.telephony.ims.stub.ImsConfigImplBase;
import android.telephony.ims.stub.ImsRegistrationImplBase;
@@ -85,6 +83,11 @@
"STRING_QUERY_RESULT_ERROR_NOT_READY";
/**
+ * There is no existing configuration for the queried provisioning key.
+ */
+ public static final int PROVISIONING_RESULT_UNKNOWN = -1;
+
+ /**
* The integer result of provisioning for the queried key is disabled.
*/
public static final int PROVISIONING_VALUE_DISABLED = 0;
@@ -95,6 +98,151 @@
public static final int PROVISIONING_VALUE_ENABLED = 1;
+ // Inheriting values from ImsConfig for backwards compatibility.
+ /**
+ * An integer key representing the SIP T1 timer value in milliseconds for the associated
+ * subscription.
+ * <p>
+ * The SIP T1 timer is an estimate of the round-trip time and will retransmit
+ * INVITE transactions that are longer than T1 milliseconds over unreliable transports, doubling
+ * the time before retransmission every time there is no response. See RFC3261, section 17.1.1.1
+ * for more details.
+ * <p>
+ * The value is an integer.
+ * @see #setProvisioningIntValue(int, int)
+ * @see #getProvisioningIntValue(int)
+ */
+ public static final int KEY_T1_TIMER_VALUE_MS = 7;
+
+ /**
+ * An integer key representing the voice over LTE (VoLTE) provisioning status for the
+ * associated subscription. Determines whether the user can register for voice services over
+ * LTE.
+ * <p>
+ * Use {@link #PROVISIONING_VALUE_ENABLED} to enable VoLTE provisioning and
+ * {@link #PROVISIONING_VALUE_DISABLED} to disable VoLTE provisioning.
+ * @see #setProvisioningIntValue(int, int)
+ * @see #getProvisioningIntValue(int)
+ */
+ public static final int KEY_VOLTE_PROVISIONING_STATUS = 10;
+
+ /**
+ * An integer key representing the video telephony (VT) provisioning status for the
+ * associated subscription. Determines whether the user can register for video services over
+ * LTE.
+ * <p>
+ * Use {@link #PROVISIONING_VALUE_ENABLED} to enable VT provisioning and
+ * {@link #PROVISIONING_VALUE_DISABLED} to disable VT provisioning.
+ * @see #setProvisioningIntValue(int, int)
+ * @see #getProvisioningIntValue(int)
+ */
+ public static final int KEY_VT_PROVISIONING_STATUS = 11;
+
+ /**
+ * An integer key associated with the carrier configured SIP PUBLISH timer, which dictates the
+ * expiration time in seconds for published online availability in RCS presence.
+ * <p>
+ * Value is in Integer format.
+ * @see #setProvisioningIntValue(int, int)
+ * @see #getProvisioningIntValue(int)
+ */
+ public static final int KEY_RCS_PUBLISH_TIMER_SEC = 15;
+
+ /**
+ * An integer key associated with the carrier configured expiration time in seconds for
+ * RCS presence published offline availability in RCS presence.
+ * <p>
+ * Value is in Integer format.
+ * @see #setProvisioningIntValue(int, int)
+ * @see #getProvisioningIntValue(int)
+ */
+ public static final int KEY_RCS_PUBLISH_TIMER_EXTENDED_SEC = 16;
+
+ /**
+ * An integer key associated with whether or not capability discovery is provisioned for this
+ * subscription. Any capability requests will be ignored by the RCS service.
+ * <p>
+ * The value is an integer, either {@link #PROVISIONING_VALUE_DISABLED} if capability
+ * discovery is disabled or {@link #PROVISIONING_VALUE_ENABLED} if capability discovery is
+ * enabled.
+ * @see #setProvisioningIntValue(int, int)
+ * @see #getProvisioningIntValue(int)
+ */
+ public static final int KEY_RCS_CAPABILITY_DISCOVERY_ENABLED = 17;
+
+ /**
+ * An integer key associated with the period of time the capability information of each contact
+ * is cached on the device.
+ * <p>
+ * Value is in Integer format.
+ * @see #setProvisioningIntValue(int, int)
+ * @see #getProvisioningIntValue(int)
+ */
+ public static final int KEY_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC = 18;
+
+ /**
+ * An integer key associated with the period of time in seconds that the availability
+ * information of a contact is cached on the device.
+ * <p>
+ * Value is in Integer format.
+ * @see #setProvisioningIntValue(int, int)
+ * @see #getProvisioningIntValue(int)
+ */
+ public static final int KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC = 19;
+
+ /**
+ * An integer key associated with the carrier configured interval in seconds expected between
+ * successive capability polling attempts.
+ * <p>
+ * Value is in Integer format.
+ * @see #setProvisioningIntValue(int, int)
+ * @see #getProvisioningIntValue(int)
+ */
+ public static final int KEY_RCS_CAPABILITIES_POLL_INTERVAL_SEC = 20;
+
+ /**
+ * An integer key representing the minimum time allowed between two consecutive presence publish
+ * messages from the device.
+ * <p>
+ * Value is in Integer format.
+ * @see #setProvisioningIntValue(int, int)
+ * @see #getProvisioningIntValue(int)
+ */
+ public static final int KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS = 21;
+
+ /**
+ * An integer key associated with the maximum number of MDNs contained in one SIP Request
+ * Contained List (RCS) used to retrieve the RCS capabilities of the contacts book.
+ * <p>
+ * Value is in Integer format.
+ * @see #setProvisioningIntValue(int, int)
+ * @see #getProvisioningIntValue(int)
+ */
+ public static final int KEY_RCS_MAX_NUM_ENTRIES_IN_RCL = 22;
+
+ /**
+ * An integer associated with the expiration timer used duriing the SIP subscription of a
+ * Request Contained List (RCL), which is used to retrieve the RCS capabilities of the contact
+ * book.
+ * <p>
+ * Value is in Integer format.
+ * @see #setProvisioningIntValue(int, int)
+ * @see #getProvisioningIntValue(int)
+ */
+ public static final int KEY_RCS_CAPABILITY_POLL_LIST_SUB_EXP_SEC = 23;
+
+ /**
+ * An integer key representing the RCS enhanced address book (EAB) provisioning status for the
+ * associated subscription. Determines whether or not SIP OPTIONS or presence will be used to
+ * retrieve RCS capabilities for the user's contacts.
+ * <p>
+ * Use {@link #PROVISIONING_VALUE_ENABLED} to enable EAB provisioning and
+ * {@link #PROVISIONING_VALUE_DISABLED} to disable EAB provisioning.
+ * @see #setProvisioningIntValue(int, int)
+ * @see #getProvisioningIntValue(int)
+ */
+ public static final int KEY_EAB_PROVISIONING_STATUS = 25;
+
/**
* Override the user-defined WiFi Roaming enabled setting for this subscription, defined in
* {@link SubscriptionManager#WFC_ROAMING_ENABLED_CONTENT_URI}, for the purposes of provisioning
@@ -232,10 +380,6 @@
@RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
public void registerProvisioningChangedCallback(@NonNull @CallbackExecutor Executor executor,
@NonNull Callback callback) throws ImsException {
- if (!isImsAvailableOnDevice()) {
- throw new ImsException("IMS not available on device.",
- ImsException.CODE_ERROR_UNSUPPORTED_OPERATION);
- }
callback.setExecutor(executor);
try {
getITelephony().registerImsProvisioningChangedCallback(mSubId, callback.getBinder());
@@ -270,7 +414,7 @@
*
* @param key An integer that represents the provisioning key, which is defined by the OEM.
* @return an integer value for the provided key, or
- * {@link ImsConfigImplBase#CONFIG_RESULT_UNKNOWN} if the key doesn't exist.
+ * {@link #PROVISIONING_RESULT_UNKNOWN} if the key doesn't exist.
* @throws IllegalArgumentException if the key provided was invalid.
*/
@WorkerThread
@@ -397,42 +541,83 @@
}
/**
+ * Get the provisioning status for the IMS RCS capability specified.
+ *
+ * If provisioning is not required for the queried
+ * {@link RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag} this method will always return
+ * {@code true}.
+ *
+ * @see CarrierConfigManager#KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL
+ * @return true if the device is provisioned for the capability or does not require
+ * provisioning, false if the capability does require provisioning and has not been
+ * provisioned yet.
+ */
+ @WorkerThread
+ @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+ public boolean getRcsProvisioningStatusForCapability(
+ @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability) {
+ try {
+ return getITelephony().getRcsProvisioningStatusForCapability(mSubId, capability);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
+ * Set the provisioning status for the IMS RCS capability using the specified subscription.
+ *
+ * Provisioning may or may not be required, depending on the carrier configuration. If
+ * provisioning is not required for the carrier associated with this subscription or the device
+ * does not support the capability/technology combination specified, this operation will be a
+ * no-op.
+ *
+ * @see CarrierConfigManager#KEY_CARRIER_RCS_PROVISIONING_REQUIRED_BOOL
+ * @param isProvisioned true if the device is provisioned for the RCS capability specified,
+ * false otherwise.
+ */
+ @WorkerThread
+ @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
+ public void setRcsProvisioningStatusForCapability(
+ @RcsFeature.RcsImsCapabilities.RcsImsCapabilityFlag int capability,
+ boolean isProvisioned) {
+ try {
+ getITelephony().setRcsProvisioningStatusForCapability(mSubId, capability,
+ isProvisioned);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
* Notify the framework that an RCS autoconfiguration XML file has been received for
* provisioning.
+ * <p>
+ * Requires Permission: Manifest.permission.MODIFY_PHONE_STATE or that the calling app has
+ * carrier privileges (see {@link #hasCarrierPrivileges}).
* @param config The XML file to be read. ASCII/UTF8 encoded text if not compressed.
* @param isCompressed The XML file is compressed in gzip format and must be decompressed
* before being read.
- * @hide
+ *
*/
@RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
public void notifyRcsAutoConfigurationReceived(@NonNull byte[] config, boolean isCompressed) {
if (config == null) {
throw new IllegalArgumentException("Must include a non-null config XML file.");
}
- // TODO: Connect to ImsConfigImplBase.
- throw new UnsupportedOperationException("notifyRcsAutoConfigurationReceived is not"
- + "supported");
- }
-
- private static boolean isImsAvailableOnDevice() {
- IPackageManager pm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
- if (pm == null) {
- // For some reason package manger is not available.. This will fail internally anyways,
- // so do not throw error and allow.
- return true;
- }
try {
- return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY_IMS, 0);
+ getITelephony().notifyRcsAutoConfigurationReceived(mSubId, config, isCompressed);
} catch (RemoteException e) {
- // For some reason package manger is not available.. This will fail internally anyways,
- // so do not throw error and allow.
+ throw e.rethrowAsRuntimeException();
}
- return true;
+
}
private static ITelephony getITelephony() {
ITelephony binder = ITelephony.Stub.asInterface(
- ServiceManager.getService(Context.TELEPHONY_SERVICE));
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getTelephonyServiceRegisterer()
+ .get());
if (binder == null) {
throw new RuntimeException("Could not find Telephony Service.");
}
diff --git a/telephony/java/android/telephony/ims/RcsContactUceCapability.java b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
index 492170b..893a311 100644
--- a/telephony/java/android/telephony/ims/RcsContactUceCapability.java
+++ b/telephony/java/android/telephony/ims/RcsContactUceCapability.java
@@ -19,6 +19,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcelable;
@@ -35,6 +36,7 @@
* Contains the User Capability Exchange capabilities corresponding to a contact's URI.
* @hide
*/
+@SystemApi
public final class RcsContactUceCapability implements Parcelable {
/** Supports 1-to-1 chat */
@@ -135,7 +137,7 @@
* @param type The capability to map to a service URI that is different from the contact's
* URI.
*/
- public Builder add(@CapabilityFlag int type, @NonNull Uri serviceUri) {
+ public @NonNull Builder add(@CapabilityFlag int type, @NonNull Uri serviceUri) {
mCapabilities.mCapabilities |= type;
// Put each of these capabilities into the map separately.
for (int shift = 0; shift < Integer.SIZE; shift++) {
@@ -157,7 +159,7 @@
* Add a UCE capability flag that this contact supports.
* @param type the capability that the contact supports.
*/
- public Builder add(@CapabilityFlag int type) {
+ public @NonNull Builder add(@CapabilityFlag int type) {
mCapabilities.mCapabilities |= type;
return this;
}
@@ -167,7 +169,7 @@
* @param extension A string containing a carrier specific service tag that is an extension
* of the {@link CapabilityFlag}s that are defined here.
*/
- public Builder add(@NonNull String extension) {
+ public @NonNull Builder add(@NonNull String extension) {
mCapabilities.mExtensionTags.add(extension);
return this;
}
@@ -175,7 +177,7 @@
/**
* @return the constructed instance.
*/
- public RcsContactUceCapability build() {
+ public @NonNull RcsContactUceCapability build() {
return mCapabilities;
}
}
@@ -205,7 +207,7 @@
}
}
- public static final Creator<RcsContactUceCapability> CREATOR =
+ public static final @NonNull Creator<RcsContactUceCapability> CREATOR =
new Creator<RcsContactUceCapability>() {
@Override
public RcsContactUceCapability createFromParcel(Parcel in) {
@@ -219,7 +221,7 @@
};
@Override
- public void writeToParcel(Parcel out, int flags) {
+ public void writeToParcel(@NonNull Parcel out, int flags) {
out.writeParcelable(mContactUri, 0);
out.writeInt(mCapabilities);
out.writeStringList(mExtensionTags);
diff --git a/telephony/java/android/telephony/ims/RcsControllerCall.java b/telephony/java/android/telephony/ims/RcsControllerCall.java
index ce03c3c..1e93437 100644
--- a/telephony/java/android/telephony/ims/RcsControllerCall.java
+++ b/telephony/java/android/telephony/ims/RcsControllerCall.java
@@ -18,7 +18,7 @@
import android.content.Context;
import android.os.RemoteException;
-import android.os.ServiceManager;
+import android.telephony.TelephonyFrameworkInitializer;
import android.telephony.ims.aidl.IRcsMessage;
/**
@@ -35,8 +35,11 @@
}
<R> R call(RcsServiceCall<R> serviceCall) throws RcsMessageStoreException {
- IRcsMessage iRcsMessage = IRcsMessage.Stub.asInterface(ServiceManager.getService(
- Context.TELEPHONY_RCS_MESSAGE_SERVICE));
+ IRcsMessage iRcsMessage = IRcsMessage.Stub.asInterface(
+ TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getTelephonyRcsMessageServiceRegisterer()
+ .get());
if (iRcsMessage == null) {
throw new RcsMessageStoreException("Could not connect to RCS storage service");
}
diff --git a/telephony/java/android/telephony/ims/RcsUceAdapter.java b/telephony/java/android/telephony/ims/RcsUceAdapter.java
index 75e3f0a..2e3f59a 100644
--- a/telephony/java/android/telephony/ims/RcsUceAdapter.java
+++ b/telephony/java/android/telephony/ims/RcsUceAdapter.java
@@ -21,12 +21,11 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.RequiresPermission;
-import android.content.Context;
import android.net.Uri;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
-import android.os.ServiceManager;
+import android.telephony.TelephonyFrameworkInitializer;
import android.telephony.ims.aidl.IImsRcsController;
import android.telephony.ims.aidl.IRcsUceControllerCallback;
import android.util.Log;
@@ -365,7 +364,10 @@
}
private IImsRcsController getIImsRcsController() {
- IBinder binder = ServiceManager.getService(Context.TELEPHONY_IMS_SERVICE);
+ IBinder binder = TelephonyFrameworkInitializer
+ .getTelephonyServiceManager()
+ .getTelephonyImsServiceRegisterer()
+ .get();
return IImsRcsController.Stub.asInterface(binder);
}
}
diff --git a/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl b/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl
index 53e4596..57206c9 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsConfig.aidl
@@ -40,4 +40,5 @@
// Return result code defined in ImsConfig#OperationStatusConstants
int setConfigString(int item, String value);
void updateImsCarrierConfigs(in PersistableBundle bundle);
+ void notifyRcsAutoConfigurationReceived(in byte[] config, boolean isCompressed);
}
diff --git a/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl b/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl
index e81bac0..6f6aa44 100644
--- a/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IImsRcsController.aidl
@@ -19,6 +19,9 @@
import android.net.Uri;
import android.telephony.ims.aidl.IImsCapabilityCallback;
import android.telephony.ims.aidl.IRcsUceControllerCallback;
+import android.telephony.ims.aidl.IImsRegistrationCallback;
+
+import com.android.internal.telephony.IIntegerConsumer;
/**
* Interface used to interact with the Telephony IMS.
@@ -26,6 +29,13 @@
* {@hide}
*/
interface IImsRcsController {
+ // IMS RCS registration commands
+ void registerImsRegistrationCallback(int subId, IImsRegistrationCallback c);
+ void unregisterImsRegistrationCallback(int subId, IImsRegistrationCallback c);
+ void getImsRcsRegistrationState(int subId, IIntegerConsumer consumer);
+ void getImsRcsRegistrationTransportType(int subId, IIntegerConsumer consumer);
+
+ // IMS RCS capability commands
void registerRcsAvailabilityCallback(int subId, IImsCapabilityCallback c);
void unregisterRcsAvailabilityCallback(int subId, IImsCapabilityCallback c);
boolean isCapable(int subId, int capability, int radioTech);
diff --git a/telephony/java/android/telephony/ims/aidl/IRcsFeatureListener.aidl b/telephony/java/android/telephony/ims/aidl/IRcsFeatureListener.aidl
index 881b477..70cf651 100644
--- a/telephony/java/android/telephony/ims/aidl/IRcsFeatureListener.aidl
+++ b/telephony/java/android/telephony/ims/aidl/IRcsFeatureListener.aidl
@@ -32,11 +32,11 @@
oneway void onNetworkResponse(int code, in String reason, int operationToken);
oneway void onCapabilityRequestResponsePresence(in List<RcsContactUceCapability> infos,
int operationToken);
- oneway void onNotifyUpdateCapabilities();
+ oneway void onNotifyUpdateCapabilities(int publishTriggerType);
oneway void onUnpublish();
// RcsSipOptionsImplBase specific
oneway void onCapabilityRequestResponseOptions(int code, in String reason,
in RcsContactUceCapability info, int operationToken);
oneway void onRemoteCapabilityRequest(in Uri contactUri, in RcsContactUceCapability remoteInfo,
int operationToken);
-}
\ No newline at end of file
+}
diff --git a/telephony/java/android/telephony/ims/compat/ImsService.java b/telephony/java/android/telephony/ims/compat/ImsService.java
index 97a8517..eafbb14 100644
--- a/telephony/java/android/telephony/ims/compat/ImsService.java
+++ b/telephony/java/android/telephony/ims/compat/ImsService.java
@@ -17,8 +17,8 @@
package android.telephony.ims.compat;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
import android.app.Service;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteException;
diff --git a/telephony/java/android/telephony/ims/compat/feature/ImsFeature.java b/telephony/java/android/telephony/ims/compat/feature/ImsFeature.java
index de4f174..5a9e8e2 100644
--- a/telephony/java/android/telephony/ims/compat/feature/ImsFeature.java
+++ b/telephony/java/android/telephony/ims/compat/feature/ImsFeature.java
@@ -17,7 +17,7 @@
package android.telephony.ims.compat.feature;
import android.annotation.IntDef;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.os.IInterface;
import android.os.RemoteException;
diff --git a/telephony/java/android/telephony/ims/compat/feature/MMTelFeature.java b/telephony/java/android/telephony/ims/compat/feature/MMTelFeature.java
index 3fd356a..b52c371 100644
--- a/telephony/java/android/telephony/ims/compat/feature/MMTelFeature.java
+++ b/telephony/java/android/telephony/ims/compat/feature/MMTelFeature.java
@@ -16,8 +16,8 @@
package android.telephony.ims.compat.feature;
-import android.annotation.UnsupportedAppUsage;
import android.app.PendingIntent;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Message;
import android.os.RemoteException;
import android.telephony.ims.ImsCallProfile;
diff --git a/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java b/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java
index d77f78e..acab738 100644
--- a/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java
+++ b/telephony/java/android/telephony/ims/compat/stub/ImsCallSessionImplBase.java
@@ -16,7 +16,7 @@
package android.telephony.ims.compat.stub;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Message;
import android.os.RemoteException;
import android.telephony.CallQuality;
diff --git a/telephony/java/android/telephony/ims/compat/stub/ImsConfigImplBase.java b/telephony/java/android/telephony/ims/compat/stub/ImsConfigImplBase.java
index e55c3d0..aae6f92 100644
--- a/telephony/java/android/telephony/ims/compat/stub/ImsConfigImplBase.java
+++ b/telephony/java/android/telephony/ims/compat/stub/ImsConfigImplBase.java
@@ -16,7 +16,7 @@
package android.telephony.ims.compat.stub;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
import android.os.RemoteException;
diff --git a/telephony/java/android/telephony/ims/compat/stub/ImsUtListenerImplBase.java b/telephony/java/android/telephony/ims/compat/stub/ImsUtListenerImplBase.java
index e2024742..ce291d4 100644
--- a/telephony/java/android/telephony/ims/compat/stub/ImsUtListenerImplBase.java
+++ b/telephony/java/android/telephony/ims/compat/stub/ImsUtListenerImplBase.java
@@ -16,7 +16,7 @@
package android.telephony.ims.compat.stub;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Bundle;
import android.os.RemoteException;
import android.telephony.ims.ImsCallForwardInfo;
diff --git a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
index d18e93c..6a2638b 100644
--- a/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/ImsConfigImplBase.java
@@ -17,11 +17,13 @@
package android.telephony.ims.stub;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.content.Context;
import android.os.PersistableBundle;
import android.os.RemoteException;
+import android.telephony.ims.ProvisioningManager;
import android.telephony.ims.aidl.IImsConfig;
import android.telephony.ims.aidl.IImsConfigCallback;
import android.util.Log;
@@ -199,6 +201,12 @@
}
}
+ @Override
+ public void notifyRcsAutoConfigurationReceived(byte[] config, boolean isCompressed)
+ throws RemoteException {
+ getImsConfigImpl().notifyRcsAutoConfigurationReceived(config, isCompressed);
+ }
+
private void notifyImsConfigChanged(int item, int value) throws RemoteException {
getImsConfigImpl().notifyConfigChanged(item, value);
}
@@ -228,7 +236,8 @@
* The configuration requested resulted in an unknown result. This may happen if the
* IMS configurations are unavailable.
*/
- public static final int CONFIG_RESULT_UNKNOWN = -1;
+ public static final int CONFIG_RESULT_UNKNOWN = ProvisioningManager.PROVISIONING_RESULT_UNKNOWN;
+
/**
* Setting the configuration value completed.
*/
@@ -356,9 +365,9 @@
* @param config The XML file to be read, if not compressed, it should be in ASCII/UTF8 format.
* @param isCompressed The XML file is compressed in gzip format and must be decompressed
* before being read.
- * @hide
+ *
*/
- public void notifyRcsAutoConfigurationReceived(byte[] config, boolean isCompressed) {
+ public void notifyRcsAutoConfigurationReceived(@NonNull byte[] config, boolean isCompressed) {
}
/**
diff --git a/telephony/java/android/telephony/ims/stub/RcsPresenceExchangeImplBase.java b/telephony/java/android/telephony/ims/stub/RcsPresenceExchangeImplBase.java
index 055fca5..bb03448 100644
--- a/telephony/java/android/telephony/ims/stub/RcsPresenceExchangeImplBase.java
+++ b/telephony/java/android/telephony/ims/stub/RcsPresenceExchangeImplBase.java
@@ -113,6 +113,51 @@
})
public @interface PresenceResponseCode {}
+
+ /** A capability update has been requested due to the Entity Tag (ETag) expiring. */
+ public static final int CAPABILITY_UPDATE_TRIGGER_ETAG_EXPIRED = 0;
+ /** A capability update has been requested due to moving to LTE with VoPS disabled. */
+ public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_LTE_VOPS_DISABLED = 1;
+ /** A capability update has been requested due to moving to LTE with VoPS enabled. */
+ public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_LTE_VOPS_ENABLED = 2;
+ /** A capability update has been requested due to moving to eHRPD. */
+ public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_EHRPD = 3;
+ /** A capability update has been requested due to moving to HSPA+. */
+ public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_HSPAPLUS = 4;
+ /** A capability update has been requested due to moving to 3G. */
+ public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_3G = 5;
+ /** A capability update has been requested due to moving to 2G. */
+ public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_2G = 6;
+ /** A capability update has been requested due to moving to WLAN */
+ public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_WLAN = 7;
+ /** A capability update has been requested due to moving to IWLAN */
+ public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_IWLAN = 8;
+ /** A capability update has been requested but the reason is unknown. */
+ public static final int CAPABILITY_UPDATE_TRIGGER_UNKNOWN = 9;
+ /** A capability update has been requested due to moving to 5G NR with VoPS disabled. */
+ public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_NR5G_VOPS_DISABLED = 10;
+ /** A capability update has been requested due to moving to 5G NR with VoPS enabled. */
+ public static final int CAPABILITY_UPDATE_TRIGGER_MOVE_TO_NR5G_VOPS_ENABLED = 11;
+
+ /** @hide*/
+ @IntDef(value = {
+ CAPABILITY_UPDATE_TRIGGER_ETAG_EXPIRED,
+ CAPABILITY_UPDATE_TRIGGER_MOVE_TO_LTE_VOPS_DISABLED,
+ CAPABILITY_UPDATE_TRIGGER_MOVE_TO_LTE_VOPS_ENABLED,
+ CAPABILITY_UPDATE_TRIGGER_MOVE_TO_EHRPD,
+ CAPABILITY_UPDATE_TRIGGER_MOVE_TO_HSPAPLUS,
+ CAPABILITY_UPDATE_TRIGGER_MOVE_TO_3G,
+ CAPABILITY_UPDATE_TRIGGER_MOVE_TO_2G,
+ CAPABILITY_UPDATE_TRIGGER_MOVE_TO_WLAN,
+ CAPABILITY_UPDATE_TRIGGER_MOVE_TO_IWLAN,
+ CAPABILITY_UPDATE_TRIGGER_UNKNOWN,
+ CAPABILITY_UPDATE_TRIGGER_MOVE_TO_NR5G_VOPS_DISABLED,
+ CAPABILITY_UPDATE_TRIGGER_MOVE_TO_NR5G_VOPS_ENABLED
+ }, prefix = "CAPABILITY_UPDATE_TRIGGER_")
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface StackPublishTriggerType {
+ }
+
/**
* Provide the framework with a subsequent network response update to
* {@link #updateCapabilities(RcsContactUceCapability, int)} and
@@ -164,15 +209,18 @@
* This is typically used when trying to generate an initial PUBLISH for a new subscription to
* the network. The device will cache all presence publications after boot until this method is
* called once.
+ * @param publishTriggerType {@link StackPublishTriggerType} The reason for the capability
+ * update request.
* @throws ImsException If this {@link RcsPresenceExchangeImplBase} instance is not currently
* connected to the framework. This can happen if the {@link RcsFeature} is not
* {@link ImsFeature#STATE_READY} and the {@link RcsFeature} has not received the
* {@link ImsFeature#onFeatureReady()} callback. This may also happen in rare cases when the
* Telephony stack has crashed.
*/
- public final void onNotifyUpdateCapabilites() throws ImsException {
+ public final void onNotifyUpdateCapabilites(@StackPublishTriggerType int publishTriggerType)
+ throws ImsException {
try {
- getListener().onNotifyUpdateCapabilities();
+ getListener().onNotifyUpdateCapabilities(publishTriggerType);
} catch (RemoteException e) {
throw new ImsException(e.getMessage(), ImsException.CODE_ERROR_SERVICE_UNAVAILABLE);
}
diff --git a/telephony/java/com/android/ims/ImsConfig.java b/telephony/java/com/android/ims/ImsConfig.java
index cfc803c..0d86e2b 100644
--- a/telephony/java/com/android/ims/ImsConfig.java
+++ b/telephony/java/com/android/ims/ImsConfig.java
@@ -19,7 +19,7 @@
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
import android.telephony.ims.ImsReasonInfo;
import android.telephony.ims.ProvisioningManager;
import android.telephony.ims.aidl.IImsConfig;
@@ -178,7 +178,7 @@
* SIP T1 timer value in milliseconds. See RFC 3261 for define.
* Value is in Integer format.
*/
- public static final int SIP_T1_TIMER = 7;
+ public static final int SIP_T1_TIMER = ProvisioningManager.KEY_T1_TIMER_VALUE_MS;
/**
* SIP T2 timer value in milliseconds. See RFC 3261 for define.
@@ -196,13 +196,15 @@
* VoLTE status for VLT/s status of Enabled (1), or Disabled (0).
* Value is in Integer format.
*/
- public static final int VLT_SETTING_ENABLED = 10;
+ public static final int VLT_SETTING_ENABLED =
+ ProvisioningManager.KEY_VOLTE_PROVISIONING_STATUS;
/**
* VoLTE status for LVC/s status of Enabled (1), or Disabled (0).
* Value is in Integer format.
*/
- public static final int LVC_SETTING_ENABLED = 11;
+ public static final int LVC_SETTING_ENABLED =
+ ProvisioningManager.KEY_VT_PROVISIONING_STATUS;
/**
* Domain Name for the device to populate the request URI for REGISTRATION.
* Value is in String format.
@@ -222,48 +224,56 @@
* Requested expiration for Published Online availability.
* Value is in Integer format.
*/
- public static final int PUBLISH_TIMER = 15;
+ public static final int PUBLISH_TIMER = ProvisioningManager.KEY_RCS_PUBLISH_TIMER_SEC;
/**
* Requested expiration for Published Offline availability.
* Value is in Integer format.
*/
- public static final int PUBLISH_TIMER_EXTENDED = 16;
+ public static final int PUBLISH_TIMER_EXTENDED =
+ ProvisioningManager.KEY_RCS_PUBLISH_TIMER_EXTENDED_SEC;
/**
*
* Value is in Integer format.
*/
- public static final int CAPABILITY_DISCOVERY_ENABLED = 17;
+ public static final int CAPABILITY_DISCOVERY_ENABLED =
+ ProvisioningManager.KEY_RCS_CAPABILITY_DISCOVERY_ENABLED;
/**
* Period of time the capability information of the contact is cached on handset.
* Value is in Integer format.
*/
- public static final int CAPABILITIES_CACHE_EXPIRATION = 18;
+ public static final int CAPABILITIES_CACHE_EXPIRATION =
+ ProvisioningManager.KEY_RCS_CAPABILITIES_CACHE_EXPIRATION_SEC;
/**
* Peiod of time the availability information of a contact is cached on device.
* Value is in Integer format.
*/
- public static final int AVAILABILITY_CACHE_EXPIRATION = 19;
+ public static final int AVAILABILITY_CACHE_EXPIRATION =
+ ProvisioningManager.KEY_RCS_AVAILABILITY_CACHE_EXPIRATION_SEC;
/**
* Interval between successive capabilities polling.
* Value is in Integer format.
*/
- public static final int CAPABILITIES_POLL_INTERVAL = 20;
+ public static final int CAPABILITIES_POLL_INTERVAL =
+ ProvisioningManager.KEY_RCS_CAPABILITIES_POLL_INTERVAL_SEC;
/**
* Minimum time between two published messages from the device.
* Value is in Integer format.
*/
- public static final int SOURCE_THROTTLE_PUBLISH = 21;
+ public static final int SOURCE_THROTTLE_PUBLISH =
+ ProvisioningManager.KEY_RCS_PUBLISH_SOURCE_THROTTLE_MS;
/**
* The Maximum number of MDNs contained in one Request Contained List.
* Value is in Integer format.
*/
- public static final int MAX_NUMENTRIES_IN_RCL = 22;
+ public static final int MAX_NUMENTRIES_IN_RCL =
+ ProvisioningManager.KEY_RCS_MAX_NUM_ENTRIES_IN_RCL;
/**
* Expiration timer for subscription of a Request Contained List, used in capability
* polling.
* Value is in Integer format.
*/
- public static final int CAPAB_POLL_LIST_SUB_EXP = 23;
+ public static final int CAPAB_POLL_LIST_SUB_EXP =
+ ProvisioningManager.KEY_RCS_CAPABILITY_POLL_LIST_SUB_EXP_SEC;
/**
* Applies compression to LIST Subscription.
* Value is in Integer format. Enable (1), Disable(0).
@@ -273,7 +283,8 @@
* VOLTE Status for EAB/s status of Enabled (1), or Disabled (0).
* Value is in Integer format.
*/
- public static final int EAB_SETTING_ENABLED = 25;
+ public static final int EAB_SETTING_ENABLED =
+ ProvisioningManager.KEY_EAB_PROVISIONING_STATUS;
/**
* Wi-Fi calling roaming status.
* Value is in Integer format. ON (1), OFF(0).
diff --git a/telephony/java/com/android/ims/ImsUtInterface.java b/telephony/java/com/android/ims/ImsUtInterface.java
index e80087d..50b63bd 100644
--- a/telephony/java/com/android/ims/ImsUtInterface.java
+++ b/telephony/java/com/android/ims/ImsUtInterface.java
@@ -16,13 +16,12 @@
package com.android.ims;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Handler;
import android.os.Message;
import android.telephony.ims.ImsCallForwardInfo;
import android.telephony.ims.ImsSsInfo;
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
/**
* Provides APIs for the supplementary service settings using IMS (Ut interface).
* It is created from 3GPP TS 24.623 (XCAP(XML Configuration Access Protocol)
diff --git a/telephony/java/com/android/internal/telephony/DctConstants.java b/telephony/java/com/android/internal/telephony/DctConstants.java
index 9e786ce..1449a62 100644
--- a/telephony/java/com/android/internal/telephony/DctConstants.java
+++ b/telephony/java/com/android/internal/telephony/DctConstants.java
@@ -15,9 +15,9 @@
*/
package com.android.internal.telephony;
-import com.android.internal.util.Protocol;
+import android.compat.annotation.UnsupportedAppUsage;
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import com.android.internal.util.Protocol;
/**
* @hide
diff --git a/telephony/java/com/android/internal/telephony/ISms.aidl b/telephony/java/com/android/internal/telephony/ISms.aidl
index 9f4d066..c07a171 100644
--- a/telephony/java/com/android/internal/telephony/ISms.aidl
+++ b/telephony/java/com/android/internal/telephony/ISms.aidl
@@ -1,18 +1,18 @@
/*
-** Copyright 2007, 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.
-*/
+ * Copyright 2007, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.android.internal.telephony;
@@ -22,7 +22,7 @@
import com.android.internal.telephony.SmsRawData;
/**
- * Interface for applications to access the ICC phone book.
+ * Service interface to handle SMS API requests
*
* See also SmsManager.java.
*/
@@ -542,6 +542,13 @@
in List<PendingIntent> deliveryIntents);
/**
+ * Get carrier-dependent configuration values.
+ *
+ * @param subId the subscription Id
+ */
+ Bundle getCarrierConfigValuesForSubscriber(int subId);
+
+ /**
* Create an app-only incoming SMS request for the calling package.
*
* If an incoming text contains the token returned by this method the provided
diff --git a/telephony/java/com/android/internal/telephony/ISmsImplBase.java b/telephony/java/com/android/internal/telephony/ISmsImplBase.java
index 2430d82..ddd3457 100644
--- a/telephony/java/com/android/internal/telephony/ISmsImplBase.java
+++ b/telephony/java/com/android/internal/telephony/ISmsImplBase.java
@@ -18,6 +18,7 @@
import android.app.PendingIntent;
import android.net.Uri;
+import android.os.Bundle;
import java.util.List;
@@ -185,6 +186,11 @@
}
@Override
+ public Bundle getCarrierConfigValuesForSubscriber(int subId) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public String createAppSpecificSmsToken(int subId, String callingPkg, PendingIntent intent) {
throw new UnsupportedOperationException();
}
diff --git a/telephony/java/com/android/internal/telephony/ISub.aidl b/telephony/java/com/android/internal/telephony/ISub.aidl
index cc02a40..571efce 100755
--- a/telephony/java/com/android/internal/telephony/ISub.aidl
+++ b/telephony/java/com/android/internal/telephony/ISub.aidl
@@ -295,8 +295,6 @@
boolean isActiveSubId(int subId, String callingPackage, String callingFeatureId);
- boolean setAlwaysAllowMmsData(int subId, boolean alwaysAllow);
-
int getActiveDataSubscriptionId();
boolean canDisablePhysicalSubscription();
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 9d721fc..a8e76b9 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1977,6 +1977,17 @@
*/
boolean getImsProvisioningStatusForCapability(int subId, int capability, int tech);
+ /**
+ * Get the provisioning status for the IMS Rcs capability specified.
+ */
+ boolean getRcsProvisioningStatusForCapability(int subId, int capability);
+
+ /**
+ * Set the provisioning status for the IMS Rcs capability using the specified subscription.
+ */
+ void setRcsProvisioningStatusForCapability(int subId, int capability,
+ boolean isProvisioned);
+
/** Is the capability and tech flagged as provisioned in the cache */
boolean isMmTelCapabilityProvisionedInCache(int subId, int capability, int tech);
@@ -2076,6 +2087,11 @@
int getRadioHalVersion();
/**
+ * Get the current calling package name.
+ */
+ String getCurrentPackageName();
+
+ /**
* Returns true if the specified type of application (e.g. {@link #APPTYPE_CSIM} is present
* on the UICC card.
* @hide
@@ -2118,7 +2134,17 @@
boolean isDataAllowedInVoiceCall(int subId);
/**
+ * Set whether a subscription always allows MMS connection.
+ */
+ boolean setAlwaysAllowMmsData(int subId, boolean allow);
+
+ /**
* Command line command to enable or disable handling of CEP data for test purposes.
*/
oneway void setCepEnabled(boolean isCepEnabled);
+
+ /**
+ * Notify Rcs auto config received.
+ */
+ void notifyRcsAutoConfigurationReceived(int subId, in byte[] config, boolean isCompressed);
}
diff --git a/telephony/java/com/android/internal/telephony/IccCardConstants.java b/telephony/java/com/android/internal/telephony/IccCardConstants.java
index 25f03c2..94dfae8 100644
--- a/telephony/java/com/android/internal/telephony/IccCardConstants.java
+++ b/telephony/java/com/android/internal/telephony/IccCardConstants.java
@@ -15,11 +15,10 @@
*/
package com.android.internal.telephony;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Intent;
import android.telephony.TelephonyManager;
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
/**
* {@hide}
*/
diff --git a/telephony/java/com/android/internal/telephony/OperatorInfo.java b/telephony/java/com/android/internal/telephony/OperatorInfo.java
index 59c39b1..64d7863 100644
--- a/telephony/java/com/android/internal/telephony/OperatorInfo.java
+++ b/telephony/java/com/android/internal/telephony/OperatorInfo.java
@@ -16,7 +16,7 @@
package com.android.internal.telephony;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstants.java b/telephony/java/com/android/internal/telephony/PhoneConstants.java
index fadb573..db8c845 100644
--- a/telephony/java/com/android/internal/telephony/PhoneConstants.java
+++ b/telephony/java/com/android/internal/telephony/PhoneConstants.java
@@ -15,7 +15,7 @@
*/
package com.android.internal.telephony;
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* @hide
@@ -100,9 +100,6 @@
public static final String DATA_APN_TYPE_KEY = "apnType";
public static final String DATA_APN_KEY = "apn";
- public static final String PHONE_IN_ECM_STATE = "phoneinECMState";
- public static final String PHONE_IN_EMERGENCY_CALL = "phoneInEmergencyCall";
-
/**
* Return codes for supplyPinReturnResult and
* supplyPukReturnResult APIs
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 9cbcd7f..9ee26c2 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -16,10 +16,9 @@
package com.android.internal.telephony;
+import android.compat.annotation.UnsupportedAppUsage;
import android.sysprop.TelephonyProperties;
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
import java.util.Optional;
/**
@@ -234,11 +233,14 @@
/** NR 5G, LTE, TD-SCDMA, CDMA, EVDO, GSM and WCDMA */
int NETWORK_MODE_NR_LTE_TDSCDMA_CDMA_EVDO_GSM_WCDMA = 33;
+ /** Default preferred network mode */
+ int DEFAULT_PREFERRED_NETWORK_MODE = NETWORK_MODE_WCDMA_PREF;
+
@UnsupportedAppUsage
int PREFERRED_NETWORK_MODE = Optional.of(TelephonyProperties.default_network())
.filter(list -> !list.isEmpty())
.map(list -> list.get(0))
- .orElse(NETWORK_MODE_WCDMA_PREF);
+ .orElse(DEFAULT_PREFERRED_NETWORK_MODE);
int BAND_MODE_UNSPECIFIED = 0; //"unspecified" (selected by baseband automatically)
int BAND_MODE_EURO = 1; //"EURO band" (GSM-900 / DCS-1800 / WCDMA-IMT-2000)
@@ -555,4 +557,6 @@
int RIL_UNSOL_PHYSICAL_CHANNEL_CONFIG = 1101;
int RIL_UNSOL_EMERGENCY_NUMBER_LIST = 1102;
int RIL_UNSOL_UICC_APPLICATIONS_ENABLEMENT_CHANGED = 1103;
+ int RIL_UNSOL_REGISTRATION_FAILED = 1104;
+ int RIL_UNSOL_BARRING_INFO_CHANGED = 1105;
}
diff --git a/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java b/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java
index d672642..8e86ff7 100644
--- a/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java
+++ b/telephony/java/com/android/internal/telephony/Sms7BitEncodingTranslator.java
@@ -16,17 +16,16 @@
package com.android.internal.telephony;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.Resources;
import android.content.res.XmlResourceParser;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
import android.util.SparseIntArray;
import com.android.internal.telephony.cdma.sms.UserData;
import com.android.internal.telephony.util.TelephonyUtils;
import com.android.internal.telephony.util.XmlUtils;
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
public class Sms7BitEncodingTranslator {
private static final String TAG = "Sms7BitEncodingTranslator";
@UnsupportedAppUsage
diff --git a/telephony/java/com/android/internal/telephony/SmsAddress.java b/telephony/java/com/android/internal/telephony/SmsAddress.java
index 2a8de8c..f18256a 100644
--- a/telephony/java/com/android/internal/telephony/SmsAddress.java
+++ b/telephony/java/com/android/internal/telephony/SmsAddress.java
@@ -16,7 +16,7 @@
package com.android.internal.telephony;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
public abstract class SmsAddress {
// From TS 23.040 9.1.2.5 and TS 24.008 table 10.5.118
diff --git a/telephony/java/com/android/internal/telephony/SmsHeader.java b/telephony/java/com/android/internal/telephony/SmsHeader.java
index dd77b01..ab3fdf4 100644
--- a/telephony/java/com/android/internal/telephony/SmsHeader.java
+++ b/telephony/java/com/android/internal/telephony/SmsHeader.java
@@ -16,7 +16,7 @@
package com.android.internal.telephony;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import com.android.internal.util.HexDump;
diff --git a/telephony/java/com/android/internal/telephony/SmsMessageBase.java b/telephony/java/com/android/internal/telephony/SmsMessageBase.java
index d6632f3..084882b 100644
--- a/telephony/java/com/android/internal/telephony/SmsMessageBase.java
+++ b/telephony/java/com/android/internal/telephony/SmsMessageBase.java
@@ -16,7 +16,7 @@
package com.android.internal.telephony;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.telephony.SmsMessage;
import android.text.TextUtils;
diff --git a/telephony/java/com/android/internal/telephony/SmsRawData.java b/telephony/java/com/android/internal/telephony/SmsRawData.java
index 18727f3..9776c8f 100644
--- a/telephony/java/com/android/internal/telephony/SmsRawData.java
+++ b/telephony/java/com/android/internal/telephony/SmsRawData.java
@@ -17,7 +17,7 @@
package com.android.internal.telephony;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/telephony/java/com/android/internal/telephony/TelephonyIntents.java b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
index 08c536b..a15f73c 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyIntents.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyIntents.java
@@ -19,6 +19,7 @@
import android.content.Intent;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
+import android.telephony.ims.ImsManager;
/**
* The intents that the telephony services broadcast.
@@ -123,32 +124,6 @@
public static final String ACTION_EMERGENCY_CALL_STATE_CHANGED
= TelephonyManager.ACTION_EMERGENCY_CALL_STATE_CHANGED;
- /**
- * Broadcast Action: The phone's signal strength has changed. The intent will have the
- * following extra values:</p>
- * <ul>
- * <li><em>phoneName</em> - A string version of the phone name.</li>
- * <li><em>asu</em> - A numeric value for the signal strength.
- * An ASU is 0-31 or -1 if unknown (for GSM, dBm = -113 - 2 * asu).
- * The following special values are defined:
- * <ul><li>0 means "-113 dBm or less".</li><li>31 means "-51 dBm or greater".</li></ul>
- * </li>
- * </ul>
- *
- * <p class="note">
- * You can <em>not</em> receive this through components declared
- * in manifests, only by exlicitly registering for it with
- * {@link android.content.Context#registerReceiver(android.content.BroadcastReceiver,
- * android.content.IntentFilter) Context.registerReceiver()}.
- *
- * <p class="note">
- * Requires the READ_PHONE_STATE permission.
- *
- * <p class="note">This is a protected intent that can only be sent
- * by the system.
- */
- public static final String ACTION_SIGNAL_STRENGTH_CHANGED = "android.intent.action.SIG_STR";
-
/**
* Broadcast Action: The data connection state has changed for any one of the
@@ -224,9 +199,11 @@
* <p class="note">
* This is for the OEM applications to understand about possible provisioning issues.
* Used in OMA-DM applications.
+ * @deprecated Use {@link ImsManager#ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION} instead.
*/
- public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION
- = "com.android.internal.intent.action.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION";
+ @Deprecated
+ public static final String ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION =
+ ImsManager.ACTION_FORBIDDEN_NO_SERVICE_AUTHORIZATION;
/**
* Broadcast Action: A "secret code" has been entered in the dialer. Secret codes are
@@ -304,7 +281,7 @@
* </ul>
*/
public static final String ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED
- = "android.intent.action.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED";
+ = TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED;
/**
* Broadcast Action: The default voice subscription has changed. This has the following
@@ -314,7 +291,7 @@
* </ul>
*/
public static final String ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED
- = "android.intent.action.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED";
+ = TelephonyManager.ACTION_DEFAULT_VOICE_SUBSCRIPTION_CHANGED;
/**
* Broadcast Action: The default sms subscription has changed. This has the following
@@ -351,89 +328,10 @@
"android.intent.action.ACTION_SET_RADIO_CAPABILITY_FAILED";
/**
- * <p>Broadcast Action: when data connections get redirected with validation failure.
- * intended for sim/account status checks and only sent to the specified carrier app
- * The intent will have the following extra values:</p>
- * <ul>
- * <li>apnType</li><dd>A string with the apn type.</dd>
- * <li>redirectionUrl</li><dd>redirection url string</dd>
- * <li>subId</li><dd>Sub Id which associated the data connection failure.</dd>
- * </ul>
- * <p class="note">This is a protected intent that can only be sent by the system.</p>
- */
- public static final String ACTION_CARRIER_SIGNAL_REDIRECTED =
- "com.android.internal.telephony.CARRIER_SIGNAL_REDIRECTED";
- /**
- * <p>Broadcast Action: when data connections setup fails.
- * intended for sim/account status checks and only sent to the specified carrier app
- * The intent will have the following extra values:</p>
- * <ul>
- * <li>apnType</li><dd>A string with the apn type.</dd>
- * <li>errorCode</li><dd>A integer with dataFailCause.</dd>
- * <li>subId</li><dd>Sub Id which associated the data connection failure.</dd>
- * </ul>
- * <p class="note">This is a protected intent that can only be sent by the system. </p>
- */
- public static final String ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED =
- "com.android.internal.telephony.CARRIER_SIGNAL_REQUEST_NETWORK_FAILED";
-
- /**
- * <p>Broadcast Action: when pco value is available.
- * intended for sim/account status checks and only sent to the specified carrier app
- * The intent will have the following extra values:</p>
- * <ul>
- * <li>apnType</li><dd>A string with the apn type.</dd>
- * <li>apnProto</li><dd>A string with the protocol of the apn connection (IP,IPV6,
- * IPV4V6)</dd>
- * <li>pcoId</li><dd>An integer indicating the pco id for the data.</dd>
- * <li>pcoValue</li><dd>A byte array of pco data read from modem.</dd>
- * <li>subId</li><dd>Sub Id which associated the data connection.</dd>
- * </ul>
- * <p class="note">This is a protected intent that can only be sent by the system. </p>
- */
- public static final String ACTION_CARRIER_SIGNAL_PCO_VALUE =
- "com.android.internal.telephony.CARRIER_SIGNAL_PCO_VALUE";
-
- /**
- * <p>Broadcast Action: when system default network available/unavailable with
- * carrier-disabled mobile data. Intended for carrier apps to set/reset carrier actions when
- * other network becomes system default network, Wi-Fi for example.
- * The intent will have the following extra values:</p>
- * <ul>
- * <li>defaultNetworkAvailable</li><dd>A boolean indicates default network available.</dd>
- * <li>subId</li><dd>Sub Id which associated the default data.</dd>
- * </ul>
- * <p class="note">This is a protected intent that can only be sent by the system. </p>
- */
- public static final String ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE =
- "com.android.internal.telephony.CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE";
-
- /**
- * <p>Broadcast Action: when framework reset all carrier actions on sim load or absent.
- * intended for carrier apps clean up (clear UI e.g.) and only sent to the specified carrier app
- * The intent will have the following extra values:</p>
- * <ul>
- * <li>subId</li><dd>Sub Id which associated the data connection failure.</dd>
- * </ul>
- * <p class="note">This is a protected intent that can only be sent by the system.</p>
- */
- public static final String ACTION_CARRIER_SIGNAL_RESET =
- "com.android.internal.telephony.CARRIER_SIGNAL_RESET";
-
- // CARRIER_SIGNAL_ACTION extra keys
- public static final String EXTRA_REDIRECTION_URL_KEY = "redirectionUrl";
- public static final String EXTRA_ERROR_CODE_KEY = "errorCode";
- public static final String EXTRA_APN_TYPE_KEY = "apnType";
- public static final String EXTRA_APN_PROTO_KEY = "apnProto";
- public static final String EXTRA_PCO_ID_KEY = "pcoId";
- public static final String EXTRA_PCO_VALUE_KEY = "pcoValue";
- public static final String EXTRA_DEFAULT_NETWORK_AVAILABLE_KEY = "defaultNetworkAvailable";
-
- /**
* Broadcast action to trigger CI OMA-DM Session.
*/
public static final String ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE =
- "com.android.omadm.service.CONFIGURATION_UPDATE";
+ TelephonyManager.ACTION_REQUEST_OMADM_CONFIGURATION_UPDATE;
/**
* Broadcast action to trigger the Carrier Certificate download.
diff --git a/telephony/java/com/android/internal/telephony/TelephonyProperties.java b/telephony/java/com/android/internal/telephony/TelephonyProperties.java
index 4e42c20..ff70f8b 100644
--- a/telephony/java/com/android/internal/telephony/TelephonyProperties.java
+++ b/telephony/java/com/android/internal/telephony/TelephonyProperties.java
@@ -16,7 +16,7 @@
package com.android.internal.telephony;
-import dalvik.annotation.compat.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* Contains a list of string constants used to get or set telephone properties
diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
index 1f7715b..832502c 100644
--- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java
@@ -16,10 +16,11 @@
package com.android.internal.telephony.cdma;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.Resources;
import android.sysprop.TelephonyProperties;
import android.telephony.PhoneNumberUtils;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
import android.telephony.SmsCbLocation;
import android.telephony.SmsCbMessage;
import android.telephony.cdma.CdmaSmsCbProgramData;
@@ -41,8 +42,6 @@
import com.android.internal.util.BitwiseInputStream;
import com.android.internal.util.HexDump;
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
diff --git a/telephony/java/com/android/internal/telephony/cdma/UserData.java b/telephony/java/com/android/internal/telephony/cdma/UserData.java
index 7187ae4..524cb0c 100644
--- a/telephony/java/com/android/internal/telephony/cdma/UserData.java
+++ b/telephony/java/com/android/internal/telephony/cdma/UserData.java
@@ -16,13 +16,12 @@
package com.android.internal.telephony.cdma.sms;
+import android.compat.annotation.UnsupportedAppUsage;
import android.util.SparseIntArray;
import com.android.internal.telephony.SmsHeader;
import com.android.internal.util.HexDump;
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
public class UserData {
/**
diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
index d9be548..cbf0f5c 100644
--- a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
+++ b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java
@@ -16,8 +16,9 @@
package com.android.internal.telephony.cdma.sms;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.Resources;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
import android.telephony.SmsCbCmasInfo;
import android.telephony.cdma.CdmaSmsCbProgramData;
import android.telephony.cdma.CdmaSmsCbProgramResults;
@@ -31,8 +32,6 @@
import com.android.internal.util.BitwiseInputStream;
import com.android.internal.util.BitwiseOutputStream;
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java b/telephony/java/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java
index a82b975..6f0de34 100644
--- a/telephony/java/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java
+++ b/telephony/java/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java
@@ -16,7 +16,7 @@
package com.android.internal.telephony.cdma.sms;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.util.SparseBooleanArray;
import com.android.internal.annotations.VisibleForTesting;
diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java b/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java
index c924ab3..9e2d29c 100644
--- a/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java
+++ b/telephony/java/com/android/internal/telephony/cdma/sms/SmsEnvelope.java
@@ -16,8 +16,7 @@
package com.android.internal.telephony.cdma.sms;
-
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.telephony.cdma.CdmaSmsCbProgramData;
public final class SmsEnvelope {
diff --git a/telephony/java/com/android/internal/telephony/gsm/GsmSmsAddress.java b/telephony/java/com/android/internal/telephony/gsm/GsmSmsAddress.java
index 17f69b3..5409c09 100644
--- a/telephony/java/com/android/internal/telephony/gsm/GsmSmsAddress.java
+++ b/telephony/java/com/android/internal/telephony/gsm/GsmSmsAddress.java
@@ -16,13 +16,12 @@
package com.android.internal.telephony.gsm;
+import android.compat.annotation.UnsupportedAppUsage;
import android.telephony.PhoneNumberUtils;
import com.android.internal.telephony.GsmAlphabet;
import com.android.internal.telephony.SmsAddress;
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
import java.text.ParseException;
public class GsmSmsAddress extends SmsAddress {
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java b/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java
index 216f616..d190345 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsCbHeader.java
@@ -16,13 +16,12 @@
package com.android.internal.telephony.gsm;
+import android.compat.annotation.UnsupportedAppUsage;
import android.telephony.SmsCbCmasInfo;
import android.telephony.SmsCbEtwsInfo;
import com.android.internal.telephony.SmsConstants;
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
import java.util.Arrays;
import java.util.Locale;
diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
index da32c8c..417aafd 100644
--- a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
+++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java
@@ -25,9 +25,10 @@
import static com.android.internal.telephony.SmsConstants.MAX_USER_DATA_SEPTETS;
import static com.android.internal.telephony.SmsConstants.MessageClass;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.Resources;
import android.telephony.PhoneNumberUtils;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
import android.text.TextUtils;
import com.android.internal.telephony.EncodeException;
@@ -38,8 +39,6 @@
import com.android.internal.telephony.SmsMessageBase;
import com.android.internal.telephony.uicc.IccUtils;
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.text.ParseException;
diff --git a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
index f2d4624..0dc7401 100644
--- a/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
+++ b/telephony/java/com/android/internal/telephony/uicc/IccUtils.java
@@ -16,17 +16,16 @@
package com.android.internal.telephony.uicc;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
import android.graphics.Bitmap;
import android.graphics.Color;
-import android.telephony.Rlog;
+import com.android.telephony.Rlog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.GsmAlphabet;
-import dalvik.annotation.compat.UnsupportedAppUsage;
-
import java.io.UnsupportedEncodingException;
import java.util.List;
diff --git a/telephony/java/com/android/telephony/Rlog.java b/telephony/java/com/android/telephony/Rlog.java
new file mode 100644
index 0000000..9d6c930
--- /dev/null
+++ b/telephony/java/com/android/telephony/Rlog.java
@@ -0,0 +1,154 @@
+/*
+ * 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.telephony;
+
+import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Log;
+
+import com.android.internal.telephony.util.TelephonyUtils;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * A copy of {@link android.telephony.Rlog} to be used within the telephony mainline module.
+ *
+ * @hide
+ */
+public final class Rlog {
+
+ private static final boolean USER_BUILD = TelephonyUtils.IS_USER;
+
+ private Rlog() {
+ }
+
+ private static int log(int priority, String tag, String msg) {
+ return Log.logToRadioBuffer(priority, tag, msg);
+ }
+
+ public static int v(String tag, String msg) {
+ return log(Log.VERBOSE, tag, msg);
+ }
+
+ public static int v(String tag, String msg, Throwable tr) {
+ return log(Log.VERBOSE, tag,
+ msg + '\n' + Log.getStackTraceString(tr));
+ }
+
+ public static int d(String tag, String msg) {
+ return log(Log.DEBUG, tag, msg);
+ }
+
+ public static int d(String tag, String msg, Throwable tr) {
+ return log(Log.DEBUG, tag,
+ msg + '\n' + Log.getStackTraceString(tr));
+ }
+
+ public static int i(String tag, String msg) {
+ return log(Log.INFO, tag, msg);
+ }
+
+ public static int i(String tag, String msg, Throwable tr) {
+ return log(Log.INFO, tag,
+ msg + '\n' + Log.getStackTraceString(tr));
+ }
+
+ public static int w(String tag, String msg) {
+ return log(Log.WARN, tag, msg);
+ }
+
+ public static int w(String tag, String msg, Throwable tr) {
+ return log(Log.WARN, tag,
+ msg + '\n' + Log.getStackTraceString(tr));
+ }
+
+ public static int w(String tag, Throwable tr) {
+ return log(Log.WARN, tag, Log.getStackTraceString(tr));
+ }
+
+ public static int e(String tag, String msg) {
+ return log(Log.ERROR, tag, msg);
+ }
+
+ public static int e(String tag, String msg, Throwable tr) {
+ return log(Log.ERROR, tag,
+ msg + '\n' + Log.getStackTraceString(tr));
+ }
+
+ public static int println(int priority, String tag, String msg) {
+ return log(priority, tag, msg);
+ }
+
+ public static boolean isLoggable(String tag, int level) {
+ return Log.isLoggable(tag, level);
+ }
+
+ /**
+ * Redact personally identifiable information for production users.
+ * @param tag used to identify the source of a log message
+ * @param pii the personally identifiable information we want to apply secure hash on.
+ * @return If tag is loggable in verbose mode or pii is null, return the original input.
+ * otherwise return a secure Hash of input pii
+ */
+ public static String pii(String tag, Object pii) {
+ String val = String.valueOf(pii);
+ if (pii == null || TextUtils.isEmpty(val) || isLoggable(tag, Log.VERBOSE)) {
+ return val;
+ }
+ return "[" + secureHash(val.getBytes()) + "]";
+ }
+
+ /**
+ * Redact personally identifiable information for production users.
+ * @param enablePiiLogging set when caller explicitly want to enable sensitive logging.
+ * @param pii the personally identifiable information we want to apply secure hash on.
+ * @return If enablePiiLogging is set to true or pii is null, return the original input.
+ * otherwise return a secure Hash of input pii
+ */
+ public static String pii(boolean enablePiiLogging, Object pii) {
+ String val = String.valueOf(pii);
+ if (pii == null || TextUtils.isEmpty(val) || enablePiiLogging) {
+ return val;
+ }
+ return "[" + secureHash(val.getBytes()) + "]";
+ }
+
+ /**
+ * Returns a secure hash (using the SHA1 algorithm) of the provided input.
+ *
+ * @return "****" if the build type is user, otherwise the hash
+ * @param input the bytes for which the secure hash should be computed.
+ */
+ private static String secureHash(byte[] input) {
+ // Refrain from logging user personal information in user build.
+ if (USER_BUILD) {
+ return "****";
+ }
+
+ MessageDigest messageDigest;
+
+ try {
+ messageDigest = MessageDigest.getInstance("SHA-1");
+ } catch (NoSuchAlgorithmException e) {
+ return "####";
+ }
+
+ byte[] result = messageDigest.digest(input);
+ return Base64.encodeToString(
+ result, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP);
+ }
+}
diff --git a/test-base/hiddenapi/Android.bp b/test-base/hiddenapi/Android.bp
index c4e0fab..c202467 100644
--- a/test-base/hiddenapi/Android.bp
+++ b/test-base/hiddenapi/Android.bp
@@ -25,5 +25,8 @@
srcs: ["src/**/*.java"],
- libs: ["android.test.base"],
+ libs: [
+ "android.test.base",
+ "unsupportedappusage",
+ ],
}
diff --git a/test-base/hiddenapi/src/android/test/AndroidTestCase.java b/test-base/hiddenapi/src/android/test/AndroidTestCase.java
index 2b9beb1..fcb8d43 100644
--- a/test-base/hiddenapi/src/android/test/AndroidTestCase.java
+++ b/test-base/hiddenapi/src/android/test/AndroidTestCase.java
@@ -16,7 +16,7 @@
package android.test;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import junit.framework.TestCase;
diff --git a/test-base/hiddenapi/src/android/test/InstrumentationTestCase.java b/test-base/hiddenapi/src/android/test/InstrumentationTestCase.java
index 139cd18..a48a56c 100644
--- a/test-base/hiddenapi/src/android/test/InstrumentationTestCase.java
+++ b/test-base/hiddenapi/src/android/test/InstrumentationTestCase.java
@@ -16,7 +16,7 @@
package android.test;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import junit.framework.TestCase;
diff --git a/test-base/hiddenapi/src/junit/framework/TestCase.java b/test-base/hiddenapi/src/junit/framework/TestCase.java
index 5a54861..59fbe2b 100644
--- a/test-base/hiddenapi/src/junit/framework/TestCase.java
+++ b/test-base/hiddenapi/src/junit/framework/TestCase.java
@@ -16,7 +16,7 @@
package junit.framework;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* Stub only
diff --git a/test-base/hiddenapi/src/junit/framework/TestSuite.java b/test-base/hiddenapi/src/junit/framework/TestSuite.java
index 368c661..32305c9 100644
--- a/test-base/hiddenapi/src/junit/framework/TestSuite.java
+++ b/test-base/hiddenapi/src/junit/framework/TestSuite.java
@@ -16,7 +16,7 @@
package junit.framework;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import java.lang.reflect.Method;
diff --git a/tests/ApkVerityTest/TEST_MAPPING b/tests/ApkVerityTest/TEST_MAPPING
index a660839..72d9614 100644
--- a/tests/ApkVerityTest/TEST_MAPPING
+++ b/tests/ApkVerityTest/TEST_MAPPING
@@ -1,15 +1,12 @@
{
"presubmit": [
+ {
+ "name": "ApkVerityTest"
+ },
// nextgen test only runs during postsubmit.
{
"name": "ApkVerityTest",
"keywords": ["nextgen"]
}
- ],
- "postsubmit": [
- // TODO: move to presubmit once it's confirmed stable.
- {
- "name": "ApkVerityTest"
- }
]
}
diff --git a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
index 2445a6a..20d0e96 100644
--- a/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
+++ b/tests/ApkVerityTest/src/com/android/apkverity/ApkVerityTest.java
@@ -27,6 +27,7 @@
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import com.android.tradefed.util.CommandResult;
@@ -412,6 +413,7 @@
break;
}
try {
+ CLog.d("lsof: " + expectRemoteCommandToSucceed("lsof " + apkPath));
Thread.sleep(1000);
String pid = expectRemoteCommandToSucceed("pidof system_server");
mDevice.executeShellV2Command("kill -10 " + pid); // force GC
diff --git a/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java b/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java
index 653282d..1361df3 100644
--- a/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java
+++ b/tests/MemoryUsage/src/com/android/tests/memoryusage/MemoryUsageTest.java
@@ -320,8 +320,9 @@
UserHandle.USER_CURRENT);
}
- mAtm.startActivityAndWait(null, null, mLaunchIntent, mimeType,
- null, null, 0, mLaunchIntent.getFlags(), null, null,
+ mAtm.startActivityAndWait(null,
+ getInstrumentation().getContext().getBasePackageName(), mLaunchIntent,
+ mimeType, null, null, 0, mLaunchIntent.getFlags(), null, null,
UserHandle.USER_CURRENT_OR_SELF);
} catch (RemoteException e) {
Log.w(TAG, "Error launching app", e);
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index b4cafe4..8cc8cf4 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -18,10 +18,13 @@
import static android.service.watchdog.ExplicitHealthCheckService.PackageConfig;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
@@ -36,12 +39,14 @@
import android.net.ConnectivityModuleConnector;
import android.net.ConnectivityModuleConnector.ConnectivityModuleHealthListener;
import android.os.Handler;
+import android.os.SystemProperties;
import android.os.test.TestLooper;
import android.provider.DeviceConfig;
import android.util.AtomicFile;
import androidx.test.InstrumentationRegistry;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.server.PackageWatchdog.HealthCheckState;
import com.android.server.PackageWatchdog.MonitoredPackage;
import com.android.server.PackageWatchdog.PackageHealthObserver;
@@ -54,11 +59,15 @@
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@@ -88,6 +97,8 @@
private PackageManager mMockPackageManager;
@Captor
private ArgumentCaptor<ConnectivityModuleHealthListener> mConnectivityModuleCallbackCaptor;
+ private MockitoSession mSession;
+ private HashMap<String, String> mSystemSettingsMap;
@Before
public void setUp() throws Exception {
@@ -104,11 +115,47 @@
res.setLongVersionCode(VERSION_CODE);
return res;
});
+ mSession = ExtendedMockito.mockitoSession()
+ .initMocks(this)
+ .strictness(Strictness.LENIENT)
+ .spyStatic(SystemProperties.class)
+ .startMocking();
+ mSystemSettingsMap = new HashMap<>();
+
+
+ // Mock SystemProperties setter and various getters
+ doAnswer((Answer<Void>) invocationOnMock -> {
+ String key = invocationOnMock.getArgument(0);
+ String value = invocationOnMock.getArgument(1);
+
+ mSystemSettingsMap.put(key, value);
+ return null;
+ }
+ ).when(() -> SystemProperties.set(anyString(), anyString()));
+
+ doAnswer((Answer<Integer>) invocationOnMock -> {
+ String key = invocationOnMock.getArgument(0);
+ int defaultValue = invocationOnMock.getArgument(1);
+
+ String storedValue = mSystemSettingsMap.get(key);
+ return storedValue == null ? defaultValue : Integer.parseInt(storedValue);
+ }
+ ).when(() -> SystemProperties.getInt(anyString(), anyInt()));
+
+ doAnswer((Answer<Long>) invocationOnMock -> {
+ String key = invocationOnMock.getArgument(0);
+ long defaultValue = invocationOnMock.getArgument(1);
+
+ String storedValue = mSystemSettingsMap.get(key);
+ return storedValue == null ? defaultValue : Long.parseLong(storedValue);
+ }
+ ).when(() -> SystemProperties.getLong(anyString(), anyLong()));
}
@After
public void tearDown() throws Exception {
dropShellPermissions();
+ mSession.finishMocking();
}
@Test
@@ -896,37 +943,124 @@
assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A);
}
- /** Test that observers execute correctly for different failure reasons */
+ /** Test that observers execute correctly for failures reasons that go through thresholding. */
@Test
- public void testFailureReasons() {
+ public void testNonImmediateFailureReasons() {
PackageWatchdog watchdog = createWatchdog();
TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
- TestObserver observer3 = new TestObserver(OBSERVER_NAME_3);
- TestObserver observer4 = new TestObserver(OBSERVER_NAME_4);
watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
watchdog.startObservingHealth(observer2, Arrays.asList(APP_B), SHORT_DURATION);
- watchdog.startObservingHealth(observer3, Arrays.asList(APP_C), SHORT_DURATION);
- watchdog.startObservingHealth(observer4, Arrays.asList(APP_D), SHORT_DURATION);
+
+ raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
+ VERSION_CODE)), PackageWatchdog.FAILURE_REASON_APP_CRASH);
+ raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_B,
+ VERSION_CODE)), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING);
+
+ assertThat(observer1.getLastFailureReason()).isEqualTo(
+ PackageWatchdog.FAILURE_REASON_APP_CRASH);
+ assertThat(observer2.getLastFailureReason()).isEqualTo(
+ PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING);
+ }
+
+ /** Test that observers execute correctly for failures reasons that skip thresholding. */
+ @Test
+ public void testImmediateFailures() {
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
+
+ watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
VERSION_CODE)), PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_B,
VERSION_CODE)), PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
- raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_C,
- VERSION_CODE)), PackageWatchdog.FAILURE_REASON_APP_CRASH);
- raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_D,
- VERSION_CODE)), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING);
- assertThat(observer1.getLastFailureReason()).isEqualTo(
- PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
- assertThat(observer2.getLastFailureReason()).isEqualTo(
- PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
- assertThat(observer3.getLastFailureReason()).isEqualTo(
- PackageWatchdog.FAILURE_REASON_APP_CRASH);
- assertThat(observer4.getLastFailureReason()).isEqualTo(
- PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING);
+ assertThat(observer1.mMitigatedPackages).containsExactly(APP_A, APP_B);
+ }
+
+ /**
+ * Test that a persistent observer will mitigate failures if it wishes to observe a package.
+ */
+ @Test
+ public void testPersistentObserverWatchesPackage() {
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver persistentObserver = new TestObserver(OBSERVER_NAME_1);
+ persistentObserver.setPersistent(true);
+ persistentObserver.setMayObservePackages(true);
+
+ watchdog.startObservingHealth(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION);
+
+ raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
+ VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN);
+ assertThat(persistentObserver.mHealthCheckFailedPackages).containsExactly(APP_A);
+ }
+
+ /**
+ * Test that a persistent observer will not mitigate failures if it does not wish to observe
+ * a given package.
+ */
+ @Test
+ public void testPersistentObserverDoesNotWatchPackage() {
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver persistentObserver = new TestObserver(OBSERVER_NAME_1);
+ persistentObserver.setPersistent(true);
+ persistentObserver.setMayObservePackages(false);
+
+ watchdog.startObservingHealth(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION);
+
+ raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
+ VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN);
+ assertThat(persistentObserver.mHealthCheckFailedPackages).isEmpty();
+ }
+
+
+ /** Ensure that boot loop mitigation is done when the number of boots meets the threshold. */
+ @Test
+ public void testBootLoopDetection_meetsThreshold() {
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1);
+ watchdog.registerHealthObserver(bootObserver);
+ for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
+ watchdog.noteBoot();
+ }
+ assertThat(bootObserver.mitigatedBootLoop()).isTrue();
+ }
+
+
+ /**
+ * Ensure that boot loop mitigation is not done when the number of boots does not meet the
+ * threshold.
+ */
+ @Test
+ public void testBootLoopDetection_doesNotMeetThreshold() {
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1);
+ watchdog.registerHealthObserver(bootObserver);
+ for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT - 1; i++) {
+ watchdog.noteBoot();
+ }
+ assertThat(bootObserver.mitigatedBootLoop()).isFalse();
+ }
+
+ /**
+ * Ensure that boot loop mitigation is done for the observer with the lowest user impact
+ */
+ @Test
+ public void testBootLoopMitigationDoneForLowestUserImpact() {
+ PackageWatchdog watchdog = createWatchdog();
+ TestObserver bootObserver1 = new TestObserver(OBSERVER_NAME_1);
+ bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LOW);
+ TestObserver bootObserver2 = new TestObserver(OBSERVER_NAME_2);
+ bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
+ watchdog.registerHealthObserver(bootObserver1);
+ watchdog.registerHealthObserver(bootObserver2);
+ for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
+ watchdog.noteBoot();
+ }
+ assertThat(bootObserver1.mitigatedBootLoop()).isTrue();
+ assertThat(bootObserver2.mitigatedBootLoop()).isFalse();
}
private void adoptShellPermissions(String... permissions) {
@@ -964,7 +1098,12 @@
/** Trigger package failures above the threshold. */
private void raiseFatalFailureAndDispatch(PackageWatchdog watchdog,
List<VersionedPackage> packages, int failureReason) {
- for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) {
+ long triggerFailureCount = watchdog.getTriggerFailureCount();
+ if (failureReason == PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK
+ || failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
+ triggerFailureCount = 1;
+ }
+ for (int i = 0; i < triggerFailureCount; i++) {
watchdog.onPackageFailure(packages, failureReason);
}
mTestLooper.dispatchAll();
@@ -1000,6 +1139,9 @@
private final String mName;
private int mImpact;
private int mLastFailureReason;
+ private boolean mIsPersistent = false;
+ private boolean mMayObservePackages = false;
+ private boolean mMitigatedBootLoop = false;
final List<String> mHealthCheckFailedPackages = new ArrayList<>();
final List<String> mMitigatedPackages = new ArrayList<>();
@@ -1028,9 +1170,42 @@
return mName;
}
+ public boolean isPersistent() {
+ return mIsPersistent;
+ }
+
+ public boolean mayObservePackage(String packageName) {
+ return mMayObservePackages;
+ }
+
+ public int onBootLoop() {
+ return mImpact;
+ }
+
+ public boolean executeBootLoopMitigation() {
+ mMitigatedBootLoop = true;
+ return true;
+ }
+
+ public boolean mitigatedBootLoop() {
+ return mMitigatedBootLoop;
+ }
+
public int getLastFailureReason() {
return mLastFailureReason;
}
+
+ public void setPersistent(boolean persistent) {
+ mIsPersistent = persistent;
+ }
+
+ public void setImpact(int impact) {
+ mImpact = impact;
+ }
+
+ public void setMayObservePackages(boolean mayObservePackages) {
+ mMayObservePackages = mayObservePackages;
+ }
}
private static class TestController extends ExplicitHealthCheckController {
diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp
index 2bc129a..98e7b4e 100644
--- a/tests/RollbackTest/Android.bp
+++ b/tests/RollbackTest/Android.bp
@@ -19,15 +19,20 @@
static_libs: ["androidx.test.rules", "cts-rollback-lib", "cts-install-lib"],
test_suites: ["general-tests"],
test_config: "RollbackTest.xml",
+ java_resources: [
+ ":com.android.apex.apkrollback.test_v2",
+ ":com.android.apex.apkrollback.test_v2Crashing"
+ ],
}
java_test_host {
name: "StagedRollbackTest",
srcs: ["StagedRollbackTest/src/**/*.java"],
libs: ["tradefed"],
- static_libs: ["testng"],
+ static_libs: ["testng", "compatibility-tradefed"],
test_suites: ["general-tests"],
test_config: "StagedRollbackTest.xml",
+ data: [":com.android.apex.apkrollback.test_v1"],
}
java_test_host {
@@ -37,3 +42,54 @@
test_suites: ["general-tests"],
test_config: "MultiUserRollbackTest.xml",
}
+
+genrule {
+ name: "com.android.apex.apkrollback.test.pem",
+ out: ["com.android.apex.apkrollback.test.pem"],
+ cmd: "openssl genrsa -out $(out) 4096",
+}
+
+genrule {
+ name: "com.android.apex.apkrollback.test.pubkey",
+ srcs: [":com.android.apex.apkrollback.test.pem"],
+ out: ["com.android.apex.apkrollback.test.pubkey"],
+ tools: ["avbtool"],
+ cmd: "$(location avbtool) extract_public_key --key $(in) --output $(out)",
+}
+
+apex_key {
+ name: "com.android.apex.apkrollback.test.key",
+ private_key: ":com.android.apex.apkrollback.test.pem",
+ public_key: ":com.android.apex.apkrollback.test.pubkey",
+ installable: false,
+}
+
+apex {
+ name: "com.android.apex.apkrollback.test_v1",
+ manifest: "testdata/manifest_v1.json",
+ androidManifest: "testdata/AndroidManifest.xml",
+ file_contexts: ":apex.test-file_contexts",
+ key: "com.android.apex.apkrollback.test.key",
+ apps: ["TestAppAv1"],
+ installable: false,
+}
+
+apex {
+ name: "com.android.apex.apkrollback.test_v2",
+ manifest: "testdata/manifest_v2.json",
+ androidManifest: "testdata/AndroidManifest.xml",
+ file_contexts: ":apex.test-file_contexts",
+ key: "com.android.apex.apkrollback.test.key",
+ apps: ["TestAppAv2"],
+ installable: false,
+}
+
+apex {
+ name: "com.android.apex.apkrollback.test_v2Crashing",
+ manifest: "testdata/manifest_v2.json",
+ androidManifest: "testdata/AndroidManifest.xml",
+ file_contexts: ":apex.test-file_contexts",
+ key: "com.android.apex.apkrollback.test.key",
+ apps: ["TestAppACrashingV2"],
+ installable: false,
+}
\ No newline at end of file
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
index f6699fa..5a92d68 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/RollbackTest.java
@@ -392,9 +392,6 @@
RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS,
Long.toString(expirationTime), false /* makeDefault*/);
- // Pull the new expiration time from DeviceConfig
- rm.reloadPersistedData();
-
// Uninstall TestApp.A
Uninstall.packages(TestApp.A);
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(-1);
@@ -457,9 +454,6 @@
RollbackManager.PROPERTY_ROLLBACK_LIFETIME_MILLIS,
Long.toString(expirationTime), false /* makeDefault*/);
- // Pull the new expiration time from DeviceConfig
- rm.reloadPersistedData();
-
// Install app A with rollback enabled
Uninstall.packages(TestApp.A);
Install.single(TestApp.A1).commit();
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
index 40169b8..80491cd 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/StagedRollbackTest.java
@@ -23,12 +23,14 @@
import android.Manifest;
import android.content.ComponentName;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.rollback.RollbackInfo;
import android.content.rollback.RollbackManager;
import android.os.ParcelFileDescriptor;
+import android.os.storage.StorageManager;
import android.provider.DeviceConfig;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -185,12 +187,6 @@
*/
@Test
public void testNativeWatchdogTriggersRollback_Phase1() throws Exception {
- // When multiple staged sessions are installed on a device which doesn't support checkpoint,
- // only the 1st one will prevail. We have to check no other rollbacks available to ensure
- // TestApp.A is always the 1st and the only one to commit so rollback can work as intended.
- // If there are leftover rollbacks from previous tests, this assertion will fail.
- assertThat(RollbackUtils.getRollbackManager().getAvailableRollbacks()).isEmpty();
-
Uninstall.packages(TestApp.A);
Install.single(TestApp.A1).commit();
assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
@@ -220,6 +216,64 @@
TestApp.A)).isNotNull();
}
+ /**
+ * Stage install an apk with rollback that will be later triggered by unattributable crash.
+ */
+ @Test
+ public void testNativeWatchdogTriggersRollbackForAll_Phase1() throws Exception {
+ Uninstall.packages(TestApp.A);
+ Install.single(TestApp.A1).commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+
+ Install.single(TestApp.A2).setEnableRollback().setStaged().commit();
+ }
+
+ /**
+ * Verify the rollback is available and then install another package with rollback.
+ */
+ @Test
+ public void testNativeWatchdogTriggersRollbackForAll_Phase2() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
+ TestApp.A)).isNotNull();
+
+ // Install another package with rollback
+ Uninstall.packages(TestApp.B);
+ Install.single(TestApp.B1).commit();
+ assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1);
+
+ Install.single(TestApp.B2).setEnableRollback().setStaged().commit();
+ }
+
+ /**
+ * Verify the rollbacks are available.
+ */
+ @Test
+ public void testNativeWatchdogTriggersRollbackForAll_Phase3() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(2);
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
+ TestApp.A)).isNotNull();
+ assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
+ TestApp.B)).isNotNull();
+ }
+
+ /**
+ * Verify the rollbacks are committed after crashing.
+ */
+ @Test
+ public void testNativeWatchdogTriggersRollbackForAll_Phase4() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.B)).isEqualTo(1);
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
+ TestApp.A)).isNotNull();
+ assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
+ TestApp.B)).isNotNull();
+ }
+
@Test
public void testNetworkFailedRollback_Phase1() throws Exception {
// Remove available rollbacks and uninstall NetworkStack on /data/
@@ -324,6 +378,7 @@
@Test
public void testNetworkPassedDoesNotRollback_Phase1() throws Exception {
+ // Remove available rollbacks and uninstall NetworkStack on /data/
RollbackManager rm = RollbackUtils.getRollbackManager();
String networkStack = getNetworkStackPackageName();
@@ -332,6 +387,15 @@
assertThat(getUniqueRollbackInfoForPackage(rm.getAvailableRollbacks(),
networkStack)).isNull();
+
+ // Reduce health check deadline, here unlike the network failed case, we use
+ // a longer deadline because joining a network can take a much longer time for
+ // reasons external to the device than 'not joining'
+ DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
+ PROPERTY_WATCHDOG_REQUEST_TIMEOUT_MILLIS,
+ Integer.toString(300000), false);
+ // Simulate re-installation of new NetworkStack with rollbacks enabled
+ installNetworkStackPackage();
}
@Test
@@ -343,6 +407,9 @@
@Test
public void testNetworkPassedDoesNotRollback_Phase3() throws Exception {
+ // Sleep for > health check deadline. We expect no rollback should happen during sleeping.
+ // If the device reboots for rollback, this device test will fail as well as the host test.
+ Thread.sleep(TimeUnit.SECONDS.toMillis(310));
RollbackManager rm = RollbackUtils.getRollbackManager();
assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
getNetworkStackPackageName())).isNull();
@@ -380,11 +447,6 @@
}
@Test
- public void testRollbackWhitelistedApp_cleanUp() throws Exception {
- RollbackUtils.getRollbackManager().expireRollbackForPackage(getModuleMetadataPackageName());
- }
-
- @Test
public void testRollbackDataPolicy_Phase1() throws Exception {
Uninstall.packages(TestApp.A, TestApp.B);
Install.multi(TestApp.A1, TestApp.B1).commit();
@@ -422,9 +484,116 @@
assertThat(InstallUtils.getUserDataVersion(TestApp.B)).isEqualTo(1);
}
+ @Test
+ public void testCleanUp() throws Exception {
+ // testNativeWatchdogTriggersRollback will fail if multiple staged sessions are
+ // committed on a device which doesn't support checkpoint. Let's clean up all rollbacks
+ // so there is only one rollback to commit when testing native crashes.
+ RollbackManager rm = RollbackUtils.getRollbackManager();
+ rm.getAvailableRollbacks().stream().flatMap(info -> info.getPackages().stream())
+ .map(info -> info.getPackageName()).forEach(rm::expireRollbackForPackage);
+ assertThat(RollbackUtils.getRollbackManager().getAvailableRollbacks()).isEmpty();
+ }
+
+ private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test";
+ private static final TestApp TEST_APEX_WITH_APK_V1 = new TestApp("TestApexWithApkV1",
+ APK_IN_APEX_TESTAPEX_NAME, 1, /*isApex*/true, APK_IN_APEX_TESTAPEX_NAME + "_v1.apex");
+ private static final TestApp TEST_APEX_WITH_APK_V2 = new TestApp("TestApexWithApkV2",
+ APK_IN_APEX_TESTAPEX_NAME, 2, /*isApex*/true, APK_IN_APEX_TESTAPEX_NAME + "_v2.apex");
+ private static final TestApp TEST_APEX_WITH_APK_V2_CRASHING = new TestApp(
+ "TestApexWithApkV2Crashing", APK_IN_APEX_TESTAPEX_NAME, 2, /*isApex*/true,
+ APK_IN_APEX_TESTAPEX_NAME + "_v2Crashing.apex");
+
+ @Test
+ public void testRollbackApexWithApk_Phase1() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ InstallUtils.processUserData(TestApp.A);
+
+ int sessionId = Install.single(TEST_APEX_WITH_APK_V2).setStaged().setEnableRollback()
+ .commit();
+ InstallUtils.waitForSessionReady(sessionId);
+ }
+
+ @Test
+ public void testRollbackApexWithApk_Phase2() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(2);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+ InstallUtils.processUserData(TestApp.A);
+
+ RollbackInfo available = RollbackUtils.getAvailableRollback(APK_IN_APEX_TESTAPEX_NAME);
+ assertThat(available).isStaged();
+ assertThat(available).packagesContainsExactly(
+ Rollback.from(TEST_APEX_WITH_APK_V2).to(TEST_APEX_WITH_APK_V1),
+ Rollback.from(TestApp.A, 0).to(TestApp.A1));
+
+ RollbackUtils.rollback(available.getRollbackId(), TEST_APEX_WITH_APK_V2);
+ RollbackInfo committed = RollbackUtils.getCommittedRollbackById(available.getRollbackId());
+ assertThat(committed).isNotNull();
+ assertThat(committed).isStaged();
+ assertThat(committed).packagesContainsExactly(
+ Rollback.from(TEST_APEX_WITH_APK_V2).to(TEST_APEX_WITH_APK_V1),
+ Rollback.from(TestApp.A, 0).to(TestApp.A1));
+ assertThat(committed).causePackagesContainsExactly(TEST_APEX_WITH_APK_V2);
+ assertThat(committed.getCommittedSessionId()).isNotEqualTo(-1);
+
+ // Note: The app is not rolled back until after the rollback is staged
+ // and the device has been rebooted.
+ InstallUtils.waitForSessionReady(committed.getCommittedSessionId());
+ assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(2);
+ }
+
+ @Test
+ public void testRollbackApexWithApk_Phase3() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(1);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ InstallUtils.processUserData(TestApp.A);
+ }
+
+ /**
+ * Installs an apex with an apk that can crash.
+ */
+ @Test
+ public void testRollbackApexWithApkCrashing_Phase1() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ int sessionId = Install.single(TEST_APEX_WITH_APK_V2_CRASHING).setStaged()
+ .setEnableRollback().commit();
+ InstallUtils.waitForSessionReady(sessionId);
+ }
+
+ /**
+ * Verifies rollback has been enabled successfully. Then makes TestApp.A crash.
+ */
+ @Test
+ public void testRollbackApexWithApkCrashing_Phase2() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(2);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
+
+ RollbackInfo available = RollbackUtils.getAvailableRollback(APK_IN_APEX_TESTAPEX_NAME);
+ assertThat(available).isStaged();
+ assertThat(available).packagesContainsExactly(
+ Rollback.from(TEST_APEX_WITH_APK_V2).to(TEST_APEX_WITH_APK_V1),
+ Rollback.from(TestApp.A, 0).to(TestApp.A1));
+
+ // Crash TestApp.A PackageWatchdog#TRIGGER_FAILURE_COUNT times to trigger rollback
+ RollbackUtils.sendCrashBroadcast(TestApp.A, 5);
+ }
+
+ @Test
+ public void testRollbackApexWithApkCrashing_Phase3() throws Exception {
+ assertThat(InstallUtils.getInstalledVersion(APK_IN_APEX_TESTAPEX_NAME)).isEqualTo(1);
+ assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
+ }
+
private static void runShellCommand(String cmd) {
ParcelFileDescriptor pfd = InstrumentationRegistry.getInstrumentation().getUiAutomation()
.executeShellCommand(cmd);
IoUtils.closeQuietly(pfd);
}
+
+ @Test
+ public void isCheckpointSupported() {
+ Context context = InstrumentationRegistry.getInstrumentation().getContext();
+ StorageManager sm = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
+ assertThat(sm.isCheckpointSupported()).isTrue();
+ }
}
diff --git a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
index 4644d8a..672cbb0 100644
--- a/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
+++ b/tests/RollbackTest/StagedRollbackTest/src/com/android/tests/rollback/host/StagedRollbackTest.java
@@ -17,16 +17,19 @@
package com.android.tests.rollback.host;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
import static org.testng.Assert.assertThrows;
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.File;
import java.util.concurrent.TimeUnit;
/**
@@ -48,8 +51,32 @@
phase));
}
+ private static final String APK_IN_APEX_TESTAPEX_NAME = "com.android.apex.apkrollback.test";
+
@Before
public void setUp() throws Exception {
+ if (!getDevice().isAdbRoot()) {
+ getDevice().enableAdbRoot();
+ }
+ getDevice().remountSystemWritable();
+ getDevice().executeShellCommand(
+ "rm -f /system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex "
+ + "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex");
+ getDevice().reboot();
+ runPhase("testCleanUp");
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ runPhase("testCleanUp");
+
+ if (!getDevice().isAdbRoot()) {
+ getDevice().enableAdbRoot();
+ }
+ getDevice().remountSystemWritable();
+ getDevice().executeShellCommand(
+ "rm -f /system/apex/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex "
+ + "/data/apex/active/" + APK_IN_APEX_TESTAPEX_NAME + "*.apex");
getDevice().reboot();
}
@@ -70,7 +97,6 @@
@Test
public void testNativeWatchdogTriggersRollback() throws Exception {
- //Stage install ModuleMetadata package - this simulates a Mainline module update
runPhase("testNativeWatchdogTriggersRollback_Phase1");
// Reboot device to activate staged package
@@ -96,6 +122,40 @@
runPhase("testNativeWatchdogTriggersRollback_Phase3");
}
+ @Test
+ public void testNativeWatchdogTriggersRollbackForAll() throws Exception {
+ // This test requires committing multiple staged rollbacks
+ assumeTrue(isCheckpointSupported());
+
+ // Install a package with rollback enabled.
+ runPhase("testNativeWatchdogTriggersRollbackForAll_Phase1");
+ getDevice().reboot();
+
+ // Once previous staged install is applied, install another package
+ runPhase("testNativeWatchdogTriggersRollbackForAll_Phase2");
+ getDevice().reboot();
+
+ // Verify the new staged install has also been applied successfully.
+ runPhase("testNativeWatchdogTriggersRollbackForAll_Phase3");
+
+ // crash system_server enough times to trigger a rollback
+ crashProcess("system_server", NATIVE_CRASHES_THRESHOLD);
+
+ // Rollback should be committed automatically now.
+ // Give time for rollback to be committed. This could take a while,
+ // because we need all of the following to happen:
+ // 1. system_server comes back up and boot completes.
+ // 2. Rollback health observer detects updatable crashing signal.
+ // 3. Staged rollback session becomes ready.
+ // 4. Device actually reboots.
+ // So we give a generous timeout here.
+ assertTrue(getDevice().waitForDeviceNotAvailable(TimeUnit.MINUTES.toMillis(5)));
+ getDevice().waitForDeviceAvailable();
+
+ // verify all available rollbacks have been committed
+ runPhase("testNativeWatchdogTriggersRollbackForAll_Phase4");
+ }
+
/**
* Tests failed network health check triggers watchdog staged rollbacks.
*/
@@ -128,21 +188,8 @@
* Tests passed network health check does not trigger watchdog staged rollbacks.
*/
@Test
- @Ignore("b/143514090")
public void testNetworkPassedDoesNotRollback() throws Exception {
- // Remove available rollbacks and uninstall NetworkStack on /data/
runPhase("testNetworkPassedDoesNotRollback_Phase1");
- // Reduce health check deadline, here unlike the network failed case, we use
- // a longer deadline because joining a network can take a much longer time for
- // reasons external to the device than 'not joining'
- getDevice().executeShellCommand("device_config put rollback "
- + "watchdog_request_timeout_millis 300000");
- // Simulate re-installation of new NetworkStack with rollbacks enabled
- getDevice().executeShellCommand("pm install -r --staged --enable-rollback "
- + getNetworkStackPath());
-
- // Sleep to allow writes to disk before reboot
- Thread.sleep(5000);
// Reboot device to activate staged package
getDevice().reboot();
@@ -157,8 +204,6 @@
// on mobile data
getDevice().waitForDeviceAvailable();
- // Sleep for > health check deadline
- Thread.sleep(310000);
// Verify rollback was not executed after health check deadline
runPhase("testNetworkPassedDoesNotRollback_Phase3");
}
@@ -180,16 +225,9 @@
*/
@Test
public void testRollbackWhitelistedApp() throws Exception {
- try {
- runPhase("testRollbackWhitelistedApp_Phase1");
- getDevice().reboot();
- runPhase("testRollbackWhitelistedApp_Phase2");
- } finally {
- // testNativeWatchdogTriggersRollback will fail if multiple staged sessions are
- // committed on a device which doesn't support checkpoint. Let's clean up the rollback
- // so there is only one rollback to commit when testing native crashes.
- runPhase("testRollbackWhitelistedApp_cleanUp");
- }
+ runPhase("testRollbackWhitelistedApp_Phase1");
+ getDevice().reboot();
+ runPhase("testRollbackWhitelistedApp_Phase2");
}
@Test
@@ -201,6 +239,56 @@
runPhase("testRollbackDataPolicy_Phase3");
}
+ /**
+ * Tests that userdata of apk-in-apex is restored when apex is rolled back.
+ */
+ @Test
+ public void testRollbackApexWithApk() throws Exception {
+ getDevice().uninstallPackage("com.android.cts.install.lib.testapp.A");
+ CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
+ final String fileName = APK_IN_APEX_TESTAPEX_NAME + "_v1.apex";
+ final File apex = buildHelper.getTestFile(fileName);
+ if (!getDevice().isAdbRoot()) {
+ getDevice().enableAdbRoot();
+ }
+ getDevice().remountSystemWritable();
+ assertTrue(getDevice().pushFile(apex, "/system/apex/" + fileName));
+ getDevice().reboot();
+ runPhase("testRollbackApexWithApk_Phase1");
+ getDevice().reboot();
+ runPhase("testRollbackApexWithApk_Phase2");
+ getDevice().reboot();
+ runPhase("testRollbackApexWithApk_Phase3");
+ }
+
+ /**
+ * Tests that RollbackPackageHealthObserver is observing apk-in-apex.
+ */
+ @Test
+ public void testRollbackApexWithApkCrashing() throws Exception {
+ getDevice().uninstallPackage("com.android.cts.install.lib.testapp.A");
+ CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
+ final String fileName = APK_IN_APEX_TESTAPEX_NAME + "_v1.apex";
+ final File apex = buildHelper.getTestFile(fileName);
+ if (!getDevice().isAdbRoot()) {
+ getDevice().enableAdbRoot();
+ }
+ getDevice().remountSystemWritable();
+ assertTrue(getDevice().pushFile(apex, "/system/apex/" + fileName));
+ getDevice().reboot();
+
+ // Install an apex with apk that crashes
+ runPhase("testRollbackApexWithApkCrashing_Phase1");
+ getDevice().reboot();
+ // Verify apex was installed and then crash the apk
+ runPhase("testRollbackApexWithApkCrashing_Phase2");
+ // Wait for crash to trigger rollback
+ assertTrue(getDevice().waitForDeviceNotAvailable(TimeUnit.MINUTES.toMillis(5)));
+ getDevice().waitForDeviceAvailable();
+ // Verify rollback occurred due to crash of apk-in-apex
+ runPhase("testRollbackApexWithApkCrashing_Phase3");
+ }
+
private void crashProcess(String processName, int numberOfCrashes) throws Exception {
String pid = "";
String lastPid = "invalid";
@@ -219,4 +307,13 @@
// Find the NetworkStack path (can be NetworkStack.apk or NetworkStackNext.apk)
return getDevice().executeShellCommand("ls /system/priv-app/NetworkStack*/*.apk");
}
+
+ private boolean isCheckpointSupported() throws Exception {
+ try {
+ runPhase("isCheckpointSupported");
+ return true;
+ } catch (AssertionError ignore) {
+ return false;
+ }
+ }
}
diff --git a/tests/RollbackTest/testdata/AndroidManifest.xml b/tests/RollbackTest/testdata/AndroidManifest.xml
new file mode 100644
index 0000000..f21ec89
--- /dev/null
+++ b/tests/RollbackTest/testdata/AndroidManifest.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.apex.apkrollback.test">
+ <!-- APEX does not have classes.dex -->
+ <application android:hasCode="false" />
+ <uses-sdk android:minSdkVersion="28" android:targetSdkVersion="29"/>
+</manifest>
+
diff --git a/tests/RollbackTest/testdata/manifest_v1.json b/tests/RollbackTest/testdata/manifest_v1.json
new file mode 100644
index 0000000..1762fc6
--- /dev/null
+++ b/tests/RollbackTest/testdata/manifest_v1.json
@@ -0,0 +1,4 @@
+{
+ "name": "com.android.apex.apkrollback.test",
+ "version": 1
+}
diff --git a/tests/RollbackTest/testdata/manifest_v2.json b/tests/RollbackTest/testdata/manifest_v2.json
new file mode 100644
index 0000000..c5127b9c
--- /dev/null
+++ b/tests/RollbackTest/testdata/manifest_v2.json
@@ -0,0 +1,4 @@
+{
+ "name": "com.android.apex.apkrollback.test",
+ "version": 2
+}
diff --git a/tests/TaskOrganizerTest/Android.bp b/tests/TaskOrganizerTest/Android.bp
new file mode 100644
index 0000000..8a13dbc
--- /dev/null
+++ b/tests/TaskOrganizerTest/Android.bp
@@ -0,0 +1,22 @@
+//
+// 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 {
+ name: "TaskOrganizerTest",
+ srcs: ["**/*.java"],
+ platform_apis: true,
+ certificate: "platform",
+}
diff --git a/tests/TaskOrganizerTest/AndroidManifest.xml b/tests/TaskOrganizerTest/AndroidManifest.xml
new file mode 100644
index 0000000..0cb6c10
--- /dev/null
+++ b/tests/TaskOrganizerTest/AndroidManifest.xml
@@ -0,0 +1,23 @@
+<?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.test.taskembed">
+ <uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
+ <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+ <application>
+ <service android:name=".TaskOrganizerPipTest"
+ android:exported="true">
+ </service>
+ </application>
+</manifest>
diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java
new file mode 100644
index 0000000..6ffa19d
--- /dev/null
+++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java
@@ -0,0 +1,127 @@
+/*
+ * 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 com.android.test.taskembed;
+
+import android.app.ActivityManager;
+import android.app.ActivityTaskManager;
+import android.app.Service;
+import android.app.WindowConfiguration;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Rect;
+import android.os.IBinder;
+import android.view.ITaskOrganizer;
+import android.view.IWindowContainer;
+import android.view.WindowContainerTransaction;
+import android.view.SurfaceControl;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+public class TaskOrganizerPipTest extends Service {
+ static final int PIP_WIDTH = 640;
+ static final int PIP_HEIGHT = 360;
+
+ class PipOrgView extends SurfaceView implements SurfaceHolder.Callback {
+ PipOrgView(Context c) {
+ super(c);
+ getHolder().addCallback(this);
+ setZOrderOnTop(true);
+ }
+ @Override
+ public void surfaceCreated(SurfaceHolder holder) {
+ try {
+ ActivityTaskManager.getService().registerTaskOrganizer(mOrganizer,
+ WindowConfiguration.WINDOWING_MODE_PINNED);
+ } catch (Exception e) {
+ }
+ }
+
+ @Override
+ public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ }
+
+ void reparentTask(IWindowContainer wc) {
+ SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ SurfaceControl leash = null;
+ try {
+ leash = wc.getLeash();
+ } catch (Exception e) {
+ // System server died.. oh well
+ }
+ t.reparent(leash, getSurfaceControl())
+ .setPosition(leash, 0, 0)
+ .apply();
+ }
+ }
+
+ PipOrgView mPipView;
+
+ class Organizer extends ITaskOrganizer.Stub {
+ public void taskAppeared(IWindowContainer wc, ActivityManager.RunningTaskInfo ti) {
+ mPipView.reparentTask(wc);
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.scheduleFinishEnterPip(wc, new Rect(0, 0, PIP_WIDTH, PIP_HEIGHT));
+ try {
+ ActivityTaskManager.getService().applyContainerTransaction(wct);
+ } catch (Exception e) {
+ }
+ }
+ public void taskVanished(IWindowContainer wc) {
+ }
+ public void transactionReady(int id, SurfaceControl.Transaction t) {
+ }
+ }
+
+ Organizer mOrganizer = new Organizer();
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ final WindowManager.LayoutParams wlp = new WindowManager.LayoutParams();
+ wlp.setTitle("TaskOrganizerPipTest");
+ wlp.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+ wlp.width = wlp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+
+ FrameLayout layout = new FrameLayout(this);
+ ViewGroup.LayoutParams lp =
+ new ViewGroup.LayoutParams(PIP_WIDTH, PIP_HEIGHT);
+ mPipView = new PipOrgView(this);
+ layout.addView(mPipView, lp);
+
+ WindowManager wm = getSystemService(WindowManager.class);
+ wm.addView(layout, wlp);
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ }
+}
diff --git a/tests/TelephonyCommonTests/Android.bp b/tests/TelephonyCommonTests/Android.bp
new file mode 100644
index 0000000..4f7569d
--- /dev/null
+++ b/tests/TelephonyCommonTests/Android.bp
@@ -0,0 +1,49 @@
+//
+// 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 {
+ name: "TelephonyCommonTests",
+ srcs: [
+ ":framework-telephony-common-sources",
+ "**/*.java",
+ ],
+ static_libs: [
+ "mockito-target-extended",
+ "androidx.test.rules",
+ "truth-prebuilt",
+ "platform-test-annotations",
+ "androidx.core_core",
+ "androidx.fragment_fragment",
+ "androidx.test.ext.junit"
+ ],
+
+ jni_libs: ["libdexmakerjvmtiagent"],
+
+ // We need to rename SmsApplication to the test package or else it'll get clobbered by the
+ // hidden api checker
+ jarjar_rules: "jarjar-rules.txt",
+
+ // Uncomment this and comment out the jarjar rule if you want to attach a debugger and step
+ // through the renamed classes.
+ // platform_apis: true,
+
+ libs: [
+ "android.test.runner",
+ "android.test.mock",
+ "android.test.base",
+ "unsupportedappusage",
+ ],
+}
diff --git a/tests/TelephonyCommonTests/AndroidManifest.xml b/tests/TelephonyCommonTests/AndroidManifest.xml
new file mode 100644
index 0000000..63f38c6
--- /dev/null
+++ b/tests/TelephonyCommonTests/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?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.internal.telephony.tests"
+ android:debuggable="true">
+
+ <application android:label="TelephonyCommonTests"
+ android:debuggable="true">
+ <uses-library android:name="android.test.runner" />
+ </application>
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+ android:targetPackage="com.android.internal.telephony.tests"
+ android:label="Telephony common tests"
+ android:debuggable="true"/>
+</manifest>
diff --git a/tests/TelephonyCommonTests/AndroidTest.xml b/tests/TelephonyCommonTests/AndroidTest.xml
new file mode 100644
index 0000000..e9fdabc
--- /dev/null
+++ b/tests/TelephonyCommonTests/AndroidTest.xml
@@ -0,0 +1,30 @@
+<?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.
+ -->
+<configuration description="Runs Telephony Common Test Cases.">
+ <option name="test-suite-tag" value="apct" />
+ <option name="test-suite-tag" value="apct-instrumentation" />
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="TelephonyCommonTests.apk" />
+ </target_preparer>
+
+ <option name="test-tag" value="TelephonyCommonTests" />
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+ <option name="package" value="com.android.internal.telephony.tests" />
+ <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+ </test>
+</configuration>
diff --git a/tests/TelephonyCommonTests/jarjar-rules.txt b/tests/TelephonyCommonTests/jarjar-rules.txt
new file mode 100644
index 0000000..4d1115f
--- /dev/null
+++ b/tests/TelephonyCommonTests/jarjar-rules.txt
@@ -0,0 +1,3 @@
+rule com.android.internal.telephony.SmsApplication* com.android.internal.telephony.tests.SmsApplication@1
+rule android.telephony.PackageChangeReceiver* com.android.internal.telephony.tests.PackageChangeReceiver@1
+rule com.android.internal.os.BackgroundThread* com.android.internal.telephony.tests.BackgroundThread@1
diff --git a/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java
new file mode 100644
index 0000000..83fd208
--- /dev/null
+++ b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/SmsApplicationTest.java
@@ -0,0 +1,291 @@
+/*
+ * 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 com.android.internal.telephony.tests;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNotNull;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.Manifest;
+import android.app.AppOpsManager;
+import android.app.role.RoleManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.UserHandle;
+import android.provider.Telephony;
+import android.telephony.TelephonyManager;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.internal.telephony.SmsApplication;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Unit tests for the {@link SmsApplication} utility class
+ */
+@RunWith(AndroidJUnit4.class)
+public class SmsApplicationTest {
+ private static final ComponentName TEST_COMPONENT_NAME =
+ ComponentName.unflattenFromString("com.android.test/.TestSmsApp");
+ private static final String MMS_RECEIVER_NAME = "TestMmsReceiver";
+ private static final String RESPOND_VIA_SMS_NAME = "TestRespondViaSmsHandler";
+ private static final String SEND_TO_NAME = "TestSendTo";
+ private static final int SMS_APP_UID = 10001;
+
+ private static final int FAKE_PHONE_UID = 10002;
+ private static final int FAKE_MMS_UID = 10003;
+ private static final int FAKE_BT_UID = 10004;
+ private static final int FAKE_TELEPHONY_PROVIDER_UID = 10005;
+
+ private static final String[] APP_OPS_TO_CHECK = {
+ AppOpsManager.OPSTR_READ_SMS,
+ AppOpsManager.OPSTR_WRITE_SMS,
+ AppOpsManager.OPSTR_RECEIVE_SMS,
+ AppOpsManager.OPSTR_RECEIVE_WAP_PUSH,
+ AppOpsManager.OPSTR_SEND_SMS,
+ AppOpsManager.OPSTR_READ_CELL_BROADCASTS
+ };
+
+ private static final Set<String> SCHEMES_FOR_PREFERRED_APP = Arrays.stream(new String[]{
+ "mms",
+ "mmsto",
+ "sms",
+ "smsto"
+ }).collect(Collectors.toSet());
+
+ @Mock private Context mContext;
+ @Mock private TelephonyManager mTelephonyManager;
+ @Mock private RoleManager mRoleManager;
+ @Mock private PackageManager mPackageManager;
+ @Mock private AppOpsManager mAppOpsManager;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTelephonyManager);
+ when(mContext.getSystemService(Context.ROLE_SERVICE)).thenReturn(mRoleManager);
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ when(mContext.getSystemService(RoleManager.class)).thenReturn(mRoleManager);
+ when(mContext.getSystemService(AppOpsManager.class)).thenReturn(mAppOpsManager);
+ when(mContext.createContextAsUser(isNotNull(), anyInt())).thenReturn(mContext);
+
+ doAnswer(invocation -> getResolveInfosForIntent(invocation.getArgument(0)))
+ .when(mPackageManager)
+ .queryBroadcastReceiversAsUser(nullable(Intent.class), anyInt(),
+ nullable(UserHandle.class));
+ doAnswer(invocation -> getResolveInfosForIntent(invocation.getArgument(0)))
+ .when(mPackageManager)
+ .queryIntentActivitiesAsUser(nullable(Intent.class), anyInt(),
+ nullable(UserHandle.class));
+ doAnswer(invocation -> getResolveInfosForIntent(invocation.getArgument(0)))
+ .when(mPackageManager)
+ .queryIntentServicesAsUser(nullable(Intent.class), anyInt(),
+ nullable(UserHandle.class));
+
+ when(mTelephonyManager.isSmsCapable()).thenReturn(true);
+ when(mRoleManager.isRoleAvailable(RoleManager.ROLE_SMS)).thenReturn(true);
+ when(mRoleManager.getDefaultSmsPackage(anyInt()))
+ .thenReturn(TEST_COMPONENT_NAME.getPackageName());
+
+ for (String opStr : APP_OPS_TO_CHECK) {
+ when(mAppOpsManager.unsafeCheckOp(
+ opStr, SMS_APP_UID, TEST_COMPONENT_NAME.getPackageName()))
+ .thenReturn(AppOpsManager.MODE_ALLOWED);
+ }
+ }
+
+ @Test
+ public void testGetDefaultSmsApplication() {
+ assertEquals(TEST_COMPONENT_NAME,
+ SmsApplication.getDefaultSmsApplicationAsUser(mContext, false, 0));
+ }
+
+ @Test
+ public void testGetDefaultSmsApplicationWithAppOpsFix() throws Exception {
+ when(mAppOpsManager.unsafeCheckOp(AppOpsManager.OPSTR_READ_SMS, SMS_APP_UID,
+ TEST_COMPONENT_NAME.getPackageName()))
+ .thenReturn(AppOpsManager.MODE_IGNORED);
+ setupPackageInfosForCoreApps();
+
+ assertEquals(TEST_COMPONENT_NAME,
+ SmsApplication.getDefaultSmsApplicationAsUser(mContext, true, 0));
+ verify(mAppOpsManager, atLeastOnce()).setUidMode(AppOpsManager.OPSTR_READ_SMS, SMS_APP_UID,
+ AppOpsManager.MODE_ALLOWED);
+ }
+
+ @Test
+ public void testPackageChanged() throws Exception {
+ setupPackageInfosForCoreApps();
+ SmsApplication.initSmsPackageMonitor(mContext);
+ verify(mContext).createContextAsUser(eq(UserHandle.ALL), anyInt());
+ ArgumentCaptor<BroadcastReceiver> captor = ArgumentCaptor.forClass(BroadcastReceiver.class);
+ verify(mContext).registerReceiver(captor.capture(), isNotNull(),
+ isNull(), nullable(Handler.class));
+ BroadcastReceiver smsPackageMonitor = captor.getValue();
+
+ Intent packageChangedIntent = new Intent(Intent.ACTION_PACKAGE_CHANGED);
+ packageChangedIntent.setData(
+ Uri.fromParts("package", TEST_COMPONENT_NAME.getPackageName(), null));
+ smsPackageMonitor.onReceive(mContext, packageChangedIntent);
+
+ ArgumentCaptor<IntentFilter> intentFilterCaptor =
+ ArgumentCaptor.forClass(IntentFilter.class);
+ verify(mPackageManager, times(SCHEMES_FOR_PREFERRED_APP.size()))
+ .replacePreferredActivity(intentFilterCaptor.capture(),
+ eq(IntentFilter.MATCH_CATEGORY_SCHEME
+ | IntentFilter.MATCH_ADJUSTMENT_NORMAL),
+ isNotNull(List.class),
+ eq(new ComponentName(TEST_COMPONENT_NAME.getPackageName(), SEND_TO_NAME)));
+
+ Set<String> capturedSchemes = intentFilterCaptor.getAllValues().stream()
+ .map(intentFilter -> intentFilter.getDataScheme(0))
+ .collect(Collectors.toSet());
+ assertEquals(SCHEMES_FOR_PREFERRED_APP.size(), capturedSchemes.size());
+ assertTrue(SCHEMES_FOR_PREFERRED_APP.containsAll(capturedSchemes));
+ }
+
+ private void setupPackageInfosForCoreApps() throws Exception {
+ PackageInfo phonePackageInfo = new PackageInfo();
+ ApplicationInfo phoneApplicationInfo = new ApplicationInfo();
+ phoneApplicationInfo.uid = FAKE_PHONE_UID;
+ phonePackageInfo.applicationInfo = phoneApplicationInfo;
+ when(mPackageManager.getPackageInfo(eq(SmsApplication.PHONE_PACKAGE_NAME), anyInt()))
+ .thenReturn(phonePackageInfo);
+
+ PackageInfo mmsPackageInfo = new PackageInfo();
+ ApplicationInfo mmsApplicationInfo = new ApplicationInfo();
+ mmsApplicationInfo.uid = FAKE_MMS_UID;
+ mmsPackageInfo.applicationInfo = mmsApplicationInfo;
+ when(mPackageManager.getPackageInfo(eq(SmsApplication.MMS_SERVICE_PACKAGE_NAME), anyInt()))
+ .thenReturn(mmsPackageInfo);
+
+ PackageInfo bluetoothPackageInfo = new PackageInfo();
+ ApplicationInfo bluetoothApplicationInfo = new ApplicationInfo();
+ bluetoothApplicationInfo.uid = FAKE_BT_UID;
+ bluetoothPackageInfo.applicationInfo = bluetoothApplicationInfo;
+ when(mPackageManager.getPackageInfo(eq(SmsApplication.BLUETOOTH_PACKAGE_NAME), anyInt()))
+ .thenReturn(bluetoothPackageInfo);
+
+ PackageInfo telephonyProviderPackageInfo = new PackageInfo();
+ ApplicationInfo telephonyProviderApplicationInfo = new ApplicationInfo();
+ telephonyProviderApplicationInfo.uid = FAKE_TELEPHONY_PROVIDER_UID;
+ telephonyProviderPackageInfo.applicationInfo = telephonyProviderApplicationInfo;
+ when(mPackageManager.getPackageInfo(
+ eq(SmsApplication.TELEPHONY_PROVIDER_PACKAGE_NAME), anyInt()))
+ .thenReturn(telephonyProviderPackageInfo);
+ }
+
+ private List<ResolveInfo> getResolveInfosForIntent(Intent intent) {
+ switch (intent.getAction()) {
+ case Telephony.Sms.Intents.SMS_DELIVER_ACTION:
+ return Collections.singletonList(makeSmsDeliverResolveInfo());
+ case Telephony.Sms.Intents.WAP_PUSH_DELIVER_ACTION:
+ return Collections.singletonList(makeWapPushResolveInfo());
+ case TelephonyManager.ACTION_RESPOND_VIA_MESSAGE:
+ return Collections.singletonList(makeRespondViaMessageResolveInfo());
+ case Intent.ACTION_SENDTO:
+ return Collections.singletonList(makeSendToResolveInfo());
+ }
+ return Collections.emptyList();
+ }
+
+ private ApplicationInfo makeSmsApplicationInfo() {
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.uid = SMS_APP_UID;
+ return applicationInfo;
+ }
+
+ private ResolveInfo makeSmsDeliverResolveInfo() {
+ ResolveInfo info = new ResolveInfo();
+ ActivityInfo activityInfo = new ActivityInfo();
+ activityInfo.applicationInfo = makeSmsApplicationInfo();
+
+ activityInfo.permission = Manifest.permission.BROADCAST_SMS;
+ activityInfo.packageName = TEST_COMPONENT_NAME.getPackageName();
+ activityInfo.name = TEST_COMPONENT_NAME.getClassName();
+
+ info.activityInfo = activityInfo;
+ return info;
+ }
+
+ private ResolveInfo makeWapPushResolveInfo() {
+ ResolveInfo info = new ResolveInfo();
+ ActivityInfo activityInfo = new ActivityInfo();
+
+ activityInfo.permission = Manifest.permission.BROADCAST_WAP_PUSH;
+ activityInfo.packageName = TEST_COMPONENT_NAME.getPackageName();
+ activityInfo.name = MMS_RECEIVER_NAME;
+
+ info.activityInfo = activityInfo;
+ return info;
+ }
+
+ private ResolveInfo makeRespondViaMessageResolveInfo() {
+ ResolveInfo info = new ResolveInfo();
+ ServiceInfo serviceInfo = new ServiceInfo();
+
+ serviceInfo.permission = Manifest.permission.SEND_RESPOND_VIA_MESSAGE;
+ serviceInfo.packageName = TEST_COMPONENT_NAME.getPackageName();
+ serviceInfo.name = RESPOND_VIA_SMS_NAME;
+
+ info.serviceInfo = serviceInfo;
+ return info;
+ }
+
+ private ResolveInfo makeSendToResolveInfo() {
+ ResolveInfo info = new ResolveInfo();
+ ActivityInfo activityInfo = new ActivityInfo();
+
+ activityInfo.packageName = TEST_COMPONENT_NAME.getPackageName();
+ activityInfo.name = SEND_TO_NAME;
+
+ info.activityInfo = activityInfo;
+ return info;
+ }
+}
diff --git a/tests/WindowInsetsTests/Android.bp b/tests/WindowInsetsTests/Android.bp
new file mode 100644
index 0000000..12395e7
--- /dev/null
+++ b/tests/WindowInsetsTests/Android.bp
@@ -0,0 +1,22 @@
+// 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: "WindowInsetsTests",
+ srcs: ["src/**/*.java"],
+ resource_dirs: ["res"],
+ certificate: "platform",
+ platform_apis: true,
+}
+
diff --git a/tests/WindowInsetsTests/AndroidManifest.xml b/tests/WindowInsetsTests/AndroidManifest.xml
new file mode 100644
index 0000000..8d33f70
--- /dev/null
+++ b/tests/WindowInsetsTests/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (018C) 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
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.google.android.test.windowinsetstests">
+
+ <application android:label="@string/activity_title">
+ <activity android:name=".WindowInsetsActivity"
+ android:theme="@android:style/Theme.Material"
+ android:windowSoftInputMode="adjustResize">
+
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/WindowInsetsTests/res/layout/window_inset_activity.xml b/tests/WindowInsetsTests/res/layout/window_inset_activity.xml
new file mode 100644
index 0000000..38e0029
--- /dev/null
+++ b/tests/WindowInsetsTests/res/layout/window_inset_activity.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:id="@+id/root">
+
+ <Button
+ android:id="@+id/button"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:text="Hello insets" />
+
+</LinearLayout>
+
diff --git a/tests/WindowInsetsTests/res/values/strings.xml b/tests/WindowInsetsTests/res/values/strings.xml
new file mode 100644
index 0000000..242823d
--- /dev/null
+++ b/tests/WindowInsetsTests/res/values/strings.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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
+ -->
+
+<resources>
+ <string name="activity_title">Window Insets Tests</string>
+</resources>
diff --git a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java
new file mode 100644
index 0000000..b8b2de5
--- /dev/null
+++ b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/WindowInsetsActivity.java
@@ -0,0 +1,159 @@
+/*
+ * 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 com.google.android.test.windowinsetstests;
+
+import android.animation.ObjectAnimator;
+import android.animation.TypeEvaluator;
+import android.animation.ValueAnimator;
+import android.app.Activity;
+import android.graphics.Insets;
+import android.os.Bundle;
+import android.util.Property;
+import android.view.View;
+import android.view.WindowInsets;
+import android.view.WindowInsets.Type;
+import android.view.WindowInsetsAnimationCallback;
+import android.view.WindowInsetsAnimationCallback.InsetsAnimation;
+import android.view.WindowInsetsAnimationControlListener;
+import android.view.WindowInsetsAnimationController;
+
+import com.google.android.test.windowinsetstests.R;
+
+public class WindowInsetsActivity extends Activity {
+
+ private View mRoot;
+ private View mButton;
+
+ private static class InsetsProperty extends Property<WindowInsetsAnimationController, Insets> {
+
+ private final View mViewToAnimate;
+ private final Insets mShowingInsets;
+
+ public InsetsProperty(View viewToAnimate, Insets showingInsets) {
+ super(Insets.class, "Insets");
+ mViewToAnimate = viewToAnimate;
+ mShowingInsets = showingInsets;
+ }
+
+ @Override
+ public Insets get(WindowInsetsAnimationController object) {
+ return object.getCurrentInsets();
+ }
+
+ @Override
+ public void set(WindowInsetsAnimationController object, Insets value) {
+ object.setInsetsAndAlpha(value, 1.0f, 0.5f);
+ if (mShowingInsets.bottom != 0) {
+ mViewToAnimate.setTranslationY(mShowingInsets.bottom - value.bottom);
+ } else if (mShowingInsets.right != 0) {
+ mViewToAnimate.setTranslationX(mShowingInsets.right - value.right);
+ } else if (mShowingInsets.left != 0) {
+ mViewToAnimate.setTranslationX(value.left - mShowingInsets.left);
+ }
+ }
+ };
+
+ float showY;
+ float hideY;
+ InsetsAnimation imeAnim;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.window_inset_activity);
+ mRoot = findViewById(R.id.root);
+ mButton = findViewById(R.id.button);
+ mButton.setOnClickListener(v -> {
+ if (!v.getRootWindowInsets().isVisible(Type.ime())) {
+ v.getWindowInsetsController().show(Type.ime());
+ } else {
+ v.getWindowInsetsController().hide(Type.ime());
+ }
+ });
+ mRoot.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
+ if (imeAnim == null) {
+ return;
+ }
+ if (mRoot.getRootWindowInsets().isVisible(Type.ime())) {
+ showY = mButton.getTop();
+ } else {
+ hideY = mButton.getTop();
+ }
+ });
+ mRoot.setWindowInsetsAnimationCallback(new WindowInsetsAnimationCallback() {
+
+ @Override
+ public void onPrepare(InsetsAnimation animation) {
+ if ((animation.getTypeMask() & Type.ime()) != 0) {
+ imeAnim = animation;
+ }
+ if (mRoot.getRootWindowInsets().isVisible(Type.ime())) {
+ showY = mButton.getTop();
+ } else {
+ hideY = mButton.getTop();
+ }
+ }
+
+ @Override
+ public WindowInsets onProgress(WindowInsets insets) {
+ mButton.setY(hideY + (showY - hideY) * imeAnim.getInterpolatedFraction());
+ return insets;
+ }
+
+ @Override
+ public AnimationBounds onStart(InsetsAnimation animation,
+ AnimationBounds bounds) {
+ return bounds;
+ }
+
+ @Override
+ public void onFinish(InsetsAnimation animation) {
+ imeAnim = null;
+ }
+ });
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ TypeEvaluator<Insets> evaluator = (fraction, startValue, endValue) -> Insets.of(
+ (int)(startValue.left + fraction * (endValue.left - startValue.left)),
+ (int)(startValue.top + fraction * (endValue.top - startValue.top)),
+ (int)(startValue.right + fraction * (endValue.right - startValue.right)),
+ (int)(startValue.bottom + fraction * (endValue.bottom - startValue.bottom)));
+
+ WindowInsetsAnimationControlListener listener = new WindowInsetsAnimationControlListener() {
+ @Override
+ public void onReady(WindowInsetsAnimationController controller, int types) {
+ ObjectAnimator animator = ObjectAnimator.ofObject(controller,
+ new InsetsProperty(findViewById(R.id.button),
+ controller.getShownStateInsets()),
+ evaluator, controller.getShownStateInsets(),
+ controller.getHiddenStateInsets());
+ animator.setRepeatCount(ValueAnimator.INFINITE);
+ animator.setRepeatMode(ValueAnimator.REVERSE);
+ animator.start();
+ }
+
+ @Override
+ public void onCancelled() {
+
+ }
+ };
+ }
+}
diff --git a/tests/net/TEST_MAPPING b/tests/net/TEST_MAPPING
new file mode 100644
index 0000000..a7853b6
--- /dev/null
+++ b/tests/net/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "postsubmit": [
+ {
+ "name": "FrameworksNetIntegrationTests"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/tests/net/common/java/android/net/CaptivePortalTest.java b/tests/net/common/java/android/net/CaptivePortalTest.java
index eed7159f..ca4ba63 100644
--- a/tests/net/common/java/android/net/CaptivePortalTest.java
+++ b/tests/net/common/java/android/net/CaptivePortalTest.java
@@ -44,6 +44,11 @@
}
@Override
+ public void appRequest(final int request) throws RemoteException {
+ mCode = request;
+ }
+
+ @Override
public void logEvent(int eventId, String packageName) throws RemoteException {
mCode = eventId;
mPackageName = packageName;
@@ -80,6 +85,12 @@
}
@Test
+ public void testReevaluateNetwork() {
+ final MyCaptivePortalImpl result = runCaptivePortalTest(c -> c.reevaluateNetwork());
+ assertEquals(result.mCode, CaptivePortal.APP_REQUEST_REEVALUATION_REQUIRED);
+ }
+
+ @Test
public void testLogEvent() {
final MyCaptivePortalImpl result = runCaptivePortalTest(c -> c.logEvent(
MetricsEvent.ACTION_CAPTIVE_PORTAL_LOGIN_ACTIVITY,
diff --git a/tests/net/common/java/android/net/LinkPropertiesTest.java b/tests/net/common/java/android/net/LinkPropertiesTest.java
index a7eef05..6005cc3 100644
--- a/tests/net/common/java/android/net/LinkPropertiesTest.java
+++ b/tests/net/common/java/android/net/LinkPropertiesTest.java
@@ -27,8 +27,8 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
-import android.net.LinkProperties.CompareResult;
import android.net.LinkProperties.ProvisioningChange;
+import android.net.util.LinkPropertiesUtils.CompareResult;
import android.system.OsConstants;
import android.util.ArraySet;
@@ -38,6 +38,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.net.Inet4Address;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
@@ -65,6 +66,7 @@
private static final InetAddress GATEWAY62 = address("fe80::6:22%lo");
private static final InetAddress TESTIPV4ADDR = address("192.168.47.42");
private static final InetAddress TESTIPV6ADDR = address("fe80::7:33%43");
+ private static final Inet4Address DHCPSERVER = (Inet4Address) address("192.0.2.1");
private static final String NAME = "qmi0";
private static final String DOMAINS = "google.com";
private static final String PRIV_DNS_SERVER_NAME = "private.dns.com";
@@ -93,6 +95,7 @@
assertNull(lp.getHttpProxy());
assertNull(lp.getTcpBufferSizes());
assertNull(lp.getNat64Prefix());
+ assertNull(lp.getDhcpServerAddress());
assertFalse(lp.isProvisioned());
assertFalse(lp.isIpv4Provisioned());
assertFalse(lp.isIpv6Provisioned());
@@ -119,6 +122,7 @@
lp.setMtu(MTU);
lp.setTcpBufferSizes(TCP_BUFFER_SIZES);
lp.setNat64Prefix(new IpPrefix("2001:db8:0:64::/96"));
+ lp.setDhcpServerAddress(DHCPSERVER);
lp.setWakeOnLanSupported(true);
return lp;
}
@@ -960,11 +964,13 @@
source.setWakeOnLanSupported(true);
+ source.setDhcpServerAddress((Inet4Address) GATEWAY1);
+
final LinkProperties stacked = new LinkProperties();
stacked.setInterfaceName("test-stacked");
source.addStackedLink(stacked);
- assertParcelSane(source, 15 /* fieldCount */);
+ assertParcelSane(source, 16 /* fieldCount */);
}
@Test
@@ -1091,6 +1097,15 @@
}
@Test
+ public void testDhcpServerAddress() {
+ final LinkProperties lp = makeTestObject();
+ assertEquals(DHCPSERVER, lp.getDhcpServerAddress());
+
+ lp.clear();
+ assertNull(lp.getDhcpServerAddress());
+ }
+
+ @Test
public void testWakeOnLanSupported() {
final LinkProperties lp = makeTestObject();
assertTrue(lp.isWakeOnLanSupported());
diff --git a/tests/net/integration/Android.bp b/tests/net/integration/Android.bp
index 7d9b7b7..874bd4b 100644
--- a/tests/net/integration/Android.bp
+++ b/tests/net/integration/Android.bp
@@ -36,6 +36,7 @@
"services.net",
"testables",
],
+ test_suites: ["device-tests"],
use_embedded_native_libs: true,
jni_libs: [
// For mockito extended
diff --git a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
index 1e8d83c..11d5b25 100644
--- a/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
+++ b/tests/net/integration/util/com/android/server/NetworkAgentWrapper.java
@@ -35,10 +35,10 @@
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkAgent;
+import android.net.NetworkAgentConfig;
import android.net.NetworkCapabilities;
-import android.net.NetworkFactory;
import android.net.NetworkInfo;
-import android.net.NetworkMisc;
+import android.net.NetworkProvider;
import android.net.NetworkSpecifier;
import android.net.SocketKeepalive;
import android.net.UidRange;
@@ -114,7 +114,7 @@
public InstrumentedNetworkAgent(NetworkAgentWrapper wrapper, LinkProperties lp) {
super(wrapper.mHandlerThread.getLooper(), wrapper.mContext, wrapper.mLogTag,
wrapper.mNetworkInfo, wrapper.mNetworkCapabilities, lp, wrapper.mScore,
- new NetworkMisc(), NetworkFactory.SerialNumber.NONE);
+ new NetworkAgentConfig(), NetworkProvider.ID_NONE);
mWrapper = wrapper;
}
@@ -222,7 +222,7 @@
@Override
public Network getNetwork() {
- return new Network(mNetworkAgent.netId);
+ return mNetworkAgent.network;
}
public void expectPreventReconnectReceived(long timeoutMs) {
diff --git a/tests/net/java/android/net/MacAddressTest.java b/tests/net/java/android/net/MacAddressTest.java
index daf187d..91c9a2a 100644
--- a/tests/net/java/android/net/MacAddressTest.java
+++ b/tests/net/java/android/net/MacAddressTest.java
@@ -22,6 +22,8 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import android.net.util.MacAddressUtils;
+
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
@@ -122,11 +124,11 @@
for (MacAddress mac : multicastAddresses) {
String msg = mac.toString() + " expected to be a multicast address";
- assertTrue(msg, mac.isMulticastAddress());
+ assertTrue(msg, MacAddressUtils.isMulticastAddress(mac));
}
for (MacAddress mac : unicastAddresses) {
String msg = mac.toString() + " expected not to be a multicast address";
- assertFalse(msg, mac.isMulticastAddress());
+ assertFalse(msg, MacAddressUtils.isMulticastAddress(mac));
}
}
@@ -156,7 +158,7 @@
public void testMacAddressConversions() {
final int iterations = 10000;
for (int i = 0; i < iterations; i++) {
- MacAddress mac = MacAddress.createRandomUnicastAddress();
+ MacAddress mac = MacAddressUtils.createRandomUnicastAddress();
String stringRepr = mac.toString();
byte[] bytesRepr = mac.toByteArray();
@@ -188,7 +190,7 @@
final String expectedLocalOui = "26:5f:78";
final MacAddress base = MacAddress.fromString(anotherOui + ":0:0:0");
for (int i = 0; i < iterations; i++) {
- MacAddress mac = MacAddress.createRandomUnicastAddress(base, r);
+ MacAddress mac = MacAddressUtils.createRandomUnicastAddress(base, r);
String stringRepr = mac.toString();
assertTrue(stringRepr + " expected to be a locally assigned address",
@@ -199,7 +201,7 @@
}
for (int i = 0; i < iterations; i++) {
- MacAddress mac = MacAddress.createRandomUnicastAddress();
+ MacAddress mac = MacAddressUtils.createRandomUnicastAddress();
String stringRepr = mac.toString();
assertTrue(stringRepr + " expected to be a locally assigned address",
diff --git a/tests/net/java/android/net/NetworkStatsTest.java b/tests/net/java/android/net/NetworkStatsTest.java
index c16a0f4..33d77d2 100644
--- a/tests/net/java/android/net/NetworkStatsTest.java
+++ b/tests/net/java/android/net/NetworkStatsTest.java
@@ -64,15 +64,15 @@
@Test
public void testFindIndex() throws Exception {
final NetworkStats stats = new NetworkStats(TEST_START, 5)
- .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_YES, 1024L, 8L, 0L, 0L, 10)
- .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_NO, 0L, 0L, 1024L, 8L, 11)
- .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+ .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
DEFAULT_NETWORK_YES, 0L, 0L, 1024L, 8L, 11)
- .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ .addEntry(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_NO, 1024L, 8L, 1024L, 8L, 12)
- .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES,
+ .addEntry(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES,
DEFAULT_NETWORK_YES, 1024L, 8L, 1024L, 8L, 12);
assertEquals(4, stats.findIndex(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_YES,
@@ -94,21 +94,21 @@
@Test
public void testFindIndexHinted() {
final NetworkStats stats = new NetworkStats(TEST_START, 3)
- .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_YES, 1024L, 8L, 0L, 0L, 10)
- .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_NO, 0L, 0L, 1024L, 8L, 11)
- .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ .addEntry(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_YES, 1024L, 8L, 1024L, 8L, 12)
- .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+ .addEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_NO, 1024L, 8L, 0L, 0L, 10)
- .addValues(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO,
+ .addEntry(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_YES, 0L, 0L, 1024L, 8L, 11)
- .addValues(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO,
+ .addEntry(TEST_IFACE2, 101, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO,
DEFAULT_NETWORK_NO, 0L, 0L, 1024L, 8L, 11)
- .addValues(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ .addEntry(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_YES, 1024L, 8L, 1024L, 8L, 12)
- .addValues(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES,
+ .addEntry(TEST_IFACE2, 102, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES,
DEFAULT_NETWORK_NO, 1024L, 8L, 1024L, 8L, 12);
// verify that we correctly find across regardless of hinting
@@ -143,27 +143,27 @@
assertEquals(0, stats.size());
assertEquals(4, stats.internalSize());
- stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ stats.addEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_YES, 1L, 1L, 2L, 2L, 3);
- stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ stats.addEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_NO, 2L, 2L, 2L, 2L, 4);
- stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
+ stats.addEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
DEFAULT_NETWORK_YES, 3L, 3L, 2L, 2L, 5);
- stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES,
+ stats.addEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES,
DEFAULT_NETWORK_NO, 3L, 3L, 2L, 2L, 5);
assertEquals(4, stats.size());
assertEquals(4, stats.internalSize());
- stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ stats.addEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_NO, 4L, 40L, 4L, 40L, 7);
- stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ stats.addEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_YES, 5L, 50L, 4L, 40L, 8);
- stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ stats.addEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_NO, 6L, 60L, 5L, 50L, 10);
- stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
+ stats.addEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
DEFAULT_NETWORK_YES, 7L, 70L, 5L, 50L, 11);
- stats.addValues(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES,
+ stats.addEntry(TEST_IFACE, TEST_UID, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_YES,
DEFAULT_NETWORK_NO, 7L, 70L, 5L, 50L, 11);
assertEquals(9, stats.size());
@@ -193,8 +193,8 @@
public void testCombineExisting() throws Exception {
final NetworkStats stats = new NetworkStats(TEST_START, 10);
- stats.addValues(TEST_IFACE, 1001, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 10);
- stats.addValues(TEST_IFACE, 1001, SET_DEFAULT, 0xff, 128L, 1L, 128L, 1L, 2);
+ stats.addEntry(TEST_IFACE, 1001, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 10);
+ stats.addEntry(TEST_IFACE, 1001, SET_DEFAULT, 0xff, 128L, 1L, 128L, 1L, 2);
stats.combineValues(TEST_IFACE, 1001, SET_DEFAULT, TAG_NONE, -128L, -1L,
-128L, -1L, -1);
@@ -215,12 +215,12 @@
@Test
public void testSubtractIdenticalData() throws Exception {
final NetworkStats before = new NetworkStats(TEST_START, 2)
- .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
- .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12);
+ .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
+ .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12);
final NetworkStats after = new NetworkStats(TEST_START, 2)
- .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
- .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12);
+ .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
+ .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12);
final NetworkStats result = after.subtract(before);
@@ -234,12 +234,12 @@
@Test
public void testSubtractIdenticalRows() throws Exception {
final NetworkStats before = new NetworkStats(TEST_START, 2)
- .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
- .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12);
+ .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
+ .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12);
final NetworkStats after = new NetworkStats(TEST_START, 2)
- .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1025L, 9L, 2L, 1L, 15)
- .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 3L, 1L, 1028L, 9L, 20);
+ .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1025L, 9L, 2L, 1L, 15)
+ .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 3L, 1L, 1028L, 9L, 20);
final NetworkStats result = after.subtract(before);
@@ -253,13 +253,13 @@
@Test
public void testSubtractNewRows() throws Exception {
final NetworkStats before = new NetworkStats(TEST_START, 2)
- .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
- .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12);
+ .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
+ .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12);
final NetworkStats after = new NetworkStats(TEST_START, 3)
- .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
- .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12)
- .addValues(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 20);
+ .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 1024L, 8L, 0L, 0L, 11)
+ .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 0L, 0L, 1024L, 8L, 12)
+ .addEntry(TEST_IFACE, 102, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 20);
final NetworkStats result = after.subtract(before);
@@ -275,11 +275,11 @@
@Test
public void testSubtractMissingRows() throws Exception {
final NetworkStats before = new NetworkStats(TEST_START, 2)
- .addValues(TEST_IFACE, UID_ALL, SET_DEFAULT, TAG_NONE, 1024L, 0L, 0L, 0L, 0)
- .addValues(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 2048L, 0L, 0L, 0L, 0);
+ .addEntry(TEST_IFACE, UID_ALL, SET_DEFAULT, TAG_NONE, 1024L, 0L, 0L, 0L, 0)
+ .addEntry(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 2048L, 0L, 0L, 0L, 0);
final NetworkStats after = new NetworkStats(TEST_START, 1)
- .addValues(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 2049L, 2L, 3L, 4L, 0);
+ .addEntry(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 2049L, 2L, 3L, 4L, 0);
final NetworkStats result = after.subtract(before);
@@ -293,40 +293,40 @@
@Test
public void testTotalBytes() throws Exception {
final NetworkStats iface = new NetworkStats(TEST_START, 2)
- .addValues(TEST_IFACE, UID_ALL, SET_DEFAULT, TAG_NONE, 128L, 0L, 0L, 0L, 0L)
- .addValues(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 256L, 0L, 0L, 0L, 0L);
+ .addEntry(TEST_IFACE, UID_ALL, SET_DEFAULT, TAG_NONE, 128L, 0L, 0L, 0L, 0L)
+ .addEntry(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, 256L, 0L, 0L, 0L, 0L);
assertEquals(384L, iface.getTotalBytes());
final NetworkStats uidSet = new NetworkStats(TEST_START, 3)
- .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L)
- .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L)
- .addValues(TEST_IFACE, 101, SET_FOREGROUND, TAG_NONE, 32L, 0L, 0L, 0L, 0L);
+ .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L)
+ .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 32L, 0L, 0L, 0L, 0L)
+ .addEntry(TEST_IFACE, 101, SET_FOREGROUND, TAG_NONE, 32L, 0L, 0L, 0L, 0L);
assertEquals(96L, uidSet.getTotalBytes());
final NetworkStats uidTag = new NetworkStats(TEST_START, 6)
- .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
- .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
- .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 8L, 0L, 0L, 0L, 0L)
- .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
- .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
- .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 8L, 0L, 0L, 0L, 0L);
+ .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
+ .addEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
+ .addEntry(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 8L, 0L, 0L, 0L, 0L)
+ .addEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
+ .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 16L, 0L, 0L, 0L, 0L)
+ .addEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 8L, 0L, 0L, 0L, 0L);
assertEquals(64L, uidTag.getTotalBytes());
final NetworkStats uidMetered = new NetworkStats(TEST_START, 3)
- .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L)
- .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+ .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
DEFAULT_NETWORK_NO, 32L, 0L, 0L, 0L, 0L)
- .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+ .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L);
assertEquals(96L, uidMetered.getTotalBytes());
final NetworkStats uidRoaming = new NetworkStats(TEST_START, 3)
- .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L)
- .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+ .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
DEFAULT_NETWORK_NO, 32L, 0L, 0L, 0L, 0L)
- .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
+ .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L);
assertEquals(96L, uidRoaming.getTotalBytes());
}
@@ -343,11 +343,11 @@
@Test
public void testGroupedByIfaceAll() throws Exception {
final NetworkStats uidStats = new NetworkStats(TEST_START, 3)
- .addValues(IFACE_ALL, 100, SET_ALL, TAG_NONE, METERED_NO, ROAMING_NO,
+ .addEntry(IFACE_ALL, 100, SET_ALL, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_YES, 128L, 8L, 0L, 2L, 20L)
- .addValues(IFACE_ALL, 101, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_NO,
+ .addEntry(IFACE_ALL, 101, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_NO,
DEFAULT_NETWORK_NO, 128L, 8L, 0L, 2L, 20L)
- .addValues(IFACE_ALL, 101, SET_ALL, TAG_NONE, METERED_NO, ROAMING_YES,
+ .addEntry(IFACE_ALL, 101, SET_ALL, TAG_NONE, METERED_NO, ROAMING_YES,
DEFAULT_NETWORK_YES, 128L, 8L, 0L, 2L, 20L);
final NetworkStats grouped = uidStats.groupedByIface();
@@ -361,19 +361,19 @@
@Test
public void testGroupedByIface() throws Exception {
final NetworkStats uidStats = new NetworkStats(TEST_START, 7)
- .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_YES, 128L, 8L, 0L, 2L, 20L)
- .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ .addEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_NO, 512L, 32L, 0L, 0L, 0L)
- .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO,
+ .addEntry(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_YES, 64L, 4L, 0L, 0L, 0L)
- .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+ .addEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_NO, 512L, 32L, 0L, 0L, 0L)
- .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_YES, 128L, 8L, 0L, 0L, 0L)
- .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO,
+ .addEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO,
DEFAULT_NETWORK_NO, 128L, 8L, 0L, 0L, 0L)
- .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
+ .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
DEFAULT_NETWORK_YES, 128L, 8L, 0L, 0L, 0L);
final NetworkStats grouped = uidStats.groupedByIface();
@@ -390,19 +390,19 @@
@Test
public void testAddAllValues() {
final NetworkStats first = new NetworkStats(TEST_START, 5)
- .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+ .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L)
- .addValues(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+ .addEntry(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_NO, 32L, 0L, 0L, 0L, 0L)
- .addValues(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_YES,
+ .addEntry(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_YES,
DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L);
final NetworkStats second = new NetworkStats(TEST_START, 2)
- .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+ .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L)
- .addValues(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ .addEntry(TEST_IFACE2, UID_ALL, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_NO, 32L, 0L, 0L, 0L, 0L)
- .addValues(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_YES,
+ .addEntry(TEST_IFACE, 100, SET_FOREGROUND, TAG_NONE, METERED_YES, ROAMING_YES,
DEFAULT_NETWORK_YES, 32L, 0L, 0L, 0L, 0L);
first.combineAllValues(second);
@@ -421,19 +421,19 @@
@Test
public void testGetTotal() {
final NetworkStats stats = new NetworkStats(TEST_START, 7)
- .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_YES, 128L, 8L, 0L, 2L, 20L)
- .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ .addEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_NO, 512L, 32L, 0L, 0L, 0L)
- .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO,
+ .addEntry(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_YES, 64L, 4L, 0L, 0L, 0L)
- .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+ .addEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_NO, 512L,32L, 0L, 0L, 0L)
- .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+ .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
DEFAULT_NETWORK_YES, 128L, 8L, 0L, 0L, 0L)
- .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO,
+ .addEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_NO, 128L, 8L, 0L, 0L, 0L)
- .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
+ .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_YES,
DEFAULT_NETWORK_NO, 128L, 8L, 0L, 0L, 0L);
assertValues(stats.getTotal(null), 1408L, 88L, 0L, 2L, 20L);
@@ -459,7 +459,7 @@
assertEquals(0, after.size());
// Test 1 item stats.
- before.addValues(TEST_IFACE, 99, SET_DEFAULT, TAG_NONE, 1L, 128L, 0L, 2L, 20L);
+ before.addEntry(TEST_IFACE, 99, SET_DEFAULT, TAG_NONE, 1L, 128L, 0L, 2L, 20L);
after = before.clone();
after.removeUids(new int[0]);
assertEquals(1, after.size());
@@ -469,12 +469,12 @@
assertEquals(0, after.size());
// Append remaining test items.
- before.addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 2L, 64L, 0L, 2L, 20L)
- .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 4L, 32L, 0L, 0L, 0L)
- .addValues(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 8L, 16L, 0L, 0L, 0L)
- .addValues(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 16L, 8L, 0L, 0L, 0L)
- .addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 32L, 4L, 0L, 0L, 0L)
- .addValues(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 64L, 2L, 0L, 0L, 0L);
+ before.addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 2L, 64L, 0L, 2L, 20L)
+ .addEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 4L, 32L, 0L, 0L, 0L)
+ .addEntry(TEST_IFACE2, 100, SET_DEFAULT, 0xF00D, 8L, 16L, 0L, 0L, 0L)
+ .addEntry(TEST_IFACE2, 100, SET_FOREGROUND, TAG_NONE, 16L, 8L, 0L, 0L, 0L)
+ .addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 32L, 4L, 0L, 0L, 0L)
+ .addEntry(TEST_IFACE, 101, SET_DEFAULT, 0xF00D, 64L, 2L, 0L, 0L, 0L);
assertEquals(7, before.size());
// Test remove with empty uid list.
@@ -505,12 +505,12 @@
@Test
public void testClone() throws Exception {
final NetworkStats original = new NetworkStats(TEST_START, 5)
- .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L)
- .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 512L, 32L, 0L, 0L, 0L);
+ .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L)
+ .addEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 512L, 32L, 0L, 0L, 0L);
// make clone and mutate original
final NetworkStats clone = original.clone();
- original.addValues(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 0L, 0L);
+ original.addEntry(TEST_IFACE, 101, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 0L, 0L);
assertEquals(3, original.size());
assertEquals(2, clone.size());
@@ -523,8 +523,8 @@
public void testAddWhenEmpty() throws Exception {
final NetworkStats red = new NetworkStats(TEST_START, -1);
final NetworkStats blue = new NetworkStats(TEST_START, 5)
- .addValues(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L)
- .addValues(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 512L, 32L, 0L, 0L, 0L);
+ .addEntry(TEST_IFACE, 100, SET_DEFAULT, TAG_NONE, 128L, 8L, 0L, 2L, 20L)
+ .addEntry(TEST_IFACE2, 100, SET_DEFAULT, TAG_NONE, 512L, 32L, 0L, 0L, 0L);
// We're mostly checking that we don't crash
red.combineAllValues(blue);
@@ -537,39 +537,39 @@
final String underlyingIface = "wlan0";
final int testTag1 = 8888;
NetworkStats delta = new NetworkStats(TEST_START, 17)
- .addValues(tunIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
- DEFAULT_NETWORK_NO, 39605L, 46L, 12259L, 55L, 0L)
- .addValues(tunIface, 10100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
- DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L)
- .addValues(tunIface, 10120, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
- DEFAULT_NETWORK_NO, 72667L, 197L, 43909L, 241L, 0L)
- .addValues(tunIface, 10120, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
- DEFAULT_NETWORK_NO, 9297L, 17L, 4128L, 21L, 0L)
- // VPN package also uses some traffic through unprotected network.
- .addValues(tunIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
- DEFAULT_NETWORK_NO, 4983L, 10L, 1801L, 12L, 0L)
- .addValues(tunIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
- DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L)
- // Tag entries
- .addValues(tunIface, 10120, SET_DEFAULT, testTag1, METERED_NO, ROAMING_NO,
- DEFAULT_NETWORK_NO, 21691L, 41L, 13820L, 51L, 0L)
- .addValues(tunIface, 10120, SET_FOREGROUND, testTag1, METERED_NO, ROAMING_NO,
- DEFAULT_NETWORK_NO, 1281L, 2L, 665L, 2L, 0L)
- // Irrelevant entries
- .addValues(TEST_IFACE, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
- DEFAULT_NETWORK_NO, 1685L, 5L, 2070L, 6L, 0L)
- // Underlying Iface entries
- .addValues(underlyingIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
- DEFAULT_NETWORK_NO, 5178L, 8L, 2139L, 11L, 0L)
- .addValues(underlyingIface, 10100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
- DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L)
- .addValues(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
- DEFAULT_NETWORK_NO, 149873L, 287L, 59217L /* smaller than sum(tun0) */,
- 299L /* smaller than sum(tun0) */, 0L)
- .addValues(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
- DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L);
+ .addEntry(tunIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 39605L, 46L, 12259L, 55L, 0L)
+ .addEntry(tunIface, 10100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L)
+ .addEntry(tunIface, 10120, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 72667L, 197L, 43909L, 241L, 0L)
+ .addEntry(tunIface, 10120, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 9297L, 17L, 4128L, 21L, 0L)
+ // VPN package also uses some traffic through unprotected network.
+ .addEntry(tunIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 4983L, 10L, 1801L, 12L, 0L)
+ .addEntry(tunIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L)
+ // Tag entries
+ .addEntry(tunIface, 10120, SET_DEFAULT, testTag1, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 21691L, 41L, 13820L, 51L, 0L)
+ .addEntry(tunIface, 10120, SET_FOREGROUND, testTag1, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 1281L, 2L, 665L, 2L, 0L)
+ // Irrelevant entries
+ .addEntry(TEST_IFACE, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 1685L, 5L, 2070L, 6L, 0L)
+ // Underlying Iface entries
+ .addEntry(underlyingIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 5178L, 8L, 2139L, 11L, 0L)
+ .addEntry(underlyingIface, 10100, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L)
+ .addEntry(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 149873L, 287L, 59217L /* smaller than sum(tun0) */,
+ 299L /* smaller than sum(tun0) */, 0L)
+ .addEntry(underlyingIface, tunUid, SET_FOREGROUND, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 0L, 0L, 0L, 0L, 0L);
- delta.migrateTun(tunUid, tunIface, new String[] {underlyingIface});
+ delta.migrateTun(tunUid, tunIface, new String[]{underlyingIface});
assertEquals(20, delta.size());
// tunIface and TEST_IFACE entries are not changed.
@@ -634,21 +634,21 @@
final String tunIface = "tun0";
final String underlyingIface = "wlan0";
NetworkStats delta = new NetworkStats(TEST_START, 9)
- // 2 different apps sent/receive data via tun0.
- .addValues(tunIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
- DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L)
- .addValues(tunIface, 20100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
- DEFAULT_NETWORK_NO, 500L, 2L, 200L, 5L, 0L)
- // VPN package resends data through the tunnel (with exaggerated overhead)
- .addValues(tunIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
- DEFAULT_NETWORK_NO, 240000, 100L, 120000L, 60L, 0L)
- // 1 app already has some traffic on the underlying interface, the other doesn't yet
- .addValues(underlyingIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
- DEFAULT_NETWORK_NO, 1000L, 10L, 2000L, 20L, 0L)
- // Traffic through the underlying interface via the vpn app.
- // This test should redistribute this data correctly.
- .addValues(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
- DEFAULT_NETWORK_NO, 75500L, 37L, 130000L, 70L, 0L);
+ // 2 different apps sent/receive data via tun0.
+ .addEntry(tunIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L)
+ .addEntry(tunIface, 20100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 500L, 2L, 200L, 5L, 0L)
+ // VPN package resends data through the tunnel (with exaggerated overhead)
+ .addEntry(tunIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 240000, 100L, 120000L, 60L, 0L)
+ // 1 app already has some traffic on the underlying interface, the other doesn't yet
+ .addEntry(underlyingIface, 10100, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 1000L, 10L, 2000L, 20L, 0L)
+ // Traffic through the underlying interface via the vpn app.
+ // This test should redistribute this data correctly.
+ .addEntry(underlyingIface, tunUid, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ DEFAULT_NETWORK_NO, 75500L, 37L, 130000L, 70L, 0L);
delta.migrateTun(tunUid, tunIface, new String[]{underlyingIface});
assertEquals(9, delta.size());
@@ -697,9 +697,9 @@
DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
NetworkStats stats = new NetworkStats(TEST_START, 3)
- .addValues(entry1)
- .addValues(entry2)
- .addValues(entry3);
+ .addEntry(entry1)
+ .addEntry(entry2)
+ .addEntry(entry3);
stats.filter(UID_ALL, INTERFACES_ALL, TAG_ALL);
assertEquals(3, stats.size());
@@ -724,9 +724,9 @@
DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
NetworkStats stats = new NetworkStats(TEST_START, 3)
- .addValues(entry1)
- .addValues(entry2)
- .addValues(entry3);
+ .addEntry(entry1)
+ .addEntry(entry2)
+ .addEntry(entry3);
stats.filter(testUid, INTERFACES_ALL, TAG_ALL);
assertEquals(2, stats.size());
@@ -755,10 +755,10 @@
DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
NetworkStats stats = new NetworkStats(TEST_START, 4)
- .addValues(entry1)
- .addValues(entry2)
- .addValues(entry3)
- .addValues(entry4);
+ .addEntry(entry1)
+ .addEntry(entry2)
+ .addEntry(entry3)
+ .addEntry(entry4);
stats.filter(UID_ALL, new String[] { testIf1, testIf2 }, TAG_ALL);
assertEquals(3, stats.size());
@@ -778,8 +778,8 @@
DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
NetworkStats stats = new NetworkStats(TEST_START, 3)
- .addValues(entry1)
- .addValues(entry2);
+ .addEntry(entry1)
+ .addEntry(entry2);
stats.filter(UID_ALL, new String[] { }, TAG_ALL);
assertEquals(0, stats.size());
@@ -802,9 +802,9 @@
DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
NetworkStats stats = new NetworkStats(TEST_START, 3)
- .addValues(entry1)
- .addValues(entry2)
- .addValues(entry3);
+ .addEntry(entry1)
+ .addEntry(entry2)
+ .addEntry(entry3);
stats.filter(UID_ALL, INTERFACES_ALL, testTag);
assertEquals(2, stats.size());
@@ -831,10 +831,10 @@
DEFAULT_NETWORK_NO, 50000L, 25L, 100000L, 50L, 0L);
NetworkStats stats = new NetworkStats(TEST_START, 4)
- .addValues(entry1)
- .addValues(entry2)
- .addValues(entry3)
- .addValues(entry4);
+ .addEntry(entry1)
+ .addEntry(entry2)
+ .addEntry(entry3)
+ .addEntry(entry4);
stats.filterDebugEntries();
@@ -891,14 +891,14 @@
0 /* operations */);
final NetworkStats statsXt = new NetworkStats(TEST_START, 3)
- .addValues(appEntry)
- .addValues(xtRootUidEntry)
- .addValues(otherEntry);
+ .addEntry(appEntry)
+ .addEntry(xtRootUidEntry)
+ .addEntry(otherEntry);
final NetworkStats statsEbpf = new NetworkStats(TEST_START, 3)
- .addValues(appEntry)
- .addValues(ebpfRootUidEntry)
- .addValues(otherEntry);
+ .addEntry(appEntry)
+ .addEntry(ebpfRootUidEntry)
+ .addEntry(otherEntry);
statsXt.apply464xlatAdjustments(stackedIface, false);
statsEbpf.apply464xlatAdjustments(stackedIface, true);
@@ -945,8 +945,8 @@
0 /* operations */);
NetworkStats stats = new NetworkStats(TEST_START, 2)
- .addValues(firstEntry)
- .addValues(secondEntry);
+ .addEntry(firstEntry)
+ .addEntry(secondEntry);
// Empty map: no adjustment
stats.apply464xlatAdjustments(new ArrayMap<>(), false);
diff --git a/tests/net/java/android/net/TelephonyNetworkSpecifierTest.java b/tests/net/java/android/net/TelephonyNetworkSpecifierTest.java
new file mode 100644
index 0000000..47afed4
--- /dev/null
+++ b/tests/net/java/android/net/TelephonyNetworkSpecifierTest.java
@@ -0,0 +1,82 @@
+/*
+ * 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.net;
+
+import static com.android.testutils.ParcelUtilsKt.assertParcelSane;
+
+import static org.junit.Assert.assertEquals;
+
+import android.telephony.SubscriptionManager;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+
+/**
+ * Unit test for {@link android.net.TelephonyNetworkSpecifier}.
+ */
+@SmallTest
+public class TelephonyNetworkSpecifierTest {
+ private static final int TEST_SUBID = 5;
+
+ /**
+ * Validate that IllegalArgumentException will be thrown if build TelephonyNetworkSpecifier
+ * without calling {@link TelephonyNetworkSpecifier.Builder#setSubscriptionId(int)}.
+ */
+ @Test
+ public void testBuilderBuildWithDefault() {
+ try {
+ new TelephonyNetworkSpecifier.Builder().build();
+ } catch (IllegalArgumentException iae) {
+ // expected, test pass
+ }
+ }
+
+ /**
+ * Validate that no exception will be thrown even if pass invalid subscription id to
+ * {@link TelephonyNetworkSpecifier.Builder#setSubscriptionId(int)}.
+ */
+ @Test
+ public void testBuilderBuildWithInvalidSubId() {
+ TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier.Builder()
+ .setSubscriptionId(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+ .build();
+ assertEquals(specifier.getSubscriptionId(), SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ }
+
+ /**
+ * Validate the correctness of TelephonyNetworkSpecifier when provide valid subId.
+ */
+ @Test
+ public void testBuilderBuildWithValidSubId() {
+ final TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier.Builder()
+ .setSubscriptionId(TEST_SUBID)
+ .build();
+ assertEquals(TEST_SUBID, specifier.getSubscriptionId());
+ }
+
+ /**
+ * Validate that parcel marshalling/unmarshalling works.
+ */
+ @Test
+ public void testParcel() {
+ TelephonyNetworkSpecifier specifier = new TelephonyNetworkSpecifier.Builder()
+ .setSubscriptionId(TEST_SUBID)
+ .build();
+ assertParcelSane(specifier, 1 /* fieldCount */);
+ }
+}
diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index b2d363e..1901a1d 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -575,7 +575,7 @@
}
};
- assertEquals(na.netId, nmNetworkCaptor.getValue().netId);
+ assertEquals(na.network.netId, nmNetworkCaptor.getValue().netId);
mNmCallbacks = nmCbCaptor.getValue();
mNmCallbacks.onNetworkMonitorCreated(mNetworkMonitor);
diff --git a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
index 535298f..e863266 100644
--- a/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
+++ b/tests/net/java/com/android/server/connectivity/LingerMonitorTest.java
@@ -36,9 +36,8 @@
import android.net.INetd;
import android.net.Network;
import android.net.NetworkCapabilities;
-import android.net.NetworkFactory;
import android.net.NetworkInfo;
-import android.net.NetworkMisc;
+import android.net.NetworkProvider;
import android.net.NetworkScore;
import android.os.INetworkManagementService;
import android.text.format.DateUtils;
@@ -75,7 +74,6 @@
@Mock INetd mNetd;
@Mock INetworkManagementService mNMS;
@Mock Context mCtx;
- @Mock NetworkMisc mMisc;
@Mock NetworkNotificationManager mNotifier;
@Mock Resources mResources;
@@ -358,8 +356,8 @@
NetworkScore ns = new NetworkScore();
ns.putIntExtension(NetworkScore.LEGACY_SCORE, 50);
NetworkAgentInfo nai = new NetworkAgentInfo(null, null, new Network(netId), info, null,
- caps, ns, mCtx, null, mMisc, mConnService, mNetd, mDnsResolver, mNMS,
- NetworkFactory.SerialNumber.NONE);
+ caps, ns, mCtx, null, null /* config */, mConnService, mNetd, mDnsResolver, mNMS,
+ NetworkProvider.ID_NONE);
nai.everValidated = true;
return nai;
}
diff --git a/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java b/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
index b783467..de1028c 100644
--- a/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
+++ b/tests/net/java/com/android/server/connectivity/MultipathPolicyTrackerTest.java
@@ -51,6 +51,7 @@
import android.net.NetworkPolicyManager;
import android.net.NetworkTemplate;
import android.net.StringNetworkSpecifier;
+import android.net.TelephonyNetworkSpecifier;
import android.os.Handler;
import android.provider.Settings;
import android.telephony.TelephonyManager;
@@ -229,7 +230,7 @@
verify(mCM).registerNetworkCallback(any(), networkCallback.capture(), any());
// Simulate callback after capability changes
- final NetworkCapabilities capabilities = new NetworkCapabilities()
+ NetworkCapabilities capabilities = new NetworkCapabilities()
.addCapability(NET_CAPABILITY_INTERNET)
.addTransportType(TRANSPORT_CELLULAR)
.setNetworkSpecifier(new StringNetworkSpecifier("234"));
@@ -239,6 +240,19 @@
networkCallback.getValue().onCapabilitiesChanged(
TEST_NETWORK,
capabilities);
+
+ // make sure it also works with the new introduced TelephonyNetworkSpecifier
+ capabilities = new NetworkCapabilities()
+ .addCapability(NET_CAPABILITY_INTERNET)
+ .addTransportType(TRANSPORT_CELLULAR)
+ .setNetworkSpecifier(new TelephonyNetworkSpecifier.Builder()
+ .setSubscriptionId(234).build());
+ if (!roaming) {
+ capabilities.addCapability(NET_CAPABILITY_NOT_ROAMING);
+ }
+ networkCallback.getValue().onCapabilitiesChanged(
+ TEST_NETWORK,
+ capabilities);
}
@Test
diff --git a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
index b709af1..9b24887 100644
--- a/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
+++ b/tests/net/java/com/android/server/connectivity/Nat464XlatTest.java
@@ -33,8 +33,8 @@
import android.net.IpPrefix;
import android.net.LinkAddress;
import android.net.LinkProperties;
+import android.net.NetworkAgentConfig;
import android.net.NetworkInfo;
-import android.net.NetworkMisc;
import android.os.Handler;
import android.os.INetworkManagementService;
import android.os.test.TestLooper;
@@ -63,7 +63,6 @@
static final int NETID = 42;
@Mock ConnectivityService mConnectivity;
- @Mock NetworkMisc mMisc;
@Mock IDnsResolver mDnsResolver;
@Mock INetd mNetd;
@Mock INetworkManagementService mNms;
@@ -72,6 +71,7 @@
TestLooper mLooper;
Handler mHandler;
+ NetworkAgentConfig mAgentConfig = new NetworkAgentConfig();
Nat464Xlat makeNat464Xlat() {
return new Nat464Xlat(mNai, mNetd, mDnsResolver, mNms) {
@@ -93,7 +93,7 @@
mNai.networkInfo = new NetworkInfo(null);
mNai.networkInfo.setType(ConnectivityManager.TYPE_WIFI);
when(mNai.connService()).thenReturn(mConnectivity);
- when(mNai.netMisc()).thenReturn(mMisc);
+ when(mNai.netAgentConfig()).thenReturn(mAgentConfig);
when(mNai.handler()).thenReturn(mHandler);
when(mNms.getInterfaceConfig(eq(STACKED_IFACE))).thenReturn(mConfig);
@@ -104,7 +104,7 @@
String msg = String.format("requiresClat expected %b for type=%d state=%s skip=%b "
+ "nat64Prefix=%s addresses=%s", expected, nai.networkInfo.getType(),
nai.networkInfo.getDetailedState(),
- mMisc.skip464xlat, nai.linkProperties.getNat64Prefix(),
+ mAgentConfig.skip464xlat, nai.linkProperties.getNat64Prefix(),
nai.linkProperties.getLinkAddresses());
assertEquals(msg, expected, Nat464Xlat.requiresClat(nai));
}
@@ -113,7 +113,7 @@
String msg = String.format("shouldStartClat expected %b for type=%d state=%s skip=%b "
+ "nat64Prefix=%s addresses=%s", expected, nai.networkInfo.getType(),
nai.networkInfo.getDetailedState(),
- mMisc.skip464xlat, nai.linkProperties.getNat64Prefix(),
+ mAgentConfig.skip464xlat, nai.linkProperties.getNat64Prefix(),
nai.linkProperties.getLinkAddresses());
assertEquals(msg, expected, Nat464Xlat.shouldStartClat(nai));
}
@@ -151,11 +151,11 @@
assertRequiresClat(true, mNai);
assertShouldStartClat(true, mNai);
- mMisc.skip464xlat = true;
+ mAgentConfig.skip464xlat = true;
assertRequiresClat(false, mNai);
assertShouldStartClat(false, mNai);
- mMisc.skip464xlat = false;
+ mAgentConfig.skip464xlat = false;
assertRequiresClat(true, mNai);
assertShouldStartClat(true, mNai);
diff --git a/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java b/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java
index c0f9dc1..f0e5774 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsObserversTest.java
@@ -326,14 +326,14 @@
// Baseline
NetworkStats xtSnapshot = null;
NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_YES, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
mStatsObservers.updateStats(
xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
// Delta
uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_NO, BASE_BYTES + THRESHOLD_BYTES, 2L,
BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
mStatsObservers.updateStats(
@@ -359,14 +359,14 @@
// Baseline
NetworkStats xtSnapshot = null;
NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_NO, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
mStatsObservers.updateStats(
xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
// Delta
uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_NO, BASE_BYTES + THRESHOLD_BYTES, 2L,
BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
mStatsObservers.updateStats(
@@ -391,14 +391,14 @@
// Baseline
NetworkStats xtSnapshot = null;
NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_YES, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
mStatsObservers.updateStats(
xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
// Delta
uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_YES, BASE_BYTES + THRESHOLD_BYTES, 2L,
BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
mStatsObservers.updateStats(
@@ -424,14 +424,14 @@
// Baseline
NetworkStats xtSnapshot = null;
NetworkStats uidSnapshot = new NetworkStats(TEST_START, 2 /* initialSize */)
- .addValues(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, METERED_NO,
+ .addEntry(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, METERED_NO,
ROAMING_NO, DEFAULT_NETWORK_YES, BASE_BYTES, 2L, BASE_BYTES, 2L, 0L);
mStatsObservers.updateStats(
xtSnapshot, uidSnapshot, mActiveIfaces, mActiveUidIfaces, TEST_START);
// Delta
uidSnapshot = new NetworkStats(TEST_START + 2 * MINUTE_IN_MILLIS, 2 /* initialSize */)
- .addValues(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, METERED_NO,
+ .addEntry(TEST_IFACE, UID_ANOTHER_USER, SET_DEFAULT, TAG_NONE, METERED_NO,
ROAMING_NO, DEFAULT_NETWORK_NO, BASE_BYTES + THRESHOLD_BYTES, 2L,
BASE_BYTES + THRESHOLD_BYTES, 2L, 0L);
mStatsObservers.updateStats(
diff --git a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
index 4d42a61..a9e0b9a 100644
--- a/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
+++ b/tests/net/java/com/android/server/net/NetworkStatsServiceTest.java
@@ -80,6 +80,7 @@
import android.net.NetworkStats;
import android.net.NetworkStatsHistory;
import android.net.NetworkTemplate;
+import android.net.netstats.provider.INetworkStatsProviderCallback;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
@@ -102,6 +103,7 @@
import com.android.server.net.NetworkStatsService.NetworkStatsSettings;
import com.android.server.net.NetworkStatsService.NetworkStatsSettings.Config;
import com.android.testutils.HandlerUtilsKt;
+import com.android.testutils.TestableNetworkStatsProvider;
import libcore.io.IoUtils;
@@ -298,11 +300,11 @@
expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
.addIfaceValues(TEST_IFACE, 1024L, 8L, 2048L, 16L));
expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 2)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 256L, 2L, 128L, 1L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 256L, 2L, 128L, 1L, 0L)
- .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 0L));
+ .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
+ .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 256L, 2L, 128L, 1L, 0L)
+ .addEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
+ .addEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 256L, 2L, 128L, 1L, 0L)
+ .addEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 128L, 1L, 128L, 1L, 0L));
mService.setUidForeground(UID_RED, false);
mService.incrementOperationCount(UID_RED, 0xFAAD, 4);
mService.setUidForeground(UID_RED, true);
@@ -407,9 +409,9 @@
expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
.addIfaceValues(TEST_IFACE, 2048L, 16L, 512L, 4L));
expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
- .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L));
+ .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
+ .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
+ .addEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L));
mService.incrementOperationCount(UID_RED, 0xF00D, 10);
forcePollAndWaitForIdle();
@@ -429,9 +431,9 @@
expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
.addIfaceValues(TEST_IFACE, 2048L, 16L, 512L, 4L));
expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
- .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L));
+ .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
+ .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
+ .addEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 512L, 4L, 0L, 0L, 0L));
mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), new VpnInfo[0]);
forcePollAndWaitForIdle();
@@ -443,10 +445,10 @@
expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
.addIfaceValues(TEST_IFACE, 2176L, 17L, 1536L, 12L));
expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
- .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 640L, 5L, 1024L, 8L, 0L)
- .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, 0xFAAD, 128L, 1L, 1024L, 8L, 0L));
+ .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1536L, 12L, 512L, 4L, 0L)
+ .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
+ .addEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 640L, 5L, 1024L, 8L, 0L)
+ .addEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, 0xFAAD, 128L, 1L, 1024L, 8L, 0L));
mService.incrementOperationCount(UID_BLUE, 0xFAAD, 10);
forcePollAndWaitForIdle();
@@ -480,10 +482,10 @@
expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
.addIfaceValues(TEST_IFACE, 4128L, 258L, 544L, 34L));
expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 16L, 1L, 16L, 1L, 0L)
- .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 4096L, 258L, 512L, 32L, 0L)
- .addValues(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L));
+ .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L)
+ .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 16L, 1L, 16L, 1L, 0L)
+ .addEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 4096L, 258L, 512L, 32L, 0L)
+ .addEntry(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L));
mService.incrementOperationCount(UID_RED, 0xFAAD, 10);
forcePollAndWaitForIdle();
@@ -501,10 +503,10 @@
expectNetworkStatsSummary(new NetworkStats(getElapsedRealtime(), 1)
.addIfaceValues(TEST_IFACE, 4128L, 258L, 544L, 34L));
expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 16L, 1L, 16L, 1L, 0L)
- .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 4096L, 258L, 512L, 32L, 0L)
- .addValues(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L));
+ .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L)
+ .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xFAAD, 16L, 1L, 16L, 1L, 0L)
+ .addEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 4096L, 258L, 512L, 32L, 0L)
+ .addEntry(TEST_IFACE, UID_GREEN, SET_DEFAULT, TAG_NONE, 16L, 1L, 16L, 1L, 0L));
final Intent intent = new Intent(ACTION_UID_REMOVED);
intent.putExtra(EXTRA_UID, UID_BLUE);
mServiceContext.sendBroadcast(intent);
@@ -536,8 +538,8 @@
expectDefaultSettings();
expectNetworkStatsSummary(buildEmptyStats());
expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L));
+ .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
+ .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L));
mService.incrementOperationCount(UID_RED, 0xF00D, 5);
forcePollAndWaitForIdle();
@@ -552,8 +554,8 @@
states = new NetworkState[] {buildMobile4gState(TEST_IFACE2)};
expectNetworkStatsSummary(buildEmptyStats());
expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L));
+ .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
+ .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L));
mService.forceUpdateIfaces(NETWORKS_MOBILE, states, getActiveIface(states), new VpnInfo[0]);
forcePollAndWaitForIdle();
@@ -564,10 +566,10 @@
expectDefaultSettings();
expectNetworkStatsSummary(buildEmptyStats());
expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
- .addValues(TEST_IFACE2, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
- .addValues(TEST_IFACE2, UID_RED, SET_DEFAULT, 0xFAAD, 512L, 4L, 256L, 2L, 0L));
+ .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 1024L, 8L, 1024L, 8L, 0L)
+ .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 512L, 4L, 512L, 4L, 0L)
+ .addEntry(TEST_IFACE2, UID_RED, SET_DEFAULT, TAG_NONE, 512L, 4L, 256L, 2L, 0L)
+ .addEntry(TEST_IFACE2, UID_RED, SET_DEFAULT, 0xFAAD, 512L, 4L, 256L, 2L, 0L));
mService.incrementOperationCount(UID_RED, 0xFAAD, 5);
forcePollAndWaitForIdle();
@@ -591,9 +593,9 @@
expectDefaultSettings();
expectNetworkStatsSummary(buildEmptyStats());
expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 0L)
- .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 1024L, 8L, 512L, 4L, 0L));
+ .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L)
+ .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 0L)
+ .addEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 1024L, 8L, 512L, 4L, 0L));
mService.incrementOperationCount(UID_RED, 0xF00D, 1);
forcePollAndWaitForIdle();
@@ -608,9 +610,9 @@
expectDefaultSettings();
expectNetworkStatsSummary(buildEmptyStats());
expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 0L)
- .addValues(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 2048L, 16L, 1024L, 8L, 0L));
+ .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 50L, 5L, 50L, 5L, 0L)
+ .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 10L, 1L, 10L, 1L, 0L)
+ .addEntry(TEST_IFACE, UID_BLUE, SET_DEFAULT, TAG_NONE, 2048L, 16L, 1024L, 8L, 0L));
forcePollAndWaitForIdle();
// first verify entire history present
@@ -654,9 +656,9 @@
expectDefaultSettings();
expectNetworkStatsSummary(buildEmptyStats());
expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 3)
- .addValues(entry1)
- .addValues(entry2)
- .addValues(entry3));
+ .addEntry(entry1)
+ .addEntry(entry2)
+ .addEntry(entry3));
mService.incrementOperationCount(UID_RED, 0xF00D, 1);
NetworkStats stats = mService.getDetailedUidStats(INTERFACES_ALL);
@@ -704,11 +706,11 @@
.thenReturn(augmentedIfaceFilter);
when(mStatsFactory.readNetworkStatsDetail(eq(UID_ALL), any(), eq(TAG_ALL)))
.thenReturn(new NetworkStats(getElapsedRealtime(), 1)
- .addValues(uidStats));
+ .addEntry(uidStats));
when(mNetManager.getNetworkStatsTethering(STATS_PER_UID))
.thenReturn(new NetworkStats(getElapsedRealtime(), 2)
- .addValues(tetheredStats1)
- .addValues(tetheredStats2));
+ .addEntry(tetheredStats1)
+ .addEntry(tetheredStats2));
NetworkStats stats = mService.getDetailedUidStats(ifaceFilter);
@@ -745,8 +747,8 @@
expectDefaultSettings();
expectNetworkStatsSummary(buildEmptyStats());
expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 0L));
+ .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L)
+ .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 0L));
mService.incrementOperationCount(UID_RED, 0xF00D, 1);
forcePollAndWaitForIdle();
@@ -760,10 +762,10 @@
expectDefaultSettings();
expectNetworkStatsSummary(buildEmptyStats());
expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 32L, 2L, 32L, 2L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 1L, 1L, 1L, 1L, 0L));
+ .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L)
+ .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, 64L, 1L, 64L, 1L, 0L)
+ .addEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, TAG_NONE, 32L, 2L, 32L, 2L, 0L)
+ .addEntry(TEST_IFACE, UID_RED, SET_FOREGROUND, 0xFAAD, 1L, 1L, 1L, 1L, 0L));
mService.setUidForeground(UID_RED, true);
mService.incrementOperationCount(UID_RED, 0xFAAD, 1);
@@ -804,9 +806,9 @@
// and DEFAULT_NETWORK_YES, because these three properties aren't tracked at that layer.
// We layer them on top by inspecting the iface properties.
expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
+ .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_YES, 128L, 2L, 128L, 2L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO,
+ .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, METERED_NO, ROAMING_NO,
DEFAULT_NETWORK_YES, 64L, 1L, 64L, 1L, 0L));
mService.incrementOperationCount(UID_RED, 0xF00D, 1);
@@ -843,9 +845,9 @@
// ROAMING_NO, because metered and roaming isn't tracked at that layer. We layer it
// on top by inspecting the iface properties.
expectNetworkStatsUidDetail(new NetworkStats(getElapsedRealtime(), 1)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_ALL, ROAMING_NO,
+ .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, METERED_ALL, ROAMING_NO,
DEFAULT_NETWORK_YES, 128L, 2L, 128L, 2L, 0L)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, METERED_ALL, ROAMING_NO,
+ .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, 0xF00D, METERED_ALL, ROAMING_NO,
DEFAULT_NETWORK_YES, 64L, 1L, 64L, 1L, 0L));
forcePollAndWaitForIdle();
@@ -885,10 +887,10 @@
// Traffic for UID_RED.
final NetworkStats uidStats = new NetworkStats(getElapsedRealtime(), 1)
- .addValues(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L);
+ .addEntry(TEST_IFACE, UID_RED, SET_DEFAULT, TAG_NONE, 128L, 2L, 128L, 2L, 0L);
// All tethering traffic, both hardware and software.
final NetworkStats tetherStats = new NetworkStats(getElapsedRealtime(), 1)
- .addValues(TEST_IFACE, UID_TETHERING, SET_DEFAULT, TAG_NONE, 1920L, 14L, 384L, 2L,
+ .addEntry(TEST_IFACE, UID_TETHERING, SET_DEFAULT, TAG_NONE, 1920L, 14L, 384L, 2L,
0L);
expectNetworkStatsSummary(ifaceStats, tetherStatsHardware);
@@ -1001,6 +1003,88 @@
mService.unregisterUsageRequest(unknownRequest);
}
+ @Test
+ public void testStatsProviderUpdateStats() throws Exception {
+ // Pretend that network comes online.
+ expectDefaultSettings();
+ final NetworkState[] states = new NetworkState[]{buildWifiState(true /* isMetered */)};
+ expectNetworkStatsSummary(buildEmptyStats());
+ expectNetworkStatsUidDetail(buildEmptyStats());
+
+ // Register custom provider and retrieve callback.
+ final TestableNetworkStatsProvider provider = new TestableNetworkStatsProvider();
+ final INetworkStatsProviderCallback cb =
+ mService.registerNetworkStatsProvider("TEST", provider);
+ assertNotNull(cb);
+
+ mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
+
+ // Verifies that one requestStatsUpdate will be called during iface update.
+ provider.expectStatsUpdate(0 /* unused */);
+
+ // Create some initial traffic and report to the service.
+ incrementCurrentTime(HOUR_IN_MILLIS);
+ final NetworkStats expectedStats = new NetworkStats(0L, 1)
+ .addValues(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT,
+ TAG_NONE, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES,
+ 128L, 2L, 128L, 2L, 1L))
+ .addValues(new NetworkStats.Entry(TEST_IFACE, UID_RED, SET_DEFAULT,
+ 0xF00D, METERED_YES, ROAMING_NO, DEFAULT_NETWORK_YES,
+ 64L, 1L, 64L, 1L, 1L));
+ cb.onStatsUpdated(0 /* unused */, expectedStats, expectedStats);
+
+ // Make another empty mutable stats object. This is necessary since the new NetworkStats
+ // object will be used to compare with the old one in NetworkStatsRecoder, two of them
+ // cannot be the same object.
+ expectNetworkStatsUidDetail(buildEmptyStats());
+
+ forcePollAndWaitForIdle();
+
+ // Verifies that one requestStatsUpdate and setAlert will be called during polling.
+ provider.expectStatsUpdate(0 /* unused */);
+ provider.expectSetAlert(MB_IN_BYTES);
+
+ // Verifies that service recorded history, does not verify uid tag part.
+ assertUidTotal(sTemplateWifi, UID_RED, 128L, 2L, 128L, 2L, 1);
+
+ // Verifies that onStatsUpdated updates the stats accordingly.
+ final NetworkStats stats = mSession.getSummaryForAllUid(
+ sTemplateWifi, Long.MIN_VALUE, Long.MAX_VALUE, true);
+ assertEquals(2, stats.size());
+ assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, TAG_NONE, METERED_YES, ROAMING_NO,
+ DEFAULT_NETWORK_YES, 128L, 2L, 128L, 2L, 1L);
+ assertValues(stats, IFACE_ALL, UID_RED, SET_DEFAULT, 0xF00D, METERED_YES, ROAMING_NO,
+ DEFAULT_NETWORK_YES, 64L, 1L, 64L, 1L, 1L);
+
+ // Verifies that unregister the callback will remove the provider from service.
+ cb.unregister();
+ forcePollAndWaitForIdle();
+ provider.assertNoCallback();
+ }
+
+ @Test
+ public void testStatsProviderSetAlert() throws Exception {
+ // Pretend that network comes online.
+ expectDefaultSettings();
+ NetworkState[] states = new NetworkState[]{buildWifiState(true /* isMetered */)};
+ mService.forceUpdateIfaces(NETWORKS_WIFI, states, getActiveIface(states), new VpnInfo[0]);
+
+ // Register custom provider and retrieve callback.
+ final TestableNetworkStatsProvider provider = new TestableNetworkStatsProvider();
+ final INetworkStatsProviderCallback cb =
+ mService.registerNetworkStatsProvider("TEST", provider);
+ assertNotNull(cb);
+
+ // Simulates alert quota of the provider has been reached.
+ cb.onAlertReached();
+ HandlerUtilsKt.waitForIdle(mHandler, WAIT_TIMEOUT);
+
+ // Verifies that polling is triggered by alert reached.
+ provider.expectStatsUpdate(0 /* unused */);
+ // Verifies that global alert will be re-armed.
+ provider.expectSetAlert(MB_IN_BYTES);
+ }
+
private static File getBaseDir(File statsDir) {
File baseDir = new File(statsDir, "netstats");
baseDir.mkdirs();
@@ -1102,6 +1186,7 @@
private void expectSettings(long persistBytes, long bucketDuration, long deleteAge)
throws Exception {
when(mSettings.getPollInterval()).thenReturn(HOUR_IN_MILLIS);
+ when(mSettings.getPollDelay()).thenReturn(0L);
when(mSettings.getSampleEnabled()).thenReturn(true);
final Config config = new Config(bucketDuration, deleteAge, deleteAge);
diff --git a/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java b/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java
index c50229a..d1d6a26 100644
--- a/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java
+++ b/tests/permission/src/com/android/framework/permission/tests/VibratorServicePermissionTest.java
@@ -16,12 +16,12 @@
package com.android.framework.permission.tests;
-import android.media.AudioAttributes;
import android.os.Binder;
import android.os.IVibratorService;
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.test.suitebuilder.annotation.SmallTest;
@@ -52,8 +52,8 @@
try {
final VibrationEffect effect =
VibrationEffect.createOneShot(100, VibrationEffect.DEFAULT_AMPLITUDE);
- final AudioAttributes attrs = new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_ALARM)
+ final VibrationAttributes attrs = new VibrationAttributes.Builder()
+ .setUsage(VibrationAttributes.USAGE_ALARM)
.build();
mVibratorService.vibrate(Process.myUid(), null, effect, attrs,
"testVibrate", new Binder());
diff --git a/tests/testables/src/android/testing/TestableLooper.java b/tests/testables/src/android/testing/TestableLooper.java
index 8d99ac7..8eac3ea 100644
--- a/tests/testables/src/android/testing/TestableLooper.java
+++ b/tests/testables/src/android/testing/TestableLooper.java
@@ -234,6 +234,7 @@
try {
mLooper = setAsMain ? Looper.getMainLooper() : createLooper();
mTestableLooper = new TestableLooper(mLooper, false);
+ mTestableLooper.getLooper().getThread().setName(test.getClass().getName());
} catch (Exception e) {
throw new RuntimeException(e);
}
diff --git a/tests/utils/testutils/Android.bp b/tests/utils/testutils/Android.bp
index f71be7b..a6625ab 100644
--- a/tests/utils/testutils/Android.bp
+++ b/tests/utils/testutils/Android.bp
@@ -22,6 +22,7 @@
static_libs: [
"junit",
"hamcrest-library",
+ "androidx.test.runner",
],
libs: [
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/MessageCapturingHandler.java b/tests/utils/testutils/java/com/android/server/accessibility/test/MessageCapturingHandler.java
similarity index 96%
rename from services/tests/servicestests/src/com/android/server/accessibility/MessageCapturingHandler.java
rename to tests/utils/testutils/java/com/android/server/accessibility/test/MessageCapturingHandler.java
index e2b517f..bce2ab5 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/MessageCapturingHandler.java
+++ b/tests/utils/testutils/java/com/android/server/accessibility/test/MessageCapturingHandler.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.server.accessibility;
+package com.android.server.accessibility.test;
import android.os.Handler;
import android.os.Looper;
@@ -31,7 +31,7 @@
* at their target.
*/
public class MessageCapturingHandler extends Handler {
- List<Pair<Message, Long>> timedMessages = new ArrayList<>();
+ public List<Pair<Message, Long>> timedMessages = new ArrayList<>();
Handler.Callback mCallback;
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index c557656..74e2a098 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -1641,8 +1641,14 @@
ParsedResource* out_resource) {
out_resource->name.type = ResourceType::kStyleable;
- // Declare-styleable is kPrivate by default, because it technically only exists in R.java.
- out_resource->visibility_level = Visibility::Level::kPublic;
+ if (!options_.preserve_visibility_of_styleables) {
+ // This was added in change Idd21b5de4d20be06c6f8c8eb5a22ccd68afc4927 to mimic aapt1, but no one
+ // knows exactly what for.
+ //
+ // FWIW, styleables only appear in generated R classes. For custom views these should always be
+ // package-private (to be used only by the view class); themes are a different story.
+ out_resource->visibility_level = Visibility::Level::kPublic;
+ }
// Declare-styleable only ends up in default config;
if (out_resource->config != ConfigDescription::DefaultConfig()) {
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index 06bb0c9..9d3ecc8 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -46,6 +46,12 @@
*/
bool error_on_positional_arguments = true;
+ /**
+ * If true, apply the same visibility rules for styleables as are used for
+ * all other resources. Otherwise, all styleables will be made public.
+ */
+ bool preserve_visibility_of_styleables = false;
+
// If visibility was forced, we need to use it when creating a new resource and also error if we
// try to parse the <public>, <public-group>, <java-symbol> or <symbol> tags.
Maybe<Visibility::Level> visibility;
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index 4237469..24531bc 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -614,6 +614,32 @@
EXPECT_THAT(styleable->entries[2].name, Eq(make_value(test::ParseNameOrDie("attr/baz"))));
}
+TEST_F(ResourceParserTest, ParseDeclareStyleablePreservingVisibility) {
+ StringInputStream input(R"(
+ <resources>
+ <declare-styleable name="foo">
+ <attr name="myattr" />
+ </declare-styleable>
+ <declare-styleable name="bar">
+ <attr name="myattr" />
+ </declare-styleable>
+ <public type="styleable" name="bar" />
+ </resources>)");
+ ResourceParser parser(context_->GetDiagnostics(), &table_, Source{"test"},
+ ConfigDescription::DefaultConfig(),
+ ResourceParserOptions{.preserve_visibility_of_styleables = true});
+
+ xml::XmlPullParser xml_parser(&input);
+ ASSERT_TRUE(parser.Parse(&xml_parser));
+
+ EXPECT_EQ(
+ table_.FindResource(test::ParseNameOrDie("styleable/foo")).value().entry->visibility.level,
+ Visibility::Level::kUndefined);
+ EXPECT_EQ(
+ table_.FindResource(test::ParseNameOrDie("styleable/bar")).value().entry->visibility.level,
+ Visibility::Level::kPublic);
+}
+
TEST_F(ResourceParserTest, ParsePrivateAttributesDeclareStyleable) {
std::string input = R"(
<declare-styleable xmlns:privAndroid="http://schemas.android.com/apk/prv/res/android"
diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp
index 3623b11..469128b 100644
--- a/tools/aapt2/ResourceUtils.cpp
+++ b/tools/aapt2/ResourceUtils.cpp
@@ -800,7 +800,12 @@
}
// This is a normal reference.
- return util::make_unique<Reference>(data, ref_type);
+ auto reference = util::make_unique<Reference>(data, ref_type);
+ if (res_value.dataType == android::Res_value::TYPE_DYNAMIC_REFERENCE ||
+ res_value.dataType == android::Res_value::TYPE_DYNAMIC_ATTRIBUTE) {
+ reference->is_dynamic = true;
+ }
+ return reference;
} break;
}
diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp
index c016cb4..b08bf9a 100644
--- a/tools/aapt2/ResourceUtils_test.cpp
+++ b/tools/aapt2/ResourceUtils_test.cpp
@@ -109,6 +109,20 @@
EXPECT_TRUE(private_ref);
}
+TEST(ResourceUtilsTest, ParseBinaryDynamicReference) {
+ android::Res_value value = {};
+ value.data = util::HostToDevice32(0x01);
+ value.dataType = android::Res_value::TYPE_DYNAMIC_REFERENCE;
+ std::unique_ptr<Item> item = ResourceUtils::ParseBinaryResValue(ResourceType::kId,
+ android::ConfigDescription(),
+ android::ResStringPool(), value,
+ nullptr);
+
+ Reference* ref = ValueCast<Reference>(item.get());
+ EXPECT_TRUE(ref->is_dynamic);
+ EXPECT_EQ(ref->id.value().id, 0x01);
+}
+
TEST(ResourceUtilsTest, FailToParseAutoCreateNonIdReference) {
bool create = false;
bool private_ref = false;
diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto
index 7498e13..8a2f5af 100644
--- a/tools/aapt2/Resources.proto
+++ b/tools/aapt2/Resources.proto
@@ -269,6 +269,11 @@
}
}
+// Message holding a boolean, so it can be optionally encoded.
+message Boolean {
+ bool value = 1;
+}
+
// A value that is a reference to another resource. This reference can be by name or resource ID.
message Reference {
enum Type {
@@ -289,6 +294,9 @@
// Whether this reference is referencing a private resource (@*package:type/entry).
bool private = 4;
+
+ // Whether this reference is dynamic.
+ Boolean is_dynamic = 5;
}
// A value that represents an ID. This is just a placeholder, as ID values are used to occupy a
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index d50b1de..3268653 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -159,6 +159,7 @@
ResourceParserOptions parser_options;
parser_options.error_on_positional_arguments = !options.legacy_mode;
+ parser_options.preserve_visibility_of_styleables = options.preserve_visibility_of_styleables;
parser_options.translatable = translatable_file;
// If visibility was forced, we need to use it when creating a new resource and also error if
diff --git a/tools/aapt2/cmd/Compile.h b/tools/aapt2/cmd/Compile.h
index d3456b2..1752a1a 100644
--- a/tools/aapt2/cmd/Compile.h
+++ b/tools/aapt2/cmd/Compile.h
@@ -35,6 +35,8 @@
bool pseudolocalize = false;
bool no_png_crunch = false;
bool legacy_mode = false;
+ // See comments on aapt::ResourceParserOptions.
+ bool preserve_visibility_of_styleables = false;
bool verbose = false;
};
@@ -56,6 +58,11 @@
AddOptionalSwitch("--no-crunch", "Disables PNG processing", &options_.no_png_crunch);
AddOptionalSwitch("--legacy", "Treat errors that used to be valid in AAPT as warnings",
&options_.legacy_mode);
+ AddOptionalSwitch("--preserve-visibility-of-styleables",
+ "If specified, apply the same visibility rules for\n"
+ "styleables as are used for all other resources.\n"
+ "Otherwise, all stylesables will be made public.",
+ &options_.preserve_visibility_of_styleables);
AddOptionalFlag("--visibility",
"Sets the visibility of the compiled resources to the specified\n"
"level. Accepted levels: public, private, default", &visibility_);
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 4555caa..5b6935b 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -895,7 +895,7 @@
// android:versionCode from the framework AndroidManifest.xml.
ExtractCompileSdkVersions(asset_source->GetAssetManager());
}
- } else if (asset_source->IsPackageDynamic(entry.first)) {
+ } else if (asset_source->IsPackageDynamic(entry.first, entry.second)) {
final_table_.included_packages_[entry.first] = entry.second;
}
}
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index 58e232c..cbce8a5 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -59,10 +59,22 @@
dst[i] = 0;
}
+static bool cmp_style_ids(ResourceId a, ResourceId b) {
+ // If one of a and b is from the framework package (package ID 0x01), and the
+ // other is a dynamic ID (package ID 0x00), then put the dynamic ID after the
+ // framework ID. This ensures that when AssetManager resolves the dynamic IDs,
+ // they will be in sorted order as expected by AssetManager.
+ if ((a.package_id() == kFrameworkPackageId && b.package_id() == 0x00) ||
+ (a.package_id() == 0x00 && b.package_id() == kFrameworkPackageId)) {
+ return b < a;
+ }
+ return a < b;
+}
+
static bool cmp_style_entries(const Style::Entry& a, const Style::Entry& b) {
if (a.key.id) {
if (b.key.id) {
- return a.key.id.value() < b.key.id.value();
+ return cmp_style_ids(a.key.id.value(), b.key.id.value());
}
return true;
} else if (!b.key.id) {
diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp
index 8fbdd7f..af2293f 100644
--- a/tools/aapt2/format/binary/TableFlattener_test.cpp
+++ b/tools/aapt2/format/binary/TableFlattener_test.cpp
@@ -431,6 +431,47 @@
EXPECT_EQ("lib", iter->second);
}
+TEST_F(TableFlattenerTest, FlattenSharedLibraryWithStyle) {
+ std::unique_ptr<IAaptContext> context =
+ test::ContextBuilder().SetCompilationPackage("lib").SetPackageId(0x00).Build();
+ std::unique_ptr<ResourceTable> table =
+ test::ResourceTableBuilder()
+ .SetPackageId("lib", 0x00)
+ .AddValue("lib:style/Theme",
+ ResourceId(0x00030001),
+ test::StyleBuilder()
+ .AddItem("lib:attr/bar", ResourceId(0x00010002),
+ ResourceUtils::TryParseInt("2"))
+ .AddItem("lib:attr/foo", ResourceId(0x00010001),
+ ResourceUtils::TryParseInt("1"))
+ .AddItem("android:attr/bar", ResourceId(0x01010002),
+ ResourceUtils::TryParseInt("4"))
+ .AddItem("android:attr/foo", ResourceId(0x01010001),
+ ResourceUtils::TryParseInt("3"))
+ .Build())
+ .Build();
+ ResourceTable result;
+ ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result));
+
+ Maybe<ResourceTable::SearchResult> search_result =
+ result.FindResource(test::ParseNameOrDie("lib:style/Theme"));
+ ASSERT_TRUE(search_result);
+ EXPECT_EQ(0x00u, search_result.value().package->id.value());
+ EXPECT_EQ(0x03u, search_result.value().type->id.value());
+ EXPECT_EQ(0x01u, search_result.value().entry->id.value());
+ ASSERT_EQ(1u, search_result.value().entry->values.size());
+ Value* value = search_result.value().entry->values[0]->value.get();
+ Style* style = ValueCast<Style>(value);
+ ASSERT_TRUE(style);
+ ASSERT_EQ(4u, style->entries.size());
+ // Ensure the attributes from the shared library come after the items from
+ // android.
+ EXPECT_EQ(0x01010001, style->entries[0].key.id.value());
+ EXPECT_EQ(0x01010002, style->entries[1].key.id.value());
+ EXPECT_EQ(0x00010001, style->entries[2].key.id.value());
+ EXPECT_EQ(0x00010002, style->entries[3].key.id.value());
+}
+
TEST_F(TableFlattenerTest, FlattenTableReferencingSharedLibraries) {
std::unique_ptr<IAaptContext> context =
test::ContextBuilder().SetCompilationPackage("app").SetPackageId(0x7f).Build();
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index efbf636..4cd6e93 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -634,6 +634,7 @@
std::string* out_error) {
out_ref->reference_type = DeserializeReferenceTypeFromPb(pb_ref.type());
out_ref->private_reference = pb_ref.private_();
+ out_ref->is_dynamic = pb_ref.is_dynamic().value();
if (pb_ref.id() != 0) {
out_ref->id = ResourceId(pb_ref.id());
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index e4b3fce..d9f6c19 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -418,6 +418,9 @@
pb_ref->set_private_(ref.private_reference);
pb_ref->set_type(SerializeReferenceTypeToPb(ref.reference_type));
+ if (ref.is_dynamic) {
+ pb_ref->mutable_is_dynamic()->set_value(ref.is_dynamic);
+ }
}
template <typename T>
diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
index e7f2330..61a8335 100644
--- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
@@ -608,4 +608,41 @@
ASSERT_FALSE(search_result.value().entry->overlayable_item);
}
+TEST(ProtoSerializeTest, SerializeAndDeserializeDynamicReference) {
+ Reference ref(ResourceId(0x00010001));
+ ref.is_dynamic = true;
+
+ pb::Item pb_item;
+ SerializeItemToPb(ref, &pb_item);
+
+ ASSERT_TRUE(pb_item.has_ref());
+ EXPECT_EQ(pb_item.ref().id(), ref.id.value().id);
+ EXPECT_TRUE(pb_item.ref().is_dynamic().value());
+
+ std::unique_ptr<Item> item = DeserializeItemFromPb(pb_item, android::ResStringPool(),
+ android::ConfigDescription(), nullptr,
+ nullptr, nullptr);
+ Reference* actual_ref = ValueCast<Reference>(item.get());
+ EXPECT_EQ(actual_ref->id.value().id, ref.id.value().id);
+ EXPECT_TRUE(actual_ref->is_dynamic);
+}
+
+TEST(ProtoSerializeTest, SerializeAndDeserializeNonDynamicReference) {
+ Reference ref(ResourceId(0x00010001));
+
+ pb::Item pb_item;
+ SerializeItemToPb(ref, &pb_item);
+
+ ASSERT_TRUE(pb_item.has_ref());
+ EXPECT_EQ(pb_item.ref().id(), ref.id.value().id);
+ EXPECT_FALSE(pb_item.ref().has_is_dynamic());
+
+ std::unique_ptr<Item> item = DeserializeItemFromPb(pb_item, android::ResStringPool(),
+ android::ConfigDescription(), nullptr,
+ nullptr, nullptr);
+ Reference* actual_ref = ValueCast<Reference>(item.get());
+ EXPECT_EQ(actual_ref->id.value().id, ref.id.value().id);
+ EXPECT_FALSE(actual_ref->is_dynamic);
+}
+
} // namespace aapt
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index 27960c8..954d401 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -349,6 +349,7 @@
}
return true;
});
+ manifest_action["uses-sdk"]["extension-sdk"];
// Instrumentation actions.
manifest_action["instrumentation"].Action(RequiredNameIsJavaClassName);
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index 83e20b5..897fa80 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -245,7 +245,8 @@
return package_map;
}
-bool AssetManagerSymbolSource::IsPackageDynamic(uint32_t packageId) const {
+bool AssetManagerSymbolSource::IsPackageDynamic(uint32_t packageId,
+ const std::string& package_name) const {
if (packageId == 0) {
return true;
}
@@ -253,7 +254,7 @@
for (const std::unique_ptr<const ApkAssets>& assets : apk_assets_) {
for (const std::unique_ptr<const android::LoadedPackage>& loaded_package
: assets->GetLoadedArsc()->GetPackages()) {
- if (packageId == loaded_package->GetPackageId() && loaded_package->IsDynamic()) {
+ if (package_name == loaded_package->GetPackageName() && loaded_package->IsDynamic()) {
return true;
}
}
@@ -328,12 +329,12 @@
bool found = false;
ResourceId res_id = 0;
uint32_t type_spec_flags;
+ ResourceName real_name;
// There can be mangled resources embedded within other packages. Here we will
// look into each package and look-up the mangled name until we find the resource.
asset_manager_.ForEachPackage([&](const std::string& package_name, uint8_t id) -> bool {
- ResourceName real_name(name.package, name.type, name.entry);
-
+ real_name = ResourceName(name.package, name.type, name.entry);
if (package_name != name.package) {
real_name.entry = mangled_entry;
real_name.package = package_name;
@@ -353,12 +354,12 @@
}
std::unique_ptr<SymbolTable::Symbol> s;
- if (name.type == ResourceType::kAttr) {
+ if (real_name.type == ResourceType::kAttr) {
s = LookupAttributeInTable(asset_manager_, res_id);
} else {
s = util::make_unique<SymbolTable::Symbol>();
s->id = res_id;
- s->is_dynamic = IsPackageDynamic(ResourceId(res_id).package_id());
+ s->is_dynamic = IsPackageDynamic(ResourceId(res_id).package_id(), real_name.package);
}
if (s) {
@@ -406,7 +407,7 @@
} else {
s = util::make_unique<SymbolTable::Symbol>();
s->id = id;
- s->is_dynamic = IsPackageDynamic(ResourceId(id).package_id());
+ s->is_dynamic = IsPackageDynamic(ResourceId(id).package_id(), name.package);
}
if (s) {
diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h
index 6997cd6..06eaf63 100644
--- a/tools/aapt2/process/SymbolTable.h
+++ b/tools/aapt2/process/SymbolTable.h
@@ -194,7 +194,7 @@
bool AddAssetPath(const android::StringPiece& path);
std::map<size_t, std::string> GetAssignedPackageIds() const;
- bool IsPackageDynamic(uint32_t packageId) const;
+ bool IsPackageDynamic(uint32_t packageId, const std::string& package_name) const;
std::unique_ptr<SymbolTable::Symbol> FindByName(
const ResourceName& name) override;
diff --git a/tools/hiddenapi/generate_hiddenapi_lists.py b/tools/hiddenapi/generate_hiddenapi_lists.py
index 46105f4..0b2077d 100755
--- a/tools/hiddenapi/generate_hiddenapi_lists.py
+++ b/tools/hiddenapi/generate_hiddenapi_lists.py
@@ -149,7 +149,12 @@
The package name of the class containing the field/method.
"""
full_class_name = signature.split(";->")[0]
- package_name = full_class_name[1:full_class_name.rindex("/")]
+ # Example: Landroid/hardware/radio/V1_2/IRadio$Proxy
+ if (full_class_name[0] != "L"):
+ raise ValueError("Expected to start with 'L': %s" % full_class_name)
+ full_class_name = full_class_name[1:]
+ # If full_class_name doesn't contain '/', then package_name will be ''.
+ package_name = full_class_name.rpartition("/")[0]
return package_name.replace('/', '.')
class FlagsDict:
diff --git a/tools/lock_agent/Android.bp b/tools/lock_agent/Android.bp
index 79dce4a..7b2ca9a 100644
--- a/tools/lock_agent/Android.bp
+++ b/tools/lock_agent/Android.bp
@@ -25,6 +25,7 @@
srcs: ["agent.cpp"],
static_libs: [
"libbase",
+ "liblog",
"libz",
"slicer",
],
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
index 99a26dc..3c55237 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
@@ -53,36 +53,38 @@
val executor = newThreadPool()
- command.javaSourceArgs.map { path ->
- executor.submitCallable {
- val transformer = SourceTransformer(command.protoLogImplClassNameArg,
- command.protoLogCacheClassNameArg, processor)
- val file = File(path)
- val text = injector.readText(file)
- val outSrc = try {
- val code = tryParse(text, path)
- if (containsProtoLogText(text, command.protoLogClassNameArg)) {
- transformer.processClass(text, path, packagePath(file, code), code)
- } else {
+ try {
+ command.javaSourceArgs.map { path ->
+ executor.submitCallable {
+ val transformer = SourceTransformer(command.protoLogImplClassNameArg,
+ command.protoLogCacheClassNameArg, processor)
+ val file = File(path)
+ val text = injector.readText(file)
+ val outSrc = try {
+ val code = tryParse(text, path)
+ if (containsProtoLogText(text, command.protoLogClassNameArg)) {
+ transformer.processClass(text, path, packagePath(file, code), code)
+ } else {
+ text
+ }
+ } catch (ex: ParsingException) {
+ // If we cannot parse this file, skip it (and log why). Compilation will
+ // fail in a subsequent build step.
+ injector.reportParseError(ex)
text
}
- } catch (ex: ParsingException) {
- // If we cannot parse this file, skip it (and log why). Compilation will fail
- // in a subsequent build step.
- injector.reportParseError(ex)
- text
+ path to outSrc
}
- path to outSrc
+ }.map { future ->
+ val (path, outSrc) = future.get()
+ outJar.putNextEntry(ZipEntry(path))
+ outJar.write(outSrc.toByteArray())
+ outJar.closeEntry()
}
- }.map { future ->
- val (path, outSrc) = future.get()
- outJar.putNextEntry(ZipEntry(path))
- outJar.write(outSrc.toByteArray())
- outJar.closeEntry()
+ } finally {
+ executor.shutdown()
}
- executor.shutdown()
-
val cacheSplit = command.protoLogCacheClassNameArg.split(".")
val cacheName = cacheSplit.last()
val cachePackage = cacheSplit.dropLast(1).joinToString(".")
@@ -153,30 +155,32 @@
val executor = newThreadPool()
- command.javaSourceArgs.map { path ->
- executor.submitCallable {
- val file = File(path)
- val text = injector.readText(file)
- if (containsProtoLogText(text, command.protoLogClassNameArg)) {
- try {
- val code = tryParse(text, path)
- builder.findLogCalls(code, path, packagePath(file, code))
- } catch (ex: ParsingException) {
- // If we cannot parse this file, skip it (and log why). Compilation will fail
- // in a subsequent build step.
- injector.reportParseError(ex)
+ try {
+ command.javaSourceArgs.map { path ->
+ executor.submitCallable {
+ val file = File(path)
+ val text = injector.readText(file)
+ if (containsProtoLogText(text, command.protoLogClassNameArg)) {
+ try {
+ val code = tryParse(text, path)
+ builder.findLogCalls(code, path, packagePath(file, code))
+ } catch (ex: ParsingException) {
+ // If we cannot parse this file, skip it (and log why). Compilation will
+ // fail in a subsequent build step.
+ injector.reportParseError(ex)
+ null
+ }
+ } else {
null
}
- } else {
- null
}
+ }.forEach { future ->
+ builder.addLogCalls(future.get() ?: return@forEach)
}
- }.forEach { future ->
- builder.addLogCalls(future.get() ?: return@forEach)
+ } finally {
+ executor.shutdown()
}
- executor.shutdown()
-
val out = injector.fileOutputStream(command.viewerConfigJsonArg)
out.write(builder.build().toByteArray())
out.close()
diff --git a/tools/stats_log_api_gen/Collation.cpp b/tools/stats_log_api_gen/Collation.cpp
index 0b82a3d..7bbac13 100644
--- a/tools/stats_log_api_gen/Collation.cpp
+++ b/tools/stats_log_api_gen/Collation.cpp
@@ -291,6 +291,15 @@
}
if (field->options().GetExtension(os::statsd::state_field_option).option() ==
+ os::statsd::StateField::PRIMARY_FIELD_FIRST_UID) {
+ if (javaType != JAVA_TYPE_ATTRIBUTION_CHAIN) {
+ errorCount++;
+ } else {
+ atomDecl->primaryFields.push_back(FIRST_UID_IN_CHAIN_ID);
+ }
+ }
+
+ if (field->options().GetExtension(os::statsd::state_field_option).option() ==
os::statsd::StateField::EXCLUSIVE) {
if (javaType == JAVA_TYPE_UNKNOWN ||
javaType == JAVA_TYPE_ATTRIBUTION_CHAIN ||
diff --git a/tools/stats_log_api_gen/Collation.h b/tools/stats_log_api_gen/Collation.h
index 3efdd52..87d4d5d 100644
--- a/tools/stats_log_api_gen/Collation.h
+++ b/tools/stats_log_api_gen/Collation.h
@@ -36,6 +36,8 @@
const int PULL_ATOM_START_ID = 10000;
+const int FIRST_UID_IN_CHAIN_ID = 0;
+
/**
* The types for atom parameters.
*/
diff --git a/tools/stats_log_api_gen/atoms_info_writer.cpp b/tools/stats_log_api_gen/atoms_info_writer.cpp
index 54a9982..66ae964 100644
--- a/tools/stats_log_api_gen/atoms_info_writer.cpp
+++ b/tools/stats_log_api_gen/atoms_info_writer.cpp
@@ -25,6 +25,8 @@
namespace stats_log_api_gen {
static void write_atoms_info_header_body(FILE* out, const Atoms& atoms) {
+ fprintf(out, "static int FIRST_UID_IN_CHAIN = 0;\n");
+
fprintf(out, "struct StateAtomFieldOptions {\n");
fprintf(out, " std::vector<int> primaryFields;\n");
fprintf(out, " int exclusiveField;\n");
diff --git a/tools/stats_log_api_gen/java_writer.cpp b/tools/stats_log_api_gen/java_writer.cpp
index 7f0872c..b09dcd5 100644
--- a/tools/stats_log_api_gen/java_writer.cpp
+++ b/tools/stats_log_api_gen/java_writer.cpp
@@ -85,8 +85,9 @@
string indent("");
if (supportQ) {
// TODO(b/146235828): Use just SDK_INT check once it is incremented from Q.
- fprintf(out, " if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q ||\n");
- fprintf(out, " Build.VERSION.CODENAME.equals(\"R\")) {\n");
+ fprintf(out, " if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q\n");
+ fprintf(out, " || (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q\n");
+ fprintf(out, " && Build.VERSION.PREVIEW_SDK_INT > 0)) {\n");
indent = " ";
}
@@ -141,16 +142,16 @@
fprintf(out,
"%s final int count = valueMap.size();\n", indent.c_str());
fprintf(out,
- "%s final SparseIntArray intMap = new SparseIntArray();\n",
+ "%s SparseIntArray intMap = null;\n",
indent.c_str());
fprintf(out,
- "%s final SparseLongArray longMap = new SparseLongArray();\n",
+ "%s SparseLongArray longMap = null;\n",
indent.c_str());
fprintf(out,
- "%s final SparseArray<String> stringMap = new SparseArray<>();\n",
+ "%s SparseArray<String> stringMap = null;\n",
indent.c_str());
fprintf(out,
- "%s final SparseArray<Float> floatMap = new SparseArray<>();\n",
+ "%s SparseArray<Float> floatMap = null;\n",
indent.c_str());
fprintf(out,
"%s for (int i = 0; i < count; i++) {\n", indent.c_str());
@@ -162,18 +163,42 @@
fprintf(out,
"%s if (value instanceof Integer) {\n", indent.c_str());
fprintf(out,
+ "%s if (null == intMap) {\n", indent.c_str());
+ fprintf(out,
+ "%s intMap = new SparseIntArray();\n", indent.c_str());
+ fprintf(out,
+ "%s }\n", indent.c_str());
+ fprintf(out,
"%s intMap.put(key, (Integer) value);\n", indent.c_str());
fprintf(out,
"%s } else if (value instanceof Long) {\n", indent.c_str());
fprintf(out,
+ "%s if (null == longMap) {\n", indent.c_str());
+ fprintf(out,
+ "%s longMap = new SparseLongArray();\n", indent.c_str());
+ fprintf(out,
+ "%s }\n", indent.c_str());
+ fprintf(out,
"%s longMap.put(key, (Long) value);\n", indent.c_str());
fprintf(out,
"%s } else if (value instanceof String) {\n", indent.c_str());
fprintf(out,
+ "%s if (null == stringMap) {\n", indent.c_str());
+ fprintf(out,
+ "%s stringMap = new SparseArray<>();\n", indent.c_str());
+ fprintf(out,
+ "%s }\n", indent.c_str());
+ fprintf(out,
"%s stringMap.put(key, (String) value);\n", indent.c_str());
fprintf(out,
"%s } else if (value instanceof Float) {\n", indent.c_str());
fprintf(out,
+ "%s if (null == floatMap) {\n", indent.c_str());
+ fprintf(out,
+ "%s floatMap = new SparseArray<>();\n", indent.c_str());
+ fprintf(out,
+ "%s }\n", indent.c_str());
+ fprintf(out,
"%s floatMap.put(key, (Float) value);\n", indent.c_str());
fprintf(out,
"%s }\n", indent.c_str());
diff --git a/wifi/Android.bp b/wifi/Android.bp
index 35a892a..4c9ee85 100644
--- a/wifi/Android.bp
+++ b/wifi/Android.bp
@@ -32,6 +32,7 @@
// framework-wifi.jar. This is not a good idea, should move WifiNetworkScoreCache
// to a separate package.
"java/android/net/wifi/WifiNetworkScoreCache.java",
+ "java/android/net/wifi/WifiOemConfigStoreMigrationHook.java",
"java/android/net/wifi/wificond/*.java",
":libwificond_ipc_aidl",
],
@@ -48,27 +49,62 @@
"//frameworks/opt/net/wifi/tests/wifitests:__subpackages__",
"//frameworks/opt/net/wifi/libs/WifiTrackerLib/tests",
+ "//external/robolectric-shadows:__subpackages__",
+ "//frameworks/base/packages/SettingsLib/tests/integ",
+ "//external/sl4a:__subpackages__",
]
+// wifi-service needs pre-jarjared version of framework-wifi so it can reference copied utility
+// classes before they are renamed.
java_library {
- name: "framework-wifi",
+ name: "framework-wifi-pre-jarjar",
// TODO(b/140299412) should be core_current once we build against framework-system-stubs
sdk_version: "core_platform",
+ static_libs: [
+ "framework-wifi-util-lib",
+ "android.hardware.wifi-V1.0-java-constants",
+ ],
libs: [
// TODO(b/140299412) should be framework-system-stubs once we fix all @hide dependencies
"framework-minus-apex",
+ "framework-annotations-lib",
+ "unsupportedappusage", // for android.compat.annotation.UnsupportedAppUsage
+ "unsupportedappusage-annotation", // for dalvik.annotation.compat.UnsupportedAppUsage
+ "framework-telephony-stubs",
],
srcs: [
":framework-wifi-updatable-sources",
],
+ installable: false,
+ visibility: [
+ "//frameworks/opt/net/wifi/service",
+ "//frameworks/opt/net/wifi/tests/wifitests",
+ ],
+}
+
+// post-jarjar version of framework-wifi
+java_library {
+ name: "framework-wifi",
+ // TODO(b/140299412) should be core_current once we build against framework-system-stubs
+ sdk_version: "core_platform",
+ static_libs: [
+ "framework-wifi-pre-jarjar",
+ ],
+ jarjar_rules: ":wifi-jarjar-rules",
+
installable: true,
optimize: {
enabled: false
},
+ hostdex: true, // for hiddenapi check
visibility: [
"//frameworks/base", // TODO(b/140299412) remove once all dependencies are fixed
"//frameworks/opt/net/wifi/service:__subpackages__",
] + test_access_hidden_api_whitelist,
+ apex_available: [
+ "com.android.wifi",
+ "test_com.android.wifi",
+ ],
plugins: ["java_api_finder"],
}
@@ -104,11 +140,18 @@
name: "framework-wifi-test-defaults",
sdk_version: "core_platform", // tests can use @CorePlatformApi's
libs: [
+ // order matters: classes in framework-wifi are resolved before framework, meaning
+ // @hide APIs in framework-wifi are resolved before @SystemApi stubs in framework
"framework-wifi",
- "framework-minus-apex",
+ "framework",
// if sdk_version="" this gets automatically included, but here we need to add manually.
"framework-res",
],
visibility: test_access_hidden_api_whitelist,
}
+
+filegroup {
+ name: "wifi-jarjar-rules",
+ srcs: ["jarjar-rules.txt"],
+}
diff --git a/wifi/jarjar-rules.txt b/wifi/jarjar-rules.txt
new file mode 100644
index 0000000..8f72040
--- /dev/null
+++ b/wifi/jarjar-rules.txt
@@ -0,0 +1,40 @@
+rule android.net.InterfaceConfigurationParcel* @0
+rule android.net.InterfaceConfiguration* com.android.server.x.wifi.net.InterfaceConfiguration@1
+
+# We don't jar-jar the entire package because, we still use some classes (like
+# AsyncChannel in com.android.internal.util) from these packages which are not
+# inside our jar (currently in framework.jar, but will be in wifisdk.jar in the future).
+rule com.android.internal.util.FastXmlSerializer* com.android.server.x.wifi.util.FastXmlSerializer@1
+rule com.android.internal.util.HexDump* com.android.server.x.wifi.util.HexDump@1
+rule com.android.internal.util.IState* com.android.server.x.wifi.util.IState@1
+rule com.android.internal.util.MessageUtils* com.android.server.x.wifi.util.MessageUtils@1
+rule com.android.internal.util.State* com.android.server.x.wifi.util.State@1
+rule com.android.internal.util.StateMachine* com.android.server.x.wifi.util.StateMachine@1
+rule com.android.internal.util.WakeupMessage* com.android.server.x.wifi.util.WakeupMessage@1
+
+rule android.util.BackupUtils* com.android.server.x.wifi.util.BackupUtils@1
+rule android.util.LocalLog* com.android.server.x.wifi.util.LocalLog@1
+rule android.util.Rational* com.android.server.x.wifi.util.Rational@1
+
+rule android.os.BasicShellCommandHandler* com.android.server.x.wifi.os.BasicShellCommandHandler@1
+
+# Use our statically linked bouncy castle library
+rule org.bouncycastle.** com.android.server.x.wifi.bouncycastle.@1
+# Use our statically linked protobuf library
+rule com.google.protobuf.** com.android.server.x.wifi.protobuf.@1
+# use statically linked SystemMessageProto
+rule com.android.internal.messages.SystemMessageProto* com.android.server.x.wifi.messages.SystemMessageProto@1
+# Use our statically linked PlatformProperties library
+rule android.sysprop.** com.android.server.x.wifi.sysprop.@1
+
+
+# used by both framework-wifi and wifi-service
+rule android.content.pm.BaseParceledListSlice* android.x.net.wifi.util.BaseParceledListSlice@1
+rule android.content.pm.ParceledListSlice* android.x.net.wifi.util.ParceledListSlice@1
+rule android.net.shared.Inet4AddressUtils* android.x.net.wifi.util.Inet4AddressUtils@1
+rule android.os.HandlerExecutor* android.x.net.wifi.util.HandlerExecutor@1
+rule android.telephony.Annotation* android.x.net.wifi.util.TelephonyAnnotation@1
+rule com.android.internal.util.AsyncChannel* android.x.net.wifi.util.AsyncChannel@1
+rule com.android.internal.util.AsyncService* android.x.net.wifi.util.AsyncService@1
+rule com.android.internal.util.Preconditions* android.x.net.wifi.util.Preconditions@1
+rule com.android.internal.util.Protocol* android.x.net.wifi.util.Protocol@1
diff --git a/wifi/java/android/net/wifi/ISoftApCallback.aidl b/wifi/java/android/net/wifi/ISoftApCallback.aidl
index 482b491..f81bcb9 100644
--- a/wifi/java/android/net/wifi/ISoftApCallback.aidl
+++ b/wifi/java/android/net/wifi/ISoftApCallback.aidl
@@ -55,9 +55,17 @@
/**
- * Service to manager callback providing information of softap.
+ * Service to manager callback providing capability of softap.
*
* @param capability is the softap capability. {@link SoftApCapability}
*/
void onCapabilityChanged(in SoftApCapability capability);
+
+ /**
+ * Service to manager callback providing blocked client of softap with specific reason code.
+ *
+ * @param client the currently blocked client.
+ * @param blockedReason one of blocked reason from {@link WifiManager.SapClientBlockedReason}
+ */
+ void onBlockedClientConnecting(in WifiClient client, int blockedReason);
}
diff --git a/wifi/java/android/net/wifi/IWifiManager.aidl b/wifi/java/android/net/wifi/IWifiManager.aidl
index 1678d5a..f490766 100644
--- a/wifi/java/android/net/wifi/IWifiManager.aidl
+++ b/wifi/java/android/net/wifi/IWifiManager.aidl
@@ -90,6 +90,8 @@
void allowAutojoin(int netId, boolean choice);
+ void allowAutojoinPasspoint(String fqdn, boolean enableAutoJoin);
+
boolean startScan(String packageName, String featureId);
List<ScanResult> getScanResults(String callingPackage, String callingFeatureId);
@@ -248,4 +250,6 @@
void unregisterSuggestionConnectionStatusListener(int listenerIdentifier, String packageName);
int calculateSignalLevel(int rssi);
+
+ List<WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(in List<ScanResult> scanResults);
}
diff --git a/wifi/java/android/net/wifi/ScanResult.java b/wifi/java/android/net/wifi/ScanResult.java
index 83a1800..3413305 100644
--- a/wifi/java/android/net/wifi/ScanResult.java
+++ b/wifi/java/android/net/wifi/ScanResult.java
@@ -19,10 +19,12 @@
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
@@ -82,16 +84,19 @@
* @hide
* No security protocol.
*/
+ @SystemApi
public static final int PROTOCOL_NONE = 0;
/**
* @hide
* Security protocol type: WPA version 1.
*/
+ @SystemApi
public static final int PROTOCOL_WPA = 1;
/**
* @hide
* Security protocol type: RSN, for WPA version 2, and version 3.
*/
+ @SystemApi
public static final int PROTOCOL_RSN = 2;
/**
* @hide
@@ -99,79 +104,94 @@
* OSU Server-only authenticated layer 2 Encryption Network.
* Used for Hotspot 2.0.
*/
+ @SystemApi
public static final int PROTOCOL_OSEN = 3;
/**
* @hide
* Security protocol type: WAPI.
*/
+ @SystemApi
public static final int PROTOCOL_WAPI = 4;
/**
* @hide
* No security key management scheme.
*/
+ @SystemApi
public static final int KEY_MGMT_NONE = 0;
/**
* @hide
* Security key management scheme: PSK.
*/
+ @SystemApi
public static final int KEY_MGMT_PSK = 1;
/**
* @hide
* Security key management scheme: EAP.
*/
+ @SystemApi
public static final int KEY_MGMT_EAP = 2;
/**
* @hide
* Security key management scheme: FT_PSK.
*/
+ @SystemApi
public static final int KEY_MGMT_FT_PSK = 3;
/**
* @hide
* Security key management scheme: FT_EAP.
*/
+ @SystemApi
public static final int KEY_MGMT_FT_EAP = 4;
/**
* @hide
* Security key management scheme: PSK_SHA256
*/
+ @SystemApi
public static final int KEY_MGMT_PSK_SHA256 = 5;
/**
* @hide
* Security key management scheme: EAP_SHA256.
*/
+ @SystemApi
public static final int KEY_MGMT_EAP_SHA256 = 6;
/**
* @hide
* Security key management scheme: OSEN.
* Used for Hotspot 2.0.
*/
+ @SystemApi
public static final int KEY_MGMT_OSEN = 7;
/**
* @hide
* Security key management scheme: SAE.
*/
+ @SystemApi
public static final int KEY_MGMT_SAE = 8;
/**
* @hide
* Security key management scheme: OWE.
*/
+ @SystemApi
public static final int KEY_MGMT_OWE = 9;
/**
* @hide
* Security key management scheme: SUITE_B_192.
*/
+ @SystemApi
public static final int KEY_MGMT_EAP_SUITE_B_192 = 10;
/**
* @hide
* Security key management scheme: FT_SAE.
*/
+ @SystemApi
public static final int KEY_MGMT_FT_SAE = 11;
/**
* @hide
* Security key management scheme: OWE in transition mode.
*/
+ @SystemApi
public static final int KEY_MGMT_OWE_TRANSITION = 12;
/**
* @hide
@@ -185,35 +205,42 @@
*/
@SystemApi
public static final int KEY_MGMT_WAPI_CERT = 14;
+
/**
* @hide
* No cipher suite.
*/
+ @SystemApi
public static final int CIPHER_NONE = 0;
/**
* @hide
* No group addressed, only used for group data cipher.
*/
+ @SystemApi
public static final int CIPHER_NO_GROUP_ADDRESSED = 1;
/**
* @hide
* Cipher suite: TKIP
*/
+ @SystemApi
public static final int CIPHER_TKIP = 2;
/**
* @hide
* Cipher suite: CCMP
*/
+ @SystemApi
public static final int CIPHER_CCMP = 3;
/**
* @hide
* Cipher suite: GCMP
*/
+ @SystemApi
public static final int CIPHER_GCMP_256 = 4;
/**
* @hide
* Cipher suite: SMS4
*/
+ @SystemApi
public static final int CIPHER_SMS4 = 5;
/**
@@ -768,10 +795,19 @@
}
}
- /** empty scan result
+ /**
+ * Construct an empty scan result.
*
- * {@hide}
- * */
+ * Test code has a need to construct a ScanResult in a specific state.
+ * (Note that mocking using Mockito does not work if the object needs to be parceled and
+ * unparceled.)
+ * Export a @SystemApi default constructor to allow tests to construct an empty ScanResult
+ * object. The test can then directly set the fields it cares about.
+ *
+ * @hide
+ */
+ @SystemApi
+ @VisibleForTesting
public ScanResult() {
}
diff --git a/wifi/java/android/net/wifi/SoftApCapability.java b/wifi/java/android/net/wifi/SoftApCapability.java
index c4474e2..2bbe7d2 100644
--- a/wifi/java/android/net/wifi/SoftApCapability.java
+++ b/wifi/java/android/net/wifi/SoftApCapability.java
@@ -61,11 +61,20 @@
*/
public static final int SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT = 1 << 1;
+
+ /**
+ * Support for WPA3 Simultaneous Authentication of Equals (WPA3-SAE).
+ *
+ * flag when {@link config_wifi_softap_sae_supported)} is true.
+ */
+ public static final int SOFTAP_FEATURE_WPA3_SAE = 1 << 2;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, prefix = { "SOFTAP_FEATURE_" }, value = {
SOFTAP_FEATURE_ACS_OFFLOAD,
SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT,
+ SOFTAP_FEATURE_WPA3_SAE,
})
public @interface HotspotFeatures {}
diff --git a/wifi/java/android/net/wifi/SoftApConfiguration.java b/wifi/java/android/net/wifi/SoftApConfiguration.java
index 05e245b..a77d30a 100644
--- a/wifi/java/android/net/wifi/SoftApConfiguration.java
+++ b/wifi/java/android/net/wifi/SoftApConfiguration.java
@@ -25,12 +25,16 @@
import android.os.Parcelable;
import android.text.TextUtils;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -55,6 +59,11 @@
@SystemApi
public final class SoftApConfiguration implements Parcelable {
+ @VisibleForTesting
+ static final int PSK_MIN_LEN = 8;
+ @VisibleForTesting
+ static final int PSK_MAX_LEN = 63;
+
/**
* 2GHz band.
* @hide
@@ -142,9 +151,10 @@
private final @Nullable MacAddress mBssid;
/**
- * Pre-shared key for WPA2-PSK encryption (non-null enables WPA2-PSK).
+ * Pre-shared key for WPA2-PSK or WPA3-SAE-Transition or WPA3-SAE encryption which depends on
+ * the security type.
*/
- private final @Nullable String mWpa2Passphrase;
+ private final @Nullable String mPassphrase;
/**
* This is a network that does not broadcast its SSID, so an
@@ -175,6 +185,28 @@
private final @SecurityType int mSecurityType;
/**
+ * The flag to indicate client need to authorize by user
+ * when client is connecting to AP.
+ */
+ private final boolean mClientControlByUser;
+
+ /**
+ * The list of blocked client that can't associate to the AP.
+ */
+ private final List<MacAddress> mBlockedClientList;
+
+ /**
+ * The list of allowed client that can associate to the AP.
+ */
+ private final List<MacAddress> mAllowedClientList;
+
+ /**
+ * Delay in milliseconds before shutting down soft AP when
+ * there are no connected devices.
+ */
+ private final int mShutdownTimeoutMillis;
+
+ /**
* Security types we support.
*/
/** @hide */
@@ -186,25 +218,41 @@
public static final int SECURITY_TYPE_WPA2_PSK = 1;
/** @hide */
+ @SystemApi
+ public static final int SECURITY_TYPE_WPA3_SAE_TRANSITION = 2;
+
+ /** @hide */
+ @SystemApi
+ public static final int SECURITY_TYPE_WPA3_SAE = 3;
+
+ /** @hide */
@Retention(RetentionPolicy.SOURCE)
- @IntDef(prefix = { "SECURITY_TYPE" }, value = {
+ @IntDef(prefix = { "SECURITY_TYPE_" }, value = {
SECURITY_TYPE_OPEN,
SECURITY_TYPE_WPA2_PSK,
+ SECURITY_TYPE_WPA3_SAE_TRANSITION,
+ SECURITY_TYPE_WPA3_SAE,
})
public @interface SecurityType {}
/** Private constructor for Builder and Parcelable implementation. */
private SoftApConfiguration(@Nullable String ssid, @Nullable MacAddress bssid,
- @Nullable String wpa2Passphrase, boolean hiddenSsid, @BandType int band, int channel,
- @SecurityType int securityType, int maxNumberOfClients) {
+ @Nullable String passphrase, boolean hiddenSsid, @BandType int band, int channel,
+ @SecurityType int securityType, int maxNumberOfClients, int shutdownTimeoutMillis,
+ boolean clientControlByUser, @NonNull List<MacAddress> blockedList,
+ @NonNull List<MacAddress> allowedList) {
mSsid = ssid;
mBssid = bssid;
- mWpa2Passphrase = wpa2Passphrase;
+ mPassphrase = passphrase;
mHiddenSsid = hiddenSsid;
mBand = band;
mChannel = channel;
mSecurityType = securityType;
mMaxNumberOfClients = maxNumberOfClients;
+ mShutdownTimeoutMillis = shutdownTimeoutMillis;
+ mClientControlByUser = clientControlByUser;
+ mBlockedClientList = new ArrayList<>(blockedList);
+ mAllowedClientList = new ArrayList<>(allowedList);
}
@Override
@@ -218,18 +266,23 @@
SoftApConfiguration other = (SoftApConfiguration) otherObj;
return Objects.equals(mSsid, other.mSsid)
&& Objects.equals(mBssid, other.mBssid)
- && Objects.equals(mWpa2Passphrase, other.mWpa2Passphrase)
+ && Objects.equals(mPassphrase, other.mPassphrase)
&& mHiddenSsid == other.mHiddenSsid
&& mBand == other.mBand
&& mChannel == other.mChannel
&& mSecurityType == other.mSecurityType
- && mMaxNumberOfClients == other.mMaxNumberOfClients;
+ && mMaxNumberOfClients == other.mMaxNumberOfClients
+ && mShutdownTimeoutMillis == other.mShutdownTimeoutMillis
+ && mClientControlByUser == other.mClientControlByUser
+ && Objects.equals(mBlockedClientList, other.mBlockedClientList)
+ && Objects.equals(mAllowedClientList, other.mAllowedClientList);
}
@Override
public int hashCode() {
- return Objects.hash(mSsid, mBssid, mWpa2Passphrase, mHiddenSsid,
- mBand, mChannel, mSecurityType, mMaxNumberOfClients);
+ return Objects.hash(mSsid, mBssid, mPassphrase, mHiddenSsid,
+ mBand, mChannel, mSecurityType, mMaxNumberOfClients, mShutdownTimeoutMillis,
+ mClientControlByUser, mBlockedClientList, mAllowedClientList);
}
@Override
@@ -237,13 +290,17 @@
StringBuilder sbuf = new StringBuilder();
sbuf.append("ssid=").append(mSsid);
if (mBssid != null) sbuf.append(" \n bssid=").append(mBssid.toString());
- sbuf.append(" \n Wpa2Passphrase =").append(
- TextUtils.isEmpty(mWpa2Passphrase) ? "<empty>" : "<non-empty>");
+ sbuf.append(" \n Passphrase =").append(
+ TextUtils.isEmpty(mPassphrase) ? "<empty>" : "<non-empty>");
sbuf.append(" \n HiddenSsid =").append(mHiddenSsid);
sbuf.append(" \n Band =").append(mBand);
sbuf.append(" \n Channel =").append(mChannel);
sbuf.append(" \n SecurityType=").append(getSecurityType());
sbuf.append(" \n MaxClient=").append(mMaxNumberOfClients);
+ sbuf.append(" \n ShutdownTimeoutMillis=").append(mShutdownTimeoutMillis);
+ sbuf.append(" \n ClientControlByUser=").append(mClientControlByUser);
+ sbuf.append(" \n BlockedClientList=").append(mBlockedClientList);
+ sbuf.append(" \n AllowedClientList=").append(mAllowedClientList);
return sbuf.toString();
}
@@ -251,12 +308,16 @@
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(mSsid);
dest.writeParcelable(mBssid, flags);
- dest.writeString(mWpa2Passphrase);
+ dest.writeString(mPassphrase);
dest.writeBoolean(mHiddenSsid);
dest.writeInt(mBand);
dest.writeInt(mChannel);
dest.writeInt(mSecurityType);
dest.writeInt(mMaxNumberOfClients);
+ dest.writeInt(mShutdownTimeoutMillis);
+ dest.writeBoolean(mClientControlByUser);
+ dest.writeTypedList(mBlockedClientList);
+ dest.writeTypedList(mAllowedClientList);
}
@Override
@@ -272,7 +333,9 @@
in.readString(),
in.readParcelable(MacAddress.class.getClassLoader()),
in.readString(), in.readBoolean(), in.readInt(), in.readInt(), in.readInt(),
- in.readInt());
+ in.readInt(), in.readInt(), in.readBoolean(),
+ in.createTypedArrayList(MacAddress.CREATOR),
+ in.createTypedArrayList(MacAddress.CREATOR));
}
@Override
@@ -300,12 +363,12 @@
}
/**
- * Returns String set to be passphrase for the WPA2-PSK AP.
- * {@link Builder#setWpa2Passphrase(String)}.
+ * Returns String set to be passphrase for current AP.
+ * {@link #setPassphrase(String, @SecurityType int)}.
*/
@Nullable
- public String getWpa2Passphrase() {
- return mWpa2Passphrase;
+ public String getPassphrase() {
+ return mPassphrase;
}
/**
@@ -351,6 +414,43 @@
}
/**
+ * Returns the shutdown timeout in milliseconds.
+ * The Soft AP will shutdown when there are no devices associated to it for
+ * the timeout duration. See {@link Builder#setShutdownTimeoutMillis(int)}.
+ */
+ public int getShutdownTimeoutMillis() {
+ return mShutdownTimeoutMillis;
+ }
+
+ /**
+ * Returns a flag indicating whether clients need to be pre-approved by the user.
+ * (true: authorization required) or not (false: not required).
+ * {@link Builder#enableClientControlByUser(Boolean)}.
+ */
+ public boolean isClientControlByUserEnabled() {
+ return mClientControlByUser;
+ }
+
+ /**
+ * Returns List of clients which aren't allowed to associate to the AP.
+ *
+ * Clients are configured using {@link Builder#setClientList(List, List)}
+ */
+ @NonNull
+ public List<MacAddress> getBlockedClientList() {
+ return mBlockedClientList;
+ }
+
+ /**
+ * List of clients which are allowed to associate to the AP.
+ * Clients are configured using {@link Builder#setClientList(List, List)}
+ */
+ @NonNull
+ public List<MacAddress> getAllowedClientList() {
+ return mAllowedClientList;
+ }
+
+ /**
* Builds a {@link SoftApConfiguration}, which allows an app to configure various aspects of a
* Soft AP.
*
@@ -360,23 +460,16 @@
public static final class Builder {
private String mSsid;
private MacAddress mBssid;
- private String mWpa2Passphrase;
+ private String mPassphrase;
private boolean mHiddenSsid;
private int mBand;
private int mChannel;
private int mMaxNumberOfClients;
-
- private int setSecurityType() {
- int securityType = SECURITY_TYPE_OPEN;
- if (!TextUtils.isEmpty(mWpa2Passphrase)) { // WPA2-PSK network.
- securityType = SECURITY_TYPE_WPA2_PSK;
- }
- return securityType;
- }
-
- private void clearAllPassphrase() {
- mWpa2Passphrase = null;
- }
+ private int mSecurityType;
+ private int mShutdownTimeoutMillis;
+ private boolean mClientControlByUser;
+ private List<MacAddress> mBlockedClientList;
+ private List<MacAddress> mAllowedClientList;
/**
* Constructs a Builder with default values (see {@link Builder}).
@@ -384,11 +477,16 @@
public Builder() {
mSsid = null;
mBssid = null;
- mWpa2Passphrase = null;
+ mPassphrase = null;
mHiddenSsid = false;
mBand = BAND_2GHZ;
mChannel = 0;
mMaxNumberOfClients = 0;
+ mSecurityType = SECURITY_TYPE_OPEN;
+ mShutdownTimeoutMillis = 0;
+ mClientControlByUser = false;
+ mBlockedClientList = new ArrayList<>();
+ mAllowedClientList = new ArrayList<>();
}
/**
@@ -399,11 +497,16 @@
mSsid = other.mSsid;
mBssid = other.mBssid;
- mWpa2Passphrase = other.mWpa2Passphrase;
+ mPassphrase = other.mPassphrase;
mHiddenSsid = other.mHiddenSsid;
mBand = other.mBand;
mChannel = other.mChannel;
mMaxNumberOfClients = other.mMaxNumberOfClients;
+ mSecurityType = other.mSecurityType;
+ mShutdownTimeoutMillis = other.mShutdownTimeoutMillis;
+ mClientControlByUser = other.mClientControlByUser;
+ mBlockedClientList = new ArrayList<>(other.mBlockedClientList);
+ mAllowedClientList = new ArrayList<>(other.mAllowedClientList);
}
/**
@@ -413,8 +516,10 @@
*/
@NonNull
public SoftApConfiguration build() {
- return new SoftApConfiguration(mSsid, mBssid, mWpa2Passphrase,
- mHiddenSsid, mBand, mChannel, setSecurityType(), mMaxNumberOfClients);
+ return new SoftApConfiguration(mSsid, mBssid, mPassphrase,
+ mHiddenSsid, mBand, mChannel, mSecurityType, mMaxNumberOfClients,
+ mShutdownTimeoutMillis, mClientControlByUser, mBlockedClientList,
+ mAllowedClientList);
}
/**
@@ -462,26 +567,43 @@
}
/**
- * Specifies that this AP should use WPA2-PSK with the given ASCII WPA2 passphrase.
- * When set to null, an open network is created.
- * <p>
+ * Specifies that this AP should use specific security type with the given ASCII passphrase.
*
- * @param passphrase The passphrase to use, or null to unset a previously-set WPA2-PSK
- * configuration.
+ * @param securityType one of the security types from {@link @SecurityType}.
+ * @param passphrase The passphrase to use for sepcific {@link @SecurityType} configuration
+ * or null with {@link @SecurityType#SECURITY_TYPE_OPEN}.
+ *
* @return Builder for chaining.
- * @throws IllegalArgumentException when the passphrase is the empty string
+ * @throws IllegalArgumentException when the passphrase length is invalid and
+ * {@code securityType} is not {@link @SecurityType#SECURITY_TYPE_OPEN}
+ * or non-null passphrase and {@code securityType} is
+ * {@link @SecurityType#SECURITY_TYPE_OPEN}.
*/
@NonNull
- public Builder setWpa2Passphrase(@Nullable String passphrase) {
- if (passphrase != null) {
+ public Builder setPassphrase(@Nullable String passphrase, @SecurityType int securityType) {
+ if (securityType == SECURITY_TYPE_OPEN) {
+ if (passphrase != null) {
+ throw new IllegalArgumentException(
+ "passphrase should be null when security type is open");
+ }
+ } else {
+ Preconditions.checkStringNotEmpty(passphrase);
final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder();
if (!asciiEncoder.canEncode(passphrase)) {
throw new IllegalArgumentException("passphrase not ASCII encodable");
}
- Preconditions.checkStringNotEmpty(passphrase);
+ if (securityType == SECURITY_TYPE_WPA2_PSK
+ || securityType == SECURITY_TYPE_WPA3_SAE_TRANSITION) {
+ if (passphrase.length() < PSK_MIN_LEN || passphrase.length() > PSK_MAX_LEN) {
+ throw new IllegalArgumentException(
+ "Password size must be at least " + PSK_MIN_LEN
+ + " and no more than " + PSK_MAX_LEN
+ + " for WPA2_PSK and WPA3_SAE_TRANSITION Mode");
+ }
+ }
}
- clearAllPassphrase();
- mWpa2Passphrase = passphrase;
+ mSecurityType = securityType;
+ mPassphrase = passphrase;
return this;
}
@@ -589,5 +711,107 @@
mMaxNumberOfClients = maxNumberOfClients;
return this;
}
+
+ /**
+ * Specifies the shutdown timeout in milliseconds.
+ * The Soft AP will shut down when there are no devices connected to it for
+ * the timeout duration.
+ *
+ * Specify a value of 0 to have the framework automatically use default timeout
+ * setting which defined in {@link R.integer.config_wifi_framework_soft_ap_timeout_delay}
+ *
+ * <p>
+ * <li>If not set, defaults to 0</li>
+ * <li>The shut down timout will apply when
+ * {@link Settings.Global.SOFT_AP_TIMEOUT_ENABLED} is true</li>
+ *
+ * @param timeoutMillis milliseconds of the timeout delay.
+ * @return Builder for chaining.
+ */
+ @NonNull
+ public Builder setShutdownTimeoutMillis(int timeoutMillis) {
+ if (timeoutMillis < 0) {
+ throw new IllegalArgumentException("Invalid timeout value");
+ }
+ mShutdownTimeoutMillis = timeoutMillis;
+ return this;
+ }
+
+ /**
+ * Configure the Soft AP to require manual user control of client association.
+ * If disabled (the default) then any client can associate to this Soft AP using the
+ * correct credentials until the Soft AP capacity is reached (capacity is hardware, carrier,
+ * or user limited - using {@link #setMaxNumberOfClients(int)}).
+ *
+ * If manual user control is enabled then clients will be accepted, rejected, or require
+ * a user approval based on the configuration provided by
+ * {@link #setClientList(List, List)}.
+ *
+ * <p>
+ * This method requires hardware support. Hardware support can be determined using
+ * {@link WifiManager.SoftApCallback#onCapabilityChanged(SoftApCapability)} and
+ * {@link SoftApCapability#isFeatureSupported(int)}
+ * with {@link SoftApCapability.SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT}
+ *
+ * <p>
+ * If the method is called on a device without hardware support then starting the soft AP
+ * using {@link WifiManager#startTetheredHotspot(SoftApConfiguration)} will fail with
+ * {@link WifiManager#SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}.
+ *
+ * <p>
+ * <li>If not set, defaults to false (i.e The authoriztion is not required).</li>
+ *
+ * @param enabled true for enabling the control by user, false otherwise.
+ * @return Builder for chaining.
+ */
+ @NonNull
+ public Builder enableClientControlByUser(boolean enabled) {
+ mClientControlByUser = enabled;
+ return this;
+ }
+
+
+ /**
+ * This method together with {@link enableClientControlByUser(boolean)} control client
+ * connections to the AP. If {@link enableClientControlByUser(false)} is configured than
+ * this API has no effect and clients are allowed to associate to the AP (within limit of
+ * max number of clients).
+ *
+ * If {@link enableClientControlByUser(true)} is configured then this API configures
+ * 2 lists:
+ * <ul>
+ * <li>List of clients which are blocked. These are rejected.</li>
+ * <li>List of clients which are explicitly allowed. These are auto-accepted.</li>
+ * </ul>
+ *
+ * <p>
+ * All other clients which attempt to associate, whose MAC addresses are on neither list,
+ * are:
+ * <ul>
+ * <li>Rejected</li>
+ * <li>A callback {@link WifiManager.SoftApCallback#onBlockedClientConnecting(WifiClient)}
+ * is issued (which allows the user to add them to the allowed client list if desired).<li>
+ * </ul>
+ *
+ * @param blockedClientList list of clients which are not allowed to associate to the AP.
+ * @param allowedClientList list of clients which are allowed to associate to the AP
+ * without user pre-approval.
+ * @return Builder for chaining.
+ */
+ @NonNull
+ public Builder setClientList(@NonNull List<MacAddress> blockedClientList,
+ @NonNull List<MacAddress> allowedClientList) {
+ mBlockedClientList = new ArrayList<>(blockedClientList);
+ mAllowedClientList = new ArrayList<>(allowedClientList);
+ Iterator<MacAddress> iterator = mAllowedClientList.iterator();
+ while (iterator.hasNext()) {
+ MacAddress client = iterator.next();
+ int index = mBlockedClientList.indexOf(client);
+ if (index != -1) {
+ throw new IllegalArgumentException("A MacAddress exist in both list");
+ }
+ }
+ return this;
+ }
}
}
diff --git a/wifi/java/android/net/wifi/WifiAnnotations.java b/wifi/java/android/net/wifi/WifiAnnotations.java
index 9223d28..05e5b1d 100644
--- a/wifi/java/android/net/wifi/WifiAnnotations.java
+++ b/wifi/java/android/net/wifi/WifiAnnotations.java
@@ -60,4 +60,45 @@
})
@Retention(RetentionPolicy.SOURCE)
public @interface Bandwidth {}
+
+ @IntDef(prefix = { "PROTOCOL_" }, value = {
+ ScanResult.PROTOCOL_NONE,
+ ScanResult.PROTOCOL_WPA,
+ ScanResult.PROTOCOL_RSN,
+ ScanResult.PROTOCOL_OSEN,
+ ScanResult.PROTOCOL_WAPI
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Protocol {}
+
+ @IntDef(prefix = { "KEY_MGMT_" }, value = {
+ ScanResult.KEY_MGMT_NONE,
+ ScanResult.KEY_MGMT_PSK,
+ ScanResult.KEY_MGMT_EAP,
+ ScanResult.KEY_MGMT_FT_PSK,
+ ScanResult.KEY_MGMT_FT_EAP,
+ ScanResult.KEY_MGMT_PSK_SHA256,
+ ScanResult.KEY_MGMT_EAP_SHA256,
+ ScanResult.KEY_MGMT_OSEN,
+ ScanResult.KEY_MGMT_SAE,
+ ScanResult.KEY_MGMT_OWE,
+ ScanResult.KEY_MGMT_EAP_SUITE_B_192,
+ ScanResult.KEY_MGMT_FT_SAE,
+ ScanResult.KEY_MGMT_OWE_TRANSITION,
+ ScanResult.KEY_MGMT_WAPI_PSK,
+ ScanResult.KEY_MGMT_WAPI_CERT
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface KeyMgmt {}
+
+ @IntDef(prefix = { "CIPHER_" }, value = {
+ ScanResult.CIPHER_NONE,
+ ScanResult.CIPHER_NO_GROUP_ADDRESSED,
+ ScanResult.CIPHER_TKIP,
+ ScanResult.CIPHER_CCMP,
+ ScanResult.CIPHER_GCMP_256,
+ ScanResult.CIPHER_SMS4
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Cipher {}
}
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 2a165d3..b2fbb40 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -20,7 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.PackageManager;
import android.net.IpConfiguration;
import android.net.IpConfiguration.ProxySettings;
@@ -29,6 +29,7 @@
import android.net.ProxyInfo;
import android.net.StaticIpConfiguration;
import android.net.Uri;
+import android.net.util.MacAddressUtils;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
@@ -39,6 +40,8 @@
import android.util.Log;
import android.util.SparseArray;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
@@ -253,9 +256,12 @@
/** LEAP/Network EAP (only used with LEAP) */
public static final int LEAP = 2;
+ /** SAE (Used only for WPA3-Personal) */
+ public static final int SAE = 3;
+
public static final String varName = "auth_alg";
- public static final String[] strings = { "OPEN", "SHARED", "LEAP" };
+ public static final String[] strings = { "OPEN", "SHARED", "LEAP", "SAE" };
}
/**
@@ -367,7 +373,6 @@
* ECDHE_ECDSA
* ECDHE_RSA
* </pre>
- * @hide
*/
public static class SuiteBCipher {
private SuiteBCipher() { }
@@ -468,10 +473,13 @@
break;
case SECURITY_TYPE_SAE:
allowedKeyManagement.set(WifiConfiguration.KeyMgmt.SAE);
+ allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
+ allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
requirePMF = true;
break;
case SECURITY_TYPE_EAP_SUITE_B:
allowedKeyManagement.set(WifiConfiguration.KeyMgmt.SUITE_B_192);
+ allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.GCMP_256);
allowedGroupCiphers.set(WifiConfiguration.GroupCipher.GCMP_256);
allowedGroupManagementCiphers.set(WifiConfiguration.GroupMgmtCipher.BIP_GMAC_256);
// Note: allowedSuiteBCiphers bitset will be set by the service once the
@@ -480,6 +488,8 @@
break;
case SECURITY_TYPE_OWE:
allowedKeyManagement.set(WifiConfiguration.KeyMgmt.OWE);
+ allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
+ allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
requirePMF = true;
break;
case SECURITY_TYPE_WAPI_PSK:
@@ -846,18 +856,6 @@
/**
* @hide
- * For debug: date at which the config was last updated
- */
- public String updateTime;
-
- /**
- * @hide
- * For debug: date at which the config was last updated
- */
- public String creationTime;
-
- /**
- * @hide
* The WiFi configuration is considered to have no internet access for purpose of autojoining
* if there has been a report of it having no internet access, and, it never have had
* internet access in the past.
@@ -1156,7 +1154,7 @@
* @return true if mac is good to use
*/
public static boolean isValidMacAddressForRandomization(MacAddress mac) {
- return mac != null && !mac.isMulticastAddress() && mac.isLocallyAssigned()
+ return mac != null && !MacAddressUtils.isMulticastAddress(mac) && mac.isLocallyAssigned()
&& !MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS).equals(mac);
}
@@ -1200,22 +1198,27 @@
*/
@SystemApi
public static class NetworkSelectionStatus {
- // Quality Network Selection Status enable, temporary disabled, permanently disabled
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "NETWORK_SELECTION_",
+ value = {
+ NETWORK_SELECTION_ENABLED,
+ NETWORK_SELECTION_TEMPORARY_DISABLED,
+ NETWORK_SELECTION_PERMANENTLY_DISABLED})
+ public @interface NetworkEnabledStatus {}
/**
- * This network is allowed to join Quality Network Selection
- * @hide
+ * This network will be considered as a potential candidate to connect to during network
+ * selection.
*/
public static final int NETWORK_SELECTION_ENABLED = 0;
/**
- * network was temporary disabled. Can be re-enabled after a time period expire
- * @hide
+ * This network was temporary disabled. May be re-enabled after a time out.
*/
- public static final int NETWORK_SELECTION_TEMPORARY_DISABLED = 1;
+ public static final int NETWORK_SELECTION_TEMPORARY_DISABLED = 1;
/**
- * network was permanently disabled.
- * @hide
+ * This network was permanently disabled.
*/
- public static final int NETWORK_SELECTION_PERMANENTLY_DISABLED = 2;
+ public static final int NETWORK_SELECTION_PERMANENTLY_DISABLED = 2;
/**
* Maximum Network selection status
* @hide
@@ -1447,6 +1450,7 @@
* Network selection status, should be in one of three status: enable, temporaily disabled
* or permanently disabled
*/
+ @NetworkEnabledStatus
private int mStatus;
/**
@@ -1477,12 +1481,6 @@
private String mConnectChoice;
/**
- * The system timestamp when we records the connectChoice. This value is obtained from
- * System.currentTimeMillis
- */
- private long mConnectChoiceTimestamp = INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP;
-
- /**
* Used to cache the temporary candidate during the network selection procedure. It will be
* kept updating once a new scan result has a higher score than current one
*/
@@ -1585,25 +1583,6 @@
mConnectChoice = newConnectChoice;
}
- /**
- * get the timeStamp when user select a choice over this configuration
- * @return returns when current connectChoice is set (time from System.currentTimeMillis)
- * @hide
- */
- public long getConnectChoiceTimestamp() {
- return mConnectChoiceTimestamp;
- }
-
- /**
- * set the timeStamp when user select a choice over this configuration
- * @param timeStamp, the timestamp set to connectChoiceTimestamp, expected timestamp should
- * be obtained from System.currentTimeMillis
- * @hide
- */
- public void setConnectChoiceTimestamp(long timeStamp) {
- mConnectChoiceTimestamp = timeStamp;
- }
-
/** Get the current Quality network selection status as a String (for debugging). */
@NonNull
public String getNetworkStatusString() {
@@ -1627,6 +1606,56 @@
}
/**
+ * NetworkSelectionStatus exports an immutable public API.
+ * However, test code has a need to construct a NetworkSelectionStatus in a specific state.
+ * (Note that mocking using Mockito does not work if the object needs to be parceled and
+ * unparceled.)
+ * Export a @SystemApi Builder to allow tests to construct a NetworkSelectionStatus object
+ * in the desired state, without sacrificing NetworkSelectionStatus's immutability.
+ */
+ @VisibleForTesting
+ public static final class Builder {
+ private final NetworkSelectionStatus mNetworkSelectionStatus =
+ new NetworkSelectionStatus();
+
+ /**
+ * Set the current network selection status.
+ * One of:
+ * {@link #NETWORK_SELECTION_ENABLED},
+ * {@link #NETWORK_SELECTION_TEMPORARY_DISABLED},
+ * {@link #NETWORK_SELECTION_PERMANENTLY_DISABLED}
+ * @see NetworkSelectionStatus#getNetworkSelectionStatus()
+ */
+ @NonNull
+ public Builder setNetworkSelectionStatus(@NetworkEnabledStatus int status) {
+ mNetworkSelectionStatus.setNetworkSelectionStatus(status);
+ return this;
+ }
+
+ /**
+ *
+ * Set the current network's disable reason.
+ * One of the {@link #NETWORK_SELECTION_ENABLE} or DISABLED_* constants.
+ * e.g. {@link #DISABLED_ASSOCIATION_REJECTION}.
+ * @see NetworkSelectionStatus#getNetworkSelectionDisableReason()
+ */
+ @NonNull
+ public Builder setNetworkSelectionDisableReason(
+ @NetworkSelectionDisableReason int reason) {
+ mNetworkSelectionStatus.setNetworkSelectionDisableReason(reason);
+ return this;
+ }
+
+ /**
+ * Build a NetworkSelectionStatus object.
+ */
+ @NonNull
+ public NetworkSelectionStatus build() {
+ return mNetworkSelectionStatus;
+ }
+ }
+
+ /**
* Get the network disable reason string for a reason code (for debugging).
* @param reason specific error reason. One of the {@link #NETWORK_SELECTION_ENABLE} or
* DISABLED_* constants e.g. {@link #DISABLED_ASSOCIATION_REJECTION}.
@@ -1652,10 +1681,13 @@
}
/**
- * get current network network selection status
- * @return return current network network selection status
- * @hide
+ * Get the current network network selection status.
+ * One of:
+ * {@link #NETWORK_SELECTION_ENABLED},
+ * {@link #NETWORK_SELECTION_TEMPORARY_DISABLED},
+ * {@link #NETWORK_SELECTION_PERMANENTLY_DISABLED}
*/
+ @NetworkEnabledStatus
public int getNetworkSelectionStatus() {
return mStatus;
}
@@ -1833,7 +1865,6 @@
setCandidate(source.getCandidate());
setCandidateScore(source.getCandidateScore());
setConnectChoice(source.getConnectChoice());
- setConnectChoiceTimestamp(source.getConnectChoiceTimestamp());
setHasEverConnected(source.getHasEverConnected());
}
@@ -1850,7 +1881,6 @@
if (getConnectChoice() != null) {
dest.writeInt(CONNECT_CHOICE_EXISTS);
dest.writeString(getConnectChoice());
- dest.writeLong(getConnectChoiceTimestamp());
} else {
dest.writeInt(CONNECT_CHOICE_NOT_EXISTS);
}
@@ -1869,10 +1899,8 @@
setNetworkSelectionBSSID(in.readString());
if (in.readInt() == CONNECT_CHOICE_EXISTS) {
setConnectChoice(in.readString());
- setConnectChoiceTimestamp(in.readLong());
} else {
setConnectChoice(null);
- setConnectChoiceTimestamp(INVALID_NETWORK_SELECTION_DISABLE_TIMESTAMP);
}
setHasEverConnected(in.readInt() != 0);
}
@@ -1957,10 +1985,11 @@
}
/**
- * Set the network selection status
+ * Set the network selection status.
* @hide
*/
- public void setNetworkSelectionStatus(NetworkSelectionStatus status) {
+ @SystemApi
+ public void setNetworkSelectionStatus(@NonNull NetworkSelectionStatus status) {
mNetworkSelectionStatus = status;
}
@@ -2095,9 +2124,6 @@
}
if (mNetworkSelectionStatus.getConnectChoice() != null) {
sbuf.append(" connect choice: ").append(mNetworkSelectionStatus.getConnectChoice());
- sbuf.append(" connect choice set time: ")
- .append(logTimeOfDay(
- mNetworkSelectionStatus.getConnectChoiceTimestamp()));
}
sbuf.append(" hasEverConnected: ")
.append(mNetworkSelectionStatus.getHasEverConnected()).append("\n");
@@ -2109,12 +2135,6 @@
sbuf.append(" numNoInternetAccessReports ");
sbuf.append(this.numNoInternetAccessReports).append("\n");
}
- if (this.updateTime != null) {
- sbuf.append(" update ").append(this.updateTime).append("\n");
- }
- if (this.creationTime != null) {
- sbuf.append(" creation ").append(this.creationTime).append("\n");
- }
if (this.validatedInternetAccess) sbuf.append(" validatedInternetAccess");
if (this.ephemeral) sbuf.append(" ephemeral");
if (this.osu) sbuf.append(" osu");
@@ -2428,7 +2448,8 @@
} else if (allowedKeyManagement.get(KeyMgmt.WPA_EAP)
|| allowedKeyManagement.get(KeyMgmt.IEEE8021X)) {
key = SSID + KeyMgmt.strings[KeyMgmt.WPA_EAP];
- } else if (wepKeys[0] != null) {
+ } else if (wepTxKeyIndex >= 0 && wepTxKeyIndex < wepKeys.length
+ && wepKeys[wepTxKeyIndex] != null) {
key = SSID + "WEP";
} else if (allowedKeyManagement.get(KeyMgmt.OWE)) {
key = SSID + KeyMgmt.strings[KeyMgmt.OWE];
@@ -2590,9 +2611,8 @@
return mPasspointManagementObjectTree;
}
- /** copy constructor {@hide} */
- @UnsupportedAppUsage
- public WifiConfiguration(WifiConfiguration source) {
+ /** Copy constructor */
+ public WifiConfiguration(@NonNull WifiConfiguration source) {
if (source != null) {
networkId = source.networkId;
status = source.status;
@@ -2661,8 +2681,6 @@
allowAutojoin = source.allowAutojoin;
numNoInternetAccessReports = source.numNoInternetAccessReports;
noInternetAccessExpected = source.noInternetAccessExpected;
- creationTime = source.creationTime;
- updateTime = source.updateTime;
shared = source.shared;
recentFailure.setAssociationStatus(source.recentFailure.getAssociationStatus());
mRandomizedMacAddress = source.mRandomizedMacAddress;
diff --git a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
index 7a59a4f..5edcc2d 100644
--- a/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
+++ b/wifi/java/android/net/wifi/WifiEnterpriseConfig.java
@@ -19,7 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.TextUtils;
@@ -1028,8 +1028,10 @@
}
/**
- * @hide
+ * Get the client private key as supplied in {@link #setClientKeyEntryWithCertificateChain}, or
+ * null if unset.
*/
+ @Nullable
public PrivateKey getClientPrivateKey() {
return mClientPrivateKey;
}
@@ -1336,20 +1338,18 @@
}
/**
- * If the current authentication method needs SIM card.
- * @return true if the credential information require SIM card for current authentication
+ * Utility method to determine whether the configuration's authentication method is SIM-based.
+ *
+ * @return true if the credential information requires SIM card for current authentication
* method, otherwise it returns false.
- * @hide
*/
- public boolean requireSimCredential() {
+ public boolean isAuthenticationSimBased() {
if (mEapMethod == Eap.SIM || mEapMethod == Eap.AKA || mEapMethod == Eap.AKA_PRIME) {
return true;
}
if (mEapMethod == Eap.PEAP) {
- if (mPhase2Method == Phase2.SIM || mPhase2Method == Phase2.AKA
- || mPhase2Method == Phase2.AKA_PRIME) {
- return true;
- }
+ return mPhase2Method == Phase2.SIM || mPhase2Method == Phase2.AKA
+ || mPhase2Method == Phase2.AKA_PRIME;
}
return false;
}
diff --git a/wifi/java/android/net/wifi/WifiInfo.java b/wifi/java/android/net/wifi/WifiInfo.java
index f728491..7cd00b9 100644
--- a/wifi/java/android/net/wifi/WifiInfo.java
+++ b/wifi/java/android/net/wifi/WifiInfo.java
@@ -17,9 +17,10 @@
package android.net.wifi;
import android.annotation.IntRange;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.net.NetworkInfo.DetailedState;
import android.net.shared.Inet4AddressUtils;
import android.os.Build;
@@ -27,6 +28,8 @@
import android.os.Parcelable;
import android.text.TextUtils;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
@@ -356,6 +359,72 @@
}
}
+ /**
+ * WifiInfo exports an immutable public API.
+ * However, test code has a need to construct a WifiInfo in a specific state.
+ * (Note that mocking using Mockito does not work if the object needs to be parceled and
+ * unparceled.)
+ * Export a @SystemApi Builder to allow tests to construct a WifiInfo object
+ * in the desired state, without sacrificing WifiInfo's immutability.
+ *
+ * @hide
+ */
+ // This builder was not made public to reduce confusion for external developers as there are
+ // no legitimate uses for this builder except for testing.
+ @SystemApi
+ @VisibleForTesting
+ public static final class Builder {
+ private final WifiInfo mWifiInfo = new WifiInfo();
+
+ /**
+ * Set the SSID, in the form of a raw byte array.
+ * @see WifiInfo#getSSID()
+ */
+ @NonNull
+ public Builder setSsid(@NonNull byte[] ssid) {
+ mWifiInfo.setSSID(WifiSsid.createFromByteArray(ssid));
+ return this;
+ }
+
+ /**
+ * Set the BSSID.
+ * @see WifiInfo#getBSSID()
+ */
+ @NonNull
+ public Builder setBssid(@NonNull String bssid) {
+ mWifiInfo.setBSSID(bssid);
+ return this;
+ }
+
+ /**
+ * Set the RSSI, in dBm.
+ * @see WifiInfo#getRssi()
+ */
+ @NonNull
+ public Builder setRssi(int rssi) {
+ mWifiInfo.setRssi(rssi);
+ return this;
+ }
+
+ /**
+ * Set the network ID.
+ * @see WifiInfo#getNetworkId()
+ */
+ @NonNull
+ public Builder setNetworkId(int networkId) {
+ mWifiInfo.setNetworkId(networkId);
+ return this;
+ }
+
+ /**
+ * Build a WifiInfo object.
+ */
+ @NonNull
+ public WifiInfo build() {
+ return mWifiInfo;
+ }
+ }
+
/** @hide */
public void setSSID(WifiSsid wifiSsid) {
mWifiSsid = wifiSsid;
@@ -631,6 +700,11 @@
/**
* Returns the Fully Qualified Domain Name of the network if it is a Passpoint network.
+ * <p>
+ * The FQDN may be
+ * <lt>{@code null} if no network currently connected, currently connected network is not
+ * passpoint network or the caller has insufficient permissions to access the FQDN.</lt>
+ * </p>
*/
public @Nullable String getPasspointFqdn() {
return mFqdn;
@@ -643,6 +717,12 @@
/**
* Returns the Provider Friendly Name of the network if it is a Passpoint network.
+ * <p>
+ * The Provider Friendly Name may be
+ * <lt>{@code null} if no network currently connected, currently connected network is not
+ * passpoint network or the caller has insufficient permissions to access the Provider Friendly
+ * Name. </lt>
+ * </p>
*/
public @Nullable String getPasspointProviderFriendlyName() {
return mProviderFriendlyName;
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index 7693f9a..9540103 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -29,8 +29,8 @@
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
import android.annotation.SystemService;
-import android.annotation.UnsupportedAppUsage;
import android.app.ActivityManager;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.net.ConnectivityManager;
@@ -233,16 +233,20 @@
public @interface SuggestionConnectionStatusCode {}
/**
- * Broadcast intent action indicating whether Wi-Fi scanning is allowed currently
- * @hide
+ * Broadcast intent action indicating whether Wi-Fi scanning is currently available.
+ * Available extras:
+ * - {@link #EXTRA_SCAN_AVAILABLE}
*/
- public static final String WIFI_SCAN_AVAILABLE = "wifi_scan_available";
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_WIFI_SCAN_AVAILABLE =
+ "android.net.wifi.action.WIFI_SCAN_AVAILABLE";
/**
- * Extra int indicating scan availability, WIFI_STATE_ENABLED and WIFI_STATE_DISABLED
- * @hide
+ * A boolean extra indicating whether scanning is currently available.
+ * Sent in the broadcast {@link #ACTION_WIFI_SCAN_AVAILABLE}.
+ * Its value is true if scanning is currently available, false otherwise.
*/
- public static final String EXTRA_SCAN_AVAILABLE = "scan_enabled";
+ public static final String EXTRA_SCAN_AVAILABLE = "android.net.wifi.extra.SCAN_AVAILABLE";
/**
* Broadcast intent action indicating that the credential of a Wi-Fi network
@@ -545,15 +549,22 @@
public static final String EXTRA_WIFI_AP_STATE = "wifi_state";
/**
- * The look up key for an int that indicates why softAP started failed
- * currently support general and no_channel
- * @see #SAP_START_FAILURE_GENERAL
- * @see #SAP_START_FAILURE_NO_CHANNEL
- * @see #SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION
+ * An extra containing the int error code for Soft AP start failure.
+ * Can be obtained from the {@link #WIFI_AP_STATE_CHANGED_ACTION} using
+ * {@link android.content.Intent#getIntExtra}.
+ * This extra will only be attached if {@link #EXTRA_WIFI_AP_STATE} is
+ * attached and is equal to {@link #WIFI_AP_STATE_FAILED}.
+ *
+ * The error code will be one of:
+ * {@link #SAP_START_FAILURE_GENERAL},
+ * {@link #SAP_START_FAILURE_NO_CHANNEL},
+ * {@link #SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}
*
* @hide
*/
- public static final String EXTRA_WIFI_AP_FAILURE_REASON = "wifi_ap_error_code";
+ @SystemApi
+ public static final String EXTRA_WIFI_AP_FAILURE_REASON =
+ "android.net.wifi.extra.WIFI_AP_FAILURE_REASON";
/**
* The previous Wi-Fi state.
*
@@ -659,7 +670,8 @@
public @interface SapStartFailure {}
/**
- * All other reasons for AP start failure besides {@link #SAP_START_FAILURE_NO_CHANNEL}.
+ * All other reasons for AP start failure besides {@link #SAP_START_FAILURE_NO_CHANNEL} and
+ * {@link #SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}.
*
* @hide
*/
@@ -684,6 +696,37 @@
@SystemApi
public static final int SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION = 2;
+
+ /** @hide */
+ @IntDef(flag = false, prefix = { "SAP_CLIENT_BLOCKED_REASON_" }, value = {
+ SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER,
+ SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SapClientBlockedReason {}
+
+ /**
+ * If Soft Ap client is blocked, this reason code means that client doesn't exist in the
+ * specified configuration {@link SoftApConfiguration.Builder#setClientList(List, List)}
+ * and the {@link SoftApConfiguration.Builder#enableClientControlByUser(true)}
+ * is configured as well.
+ * @hide
+ */
+ @SystemApi
+ public static final int SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER = 0;
+
+ /**
+ * If Soft Ap client is blocked, this reason code means that no more clients can be
+ * associated to this AP since it reached maximum capacity. The maximum capacity is
+ * the minimum of {@link SoftApConfiguration.Builder#setMaxNumberOfClients(int)} and
+ * {@link SoftApCapability#getMaxSupportedClients} which get from
+ * {@link WifiManager.SoftApCallback#onCapabilityChanged(SoftApCapability)}.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS = 1;
+
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = {"IFACE_IP_MODE_"}, value = {
@@ -1359,6 +1402,36 @@
}
/**
+ * Retrieve a list of {@link WifiConfiguration} for available {@link WifiNetworkSuggestion}
+ * matching the given list of {@link ScanResult}.
+ *
+ * An available {@link WifiNetworkSuggestion} must satisfy:
+ * <ul>
+ * <li> Matching one of the {@link ScanResult} from the given list.
+ * <li> and {@link WifiNetworkSuggestion.Builder#setIsUserAllowedToManuallyConnect(boolean)} set
+ * to true.
+ * </ul>
+ *
+ * @param scanResults a list of scanResult.
+ * @return a list of @link WifiConfiguration} for available {@link WifiNetworkSuggestion}
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.NETWORK_SETTINGS,
+ android.Manifest.permission.NETWORK_SETUP_WIZARD
+ })
+ @NonNull
+ public List<WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(
+ @NonNull List<ScanResult> scanResults) {
+ try {
+ return mService.getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(scanResults);
+ } catch (RemoteException e) {
+ throw e.rethrowAsRuntimeException();
+ }
+ }
+
+ /**
* Returns a list of unique Hotspot 2.0 OSU (Online Sign-Up) providers associated with a given
* list of ScanResult.
*
@@ -2296,6 +2369,15 @@
}
/**
+ * Query whether the device supports Station (STA) + Access point (AP) concurrency or not.
+ *
+ * @return true if this device supports STA + AP concurrency, false otherwise.
+ */
+ public boolean isStaApConcurrencySupported() {
+ return isFeatureSupported(WIFI_FEATURE_AP_STA);
+ }
+
+ /**
* @deprecated Please use {@link android.content.pm.PackageManager#hasSystemFeature(String)}
* with {@link android.content.pm.PackageManager#FEATURE_WIFI_RTT} and
* {@link android.content.pm.PackageManager#FEATURE_WIFI_AWARE}.
@@ -2533,6 +2615,8 @@
* the same permissions as {@link #getScanResults}. If such access is not allowed,
* {@link WifiInfo#getSSID} will return {@link #UNKNOWN_SSID} and
* {@link WifiInfo#getBSSID} will return {@code "02:00:00:00:00:00"}.
+ * {@link WifiInfo#getPasspointFqdn()} will return null.
+ * {@link WifiInfo#getPasspointProviderFriendlyName()} will return null.
*
* @return the Wi-Fi information, contained in {@link WifiInfo}.
*/
@@ -3192,8 +3276,17 @@
/**
* Sets the Wi-Fi AP Configuration.
*
+ * If the API is called while the soft AP is enabled, the configuration will apply to
+ * the current soft AP if the new configuration only includes
+ * {@link SoftApConfiguration.Builder#setMaxNumberOfClients(int)}
+ * or {@link SoftApConfiguration.Builder#setShutdownTimeoutMillis(int)}
+ * or {@link SoftApConfiguration.Builder#enableClientControlByUser(boolean)}
+ * or {@link SoftApConfiguration.Builder#setClientList(List, List)}.
+ *
+ * Otherwise, the configuration changes will be applied when the Soft AP is next started
+ * (the framework will not stop/start the AP).
+ *
* @param softApConfig A valid SoftApConfiguration specifying the configuration of the SAP.
-
* @return {@code true} if the operation succeeded, {@code false} otherwise
*
* @hide
@@ -3423,7 +3516,8 @@
* {@link #WIFI_AP_STATE_ENABLING}, {@link #WIFI_AP_STATE_FAILED}
* @param failureReason reason when in failed state. One of
* {@link #SAP_START_FAILURE_GENERAL},
- * {@link #SAP_START_FAILURE_NO_CHANNEL}
+ * {@link #SAP_START_FAILURE_NO_CHANNEL},
+ * {@link #SAP_START_FAILURE_UNSUPPORTED_CONFIGURATION}
*/
default void onStateChanged(@WifiApState int state, @SapStartFailure int failureReason) {}
@@ -3452,6 +3546,22 @@
// Do nothing: can be updated to add SoftApCapability details (e.g. meximum supported
// client number) to the UI.
}
+
+ /**
+ * Called when client trying to connect but device blocked the client with specific reason.
+ *
+ * Can be used to ask user to update client to allowed list or blocked list
+ * when reason is {@link SAP_CLIENT_BLOCK_REASON_CODE_BLOCKED_BY_USER}, or
+ * indicate the block due to maximum supported client number limitation when reason is
+ * {@link SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS}.
+ *
+ * @param client the currently blocked client.
+ * @param blockedReason one of blocked reason from {@link SapClientBlockedReason}
+ */
+ default void onBlockedClientConnecting(@NonNull WifiClient client,
+ @SapClientBlockedReason int blockedReason) {
+ // Do nothing: can be used to ask user to update client to allowed list or blocked list.
+ }
}
/**
@@ -3518,6 +3628,19 @@
mCallback.onCapabilityChanged(capability);
});
}
+
+ @Override
+ public void onBlockedClientConnecting(@NonNull WifiClient client, int blockedReason) {
+ if (mVerboseLoggingEnabled) {
+ Log.v(TAG, "SoftApCallbackProxy: onBlockedClientConnecting: client=" + client
+ + " with reason = " + blockedReason);
+ }
+
+ Binder.clearCallingIdentity();
+ mExecutor.execute(() -> {
+ mCallback.onBlockedClientConnecting(client, blockedReason);
+ });
+ }
}
/**
@@ -4103,6 +4226,23 @@
}
/**
+ * Configure auto-join settings for a Passpoint profile.
+ *
+ * @param fqdn the FQDN (fully qualified domain name) of the passpoint profile.
+ * @param enableAutoJoin true to enable autojoin, false to disable autojoin.
+ * @hide
+ */
+ @SystemApi
+ @RequiresPermission(android.Manifest.permission.NETWORK_SETTINGS)
+ public void allowAutojoinPasspoint(@NonNull String fqdn, boolean enableAutoJoin) {
+ try {
+ mService.allowAutojoinPasspoint(fqdn, enableAutoJoin);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Disable an ephemeral network.
*
* @param ssid in the format of WifiConfiguration's SSID.
diff --git a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
index 9c1475f..2fba5a3 100644
--- a/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
+++ b/wifi/java/android/net/wifi/WifiNetworkSuggestion.java
@@ -116,12 +116,18 @@
/**
* Whether this network is shared credential with user to allow user manually connect.
*/
- private boolean mIsUserAllowed;
+ private boolean mIsSharedWithUser;
/**
- * Whether the setIsUserAllowedToManuallyConnect have been called.
+ * Whether the setCredentialSharedWithUser have been called.
*/
- private boolean mIsUserAllowedBeenSet;
+ private boolean mIsSharedWithUserSet;
+
+ /**
+ * Whether this network is initialized with auto-join enabled (the default) or not.
+ */
+ private boolean mIsInitialAutoJoinEnabled;
+
/**
* Pre-shared key for use with WAPI-PSK networks.
*/
@@ -146,8 +152,9 @@
mIsAppInteractionRequired = false;
mIsUserInteractionRequired = false;
mIsMetered = false;
- mIsUserAllowed = true;
- mIsUserAllowedBeenSet = false;
+ mIsSharedWithUser = true;
+ mIsSharedWithUserSet = false;
+ mIsInitialAutoJoinEnabled = true;
mPriority = UNASSIGNED_PRIORITY;
mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
mWapiPskPassphrase = null;
@@ -430,13 +437,34 @@
* <li>If not set, defaults to true (i.e. allow user to manually connect) for secure
* networks and false for open networks.</li>
*
- * @param isAllowed {@code true} to indicate that the credentials may be used by the user to
+ * @param isShared {@code true} to indicate that the credentials may be used by the user to
* manually connect to the network, {@code false} otherwise.
* @return Instance of {@link Builder} to enable chaining of the builder method.
*/
- public @NonNull Builder setIsUserAllowedToManuallyConnect(boolean isAllowed) {
- mIsUserAllowed = isAllowed;
- mIsUserAllowedBeenSet = true;
+ public @NonNull Builder setCredentialSharedWithUser(boolean isShared) {
+ mIsSharedWithUser = isShared;
+ mIsSharedWithUserSet = true;
+ return this;
+ }
+
+ /**
+ * Specifies whether the suggestion is created with auto-join enabled or disabled. The
+ * user may modify the auto-join configuration of a suggestion directly once the device
+ * associates to the network.
+ * <p>
+ * If auto-join is initialized as disabled the user may still be able to manually connect
+ * to the network. Therefore, disabling auto-join only makes sense if
+ * {@link #setCredentialSharedWithUser(boolean)} is set to true (the default) which
+ * itself implies a secure (non-open) network.
+ * <p>
+ * If not set, defaults to true (i.e. auto-join is initialized as enabled).
+ *
+ * @param enabled true for initializing with auto-join enabled (the default), false to
+ * initializing with auto-join disabled.
+ * @return Instance of (@link {@link Builder} to enable chaining of the builder method.
+ */
+ public @NonNull Builder setIsInitialAutoJoinEnabled(boolean enabled) {
+ mIsInitialAutoJoinEnabled = enabled;
return this;
}
@@ -587,7 +615,6 @@
+ "suggestion with Passpoint configuration");
}
wifiConfiguration = buildWifiConfigurationForPasspoint();
-
} else {
if (mSsid == null) {
throw new IllegalStateException("setSsid should be invoked for suggestion");
@@ -602,11 +629,17 @@
}
wifiConfiguration = buildWifiConfiguration();
if (wifiConfiguration.isOpenNetwork()) {
- if (mIsUserAllowedBeenSet && mIsUserAllowed) {
+ if (mIsSharedWithUserSet && mIsSharedWithUser) {
throw new IllegalStateException("Open network should not be "
- + "setIsUserAllowedToManuallyConnect to true");
+ + "setCredentialSharedWithUser to true");
}
- mIsUserAllowed = false;
+ mIsSharedWithUser = false;
+ }
+
+ if (!mIsSharedWithUser && !mIsInitialAutoJoinEnabled) {
+ throw new IllegalStateException("Should have not a network with both "
+ + "setIsUserAllowedToManuallyConnect and "
+ + "setIsAutoJoinEnabled set to false");
}
}
@@ -615,7 +648,8 @@
mPasspointConfiguration,
mIsAppInteractionRequired,
mIsUserInteractionRequired,
- mIsUserAllowed);
+ mIsSharedWithUser,
+ mIsInitialAutoJoinEnabled);
}
}
@@ -642,6 +676,7 @@
* @hide
*/
public final boolean isUserInteractionRequired;
+
/**
* Whether app share credential with the user, allow user use provided credential to
* connect network manually.
@@ -649,6 +684,12 @@
*/
public final boolean isUserAllowedToManuallyConnect;
+ /**
+ * Whether the suggestion will be initialized as auto-joined or not.
+ * @hide
+ */
+ public final boolean isInitialAutoJoinEnabled;
+
/** @hide */
public WifiNetworkSuggestion() {
this.wifiConfiguration = null;
@@ -656,6 +697,7 @@
this.isAppInteractionRequired = false;
this.isUserInteractionRequired = false;
this.isUserAllowedToManuallyConnect = true;
+ this.isInitialAutoJoinEnabled = true;
}
/** @hide */
@@ -663,7 +705,8 @@
@Nullable PasspointConfiguration passpointConfiguration,
boolean isAppInteractionRequired,
boolean isUserInteractionRequired,
- boolean isUserAllowedToManuallyConnect) {
+ boolean isUserAllowedToManuallyConnect,
+ boolean isInitialAutoJoinEnabled) {
checkNotNull(networkConfiguration);
this.wifiConfiguration = networkConfiguration;
this.passpointConfiguration = passpointConfiguration;
@@ -671,6 +714,7 @@
this.isAppInteractionRequired = isAppInteractionRequired;
this.isUserInteractionRequired = isUserInteractionRequired;
this.isUserAllowedToManuallyConnect = isUserAllowedToManuallyConnect;
+ this.isInitialAutoJoinEnabled = isInitialAutoJoinEnabled;
}
public static final @NonNull Creator<WifiNetworkSuggestion> CREATOR =
@@ -682,7 +726,8 @@
in.readParcelable(null), // PasspointConfiguration
in.readBoolean(), // isAppInteractionRequired
in.readBoolean(), // isUserInteractionRequired
- in.readBoolean() // isSharedCredentialWithUser
+ in.readBoolean(), // isSharedCredentialWithUser
+ in.readBoolean() // isAutoJoinEnabled
);
}
@@ -704,6 +749,7 @@
dest.writeBoolean(isAppInteractionRequired);
dest.writeBoolean(isUserInteractionRequired);
dest.writeBoolean(isUserAllowedToManuallyConnect);
+ dest.writeBoolean(isInitialAutoJoinEnabled);
}
@Override
@@ -744,6 +790,7 @@
.append(", isAppInteractionRequired=").append(isAppInteractionRequired)
.append(", isUserInteractionRequired=").append(isUserInteractionRequired)
.append(", isUserAllowedToManuallyConnect=").append(isUserAllowedToManuallyConnect)
+ .append(", isInitialAutoJoinEnabled=").append(isInitialAutoJoinEnabled)
.append(" ]");
return sb.toString();
}
diff --git a/wifi/java/android/net/wifi/WifiOemConfigStoreMigrationHook.java b/wifi/java/android/net/wifi/WifiOemConfigStoreMigrationHook.java
new file mode 100755
index 0000000..642dcb9
--- /dev/null
+++ b/wifi/java/android/net/wifi/WifiOemConfigStoreMigrationHook.java
@@ -0,0 +1,180 @@
+/*
+ * 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.net.wifi;
+
+import static com.android.internal.util.Preconditions.checkNotNull;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.List;
+
+/**
+ * Class used to provide one time hooks for existing OEM devices to migrate their config store
+ * data to the wifi mainline module.
+ * <p>
+ * Note:
+ * <li> OEM's need to implement {@link #load()} only if their
+ * existing config store format or file locations differs from the vanilla AOSP implementation (
+ * which is what the wifi mainline module understands).
+ * </li>
+ * <li> The wifi mainline module will invoke {@link #load()} method on every bootup, its
+ * the responsibility of the OEM implementation to ensure that this method returns non-null data
+ * only on the first bootup. Once the migration is done, the OEM can safely delete their config
+ * store files and then return null on any subsequent reboots. The first & only relevant invocation
+ * of {@link #load()} occurs when a previously released device upgrades to the wifi
+ * mainline module from an OEM implementation of the wifi stack.
+ * </li>
+ * @hide
+ */
+@SystemApi
+public final class WifiOemConfigStoreMigrationHook {
+ /**
+ * Container for all the wifi config data to migrate.
+ */
+ public static final class MigrationData implements Parcelable {
+ /**
+ * Builder to create instance of {@link MigrationData}.
+ */
+ public static final class Builder {
+ private List<WifiConfiguration> mUserSavedNetworkConfigurations;
+ private SoftApConfiguration mUserSoftApConfiguration;
+
+ public Builder() {
+ mUserSavedNetworkConfigurations = null;
+ mUserSoftApConfiguration = null;
+ }
+
+ /**
+ * Sets the list of all user's saved network configurations parsed from OEM config
+ * store files.
+ *
+ * @param userSavedNetworkConfigurations List of {@link WifiConfiguration} representing
+ * the list of user's saved networks
+ * @return Instance of {@link Builder} to enable chaining of the builder method.
+ */
+ public @NonNull Builder setUserSavedNetworkConfigurations(
+ @NonNull List<WifiConfiguration> userSavedNetworkConfigurations) {
+ checkNotNull(userSavedNetworkConfigurations);
+ mUserSavedNetworkConfigurations = userSavedNetworkConfigurations;
+ return this;
+ }
+
+ /**
+ * Sets the user's softap configuration parsed from OEM config store files.
+ *
+ * @param userSoftApConfiguration {@link SoftApConfiguration} representing user's
+ * SoftAp configuration
+ * @return Instance of {@link Builder} to enable chaining of the builder method.
+ */
+ public @NonNull Builder setUserSoftApConfiguration(
+ @NonNull SoftApConfiguration userSoftApConfiguration) {
+ checkNotNull(userSoftApConfiguration);
+ mUserSoftApConfiguration = userSoftApConfiguration;
+ return this;
+ }
+
+ /**
+ * Build an instance of {@link MigrationData}.
+ *
+ * @return Instance of {@link MigrationData}.
+ */
+ public @NonNull MigrationData build() {
+ return new MigrationData(mUserSavedNetworkConfigurations, mUserSoftApConfiguration);
+ }
+ }
+
+ private final List<WifiConfiguration> mUserSavedNetworkConfigurations;
+ private final SoftApConfiguration mUserSoftApConfiguration;
+
+ private MigrationData(
+ @Nullable List<WifiConfiguration> userSavedNetworkConfigurations,
+ @Nullable SoftApConfiguration userSoftApConfiguration) {
+ mUserSavedNetworkConfigurations = userSavedNetworkConfigurations;
+ mUserSoftApConfiguration = userSoftApConfiguration;
+ }
+
+ public static final @NonNull Parcelable.Creator<MigrationData> CREATOR =
+ new Parcelable.Creator<MigrationData>() {
+ @Override
+ public MigrationData createFromParcel(Parcel in) {
+ List<WifiConfiguration> userSavedNetworkConfigurations =
+ in.readArrayList(null);
+ SoftApConfiguration userSoftApConfiguration = in.readParcelable(null);
+ return new MigrationData(
+ userSavedNetworkConfigurations, userSoftApConfiguration);
+ }
+
+ @Override
+ public MigrationData[] newArray(int size) {
+ return new MigrationData[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ dest.writeList(mUserSavedNetworkConfigurations);
+ dest.writeParcelable(mUserSoftApConfiguration, flags);
+ }
+
+ /**
+ * Returns list of all user's saved network configurations.
+ *
+ * Note: Only to be returned if there is any format change in how OEM persisted this info.
+ * @return List of {@link WifiConfiguration} representing the list of user's saved networks,
+ * or null if no migration necessary.
+ */
+ @Nullable
+ public List<WifiConfiguration> getUserSavedNetworkConfigurations() {
+ return mUserSavedNetworkConfigurations;
+ }
+
+ /**
+ * Returns user's softap configuration.
+ *
+ * Note: Only to be returned if there is any format change in how OEM persisted this info.
+ * @return {@link SoftApConfiguration} representing user's SoftAp configuration,
+ * or null if no migration necessary.
+ */
+ @Nullable
+ public SoftApConfiguration getUserSoftApConfiguration() {
+ return mUserSoftApConfiguration;
+ }
+ }
+
+ private WifiOemConfigStoreMigrationHook() { }
+
+ /**
+ * Load data from OEM's config store.
+ *
+ * @return Instance of {@link MigrationData} for migrating data, null if no
+ * migration is necessary.
+ */
+ @Nullable
+ public static MigrationData load() {
+ // Note: OEM's should add code to parse data from their config store format here!
+ return null;
+ }
+}
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index 2c39c32a..4f602fa 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -833,6 +833,7 @@
* delivered to the listener. It is possible that onFullResult will not be called for all
* results of the first scan if the listener was registered during the scan.
*
+ * @param executor the Executor on which to run the callback.
* @param listener specifies the object to report events to. This object is also treated as a
* key for this request, and must also be specified to cancel the request.
* Multiple requests should also not share this object.
@@ -955,15 +956,32 @@
* starts a single scan and reports results asynchronously
* @param settings specifies various parameters for the scan; for more information look at
* {@link ScanSettings}
- * @param workSource WorkSource to blame for power usage
* @param listener specifies the object to report events to. This object is also treated as a
* key for this scan, and must also be specified to cancel the scan. Multiple
* scans should also not share this object.
+ * @param workSource WorkSource to blame for power usage
*/
@RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
public void startScan(ScanSettings settings, ScanListener listener, WorkSource workSource) {
+ startScan(settings, null, listener, workSource);
+ }
+
+ /**
+ * starts a single scan and reports results asynchronously
+ * @param settings specifies various parameters for the scan; for more information look at
+ * {@link ScanSettings}
+ * @param executor the Executor on which to run the callback.
+ * @param listener specifies the object to report events to. This object is also treated as a
+ * key for this scan, and must also be specified to cancel the scan. Multiple
+ * scans should also not share this object.
+ * @param workSource WorkSource to blame for power usage
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
+ public void startScan(ScanSettings settings, @Nullable @CallbackExecutor Executor executor,
+ ScanListener listener, WorkSource workSource) {
Objects.requireNonNull(listener, "listener cannot be null");
- int key = addListener(listener);
+ int key = addListener(listener, executor);
if (key == INVALID_KEY) return;
validateChannel();
Bundle scanParams = new Bundle();
@@ -1029,16 +1047,17 @@
* {@link ScanSettings}
* @param pnoSettings specifies various parameters for PNO; for more information look at
* {@link PnoSettings}
+ * @param executor the Executor on which to run the callback.
* @param listener specifies the object to report events to. This object is also treated as a
* key for this scan, and must also be specified to cancel the scan. Multiple
* scans should also not share this object.
* {@hide}
*/
public void startConnectedPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings,
- PnoScanListener listener) {
+ @NonNull @CallbackExecutor Executor executor, PnoScanListener listener) {
Objects.requireNonNull(listener, "listener cannot be null");
Objects.requireNonNull(pnoSettings, "pnoSettings cannot be null");
- int key = addListener(listener);
+ int key = addListener(listener, executor);
if (key == INVALID_KEY) return;
validateChannel();
pnoSettings.isConnected = true;
@@ -1057,10 +1076,10 @@
*/
@RequiresPermission(android.Manifest.permission.NETWORK_STACK)
public void startDisconnectedPnoScan(ScanSettings scanSettings, PnoSettings pnoSettings,
- PnoScanListener listener) {
+ @NonNull @CallbackExecutor Executor executor, PnoScanListener listener) {
Objects.requireNonNull(listener, "listener cannot be null");
Objects.requireNonNull(pnoSettings, "pnoSettings cannot be null");
- int key = addListener(listener);
+ int key = addListener(listener, executor);
if (key == INVALID_KEY) return;
validateChannel();
pnoSettings.isConnected = false;
diff --git a/wifi/java/android/net/wifi/WifiSsid.java b/wifi/java/android/net/wifi/WifiSsid.java
index 90756d8..704ae81 100644
--- a/wifi/java/android/net/wifi/WifiSsid.java
+++ b/wifi/java/android/net/wifi/WifiSsid.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
index 5befb54..1822e84 100644
--- a/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
+++ b/wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java
@@ -17,6 +17,7 @@
package android.net.wifi.hotspot2;
import android.annotation.Nullable;
+import android.annotation.SystemApi;
import android.net.wifi.hotspot2.pps.Credential;
import android.net.wifi.hotspot2.pps.HomeSp;
import android.net.wifi.hotspot2.pps.Policy;
@@ -423,6 +424,41 @@
}
/**
+ * The auto-join configuration specifies whether or not the Passpoint Configuration is
+ * considered for auto-connection. If true then yes, if false then it isn't considered as part
+ * of auto-connection - but can still be manually connected to.
+ */
+ private boolean mIsAutoJoinEnabled = true;
+
+ /**
+ * Configures the auto-association status of this Passpoint configuration. A value of true
+ * indicates that the configuration will be considered for auto-connection, a value of false
+ * indicates that only manual connection will work - the framework will not auto-associate to
+ * this Passpoint network.
+ *
+ * @param autoJoinEnabled true to be considered for framework auto-connection, false otherwise.
+ * @hide
+ */
+ public void setAutoJoinEnabled(boolean autoJoinEnabled) {
+ mIsAutoJoinEnabled = autoJoinEnabled;
+ }
+
+ /**
+ * Indicates whether the Passpoint configuration may be auto-connected to by the framework. A
+ * value of true indicates that auto-connection can happen, a value of false indicates that it
+ * cannot. However, even when auto-connection is not possible manual connection by the user is
+ * possible.
+ *
+ * @return the auto-join configuration: true for auto-connection (or join) enabled, false
+ * otherwise.
+ * @hide
+ */
+ @SystemApi
+ public boolean isAutoJoinEnabled() {
+ return mIsAutoJoinEnabled;
+ }
+
+ /**
* Constructor for creating PasspointConfiguration with default values.
*/
public PasspointConfiguration() {}
@@ -464,6 +500,7 @@
mServiceFriendlyNames = source.mServiceFriendlyNames;
mAaaServerTrustedNames = source.mAaaServerTrustedNames;
mCarrierId = source.mCarrierId;
+ mIsAutoJoinEnabled = source.mIsAutoJoinEnabled;
}
@Override
@@ -493,6 +530,7 @@
(HashMap<String, String>) mServiceFriendlyNames);
dest.writeBundle(bundle);
dest.writeInt(mCarrierId);
+ dest.writeBoolean(mIsAutoJoinEnabled);
}
@Override
@@ -523,6 +561,7 @@
&& mUsageLimitDataLimit == that.mUsageLimitDataLimit
&& mUsageLimitTimeLimitInMinutes == that.mUsageLimitTimeLimitInMinutes
&& mCarrierId == that.mCarrierId
+ && mIsAutoJoinEnabled == that.mIsAutoJoinEnabled
&& (mServiceFriendlyNames == null ? that.mServiceFriendlyNames == null
: mServiceFriendlyNames.equals(that.mServiceFriendlyNames));
}
@@ -533,7 +572,7 @@
mUpdateIdentifier, mCredentialPriority, mSubscriptionCreationTimeInMillis,
mSubscriptionExpirationTimeInMillis, mUsageLimitUsageTimePeriodInMinutes,
mUsageLimitStartTimeInMillis, mUsageLimitDataLimit, mUsageLimitTimeLimitInMinutes,
- mServiceFriendlyNames, mCarrierId);
+ mServiceFriendlyNames, mCarrierId, mIsAutoJoinEnabled);
}
@Override
@@ -587,6 +626,7 @@
builder.append("ServiceFriendlyNames: ").append(mServiceFriendlyNames);
}
builder.append("CarrierId:" + mCarrierId);
+ builder.append("IsAutoJoinEnabled:" + mIsAutoJoinEnabled);
return builder.toString();
}
@@ -692,6 +732,7 @@
"serviceFriendlyNames");
config.setServiceFriendlyNames(friendlyNamesMap);
config.mCarrierId = in.readInt();
+ config.mIsAutoJoinEnabled = in.readBoolean();
return config;
}
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java b/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java
index c9bca4f..8fa9c3d 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pConfig.java
@@ -19,7 +19,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.net.MacAddress;
import android.net.wifi.WpsInfo;
import android.os.Parcel;
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java b/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java
index 98ec208..710175f 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pDevice.java
@@ -18,7 +18,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java b/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java
index acf06fb..ededf67 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pDeviceList.java
@@ -16,10 +16,9 @@
package android.net.wifi.p2p;
-import android.annotation.UnsupportedAppUsage;
-import android.os.Parcelable;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
-import android.net.wifi.p2p.WifiP2pDevice;
+import android.os.Parcelable;
import android.text.TextUtils;
import java.util.ArrayList;
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java b/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java
index d8c50f2..21f6704 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pGroup.java
@@ -17,7 +17,7 @@
package android.net.wifi.p2p;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pGroupList.java b/wifi/java/android/net/wifi/p2p/WifiP2pGroupList.java
index 10fd09a..cdb2806 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pGroupList.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pGroupList.java
@@ -17,7 +17,7 @@
import android.annotation.NonNull;
import android.annotation.SystemApi;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.LruCache;
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
index 6120e4e..3459c94 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pManager.java
@@ -24,7 +24,7 @@
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SystemApi;
import android.annotation.SystemService;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.net.NetworkInfo;
import android.net.wifi.WpsInfo;
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pProvDiscEvent.java b/wifi/java/android/net/wifi/p2p/WifiP2pProvDiscEvent.java
index 153e03c..d0fe92d 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pProvDiscEvent.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pProvDiscEvent.java
@@ -16,7 +16,7 @@
package android.net.wifi.p2p;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
/**
* A class representing a Wi-Fi p2p provisional discovery request/response
diff --git a/wifi/java/android/net/wifi/p2p/WifiP2pWfdInfo.java b/wifi/java/android/net/wifi/p2p/WifiP2pWfdInfo.java
index 48b0703..a411502 100644
--- a/wifi/java/android/net/wifi/p2p/WifiP2pWfdInfo.java
+++ b/wifi/java/android/net/wifi/p2p/WifiP2pWfdInfo.java
@@ -19,7 +19,7 @@
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceInfo.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceInfo.java
index e32c8e8..dad431c 100644
--- a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceInfo.java
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pDnsSdServiceInfo.java
@@ -16,8 +16,8 @@
package android.net.wifi.p2p.nsd;
-import android.annotation.UnsupportedAppUsage;
-import android.net.nsd.DnsSdTxtRecord;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.net.util.nsd.DnsSdTxtRecord;
import android.os.Build;
import android.text.TextUtils;
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.java
index db0bdb8..37b442b 100644
--- a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.java
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceInfo.java
@@ -16,7 +16,7 @@
package android.net.wifi.p2p.nsd;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
diff --git a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.java b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.java
index 87528c4..68cbb88 100644
--- a/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.java
+++ b/wifi/java/android/net/wifi/p2p/nsd/WifiP2pServiceRequest.java
@@ -16,7 +16,7 @@
package android.net.wifi.p2p.nsd;
-import android.annotation.UnsupportedAppUsage;
+import android.compat.annotation.UnsupportedAppUsage;
import android.net.wifi.p2p.WifiP2pManager;
import android.os.Build;
import android.os.Parcel;
diff --git a/wifi/java/android/net/wifi/wificond/WifiCondManager.java b/wifi/java/android/net/wifi/wificond/WifiCondManager.java
index 283f2dd..f70bdac 100644
--- a/wifi/java/android/net/wifi/wificond/WifiCondManager.java
+++ b/wifi/java/android/net/wifi/wificond/WifiCondManager.java
@@ -1154,4 +1154,68 @@
mApInterfaceListeners.clear();
mSendMgmtFrameInProgress.set(false);
}
+
+ /**
+ * OEM parsed security type
+ */
+ public static class OemSecurityType {
+ /** The protocol defined in {@link android.net.wifi.WifiAnnotations.Protocol}. */
+ public final @WifiAnnotations.Protocol int protocol;
+ /**
+ * Supported key management types defined
+ * in {@link android.net.wifi.WifiAnnotations.KeyMgmt}.
+ */
+ @NonNull public final List<Integer> keyManagement;
+ /**
+ * Supported pairwise cipher types defined
+ * in {@link android.net.wifi.WifiAnnotations.Cipher}.
+ */
+ @NonNull public final List<Integer> pairwiseCipher;
+ /** The group cipher type defined in {@link android.net.wifi.WifiAnnotations.Cipher}. */
+ public final @WifiAnnotations.Cipher int groupCipher;
+ /**
+ * Default constructor for OemSecurityType
+ *
+ * @param protocol The protocol defined in
+ * {@link android.net.wifi.WifiAnnotations.Protocol}.
+ * @param keyManagement Supported key management types defined
+ * in {@link android.net.wifi.WifiAnnotations.KeyMgmt}.
+ * @param pairwiseCipher Supported pairwise cipher types defined
+ * in {@link android.net.wifi.WifiAnnotations.Cipher}.
+ * @param groupCipher The group cipher type defined
+ * in {@link android.net.wifi.WifiAnnotations.Cipher}.
+ */
+ public OemSecurityType(
+ @WifiAnnotations.Protocol int protocol,
+ @NonNull List<Integer> keyManagement,
+ @NonNull List<Integer> pairwiseCipher,
+ @WifiAnnotations.Cipher int groupCipher) {
+ this.protocol = protocol;
+ this.keyManagement = (keyManagement != null)
+ ? keyManagement : new ArrayList<Integer>();
+ this.pairwiseCipher = (pairwiseCipher != null)
+ ? pairwiseCipher : new ArrayList<Integer>();
+ this.groupCipher = groupCipher;
+ }
+ }
+
+ /**
+ * OEM information element parser for security types not parsed by the framework.
+ *
+ * The OEM method should use the method inputs {@code id}, {@code idExt}, and {@code bytes}
+ * to perform the parsing. The method should place the results in an OemSecurityType objct.
+ *
+ * @param id The information element id.
+ * @param idExt The information element extension id. This is valid only when id is
+ * the extension id, {@code 255}.
+ * @param bytes The raw bytes of information element data, 'Element ID' and 'Length' are
+ * stripped off already.
+ * @return an OemSecurityType object if this IE is parsed successfully, null otherwise.
+ */
+ @Nullable public static OemSecurityType parseOemSecurityTypeElement(
+ int id,
+ int idExt,
+ @NonNull byte[] bytes) {
+ return null;
+ }
}
diff --git a/wifi/java/com/android/server/wifi/BaseWifiService.java b/wifi/java/com/android/server/wifi/BaseWifiService.java
index d58083c..3c13562d 100644
--- a/wifi/java/com/android/server/wifi/BaseWifiService.java
+++ b/wifi/java/com/android/server/wifi/BaseWifiService.java
@@ -182,6 +182,11 @@
}
@Override
+ public void allowAutojoinPasspoint(String fqdn, boolean enableAutoJoin) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public boolean startScan(String packageName, String featureId) {
throw new UnsupportedOperationException();
}
@@ -584,4 +589,10 @@
public int calculateSignalLevel(int rssi) {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public List<WifiConfiguration> getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(
+ List<ScanResult> scanResults) {
+ throw new UnsupportedOperationException();
+ }
}
diff --git a/wifi/tests/Android.bp b/wifi/tests/Android.bp
new file mode 100644
index 0000000..6a39959
--- /dev/null
+++ b/wifi/tests/Android.bp
@@ -0,0 +1,50 @@
+// 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.
+
+// Make test APK
+// ============================================================
+
+android_test {
+ name: "FrameworksWifiApiTests",
+
+ defaults: ["framework-wifi-test-defaults"],
+
+ srcs: ["**/*.java"],
+
+ jacoco: {
+ include_filter: ["android.net.wifi.*"],
+ // TODO(b/147521214) need to exclude test classes
+ exclude_filter: [],
+ },
+
+ static_libs: [
+ "androidx.test.rules",
+ "core-test-rules",
+ "guava",
+ "mockito-target-minus-junit4",
+ "net-tests-utils",
+ "frameworks-base-testutils",
+ "truth-prebuilt",
+ ],
+
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ ],
+
+ test_suites: [
+ "device-tests",
+ "mts",
+ ],
+}
diff --git a/wifi/tests/Android.mk b/wifi/tests/Android.mk
deleted file mode 100644
index d2c385b4..0000000
--- a/wifi/tests/Android.mk
+++ /dev/null
@@ -1,66 +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.
-
-LOCAL_PATH:= $(call my-dir)
-
-# Make test APK
-# ============================================================
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-
-# This list is generated from the java source files in this module
-# The list is a comma separated list of class names with * matching zero or more characters.
-# Example:
-# Input files: src/com/android/server/wifi/Test.java src/com/android/server/wifi/AnotherTest.java
-# Generated exclude list: com.android.server.wifi.Test*,com.android.server.wifi.AnotherTest*
-
-# Filter all src files to just java files
-local_java_files := $(filter %.java,$(LOCAL_SRC_FILES))
-# Transform java file names into full class names.
-# This only works if the class name matches the file name and the directory structure
-# matches the package.
-local_classes := $(subst /,.,$(patsubst src/%.java,%,$(local_java_files)))
-# Convert class name list to jacoco exclude list
-# This appends a * to all classes and replace the space separators with commas.
-# These patterns will match all classes in this module and their inner classes.
-jacoco_exclude := $(subst $(space),$(comma),$(patsubst %,%*,$(local_classes)))
-
-jacoco_include := android.net.wifi.*
-
-LOCAL_JACK_COVERAGE_INCLUDE_FILTER := $(jacoco_include)
-LOCAL_JACK_COVERAGE_EXCLUDE_FILTER := $(jacoco_exclude)
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
- androidx.test.rules \
- core-test-rules \
- guava \
- mockito-target-minus-junit4 \
- net-tests-utils \
- frameworks-base-testutils \
- truth-prebuilt \
-
-LOCAL_JAVA_LIBRARIES := \
- android.test.runner \
- android.test.base \
-
-LOCAL_PACKAGE_NAME := FrameworksWifiApiTests
-LOCAL_PRIVATE_PLATFORM_APIS := true
-LOCAL_COMPATIBILITY_SUITE := \
- device-tests \
- mts \
-
-include $(BUILD_PACKAGE)
diff --git a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
index 1f60103..6884a4e 100644
--- a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java
@@ -25,8 +25,14 @@
import org.junit.Test;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
@SmallTest
public class SoftApConfigurationTest {
+ private static final String TEST_CHAR_SET_AS_STRING = "abcdefghijklmnopqrstuvwxyz0123456789";
+
private SoftApConfiguration parcelUnparcel(SoftApConfiguration configIn) {
Parcel parcel = Parcel.obtain();
parcel.writeParcelable(configIn, 0);
@@ -37,6 +43,25 @@
return configOut;
}
+ /**
+ * Helper method to generate random string.
+ *
+ * Note: this method has limited use as a random string generator.
+ * The characters used in this method do no not cover all valid inputs.
+ * @param length number of characters to generate for the string
+ * @return String generated string of random characters
+ */
+ private String generateRandomString(int length) {
+ Random random = new Random();
+ StringBuilder stringBuilder = new StringBuilder(length);
+ int index = -1;
+ while (stringBuilder.length() < length) {
+ index = random.nextInt(TEST_CHAR_SET_AS_STRING.length());
+ stringBuilder.append(TEST_CHAR_SET_AS_STRING.charAt(index));
+ }
+ return stringBuilder.toString();
+ }
+
@Test
public void testBasicSettings() {
SoftApConfiguration original = new SoftApConfiguration.Builder()
@@ -45,7 +70,7 @@
.build();
assertThat(original.getSsid()).isEqualTo("ssid");
assertThat(original.getBssid()).isEqualTo(MacAddress.fromString("11:22:33:44:55:66"));
- assertThat(original.getWpa2Passphrase()).isNull();
+ assertThat(original.getPassphrase()).isNull();
assertThat(original.getSecurityType()).isEqualTo(SoftApConfiguration.SECURITY_TYPE_OPEN);
assertThat(original.getBand()).isEqualTo(SoftApConfiguration.BAND_2GHZ);
assertThat(original.getChannel()).isEqualTo(0);
@@ -66,9 +91,9 @@
@Test
public void testWpa2() {
SoftApConfiguration original = new SoftApConfiguration.Builder()
- .setWpa2Passphrase("secretsecret")
+ .setPassphrase("secretsecret", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
.build();
- assertThat(original.getWpa2Passphrase()).isEqualTo("secretsecret");
+ assertThat(original.getPassphrase()).isEqualTo("secretsecret");
assertThat(original.getSecurityType()).isEqualTo(
SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
assertThat(original.getBand()).isEqualTo(SoftApConfiguration.BAND_2GHZ);
@@ -89,20 +114,30 @@
@Test
public void testWpa2WithAllFieldCustomized() {
+ List<MacAddress> testBlockedClientList = new ArrayList<>();
+ List<MacAddress> testAllowedClientList = new ArrayList<>();
+ testBlockedClientList.add(MacAddress.fromString("11:22:33:44:55:66"));
+ testAllowedClientList.add(MacAddress.fromString("aa:bb:cc:dd:ee:ff"));
SoftApConfiguration original = new SoftApConfiguration.Builder()
- .setWpa2Passphrase("secretsecret")
- .setBand(SoftApConfiguration.BAND_ANY)
+ .setPassphrase("secretsecret", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
.setChannel(149, SoftApConfiguration.BAND_5GHZ)
.setHiddenSsid(true)
.setMaxNumberOfClients(10)
+ .setShutdownTimeoutMillis(500000)
+ .enableClientControlByUser(true)
+ .setClientList(testBlockedClientList, testAllowedClientList)
.build();
- assertThat(original.getWpa2Passphrase()).isEqualTo("secretsecret");
+ assertThat(original.getPassphrase()).isEqualTo("secretsecret");
assertThat(original.getSecurityType()).isEqualTo(
SoftApConfiguration.SECURITY_TYPE_WPA2_PSK);
assertThat(original.getBand()).isEqualTo(SoftApConfiguration.BAND_5GHZ);
assertThat(original.getChannel()).isEqualTo(149);
assertThat(original.isHiddenSsid()).isEqualTo(true);
assertThat(original.getMaxNumberOfClients()).isEqualTo(10);
+ assertThat(original.getShutdownTimeoutMillis()).isEqualTo(500000);
+ assertThat(original.isClientControlByUserEnabled()).isEqualTo(true);
+ assertThat(original.getBlockedClientList()).isEqualTo(testBlockedClientList);
+ assertThat(original.getAllowedClientList()).isEqualTo(testAllowedClientList);
SoftApConfiguration unparceled = parcelUnparcel(original);
assertThat(unparceled).isNotSameAs(original);
@@ -114,4 +149,117 @@
assertThat(copy).isEqualTo(original);
assertThat(copy.hashCode()).isEqualTo(original.hashCode());
}
+
+ @Test
+ public void testWpa3Sae() {
+ SoftApConfiguration original = new SoftApConfiguration.Builder()
+ .setPassphrase("secretsecret", SoftApConfiguration.SECURITY_TYPE_WPA3_SAE)
+ .setChannel(149, SoftApConfiguration.BAND_5GHZ)
+ .setHiddenSsid(true)
+ .build();
+ assertThat(original.getPassphrase()).isEqualTo("secretsecret");
+ assertThat(original.getSecurityType()).isEqualTo(
+ SoftApConfiguration.SECURITY_TYPE_WPA3_SAE);
+ assertThat(original.getBand()).isEqualTo(SoftApConfiguration.BAND_5GHZ);
+ assertThat(original.getChannel()).isEqualTo(149);
+ assertThat(original.isHiddenSsid()).isEqualTo(true);
+
+
+ SoftApConfiguration unparceled = parcelUnparcel(original);
+ assertThat(unparceled).isNotSameAs(original);
+ assertThat(unparceled).isEqualTo(original);
+ assertThat(unparceled.hashCode()).isEqualTo(original.hashCode());
+
+ SoftApConfiguration copy = new SoftApConfiguration.Builder(original).build();
+ assertThat(copy).isNotSameAs(original);
+ assertThat(copy).isEqualTo(original);
+ assertThat(copy.hashCode()).isEqualTo(original.hashCode());
+ }
+
+ @Test
+ public void testWpa3SaeTransition() {
+ SoftApConfiguration original = new SoftApConfiguration.Builder()
+ .setPassphrase("secretsecret",
+ SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION)
+ .setChannel(149, SoftApConfiguration.BAND_5GHZ)
+ .setHiddenSsid(true)
+ .build();
+ assertThat(original.getSecurityType()).isEqualTo(
+ SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION);
+ assertThat(original.getPassphrase()).isEqualTo("secretsecret");
+ assertThat(original.getBand()).isEqualTo(SoftApConfiguration.BAND_5GHZ);
+ assertThat(original.getChannel()).isEqualTo(149);
+ assertThat(original.isHiddenSsid()).isEqualTo(true);
+
+
+ SoftApConfiguration unparceled = parcelUnparcel(original);
+ assertThat(unparceled).isNotSameAs(original);
+ assertThat(unparceled).isEqualTo(original);
+ assertThat(unparceled.hashCode()).isEqualTo(original.hashCode());
+
+ SoftApConfiguration copy = new SoftApConfiguration.Builder(original).build();
+ assertThat(copy).isNotSameAs(original);
+ assertThat(copy).isEqualTo(original);
+ assertThat(copy.hashCode()).isEqualTo(original.hashCode());
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInvalidShortPasswordLengthForWpa2() {
+ SoftApConfiguration original = new SoftApConfiguration.Builder()
+ .setPassphrase(generateRandomString(SoftApConfiguration.PSK_MIN_LEN - 1),
+ SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
+ .setChannel(149, SoftApConfiguration.BAND_5GHZ)
+ .setHiddenSsid(true)
+ .build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInvalidLongPasswordLengthForWpa2() {
+ SoftApConfiguration original = new SoftApConfiguration.Builder()
+ .setPassphrase(generateRandomString(SoftApConfiguration.PSK_MAX_LEN + 1),
+ SoftApConfiguration.SECURITY_TYPE_WPA2_PSK)
+ .setChannel(149, SoftApConfiguration.BAND_5GHZ)
+ .setHiddenSsid(true)
+ .build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInvalidShortPasswordLengthForWpa3SaeTransition() {
+ SoftApConfiguration original = new SoftApConfiguration.Builder()
+ .setPassphrase(generateRandomString(SoftApConfiguration.PSK_MIN_LEN - 1),
+ SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION)
+ .setChannel(149, SoftApConfiguration.BAND_5GHZ)
+ .setHiddenSsid(true)
+ .build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInvalidLongPasswordLengthForWpa3SaeTransition() {
+ SoftApConfiguration original = new SoftApConfiguration.Builder()
+ .setPassphrase(generateRandomString(SoftApConfiguration.PSK_MAX_LEN + 1),
+ SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION)
+ .setChannel(149, SoftApConfiguration.BAND_5GHZ)
+ .setHiddenSsid(true)
+ .build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testInvalieShutdownTimeoutMillis() {
+ SoftApConfiguration original = new SoftApConfiguration.Builder()
+ .setShutdownTimeoutMillis(-1)
+ .build();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testsetClientListExceptionWhenExistMacAddressInBothList() {
+ final MacAddress testMacAddress_1 = MacAddress.fromString("22:33:44:55:66:77");
+ final MacAddress testMacAddress_2 = MacAddress.fromString("aa:bb:cc:dd:ee:ff");
+ ArrayList<MacAddress> testAllowedClientList = new ArrayList<>();
+ testAllowedClientList.add(testMacAddress_1);
+ testAllowedClientList.add(testMacAddress_2);
+ ArrayList<MacAddress> testBlockedClientList = new ArrayList<>();
+ testBlockedClientList.add(testMacAddress_1);
+ SoftApConfiguration.Builder configBuilder = new SoftApConfiguration.Builder();
+ configBuilder.setClientList(testBlockedClientList, testAllowedClientList);
+ }
}
diff --git a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
index 5d6549e..0ef75aa 100644
--- a/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiConfigurationTest.java
@@ -16,6 +16,10 @@
package android.net.wifi;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_OWE;
+import static android.net.wifi.WifiConfiguration.SECURITY_TYPE_SAE;
+
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -23,6 +27,7 @@
import static org.junit.Assert.assertTrue;
import android.net.MacAddress;
+import android.net.util.MacAddressUtils;
import android.net.wifi.WifiConfiguration.KeyMgmt;
import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
import android.os.Parcel;
@@ -59,7 +64,7 @@
config.updateIdentifier = "1234";
config.fromWifiNetworkSpecifier = true;
config.fromWifiNetworkSuggestion = true;
- config.setRandomizedMacAddress(MacAddress.createRandomUnicastAddress());
+ config.setRandomizedMacAddress(MacAddressUtils.createRandomUnicastAddress());
MacAddress macBeforeParcel = config.getRandomizedMacAddress();
Parcel parcelW = Parcel.obtain();
config.writeToParcel(parcelW, 0);
@@ -165,7 +170,7 @@
MacAddress defaultMac = MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS);
assertEquals(defaultMac, config.getRandomizedMacAddress());
- MacAddress macToChangeInto = MacAddress.createRandomUnicastAddress();
+ MacAddress macToChangeInto = MacAddressUtils.createRandomUnicastAddress();
config.setRandomizedMacAddress(macToChangeInto);
MacAddress macAfterChange = config.getRandomizedMacAddress();
@@ -270,7 +275,23 @@
config.allowedKeyManagement.clear();
assertEquals(mSsid + "WEP", config.getSsidAndSecurityTypeString());
+ // set WEP key and give a valid index.
config.wepKeys[0] = null;
+ config.wepKeys[2] = "TestWep";
+ config.wepTxKeyIndex = 2;
+ config.allowedKeyManagement.clear();
+ assertEquals(mSsid + "WEP", config.getSsidAndSecurityTypeString());
+
+ // set WEP key but does not give a valid index.
+ config.wepKeys[0] = null;
+ config.wepKeys[2] = "TestWep";
+ config.wepTxKeyIndex = 0;
+ config.allowedKeyManagement.clear();
+ config.allowedKeyManagement.set(KeyMgmt.OWE);
+ assertEquals(mSsid + KeyMgmt.strings[KeyMgmt.OWE], config.getSsidAndSecurityTypeString());
+
+ config.wepKeys[0] = null;
+ config.wepTxKeyIndex = 0;
config.allowedKeyManagement.clear();
config.allowedKeyManagement.set(KeyMgmt.OWE);
assertEquals(mSsid + KeyMgmt.strings[KeyMgmt.OWE], config.getSsidAndSecurityTypeString());
@@ -312,4 +333,57 @@
assertNotNull(NetworkSelectionStatus.DISABLE_REASON_INFOS.get(i));
}
}
+
+ /**
+ * Ensure that {@link WifiConfiguration#setSecurityParams(int)} sets up the
+ * {@link WifiConfiguration} object correctly for SAE security type.
+ * @throws Exception
+ */
+ @Test
+ public void testSetSecurityParamsForSae() throws Exception {
+ WifiConfiguration config = new WifiConfiguration();
+
+ config.setSecurityParams(SECURITY_TYPE_SAE);
+
+ assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE));
+ assertTrue(config.allowedPairwiseCiphers.get(WifiConfiguration.PairwiseCipher.CCMP));
+ assertTrue(config.allowedGroupCiphers.get(WifiConfiguration.GroupCipher.CCMP));
+ assertTrue(config.requirePMF);
+ }
+
+ /**
+ * Ensure that {@link WifiConfiguration#setSecurityParams(int)} sets up the
+ * {@link WifiConfiguration} object correctly for OWE security type.
+ * @throws Exception
+ */
+ @Test
+ public void testSetSecurityParamsForOwe() throws Exception {
+ WifiConfiguration config = new WifiConfiguration();
+
+ config.setSecurityParams(SECURITY_TYPE_OWE);
+
+ assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE));
+ assertTrue(config.allowedPairwiseCiphers.get(WifiConfiguration.PairwiseCipher.CCMP));
+ assertTrue(config.allowedGroupCiphers.get(WifiConfiguration.GroupCipher.CCMP));
+ assertTrue(config.requirePMF);
+ }
+
+ /**
+ * Ensure that {@link WifiConfiguration#setSecurityParams(int)} sets up the
+ * {@link WifiConfiguration} object correctly for Suite-B security type.
+ * @throws Exception
+ */
+ @Test
+ public void testSetSecurityParamsForSuiteB() throws Exception {
+ WifiConfiguration config = new WifiConfiguration();
+
+ config.setSecurityParams(SECURITY_TYPE_EAP_SUITE_B);
+
+ assertTrue(config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SUITE_B_192));
+ assertTrue(config.allowedPairwiseCiphers.get(WifiConfiguration.PairwiseCipher.GCMP_256));
+ assertTrue(config.allowedGroupCiphers.get(WifiConfiguration.GroupCipher.GCMP_256));
+ assertTrue(config.allowedGroupManagementCiphers
+ .get(WifiConfiguration.GroupMgmtCipher.BIP_GMAC_256));
+ assertTrue(config.requirePMF);
+ }
}
diff --git a/wifi/tests/src/android/net/wifi/WifiManagerTest.java b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
index f9bd31d..5bdc344 100644
--- a/wifi/tests/src/android/net/wifi/WifiManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiManagerTest.java
@@ -70,6 +70,7 @@
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.net.DhcpInfo;
+import android.net.MacAddress;
import android.net.wifi.WifiManager.LocalOnlyHotspotCallback;
import android.net.wifi.WifiManager.LocalOnlyHotspotObserver;
import android.net.wifi.WifiManager.LocalOnlyHotspotReservation;
@@ -897,6 +898,25 @@
}
/*
+ * Verify client-provided callback is being called through callback proxy
+ */
+ @Test
+ public void softApCallbackProxyCallsOnBlockedClientConnecting() throws Exception {
+ WifiClient testWifiClient = new WifiClient(MacAddress.fromString("22:33:44:55:66:77"));
+ ArgumentCaptor<ISoftApCallback.Stub> callbackCaptor =
+ ArgumentCaptor.forClass(ISoftApCallback.Stub.class);
+ mWifiManager.registerSoftApCallback(new HandlerExecutor(mHandler), mSoftApCallback);
+ verify(mWifiService).registerSoftApCallback(any(IBinder.class), callbackCaptor.capture(),
+ anyInt());
+
+ callbackCaptor.getValue().onBlockedClientConnecting(testWifiClient,
+ WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS);
+ mLooper.dispatchAll();
+ verify(mSoftApCallback).onBlockedClientConnecting(testWifiClient,
+ WifiManager.SAP_CLIENT_BLOCK_REASON_CODE_NO_MORE_STAS);
+ }
+
+ /*
* Verify client-provided callback is being called through callback proxy on multiple events
*/
@Test
@@ -1672,10 +1692,22 @@
@Test
public void testAllowAutojoin() throws Exception {
mWifiManager.allowAutojoin(1, true);
- verify(mWifiService).allowAutojoin(eq(1), eq(true));
+ verify(mWifiService).allowAutojoin(1, true);
}
/**
+ * Test behavior of {@link WifiManager#allowAutojoinPasspoint(String, boolean)}
+ * @throws Exception
+ */
+ @Test
+ public void testAllowAutojoinPasspoint() throws Exception {
+ final String fqdn = "FullyQualifiedDomainName";
+ mWifiManager.allowAutojoinPasspoint(fqdn, true);
+ verify(mWifiService).allowAutojoinPasspoint(fqdn, true);
+ }
+
+
+ /**
* Test behavior of {@link WifiManager#disconnect()}
*/
@Test
@@ -2173,4 +2205,18 @@
result = WifiManager.parseDppChannelList(channelList);
assertEquals(result.size(), 0);
}
+
+ /**
+ * Test getWifiConfigsForMatchedNetworkSuggestions for given scanResults.
+ */
+ @Test
+ public void testGetWifiConfigsForMatchedNetworkSuggestions() throws Exception {
+ List<WifiConfiguration> testResults = new ArrayList<>();
+ testResults.add(new WifiConfiguration());
+
+ when(mWifiService.getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(any(List.class)))
+ .thenReturn(testResults);
+ assertEquals(testResults, mWifiManager
+ .getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(new ArrayList<>()));
+ }
}
diff --git a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
index 4cdc4bc..cb1b774 100644
--- a/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiNetworkSuggestionTest.java
@@ -32,8 +32,6 @@
*/
@SmallTest
public class WifiNetworkSuggestionTest {
- private static final int TEST_UID = 45677;
- private static final int TEST_UID_OTHER = 45673;
private static final String TEST_SSID = "\"Test123\"";
private static final String TEST_BSSID = "12:12:12:12:12:12";
private static final String TEST_SSID_1 = "\"Test1234\"";
@@ -61,7 +59,8 @@
assertEquals(WifiConfiguration.METERED_OVERRIDE_NONE,
suggestion.wifiConfiguration.meteredOverride);
assertEquals(-1, suggestion.wifiConfiguration.priority);
- assertEquals(false, suggestion.isUserAllowedToManuallyConnect);
+ assertFalse(suggestion.isUserAllowedToManuallyConnect);
+ assertTrue(suggestion.isInitialAutoJoinEnabled);
}
/**
@@ -76,7 +75,7 @@
.setSsid(TEST_SSID)
.setWpa2Passphrase(TEST_PRESHARED_KEY)
.setIsAppInteractionRequired(true)
- .setIsUserAllowedToManuallyConnect(false)
+ .setCredentialSharedWithUser(false)
.setPriority(0)
.build();
@@ -90,7 +89,8 @@
assertEquals(WifiConfiguration.METERED_OVERRIDE_NONE,
suggestion.wifiConfiguration.meteredOverride);
assertEquals(0, suggestion.wifiConfiguration.priority);
- assertEquals(false, suggestion.isUserAllowedToManuallyConnect);
+ assertFalse(suggestion.isUserAllowedToManuallyConnect);
+ assertTrue(suggestion.isInitialAutoJoinEnabled);
}
/**
@@ -105,6 +105,7 @@
.setSsid(TEST_SSID)
.setWpa2Passphrase(TEST_PRESHARED_KEY)
.setIsUserInteractionRequired(true)
+ .setIsInitialAutoJoinEnabled(false)
.setIsMetered(true)
.build();
@@ -119,6 +120,7 @@
suggestion.wifiConfiguration.meteredOverride);
assertEquals(-1, suggestion.wifiConfiguration.priority);
assertTrue(suggestion.isUserAllowedToManuallyConnect);
+ assertFalse(suggestion.isInitialAutoJoinEnabled);
}
/**
@@ -140,6 +142,7 @@
assertNull(suggestion.wifiConfiguration.preSharedKey);
assertTrue(suggestion.wifiConfiguration.requirePMF);
assertFalse(suggestion.isUserAllowedToManuallyConnect);
+ assertTrue(suggestion.isInitialAutoJoinEnabled);
}
/**
@@ -151,7 +154,8 @@
WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
.setSsid(TEST_SSID)
.setWpa3Passphrase(TEST_PRESHARED_KEY)
- .setIsUserAllowedToManuallyConnect(true)
+ .setCredentialSharedWithUser(true)
+ .setIsInitialAutoJoinEnabled(false)
.build();
assertEquals("\"" + TEST_SSID + "\"", suggestion.wifiConfiguration.SSID);
@@ -161,6 +165,7 @@
suggestion.wifiConfiguration.preSharedKey);
assertTrue(suggestion.wifiConfiguration.requirePMF);
assertTrue(suggestion.isUserAllowedToManuallyConnect);
+ assertFalse(suggestion.isInitialAutoJoinEnabled);
}
@@ -191,6 +196,7 @@
// allowedSuiteBCiphers are set according to the loaded certificate and cannot be tested
// here.
assertTrue(suggestion.isUserAllowedToManuallyConnect);
+ assertTrue(suggestion.isInitialAutoJoinEnabled);
}
/**
@@ -526,7 +532,7 @@
configuration.BSSID = TEST_BSSID;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion(
- configuration, null, false, true, true);
+ configuration, null, false, true, true, false);
Parcel parcelW = Parcel.obtain();
suggestion.writeToParcel(parcelW, 0);
@@ -548,6 +554,8 @@
parcelSuggestion.isAppInteractionRequired);
assertEquals(suggestion.isUserInteractionRequired,
parcelSuggestion.isUserInteractionRequired);
+ assertEquals(suggestion.isInitialAutoJoinEnabled,
+ parcelSuggestion.isInitialAutoJoinEnabled);
}
/**
@@ -580,6 +588,8 @@
parcelSuggestion.isAppInteractionRequired);
assertEquals(suggestion.isUserInteractionRequired,
parcelSuggestion.isUserInteractionRequired);
+ assertEquals(suggestion.isInitialAutoJoinEnabled,
+ parcelSuggestion.isInitialAutoJoinEnabled);
}
/**
@@ -593,14 +603,14 @@
configuration.BSSID = TEST_BSSID;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
WifiNetworkSuggestion suggestion =
- new WifiNetworkSuggestion(configuration, null, true, false, true);
+ new WifiNetworkSuggestion(configuration, null, true, false, true, true);
WifiConfiguration configuration1 = new WifiConfiguration();
configuration1.SSID = TEST_SSID;
configuration1.BSSID = TEST_BSSID;
configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
WifiNetworkSuggestion suggestion1 =
- new WifiNetworkSuggestion(configuration1, null, false, true, true);
+ new WifiNetworkSuggestion(configuration1, null, false, true, true, false);
assertEquals(suggestion, suggestion1);
assertEquals(suggestion.hashCode(), suggestion1.hashCode());
@@ -616,13 +626,13 @@
configuration.SSID = TEST_SSID;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion =
- new WifiNetworkSuggestion(configuration, null, false, false, true);
+ new WifiNetworkSuggestion(configuration, null, false, false, true, false);
WifiConfiguration configuration1 = new WifiConfiguration();
configuration1.SSID = TEST_SSID_1;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion1 =
- new WifiNetworkSuggestion(configuration1, null, false, false, true);
+ new WifiNetworkSuggestion(configuration1, null, false, false, true, false);
assertNotEquals(suggestion, suggestion1);
}
@@ -638,13 +648,13 @@
configuration.BSSID = TEST_BSSID;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion =
- new WifiNetworkSuggestion(configuration, null, false, false, true);
+ new WifiNetworkSuggestion(configuration, null, false, false, true, true);
WifiConfiguration configuration1 = new WifiConfiguration();
configuration1.SSID = TEST_SSID;
configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion1 =
- new WifiNetworkSuggestion(configuration1, null, false, false, true);
+ new WifiNetworkSuggestion(configuration1, null, false, false, true, true);
assertNotEquals(suggestion, suggestion1);
}
@@ -659,13 +669,13 @@
configuration.SSID = TEST_SSID;
configuration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
WifiNetworkSuggestion suggestion =
- new WifiNetworkSuggestion(configuration, null, false, false, true);
+ new WifiNetworkSuggestion(configuration, null, false, false, true, true);
WifiConfiguration configuration1 = new WifiConfiguration();
configuration1.SSID = TEST_SSID;
configuration1.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
WifiNetworkSuggestion suggestion1 =
- new WifiNetworkSuggestion(configuration1, null, false, false, true);
+ new WifiNetworkSuggestion(configuration1, null, false, false, true, true);
assertNotEquals(suggestion, suggestion1);
}
@@ -709,14 +719,43 @@
/**
* Ensure {@link WifiNetworkSuggestion.Builder#build()} throws an exception
- * when {@link WifiNetworkSuggestion.Builder#setIsUserAllowedToManuallyConnect(boolean)} to
+ * when {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} to
* true on a open network suggestion.
*/
@Test(expected = IllegalStateException.class)
public void testSetIsUserAllowedToManuallyConnectToWithOpenNetwork() {
- WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+ new WifiNetworkSuggestion.Builder()
.setSsid(TEST_SSID)
- .setIsUserAllowedToManuallyConnect(true)
+ .setCredentialSharedWithUser(true)
+ .build();
+ }
+
+ /**
+ * Ensure {@link WifiNetworkSuggestion.Builder#build()} throws an exception
+ * when {@link WifiNetworkSuggestion.Builder#setIsInitialAutoJoinEnabled(boolean)} to
+ * false on a open network suggestion.
+ */
+ @Test(expected = IllegalStateException.class)
+ public void testSetIsAutoJoinDisabledWithOpenNetwork() {
+ new WifiNetworkSuggestion.Builder()
+ .setSsid(TEST_SSID)
+ .setIsInitialAutoJoinEnabled(false)
+ .build();
+ }
+
+ /**
+ * Ensure {@link WifiNetworkSuggestion.Builder#build()} throws an exception
+ * when set both {@link WifiNetworkSuggestion.Builder#setIsInitialAutoJoinEnabled(boolean)}
+ * and {@link WifiNetworkSuggestion.Builder#setCredentialSharedWithUser(boolean)} (boolean)}
+ * to false on a network suggestion.
+ */
+ @Test(expected = IllegalStateException.class)
+ public void testSetIsAutoJoinDisabledWithSecureNetworkNotSharedWithUser() {
+ new WifiNetworkSuggestion.Builder()
+ .setSsid(TEST_SSID)
+ .setWpa2Passphrase(TEST_PRESHARED_KEY)
+ .setCredentialSharedWithUser(false)
+ .setIsInitialAutoJoinEnabled(false)
.build();
}
}
diff --git a/wifi/tests/src/android/net/wifi/WifiScannerTest.java b/wifi/tests/src/android/net/wifi/WifiScannerTest.java
index 1af0bcb..0cc76b6 100644
--- a/wifi/tests/src/android/net/wifi/WifiScannerTest.java
+++ b/wifi/tests/src/android/net/wifi/WifiScannerTest.java
@@ -366,7 +366,7 @@
/**
* Test behavior of {@link WifiScanner#startDisconnectedPnoScan(ScanSettings, PnoSettings,
- * WifiScanner.PnoScanListener)}
+ * Executor, WifiScanner.PnoScanListener)}
* @throws Exception
*/
@Test
@@ -375,7 +375,8 @@
PnoSettings pnoSettings = new PnoSettings();
WifiScanner.PnoScanListener pnoScanListener = mock(WifiScanner.PnoScanListener.class);
- mWifiScanner.startDisconnectedPnoScan(scanSettings, pnoSettings, pnoScanListener);
+ mWifiScanner.startDisconnectedPnoScan(
+ scanSettings, pnoSettings, mock(Executor.class), pnoScanListener);
mLooper.dispatchAll();
ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
@@ -396,7 +397,7 @@
/**
* Test behavior of {@link WifiScanner#startConnectedPnoScan(ScanSettings, PnoSettings,
- * WifiScanner.PnoScanListener)}
+ * Executor, WifiScanner.PnoScanListener)}
* @throws Exception
*/
@Test
@@ -405,7 +406,8 @@
PnoSettings pnoSettings = new PnoSettings();
WifiScanner.PnoScanListener pnoScanListener = mock(WifiScanner.PnoScanListener.class);
- mWifiScanner.startConnectedPnoScan(scanSettings, pnoSettings, pnoScanListener);
+ mWifiScanner.startConnectedPnoScan(
+ scanSettings, pnoSettings, mock(Executor.class), pnoScanListener);
mLooper.dispatchAll();
ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
@@ -426,7 +428,7 @@
/**
* Test behavior of {@link WifiScanner#stopPnoScan(ScanListener)}
- * WifiScanner.PnoScanListener)}
+ * Executor, WifiScanner.PnoScanListener)}
* @throws Exception
*/
@Test
@@ -435,7 +437,8 @@
PnoSettings pnoSettings = new PnoSettings();
WifiScanner.PnoScanListener pnoScanListener = mock(WifiScanner.PnoScanListener.class);
- mWifiScanner.startDisconnectedPnoScan(scanSettings, pnoSettings, pnoScanListener);
+ mWifiScanner.startDisconnectedPnoScan(
+ scanSettings, pnoSettings, mock(Executor.class), pnoScanListener);
mLooper.dispatchAll();
mWifiScanner.stopPnoScan(pnoScanListener);
mLooper.dispatchAll();
diff --git a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
index f501b16..94054fd 100644
--- a/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
+++ b/wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java
@@ -171,6 +171,7 @@
assertFalse(config.validate());
assertFalse(config.validateForR2());
+ assertTrue(config.isAutoJoinEnabled());
}
/**